diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 5b7e1b8d..84f12f67 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -1,53 +1,180 @@ -name: Build test +name: CI on: push: - branches: - - main + branches: [main, spm-migration] pull_request: jobs: - test_iOS: - name: Test iOS on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-latest] + # ────────────────────────────────────────────── + analyze_test: + name: Analyze & Test (all packages) + runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: subosito/flutter-action@v2 with: channel: stable - - run: flutter pub get - - run: cd example/ios; pod update WechatOpenSDK-XCFramework; cd ../.. - - run: cd example; flutter build ios --no-codesign - - test_iOS_No_Pay: - name: Test iOS no_pay on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-latest] + + - name: Install Melos + run: dart pub global activate melos 7.5.1 + + - name: Bootstrap + run: melos bootstrap + + - name: Verify symlinks + run: | + test -L packages/fluwx_no_pay/lib || (echo "❌ fluwx_no_pay/lib symlink missing" && exit 1) + test -L packages/fluwx/ios/fluwx/Sources/fluwx || (echo "❌ fluwx iOS Sources symlink missing" && exit 1) + test -L packages/fluwx_no_pay/ios/fluwx_no_pay/Sources/fluwx_no_pay || (echo "❌ fluwx_no_pay iOS Sources symlink missing" && exit 1) + test -L packages/fluwx/android/src/main/kotlin || (echo "❌ fluwx Android kotlin symlink missing" && exit 1) + test -L packages/fluwx_no_pay/android/src/main/kotlin || (echo "❌ fluwx_no_pay Android kotlin symlink missing" && exit 1) + test -L packages/fluwx/ohos || (echo "❌ fluwx/ohos symlink missing" && exit 1) + test -L packages/fluwx_no_pay/ohos || (echo "❌ fluwx_no_pay/ohos symlink missing" && exit 1) + echo "✅ 所有 symlink 完好" + + - name: Analyze + run: melos exec -- flutter analyze + + - name: Test + run: melos exec -- flutter test + + # ────────────────────────────────────────────── + ios_fluwx: + name: iOS build — fluwx (with pay, SPM) + runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: subosito/flutter-action@v2 with: channel: stable - - run: flutter pub get - - run: | - cd example - sed -i.bak0 's/# no_pay: true/ no_pay: true/' pubspec.yaml - cat pubspec.yaml - flutter build ios --no-codesign - - test_android: - name: Test android on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] + + - name: Enable SPM + run: flutter config --enable-swift-package-manager + + - name: Install Melos + run: dart pub global activate melos 7.5.1 + + - name: Bootstrap + run: melos bootstrap + + - name: Build fluwx example (SPM) + run: | + cd packages/fluwx/example + flutter pub get + flutter build ios --no-codesign + + # ────────────────────────────────────────────── + ios_fluwx_no_pay: + name: iOS build — fluwx_no_pay (no pay, SPM) + runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + - uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Enable SPM + run: flutter config --enable-swift-package-manager + + - name: Install Melos + run: dart pub global activate melos 7.5.1 + + - name: Bootstrap + run: melos bootstrap + + - name: Build fluwx_no_pay example (SPM) + run: | + cd packages/fluwx_no_pay/example + flutter pub get + flutter build ios --no-codesign + + - name: Verify — no WechatOpenSDK symbols in binary + run: | + ARCHIVE=$(find ~/Library/Developer/Xcode/DerivedData \ + -name "libfluwx_no_pay.a" 2>/dev/null | head -1) + OBJECTS=$(find ~/Library/Developer/Xcode/DerivedData \ + -path "*/fluwx_no_pay.build/*.o" 2>/dev/null | head -1) + TARGET="${ARCHIVE:-$OBJECTS}" + if [ -z "$TARGET" ]; then + echo "⚠️ 未找到编译产物,跳过符号检查" + exit 0 + fi + echo "检查目标: $TARGET" + + SYMBOLS=$(nm -gjU "$TARGET" 2>/dev/null || nm "$TARGET" 2>/dev/null | awk '{print $NF}') + + if printf '%s\n' "$SYMBOLS" | grep -Fxq "_OBJC_CLASS_\$_PayResp" \ + || printf '%s\n' "$SYMBOLS" | grep -Fxq "_OBJC_CLASS_\$_WXNontaxPayResp" \ + || printf '%s\n' "$SYMBOLS" | grep -Fxq "_OBJC_CLASS_\$_WXPayInsuranceResp"; then + echo "❌ fluwx_no_pay binary 中发现支付相关符号!合规检查失败" + printf '%s\n' "$SYMBOLS" | grep -Fx \ + -e "_OBJC_CLASS_\$_PayResp" \ + -e "_OBJC_CLASS_\$_WXNontaxPayResp" \ + -e "_OBJC_CLASS_\$_WXPayInsuranceResp" | head -20 + exit 1 + fi + echo "✅ Binary 无支付相关符号,合规通过" + + # ────────────────────────────────────────────── + ios_fluwx_pod: + name: iOS build — fluwx (CocoaPods) + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + - uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Disable SPM + run: flutter config --no-enable-swift-package-manager + + - name: Install Melos + run: dart pub global activate melos 7.5.1 + + - name: Bootstrap + run: melos bootstrap + + - name: Build fluwx example (CocoaPods) + run: | + cd packages/fluwx/example + flutter pub get + flutter build ios --no-codesign + + # ────────────────────────────────────────────── + ios_fluwx_no_pay_pod: + name: iOS build — fluwx_no_pay (no pay, CocoaPods) + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + - uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Disable SPM + run: flutter config --no-enable-swift-package-manager + + - name: Install Melos + run: dart pub global activate melos 7.5.1 + + - name: Bootstrap + run: melos bootstrap + + - name: Build fluwx_no_pay example (CocoaPods) + run: | + cd packages/fluwx_no_pay/example + flutter pub get + cd ios + pod repo update + pod install + cd .. + flutter build ios --no-codesign + # ────────────────────────────────────────────── + android: + name: Android build (fluwx) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 - name: Set up JDK 21 uses: actions/setup-java@v3 with: @@ -56,6 +183,14 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: stable - - run: flutter doctor - - run: flutter pub get - - run: cd example; flutter build apk --debug + + - name: Install Melos + run: dart pub global activate melos 7.5.1 + + - name: Bootstrap + run: melos bootstrap + + - name: Build Android APK (fluwx example) + run: | + cd packages/fluwx/example + flutter build apk --debug diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b69901d6..e64613fb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,11 +9,37 @@ jobs: publish: environment: 'pub.dev' permissions: - id-token: write # Required for authentication using OIDC + id-token: write # OIDC 认证 runs-on: ubuntu-latest steps: - - name: 📚 Git Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - - uses: ./.github/actions/publish_flutter_package \ No newline at end of file + - uses: actions/checkout@v4 + + - uses: dart-lang/setup-dart@v1 + + - name: Install Melos + run: dart pub global activate melos 7.5.1 + + - name: Bootstrap + run: melos bootstrap + + - name: Verify version sync + run: dart tools/sync_version.dart --check + + - name: Verify symlinks + run: | + test -L packages/fluwx_no_pay/lib || (echo "❌ fluwx_no_pay/lib symlink missing" && exit 1) + test -L packages/fluwx/ios/fluwx/Sources/fluwx || (echo "❌ fluwx iOS Sources symlink missing" && exit 1) + test -L packages/fluwx_no_pay/ios/fluwx_no_pay/Sources/fluwx_no_pay || (echo "❌ fluwx_no_pay iOS Sources symlink missing" && exit 1) + test -L packages/fluwx/android/src/main/kotlin || (echo "❌ fluwx Android kotlin symlink missing" && exit 1) + test -L packages/fluwx_no_pay/android/src/main/kotlin || (echo "❌ fluwx_no_pay Android kotlin symlink missing" && exit 1) + test -L packages/fluwx/ohos || (echo "❌ fluwx/ohos symlink missing" && exit 1) + test -L packages/fluwx_no_pay/ohos || (echo "❌ fluwx_no_pay/ohos symlink missing" && exit 1) + echo "✅ 所有 symlink 完好" + + - name: Setup pub credentials + run: | + mkdir -p ~/.config/dart + echo '${{ secrets.PUB_CREDENTIALS }}' > ~/.config/dart/pub-credentials.json + + - name: Publish (fluwx first, then fluwx_no_pay) + run: melos publish --no-dry-run --yes diff --git a/CHANGELOG.md b/CHANGELOG.md index a599646a..17954500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 6.0.0-preview.4 +* iOS迁移到Swift Package Manager +* fluwx_no_pay又成为独立的包 + # 6.0.0-preview.3 * 修复iOS编译错误 diff --git a/MIGRATION_PLAN_V2.md b/MIGRATION_PLAN_V2.md new file mode 100644 index 00000000..aff92953 --- /dev/null +++ b/MIGRATION_PLAN_V2.md @@ -0,0 +1,726 @@ +# fluwx:CocoaPods → Swift Package Manager 迁移方案 v2 + +> **no_pay 仅针对 iOS**,Android / HarmonyOS 两端两个包行为完全一致,均完整引入微信 SDK。 +> 本方案在原始草案基础上修订,消除了 Android 差异化配置的多余复杂度,补全了 ohos 层、 +> PrivacyInfo.xcprivacy、iOS 宏名迁移、wechat_setup.rb 替代方案等遗漏项。 + +--- + +## 目录 + +1. [背景与约束](#1-背景与约束) +2. [架构决策](#2-架构决策) +3. [最终架构](#3-最终架构) +4. [目录结构](#4-目录结构) +5. [Monorepo 管理(Melos)](#5-monorepo-管理melos) +6. [代码共享机制](#6-代码共享机制) +7. [iOS SPM 配置](#7-ios-spm-配置) +8. [iOS 宏名迁移](#8-ios-宏名迁移) +9. [Android 配置](#9-android-配置) +10. [HarmonyOS 配置](#10-harmonyos-配置) +11. [Dart 层](#11-dart-层) +12. [版本管理与发布流程](#12-版本管理与发布流程) +13. [CI/CD](#13-cicd) +14. [用户迁移指引](#14-用户迁移指引) +15. [里程碑计划](#15-里程碑计划) +16. [风险与局限](#16-风险与局限) + +--- + +## 1. 背景与约束 + +### 现状 + +| 项目 | 现状 | +|------|------| +| 当前版本 | `6.0.0-preview.3` | +| iOS 依赖管理 | CocoaPods,`pay` subspec 依赖 `WechatOpenSDK-XCFramework ~> 2.0.5`,`no_pay` subspec 依赖 `OpenWeChatSDKNoPay ~> 2.0.5` | +| no_pay 切换方式 | 用户在 `pubspec.yaml` 设置 `fluwx.ios.no_pay: true`,Ruby 脚本 `wechat_setup.rb` 动态切换 podspec subspec | +| iOS 宏 | 现有代码使用 `#ifndef NO_PAY` | +| Android | 始终完整引入微信 SDK,无 no_pay 概念 | +| HarmonyOS | 已有完整 ohos 实现,无 no_pay 概念 | +| 平台支持 | iOS / Android / HarmonyOS | + +### 硬性约束 + +- **支付合规**:`fluwx_no_pay` 的 iOS binary 中**不能出现** WechatOpenSDK 的任何符号 +- **零代码重复**:三个平台的原生代码只维护一份 +- **过渡期兼容**:CocoaPods 用户不能立刻断掉,两套并行直到 Flutter 官方弃用 CocoaPods +- **Android / ohos 不受影响**:两个包的 Android 和 ohos 实现完全相同 + +--- + +## 2. 架构决策 + +SPM 的依赖声明是静态的,发生在 Flutter 工具链介入之前,因此无法像 CocoaPods 那样在 install 阶段动态切换 subspec。唯一满足 iOS 合规的方案是**两个独立 pub 包**: + +| 包名 | iOS | Android | HarmonyOS | +|------|-----|---------|-----------| +| `fluwx` | 链接 WechatOpenSDK | 完整微信 SDK | 完整微信 SDK | +| `fluwx_no_pay` | 完全不链接任何微信 SDK | 完整微信 SDK(与 fluwx 相同) | 完整微信 SDK(与 fluwx 相同) | + +两个包通过 **symlink + Gradle source sets + ohos source 引用** 共享全部原生代码,零重复。 + +--- + +## 3. 最终架构 + +``` +┌──────────────────────────────────────────────────────────────┐ +│ fluwx-monorepo │ +│ │ +│ packages/ │ +│ ├── _shared/ ← 原生代码唯一真相来源 │ +│ │ ├── ios/Sources/ ← iOS ObjC(一份) │ +│ │ ├── android/ ← Android Kotlin(一份) │ +│ │ └── ohos/ ← HarmonyOS ArkTS(一份) │ +│ │ │ +│ ├── fluwx/ ← pub 包(iOS 含支付) │ +│ │ ├── ios/fluwx/ │ +│ │ │ ├── Package.swift 依赖 WechatOpenSDK SPM wrapper│ +│ │ │ │ 定义 FLUWX_WITH_PAY │ +│ │ │ └── Sources/fluwx/ → symlink → _shared/ios/Sources │ +│ │ ├── android/ → source set → _shared/android │ +│ │ └── ohos/ → 引用 _shared/ohos │ +│ │ │ +│ └── fluwx_no_pay/ ← pub 包(iOS 无支付) │ +│ ├── ios/fluwx_no_pay/ │ +│ │ ├── Package.swift 无任何 SDK 依赖 │ +│ │ │ 定义 FLUWX_NO_PAY │ +│ │ └── Sources/ → symlink → _shared/ios/Sources │ +│ ├── lib/ → symlink → ../fluwx/lib │ +│ ├── android/ → source set → _shared/android │ +│ └── ohos/ → 引用 _shared/ohos │ +└──────────────────────────────────────────────────────────────┘ +``` + +**iOS 编译时行为对比(同一份源码,根据宏产生不同 binary):** + +```objc +#ifdef FLUWX_WITH_PAY + // fluwx 包:链接 WechatOpenSDK,编译支付代码 + PayReq *req = [[PayReq alloc] init]; + [WXApi sendReq:req completion:...]; +#endif + +#ifdef FLUWX_NO_PAY + // fluwx_no_pay 包:WechatOpenSDK 完全不存在 + result(FlutterMethodNotImplemented); +#endif +``` + +--- + +## 4. 目录结构 + +``` +fluwx-monorepo/ +├── melos.yaml +├── pubspec.yaml # Workspace 根(仅 dev 依赖) +├── tools/ +│ └── sync_version.dart # 版本同步脚本 +│ +├── .github/ +│ └── workflows/ +│ ├── ci.yml +│ └── publish.yml +│ +└── packages/ + │ + ├── _shared/ # ⚠️ 非 pub 包,不发布 + │ ├── ios/ + │ │ └── Sources/ # iOS 原生源码(唯一副本) + │ │ ├── include/ + │ │ │ ├── FluwxPlugin.h + │ │ │ └── FluwxDelegate.h + │ │ ├── FluwxPlugin.m + │ │ ├── FluwxDelegate.m + │ │ ├── FluwxStringUtil.h / .m + │ │ ├── NSStringWrapper.h / .m + │ │ ├── ThumbnailHelper.h / .m + │ │ └── Resources/ + │ │ └── PrivacyInfo.xcprivacy + │ │ + │ ├── android/ # Android 原生源码(唯一副本) + │ │ └── src/main/kotlin/com/jarvan/fluwx/ + │ │ ├── FluwxPlugin.kt + │ │ ├── FluwxFileProvider.kt + │ │ ├── handlers/ + │ │ ├── io/ + │ │ ├── utils/ + │ │ └── wxapi/ + │ │ + │ └── ohos/ # HarmonyOS 原生源码(唯一副本) + │ └── src/main/ets/components/plugin/ + │ ├── FluwxPlugin.ets + │ └── handlers/ + │ + ├── fluwx/ + │ ├── pubspec.yaml # version: 6.x.x + │ ├── CHANGELOG.md + │ ├── lib/ # Dart 源码 + │ ├── android/ + │ │ ├── build.gradle # source set 指向 _shared/android + │ │ └── src/main/AndroidManifest.xml + │ ├── ohos/ + │ │ └── oh-package.json5 # source 引用 _shared/ohos + │ └── ios/ + │ ├── fluwx.podspec # 保留,含 WechatOpenSDK 依赖(过渡期) + │ └── fluwx/ + │ ├── Package.swift + │ └── Sources/fluwx/ → symlink → ../../../_shared/ios/Sources + │ + └── fluwx_no_pay/ + ├── pubspec.yaml # version: 与 fluwx 保持一致 + ├── CHANGELOG.md + ├── lib/ → symlink → ../fluwx/lib + ├── android/ + │ ├── build.gradle # 与 fluwx 完全相同 + │ └── src/main/AndroidManifest.xml + ├── ohos/ + │ └── oh-package.json5 + └── ios/ + ├── fluwx_no_pay.podspec # 保留,无 SDK 依赖(过渡期) + └── fluwx_no_pay/ + ├── Package.swift + └── Sources/fluwx_no_pay/ → symlink → ../../../_shared/ios/Sources +``` + +--- + +## 5. Monorepo 管理(Melos) + +```yaml +# melos.yaml +name: fluwx_workspace + +packages: + - packages/fluwx + - packages/fluwx_no_pay + - "!packages/_shared" # _shared 不是 pub 包,排除 + +command: + version: + linkToCommits: true + updateGitTagRefs: true + workspaceChangelog: true + +scripts: + analyze: + run: flutter analyze + exec: + concurrency: 1 + + test: + run: flutter test + exec: + concurrency: 2 + + version:sync: + run: dart tools/sync_version.dart + description: "同步 fluwx_no_pay 版本与 fluwx 一致" + + pod:lint: + run: pod lib lint ios/$MELOS_PACKAGE_NAME.podspec --skip-tests --use-modular-headers + exec: + concurrency: 1 + packageFilters: + fileExists: "ios/*.podspec" + + publish:all: + run: melos publish --no-dry-run --yes +``` + +--- + +## 6. 代码共享机制 + +### iOS:symlink + +```bash +# 建立 symlink(一次性,提交到 git) + +# fluwx +ln -sf ../../../../_shared/ios/Sources \ + packages/fluwx/ios/fluwx/Sources/fluwx + +# fluwx_no_pay +ln -sf ../../../../_shared/ios/Sources \ + packages/fluwx_no_pay/ios/fluwx_no_pay/Sources/fluwx_no_pay + +# Dart 层共享 +ln -sf ../fluwx/lib \ + packages/fluwx_no_pay/lib +``` + +> **Windows 开发者**:`git config core.symlinks true` +> +> `dart pub publish` 发布时自动解析 symlink 打包真实文件,pub.dev 上两个包各自完整独立。 + +### Android:Gradle source sets + +两个包的 `build.gradle` **完全相同**,都指向 `_shared/android`,都完整引入微信 SDK: + +```groovy +// packages/fluwx/android/build.gradle +// packages/fluwx_no_pay/android/build.gradle ← 内容完全一样 +android { + sourceSets { + main { + java.srcDirs += ['../../_shared/android/src/main/kotlin'] + } + } +} +dependencies { + api 'com.tencent.mm.opensdk:wechat-sdk-android:6.8.34' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2' + implementation 'id.zelory:compressor:3.0.1' + implementation 'com.squareup.okhttp3:okhttp:5.2.1' +} +``` + +`generateFluwxHelperFile`(生成 `FluwxConfigurations.kt`)任务各自保留在两个包的 `build.gradle` 中,逻辑完全相同。 + +### HarmonyOS:oh-package.json5 source 引用 + +```json5 +// packages/fluwx/ohos/oh-package.json5 +// packages/fluwx_no_pay/ohos/oh-package.json5 ← 内容完全一样 +{ + "name": "fluwx", // fluwx_no_pay 包改为对应名字 + "main": "../../../_shared/ohos/index.ets" +} +``` + +--- + +## 7. iOS SPM 配置 + +### fluwx/ios/fluwx/Package.swift(含支付) + +```swift +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "fluwx", + platforms: [.iOS("12.0")], + products: [ + .library(name: "fluwx", targets: ["fluwx"]) + ], + dependencies: [ + // 推荐自托管 wrapper(见下文说明) + .package( + url: "https://github.com/YOUR_ORG/WechatOpenSDK-SPM.git", + from: "2.0.5" + ) + ], + targets: [ + .target( + name: "fluwx", + dependencies: [ + .product(name: "WechatOpenSDK", package: "WechatOpenSDK-SPM") + ], + path: "Sources/fluwx", + publicHeadersPath: "include", + resources: [ + .process("Resources/PrivacyInfo.xcprivacy") + ], + swiftSettings: [ + .define("FLUWX_WITH_PAY") + ], + cSettings: [ + .define("FLUWX_WITH_PAY"), + .headerSearchPath("include") + ], + linkerSettings: [ + .unsafeFlags(["-ObjC", "-all_load"]) + ] + ) + ] +) +``` + +### fluwx_no_pay/ios/fluwx_no_pay/Package.swift(无支付) + +```swift +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "fluwx_no_pay", + platforms: [.iOS("12.0")], + products: [ + .library(name: "fluwx-no-pay", targets: ["fluwx_no_pay"]) + ], + dependencies: [], // ✅ 完全没有外部依赖 + targets: [ + .target( + name: "fluwx_no_pay", + dependencies: [], + path: "Sources/fluwx_no_pay", + publicHeadersPath: "include", + resources: [ + .process("Resources/PrivacyInfo.xcprivacy") + ], + swiftSettings: [ + .define("FLUWX_NO_PAY") + ], + cSettings: [ + .define("FLUWX_NO_PAY"), + .headerSearchPath("include") + ] + // ⚠️ 无 -ObjC -all_load,没有静态 SDK 需要加载 + ) + ] +) +``` + +### WechatOpenSDK SPM Wrapper + +腾讯官方不提供 SPM 包,**必须自托管**: + +1. 下载腾讯官方 `WechatOpenSDK-XCFramework 2.0.5` +2. 参考 `yanyin1986/WechatOpenSDK` 结构,在自己的 GitHub org 建仓库 +3. `Package.swift` 里用 `.binaryTarget` 指向 XCFramework,填写正确的 `checksum` +4. 在 `fluwx/ios/fluwx/Package.swift` 中指向自托管地址 + +自托管的好处是版本完全自控,不依赖第三方维护节奏。 + +--- + +## 8. iOS 宏名迁移 + +### 问题 + +现有代码使用 `#ifndef NO_PAY`(共 4 处),新方案改为语义更清晰的正向宏 `FLUWX_WITH_PAY` / `FLUWX_NO_PAY`。 + +### 迁移方式 + +```objc +// 旧写法 +#ifndef NO_PAY + [WXApi sendReq:...]; +#endif + +// 新写法(等价,语义更清晰) +#ifdef FLUWX_WITH_PAY + [WXApi sendReq:...]; +#endif +``` + +全局替换规则:`#ifndef NO_PAY` → `#ifdef FLUWX_WITH_PAY`,`#ifdef NO_PAY` → `#ifdef FLUWX_NO_PAY`。 + +### CI 符号表验证(必须加) + +仅靠代码审查无法保证合规,CI 里需要对 `fluwx_no_pay` 的产物做符号表检查: + +```bash +# ci.yml 新增步骤:验证 fluwx_no_pay binary 中无 WechatOpenSDK 符号 +- name: Verify no WechatOpenSDK symbols in fluwx_no_pay + run: | + BINARY=$(find ~/Library/Developer/Xcode/DerivedData -name "fluwx_no_pay" -type f | head -1) + if nm "$BINARY" | grep -q "WX\|WechatOpenSDK"; then + echo "❌ fluwx_no_pay binary contains WechatOpenSDK symbols!" + exit 1 + fi + echo "✅ Binary is clean" +``` + +--- + +## 9. Android 配置 + +**两个包 Android 配置完全一致,均完整引入微信 SDK,无任何差异化处理。** + +每个包的 `android/` 目录只保留: +- `build.gradle`:source set 指向 `_shared/android`,包含 `generateFluwxHelperFile` 任务 +- `src/main/AndroidManifest.xml`:包名对应各自包 + +`_shared/android/` 里的所有 Kotlin 代码无需任何条件宏,维持现有逻辑不变。 + +--- + +## 10. HarmonyOS 配置 + +**两个包 ohos 配置完全一致,均完整引入微信 SDK,无任何差异化处理。** + +``` +packages/ +├── _shared/ohos/ ← 从现有 fluwx/ohos/ 迁入,内容不变 +│ └── src/main/ets/components/plugin/ +│ ├── FluwxPlugin.ets +│ └── handlers/ +│ +├── fluwx/ohos/ → oh-package.json5 引用 _shared/ohos +└── fluwx_no_pay/ohos/ → oh-package.json5 引用 _shared/ohos(内容相同) +``` + +--- + +## 11. Dart 层 + +Dart 层通过 symlink 完全共享,`lib/` 下均为相对 import,发布后不存在 `package:` 引用冲突: + +```bash +ln -sf ../fluwx/lib packages/fluwx_no_pay/lib +``` + +`fluwx_no_pay` 用户调用支付接口时,iOS native 返回 `notImplemented`,Dart 侧抛出 `MissingPluginException`,不会静默失败。 + +--- + +## 12. 版本管理与发布流程 + +### 版本同步脚本 + +```dart +// tools/sync_version.dart +import 'dart:io'; + +void main(List args) { + final fluwxPubspec = File('packages/fluwx/pubspec.yaml'); + final noPayPubspec = File('packages/fluwx_no_pay/pubspec.yaml'); + + final content = fluwxPubspec.readAsStringSync(); + final match = RegExp(r'^version:\s*(.+)$', multiLine: true).firstMatch(content); + if (match == null) { print('❌ 未找到版本号'); exit(1); } + final version = match.group(1)!.trim(); + + var noPayContent = noPayPubspec.readAsStringSync(); + noPayContent = noPayContent.replaceFirstMapped( + RegExp(r'^version: .+$', multiLine: true), + (_) => 'version: $version', + ); + noPayPubspec.writeAsStringSync(noPayContent); + print('✅ fluwx_no_pay 版本已同步至 $version'); + + // --check 模式:只校验不写入(供 CI 用) + if (args.contains('--check')) { + final noPayVersion = RegExp(r'^version:\s*(.+)$', multiLine: true) + .firstMatch(noPayPubspec.readAsStringSync())?.group(1)?.trim(); + if (noPayVersion != version) { + print('❌ 版本不同步:fluwx=$version, fluwx_no_pay=$noPayVersion'); + exit(1); + } + print('✅ 版本一致'); + } +} +``` + +### 发布步骤 + +```bash +# 1. 修改 packages/fluwx/pubspec.yaml 版本号 +# 2. 同步版本 +dart tools/sync_version.dart +# 3. 更新两个包的 CHANGELOG +# 4. 提交并打 tag +git add . +git commit -m "chore: release v6.x.x" +git tag v6.x.x +git push origin main --tags +# 5. CI 自动发布(或手动执行) +melos publish --no-dry-run --yes +``` + +**发布顺序**:先发 `fluwx`,再发 `fluwx_no_pay`。 + +--- + +## 13. CI/CD + +### ci.yml + +```yaml +name: CI +on: + pull_request: + branches: [main] + +jobs: + analyze_test: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + flutter-version: '3.x' + channel: stable + + - name: Install Melos + run: dart pub global activate melos + + - name: Bootstrap + run: melos bootstrap + + - name: Analyze + run: melos run analyze + + - name: Test + run: melos run test + + - name: Verify symlinks + run: | + test -L packages/fluwx_no_pay/lib || exit 1 + test -L packages/fluwx/ios/fluwx/Sources/fluwx || exit 1 + test -L packages/fluwx_no_pay/ios/fluwx_no_pay/Sources/fluwx_no_pay || exit 1 + echo "✅ 所有 symlink 完好" + + - name: Pod lint + run: melos run pod:lint + + - name: Build fluwx_no_pay iOS (SPM) + run: | + cd packages/fluwx_no_pay/example + flutter build ios --no-codesign + + - name: Verify no WechatOpenSDK symbols in fluwx_no_pay + run: | + BINARY=$(find ~/Library/Developer/Xcode/DerivedData \ + -name "fluwx_no_pay" -type f 2>/dev/null | head -1) + if [ -z "$BINARY" ]; then + echo "⚠️ Binary not found,跳过符号检查" + exit 0 + fi + if nm "$BINARY" | grep -qE "WX[A-Z]|WechatOpenSDK"; then + echo "❌ fluwx_no_pay binary 中发现 WechatOpenSDK 符号!" + exit 1 + fi + echo "✅ Binary 无微信 SDK 符号" +``` + +### publish.yml + +```yaml +name: Publish +on: + push: + tags: + - 'v*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1 + + - name: Install Melos + run: dart pub global activate melos + + - name: Bootstrap + run: melos bootstrap + + - name: Verify version sync + run: dart tools/sync_version.dart --check + + - name: Setup pub credentials + run: | + mkdir -p ~/.config/dart + echo '${{ secrets.PUB_CREDENTIALS }}' > ~/.config/dart/pub-credentials.json + + - name: Publish + run: melos publish --no-dry-run --yes +``` + +--- + +## 14. 用户迁移指引 + +### 含支付用户(无需换包名) + +```yaml +# pubspec.yaml +dependencies: + fluwx: ^6.0.0 # 版本号更新即可,SPM 自动生效 +``` + +开启 SPM(Flutter 3.24+): +```bash +flutter config --enable-swift-package-manager +``` + +**手动配置 Xcode(替代原 wechat_setup.rb 的工作)**: + +SPM 模式下 `wechat_setup.rb` 不再自动运行,需要手动在 Xcode 里完成: + +1. 在 `Info.plist` 添加 `LSApplicationQueriesSchemes`,包含 `weixin`、`weixinULAPI` +2. 在 `Info.plist` 添加 `CFBundleURLTypes`,URL Scheme 填写微信 AppID +3. 在项目 Capabilities 开启 `Associated Domains`,填写 Universal Link 域名 +4. 在 `AppDelegate` 中接入 `handleOpenURL` 和 `continueUserActivity` + +> CocoaPods 用户不开启 SPM 则 `wechat_setup.rb` 继续生效,无需任何改动。 + +### 无支付用户(需换包名) + +```yaml +# pubspec.yaml +dependencies: + fluwx_no_pay: ^6.0.0 # 换包名,删除原 fluwx.ios.no_pay 配置 +``` + +```dart +// Dart import 换一行,其余代码完全不变 +// 以前 +import 'package:fluwx/fluwx.dart'; +// 现在 +import 'package:fluwx_no_pay/fluwx.dart'; +``` + +### 继续使用 CocoaPods(暂不迁移) + +两个包在过渡期同时保留 podspec,不开启 SPM 的项目行为与之前完全一致。 + +--- + +## 15. 里程碑计划 + +``` +阶段 0:前置重构(1 周,在现有单包上操作) + □ 将 ios/Classes/ 内容迁移到 packages/_shared/ios/Sources/ + □ 将 android/src/ 内容迁移到 packages/_shared/android/ + □ 将 ohos/src/ 内容迁移到 packages/_shared/ohos/ + □ 将 iOS 宏名从 NO_PAY → FLUWX_WITH_PAY / FLUWX_NO_PAY 全局替换 + □ 验证单包(fluwx)仍可正常构建 + +阶段 1:Monorepo 搭建(1 周) + □ 建立 packages/ 目录结构 + □ 建立所有 symlink + □ 配置 melos.yaml + □ 验证 fluwx 和 fluwx_no_pay 可独立 flutter run + +阶段 2:SPM 支持(3 周) + □ 自托管 WechatOpenSDK SPM wrapper,生成正确的 checksum + □ 编写 fluwx Package.swift(含 PrivacyInfo 资源、FLUWX_WITH_PAY 宏) + □ 编写 fluwx_no_pay Package.swift(含 PrivacyInfo 资源、FLUWX_NO_PAY 宏) + □ 验证含支付版本 SPM 构建通过 + □ 验证无支付版本 binary 中无 WechatOpenSDK 符号(nm 检查) + □ CocoaPods 路径同时保留,双轨验证 + +阶段 3:CI/CD + 发布(1 周) + □ 配置 ci.yml(含 symlink 校验 + 符号表检查) + □ 配置 publish.yml + □ 版本同步脚本测试 + □ dry-run 发布验证 + □ v6.0.0 正式发布 + +阶段 4:过渡期运营(持续) + - 监控用户反馈 + - 等待 Flutter 官方弃用 CocoaPods 通知 + - 届时移除所有 podspec,发布 v7.0.0 纯 SPM 版本 +``` + +--- + +## 16. 风险与局限 + +| 风险 | 严重程度 | 说明 | 缓解措施 | +|------|---------|------|---------| +| WechatOpenSDK 无官方 SPM 包 | 高 | 需自托管 XCFramework wrapper | 自托管,版本完全自控 | +| ARM64 模拟器不支持 | 中 | XCFramework 不含 `ios-arm64-simulator` slice | 开发调试用真机,CI 用真机 | +| Windows 开发者 symlink 问题 | 中 | Windows 默认不支持 symlink | 文档说明,CI 校验 symlink 完整性 | +| `-ObjC -all_load` unsafeFlags | 低 | SPM 的 unsafeFlags 在非 root package 有限制 | 已有社区广泛先例,可接受 | +| CocoaPods 弃用时间未知 | 低 | Flutter 官方路线图已明确,时间不确定 | 双轨并行,随时可切断 | +| wechat_setup.rb 无 SPM 等价 | 低 | SPM 用户需手动配置 Xcode | 迁移文档详细说明,影响范围可控 | diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle deleted file mode 100644 index 45aea6e7..00000000 --- a/example/android/app/build.gradle +++ /dev/null @@ -1,77 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -android { - namespace "com.jarvan.fluwx_example" - compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion - - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "net.sourceforge.simcpux" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - minifyEnabled true - } - - debug { - signingConfig signingConfigs.debug - } - } - - signingConfigs { - debug { - storeFile file("debug.keystore") - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = '11' - } -} - -flutter { - source '../..' -} diff --git a/example/android/app/debug.keystore b/example/android/app/debug.keystore deleted file mode 100644 index b2ccfae5..00000000 Binary files a/example/android/app/debug.keystore and /dev/null differ diff --git a/example/android/app/src/main/kotlin/com/jarvan/fluwx_example/MainActivity.kt b/example/android/app/src/main/kotlin/com/jarvan/fluwx_example/MainActivity.kt deleted file mode 100644 index 08644928..00000000 --- a/example/android/app/src/main/kotlin/com/jarvan/fluwx_example/MainActivity.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.jarvan.fluwx_example - -import android.os.Bundle - -import com.jarvan.fluwx.handlers.FluwxRequestHandler -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - } -} diff --git a/example/android/build.gradle b/example/android/build.gradle deleted file mode 100644 index bc157bd1..00000000 --- a/example/android/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/example/android/gradle.properties b/example/android/gradle.properties deleted file mode 100644 index 720b3042..00000000 --- a/example/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.useAndroidX=true - diff --git a/example/android/settings.gradle b/example/android/settings.gradle deleted file mode 100644 index 41ce1584..00000000 --- a/example/android/settings.gradle +++ /dev/null @@ -1,25 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - }() - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.9.1" apply false - id "org.jetbrains.kotlin.android" version "2.1.0" apply false -} - -include ":app" \ No newline at end of file diff --git a/example/integration_test/plugin_integration_test.dart b/example/integration_test/plugin_integration_test.dart deleted file mode 100644 index 3ad85d6c..00000000 --- a/example/integration_test/plugin_integration_test.dart +++ /dev/null @@ -1,21 +0,0 @@ -// This is a basic Flutter integration test. -// -// Since integration tests run in a full Flutter application, they can interact -// with the host side of a plugin implementation, unlike Dart unit tests. -// -// For more information about Flutter integration tests, please see -// https://docs.flutter.dev/cookbook/testing/integration/introduction - - -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; - -import 'package:fluwx/fluwx.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - testWidgets('getPlatformVersion test', (WidgetTester tester) async { - - }); -} diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock deleted file mode 100644 index 88e4fc90..00000000 --- a/example/ios/Podfile.lock +++ /dev/null @@ -1,32 +0,0 @@ -PODS: - - Flutter (1.0.0) - - fluwx (0.0.1): - - Flutter - - fluwx/pay (= 0.0.1) - - fluwx/pay (0.0.1): - - Flutter - - WechatOpenSDK-XCFramework (~> 2.0.5) - - WechatOpenSDK-XCFramework (2.0.5) - -DEPENDENCIES: - - Flutter (from `Flutter`) - - fluwx (from `.symlinks/plugins/fluwx/ios`) - -SPEC REPOS: - trunk: - - WechatOpenSDK-XCFramework - -EXTERNAL SOURCES: - Flutter: - :path: Flutter - fluwx: - :path: ".symlinks/plugins/fluwx/ios" - -SPEC CHECKSUMS: - Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - fluwx: 6f0b28c68aef77900ef67728e66091cd8022739f - WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949 - -PODFILE CHECKSUM: a3b6f034f0d79a209652621b979bc9d6707da7a1 - -COCOAPODS: 1.16.2 diff --git a/example/ios/Runner/AppDelegate.h b/example/ios/Runner/AppDelegate.h deleted file mode 100644 index 01e6e1d4..00000000 --- a/example/ios/Runner/AppDelegate.h +++ /dev/null @@ -1,6 +0,0 @@ -#import -#import - -@interface AppDelegate : FlutterAppDelegate - -@end diff --git a/example/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m deleted file mode 100644 index cebafc9e..00000000 --- a/example/ios/Runner/AppDelegate.m +++ /dev/null @@ -1,16 +0,0 @@ -#import "AppDelegate.h" -#import "GeneratedPluginRegistrant.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - return [super application:application didFinishLaunchingWithOptions:launchOptions]; -} - -- (void)didInitializeImplicitFlutterEngine:(NSObject*)engineBridge { - [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; -} - -@end diff --git a/example/ios/Runner/main.m b/example/ios/Runner/main.m deleted file mode 100644 index dff6597e..00000000 --- a/example/ios/Runner/main.m +++ /dev/null @@ -1,9 +0,0 @@ -#import -#import -#import "AppDelegate.h" - -int main(int argc, char* argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/example/ios/RunnerTests/RunnerTests.m b/example/ios/RunnerTests/RunnerTests.m deleted file mode 100644 index eafc983b..00000000 --- a/example/ios/RunnerTests/RunnerTests.m +++ /dev/null @@ -1,33 +0,0 @@ -#import -#import -#import - -@import fluwx; - -// This demonstrates a simple unit test of the Objective-C portion of this plugin's implementation. -// -// See https://developer.apple.com/documentation/xctest for more information about using XCTest. - -@interface RunnerTests : XCTestCase - -@end - -@implementation RunnerTests - -- (void)testExample { - FluwxPlugin *plugin = [[FluwxPlugin alloc] init]; - - FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"getPlatformVersion" - arguments:nil]; - XCTestExpectation *expectation = [self expectationWithDescription:@"result block must be called"]; - [plugin handleMethodCall:call - result:^(id result) { - NSString *expected = [NSString - stringWithFormat:@"iOS %@", UIDevice.currentDevice.systemVersion]; - XCTAssertEqualObjects(result, expected); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - -@end diff --git a/example/ohos/.gitignore b/example/ohos/.gitignore deleted file mode 100644 index 6ca13b31..00000000 --- a/example/ohos/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -/node_modules -/oh_modules -/local.properties -/.idea -**/build -/.hvigor -.cxx -/.clangd -/.clang-format -/.clang-tidy -**/.test -*.har -**/BuildProfile.ets -**/oh-package-lock.json5 - -**/src/main/resources/rawfile/flutter_assets/ -**/libs/arm64-v8a/libapp.so -**/libs/arm64-v8a/libflutter.so -**/libs/arm64-v8a/libvmservice_snapshot.so diff --git a/example/ohos/AppScope/app.json5 b/example/ohos/AppScope/app.json5 deleted file mode 100644 index a6a34f29..00000000 --- a/example/ohos/AppScope/app.json5 +++ /dev/null @@ -1,10 +0,0 @@ -{ - "app": { - "bundleName": "com.jarvan.fluwx_example", - "vendor": "example", - "versionCode": 1000000, - "versionName": "1.0.0", - "icon": "$media:app_icon", - "label": "$string:app_name" - } -} diff --git a/example/ohos/AppScope/resources/base/element/string.json b/example/ohos/AppScope/resources/base/element/string.json deleted file mode 100644 index 41723660..00000000 --- a/example/ohos/AppScope/resources/base/element/string.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "string": [ - { - "name": "app_name", - "value": "fluwx_example" - } - ] -} diff --git a/example/ohos/AppScope/resources/base/media/app_icon.png b/example/ohos/AppScope/resources/base/media/app_icon.png deleted file mode 100644 index ce307a88..00000000 Binary files a/example/ohos/AppScope/resources/base/media/app_icon.png and /dev/null differ diff --git a/example/ohos/build-profile.json5 b/example/ohos/build-profile.json5 deleted file mode 100644 index e18a4810..00000000 --- a/example/ohos/build-profile.json5 +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -{ - app: { - signingConfigs: [], - products: [ - { - name: "default", - signingConfig: "default", - compatibleSdkVersion: "5.0.0(12)", - runtimeOS: "HarmonyOS", - }, - ], - }, - modules: [ - { - name: "entry", - srcPath: "./entry", - targets: [ - { - name: "default", - applyToProducts: ["default"], - }, - ], - }, - { - name: "fluwx", - srcPath: "../../ohos", - targets: [ - { - name: "default", - applyToProducts: ["default"], - }, - ], - }, - ], -} diff --git a/example/ohos/entry/.gitignore b/example/ohos/entry/.gitignore deleted file mode 100644 index 2795a1c5..00000000 --- a/example/ohos/entry/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ - -/node_modules -/oh_modules -/.preview -/build -/.cxx -/.test \ No newline at end of file diff --git a/example/ohos/entry/build-profile.json5 b/example/ohos/entry/build-profile.json5 deleted file mode 100644 index 633d360f..00000000 --- a/example/ohos/entry/build-profile.json5 +++ /dev/null @@ -1,29 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -{ - "apiType": 'stageMode', - "buildOption": { - }, - "targets": [ - { - "name": "default", - "runtimeOS": "HarmonyOS" - }, - { - "name": "ohosTest", - } - ] -} \ No newline at end of file diff --git a/example/ohos/entry/hvigorfile.ts b/example/ohos/entry/hvigorfile.ts deleted file mode 100644 index 894fc15c..00000000 --- a/example/ohos/entry/hvigorfile.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. -export { hapTasks } from '@ohos/hvigor-ohos-plugin'; diff --git a/example/ohos/entry/oh-package.json5 b/example/ohos/entry/oh-package.json5 deleted file mode 100644 index 567f3362..00000000 --- a/example/ohos/entry/oh-package.json5 +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -{ - "name": "entry", - "version": "1.0.0", - "description": "Please describe the basic information.", - "main": "", - "author": "", - "license": "", - "dependencies": { - "fluwx": "../../../ohos" - }, -} - diff --git a/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets deleted file mode 100644 index 8bc48be8..00000000 --- a/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets +++ /dev/null @@ -1,24 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos'; -import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant'; - -export default class EntryAbility extends FlutterAbility { - configureFlutterEngine(flutterEngine: FlutterEngine) { - super.configureFlutterEngine(flutterEngine) - GeneratedPluginRegistrant.registerWith(flutterEngine) - } -} diff --git a/example/ohos/entry/src/main/ets/pages/Index.ets b/example/ohos/entry/src/main/ets/pages/Index.ets deleted file mode 100644 index 1125f9fd..00000000 --- a/example/ohos/entry/src/main/ets/pages/Index.ets +++ /dev/null @@ -1,38 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import common from '@ohos.app.ability.common'; -import { FlutterPage } from '@ohos/flutter_ohos' - -let storage = LocalStorage.getShared() -const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS' - -@Entry(storage) -@Component -struct Index { - private context = getContext(this) as common.UIAbilityContext - @LocalStorageLink('viewId') viewId: string = ""; - - build() { - Column() { - FlutterPage({ viewId: this.viewId }) - } - } - - onBackPress(): boolean { - this.context.eventHub.emit(EVENT_BACK_PRESS) - return true - } -} \ No newline at end of file diff --git a/example/ohos/entry/src/main/module.json5 b/example/ohos/entry/src/main/module.json5 deleted file mode 100644 index 7bbf78b1..00000000 --- a/example/ohos/entry/src/main/module.json5 +++ /dev/null @@ -1,53 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -{ - "module": { - "name": "entry", - "type": "entry", - "description": "$string:module_desc", - "mainElement": "EntryAbility", - "deviceTypes": [ - "phone" - ], - "deliveryWithInstall": true, - "installationFree": false, - "pages": "$profile:main_pages", - "abilities": [ - { - "name": "EntryAbility", - "srcEntry": "./ets/entryability/EntryAbility.ets", - "description": "$string:EntryAbility_desc", - "icon": "$media:icon", - "label": "$string:EntryAbility_label", - "startWindowIcon": "$media:icon", - "startWindowBackground": "$color:start_window_background", - "exported": true, - "skills": [ - { - "entities": [ - "entity.system.home" - ], - "actions": [ - "action.system.home" - ] - } - ] - } - ], - "requestPermissions": [ - {"name" : "ohos.permission.INTERNET"}, - ] - } -} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/base/element/color.json b/example/ohos/entry/src/main/resources/base/element/color.json deleted file mode 100644 index 3c712962..00000000 --- a/example/ohos/entry/src/main/resources/base/element/color.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "color": [ - { - "name": "start_window_background", - "value": "#FFFFFF" - } - ] -} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/base/element/string.json b/example/ohos/entry/src/main/resources/base/element/string.json deleted file mode 100644 index 28c64914..00000000 --- a/example/ohos/entry/src/main/resources/base/element/string.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "string": [ - { - "name": "module_desc", - "value": "module description" - }, - { - "name": "EntryAbility_desc", - "value": "description" - }, - { - "name": "EntryAbility_label", - "value": "fluwx_example" - } - ] -} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/base/media/icon.png b/example/ohos/entry/src/main/resources/base/media/icon.png deleted file mode 100644 index ce307a88..00000000 Binary files a/example/ohos/entry/src/main/resources/base/media/icon.png and /dev/null differ diff --git a/example/ohos/entry/src/main/resources/base/profile/main_pages.json b/example/ohos/entry/src/main/resources/base/profile/main_pages.json deleted file mode 100644 index 1898d94f..00000000 --- a/example/ohos/entry/src/main/resources/base/profile/main_pages.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "src": [ - "pages/Index" - ] -} diff --git a/example/ohos/entry/src/main/resources/en_US/element/string.json b/example/ohos/entry/src/main/resources/en_US/element/string.json deleted file mode 100644 index 28c64914..00000000 --- a/example/ohos/entry/src/main/resources/en_US/element/string.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "string": [ - { - "name": "module_desc", - "value": "module description" - }, - { - "name": "EntryAbility_desc", - "value": "description" - }, - { - "name": "EntryAbility_label", - "value": "fluwx_example" - } - ] -} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/zh_CN/element/string.json b/example/ohos/entry/src/main/resources/zh_CN/element/string.json deleted file mode 100644 index 301dabbe..00000000 --- a/example/ohos/entry/src/main/resources/zh_CN/element/string.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "string": [ - { - "name": "module_desc", - "value": "模块描述" - }, - { - "name": "EntryAbility_desc", - "value": "description" - }, - { - "name": "EntryAbility_label", - "value": "fluwx_example" - } - ] -} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets b/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets deleted file mode 100644 index 25d4c71f..00000000 --- a/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets +++ /dev/null @@ -1,50 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import hilog from '@ohos.hilog'; -import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' - -export default function abilityTest() { - describe('ActsAbilityTest', function () { - // Defines a test suite. Two parameters are supported: test suite name and test suite function. - beforeAll(function () { - // Presets an action, which is performed only once before all test cases of the test suite start. - // This API supports only one parameter: preset action function. - }) - beforeEach(function () { - // Presets an action, which is performed before each unit test case starts. - // The number of execution times is the same as the number of test cases defined by **it**. - // This API supports only one parameter: preset action function. - }) - afterEach(function () { - // Presets a clear action, which is performed after each unit test case ends. - // The number of execution times is the same as the number of test cases defined by **it**. - // This API supports only one parameter: clear action function. - }) - afterAll(function () { - // Presets a clear action, which is performed after all test cases of the test suite end. - // This API supports only one parameter: clear action function. - }) - it('assertContain',0, function () { - // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. - hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); - let a = 'abc' - let b = 'b' - // Defines a variety of assertion methods, which are used to declare expected boolean conditions. - expect(a).assertContain(b) - expect(a).assertEqual(a) - }) - }) -} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/test/List.test.ets b/example/ohos/entry/src/ohosTest/ets/test/List.test.ets deleted file mode 100644 index f4140030..00000000 --- a/example/ohos/entry/src/ohosTest/ets/test/List.test.ets +++ /dev/null @@ -1,20 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import abilityTest from './Ability.test' - -export default function testsuite() { - abilityTest() -} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets b/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets deleted file mode 100644 index 4ca645e6..00000000 --- a/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets +++ /dev/null @@ -1,63 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import UIAbility from '@ohos.app.ability.UIAbility'; -import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; -import hilog from '@ohos.hilog'; -import { Hypium } from '@ohos/hypium'; -import testsuite from '../test/List.test'; -import window from '@ohos.window'; - -export default class TestAbility extends UIAbility { - onCreate(want, launchParam) { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); - hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); - hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); - var abilityDelegator: any - abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() - var abilityDelegatorArguments: any - abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() - hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); - Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) - } - - onDestroy() { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); - } - - onWindowStageCreate(windowStage: window.WindowStage) { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); - windowStage.loadContent('testability/pages/Index', (err, data) => { - if (err.code) { - hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); - return; - } - hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', - JSON.stringify(data) ?? ''); - }); - } - - onWindowStageDestroy() { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); - } - - onForeground() { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); - } - - onBackground() { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); - } -} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets deleted file mode 100644 index cef0447c..00000000 --- a/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets +++ /dev/null @@ -1,49 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import hilog from '@ohos.hilog'; - -@Entry -@Component -struct Index { - aboutToAppear() { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); - } - @State message: string = 'Hello World' - build() { - Row() { - Column() { - Text(this.message) - .fontSize(50) - .fontWeight(FontWeight.Bold) - Button() { - Text('next page') - .fontSize(20) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .width('35%') - .height('5%') - .onClick(()=>{ - }) - } - .width('100%') - } - .height('100%') - } - } \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts deleted file mode 100644 index 1def08f2..00000000 --- a/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import hilog from '@ohos.hilog'; -import TestRunner from '@ohos.application.testRunner'; -import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; - -var abilityDelegator = undefined -var abilityDelegatorArguments = undefined - -async function onAbilityCreateCallback() { - hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); -} - -async function addAbilityMonitorCallback(err: any) { - hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); -} - -export default class OpenHarmonyTestRunner implements TestRunner { - constructor() { - } - - onPrepare() { - hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); - } - - async onRun() { - hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); - abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() - abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() - var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' - let lMonitor = { - abilityName: testAbilityName, - onAbilityCreate: onAbilityCreateCallback, - }; - abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) - var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName - var debug = abilityDelegatorArguments.parameters['-D'] - if (debug == 'true') - { - cmd += ' -D' - } - hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); - abilityDelegator.executeShellCommand(cmd, - (err: any, d: any) => { - hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); - hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); - hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); - }) - hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); - } -} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/module.json5 b/example/ohos/entry/src/ohosTest/module.json5 deleted file mode 100644 index fab77ce2..00000000 --- a/example/ohos/entry/src/ohosTest/module.json5 +++ /dev/null @@ -1,51 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -{ - "module": { - "name": "entry_test", - "type": "feature", - "description": "$string:module_test_desc", - "mainElement": "TestAbility", - "deviceTypes": [ - "phone" - ], - "deliveryWithInstall": true, - "installationFree": false, - "pages": "$profile:test_pages", - "abilities": [ - { - "name": "TestAbility", - "srcEntry": "./ets/testability/TestAbility.ets", - "description": "$string:TestAbility_desc", - "icon": "$media:icon", - "label": "$string:TestAbility_label", - "exported": true, - "startWindowIcon": "$media:icon", - "startWindowBackground": "$color:start_window_background", - "skills": [ - { - "actions": [ - "action.system.home" - ], - "entities": [ - "entity.system.home" - ] - } - ] - } - ] - } -} diff --git a/example/ohos/entry/src/ohosTest/resources/base/element/color.json b/example/ohos/entry/src/ohosTest/resources/base/element/color.json deleted file mode 100644 index 3c712962..00000000 --- a/example/ohos/entry/src/ohosTest/resources/base/element/color.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "color": [ - { - "name": "start_window_background", - "value": "#FFFFFF" - } - ] -} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/resources/base/element/string.json b/example/ohos/entry/src/ohosTest/resources/base/element/string.json deleted file mode 100644 index 65d8fa5a..00000000 --- a/example/ohos/entry/src/ohosTest/resources/base/element/string.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "string": [ - { - "name": "module_test_desc", - "value": "test ability description" - }, - { - "name": "TestAbility_desc", - "value": "the test ability" - }, - { - "name": "TestAbility_label", - "value": "test label" - } - ] -} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/resources/base/media/icon.png b/example/ohos/entry/src/ohosTest/resources/base/media/icon.png deleted file mode 100644 index ce307a88..00000000 Binary files a/example/ohos/entry/src/ohosTest/resources/base/media/icon.png and /dev/null differ diff --git a/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json deleted file mode 100644 index b7e7343c..00000000 --- a/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "src": [ - "testability/pages/Index" - ] -} diff --git a/example/ohos/hvigorfile.ts b/example/ohos/hvigorfile.ts deleted file mode 100644 index 8f2d2aaf..00000000 --- a/example/ohos/hvigorfile.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import { appTasks } from '@ohos/hvigor-ohos-plugin'; - -export default { - system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ - plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ -} \ No newline at end of file diff --git a/example/ohos/oh-package.json5 b/example/ohos/oh-package.json5 deleted file mode 100644 index 2b718d3a..00000000 --- a/example/ohos/oh-package.json5 +++ /dev/null @@ -1,35 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -{ - "modelVersion": "5.0.0", - "name": "fluwx_example", - "version": "1.0.0", - "description": "Please describe the basic information.", - "main": "", - "author": "", - "license": "", - "dependencies": { - "@ohos/flutter_ohos": "file:./har/flutter.har", - "@tencent/wechat_open_sdk": "^1.0.0" - }, - "devDependencies": { - "@ohos/hypium": "1.0.6" - }, - "overrides": { - "@ohos/flutter_ohos": "file:./har/flutter.har", - "fluwx": "../../ohos", - } -} diff --git a/example/pubspec.yaml b/example/pubspec.yaml deleted file mode 100644 index cab0422b..00000000 --- a/example/pubspec.yaml +++ /dev/null @@ -1,97 +0,0 @@ -name: fluwx_example -description: Demonstrates how to use the fluwx plugin. -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -environment: - sdk: '>=3.0.0 <4.0.0' - -fluwx: - app_id: '123456' - debug_logging: true # Logging in debug mode. - android: -# interrupt_wx_request: true # Defaults to true. -# flutter_activity: 'MainActivity' # Defaults to app's launcher - ios: - universal_link: https://testdomain.com -# no_pay: true # Set to true to disable payment. -# ignore_security: true # Set to true to disable security seetings. - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - - fluwx: - # When depending on this package from a real application you should use: - # fluwx: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../ - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - -dev_dependencies: - integration_test: - sdk: flutter - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^2.0.0 - http: ^1.2.2 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - assets: - - images/logo.png - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages - diff --git a/melos.yaml b/melos.yaml new file mode 100644 index 00000000..fc6e8443 --- /dev/null +++ b/melos.yaml @@ -0,0 +1,53 @@ +name: fluwx + +packages: + - packages/fluwx + - packages/fluwx_no_pay + - "!packages/_shared" # _shared 非 pub 包,排除 + +command: + version: + linkToCommits: false + updateGitTagRefs: true + workspaceChangelog: true + +scripts: + analyze: + run: flutter analyze + exec: + concurrency: 1 + + test: + run: flutter test + exec: + concurrency: 2 + + version:sync: + run: dart tools/sync_version.dart + description: "同步 fluwx_no_pay 版本与 fluwx 一致" + + pod:lint: + run: pod lib lint ios/$MELOS_PACKAGE_NAME.podspec --skip-tests --use-modular-headers + exec: + concurrency: 1 + packageFilters: + fileExists: "ios/*.podspec" + + symlinks:check: + run: | + test -L packages/fluwx_no_pay/lib || (echo "❌ fluwx_no_pay/lib symlink missing" && exit 1) + test -L packages/fluwx/ios/fluwx/Sources/fluwx || (echo "❌ fluwx iOS Sources symlink missing" && exit 1) + test -L packages/fluwx_no_pay/ios/fluwx_no_pay/Sources/fluwx_no_pay || (echo "❌ fluwx_no_pay iOS Sources symlink missing" && exit 1) + test -L packages/fluwx/android/src/main/kotlin || (echo "❌ fluwx Android kotlin symlink missing" && exit 1) + test -L packages/fluwx_no_pay/android/src/main/kotlin || (echo "❌ fluwx_no_pay Android kotlin symlink missing" && exit 1) + test -L packages/fluwx/example/lib/pages || (echo "❌ fluwx example pages symlink missing" && exit 1) + test -L packages/fluwx_no_pay/example/lib/pages || (echo "❌ fluwx_no_pay example pages symlink missing" && exit 1) + test -L packages/fluwx/example/images || (echo "❌ fluwx example images symlink missing" && exit 1) + test -L packages/fluwx_no_pay/example/images || (echo "❌ fluwx_no_pay example images symlink missing" && exit 1) + test -L packages/fluwx/ohos || (echo "❌ fluwx/ohos symlink missing" && exit 1) + test -L packages/fluwx_no_pay/ohos || (echo "❌ fluwx_no_pay/ohos symlink missing" && exit 1) + echo "✅ 所有 symlink 完好" + + publish:all: + run: melos publish --no-dry-run --yes + description: "先发 fluwx,再发 fluwx_no_pay" diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/FluwxFileProvider.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/FluwxFileProvider.kt new file mode 100644 index 00000000..c7a8d008 --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/FluwxFileProvider.kt @@ -0,0 +1,10 @@ +package com.jarvan.fluwx + +import androidx.core.content.FileProvider + +/*** + * Created by mo on 2020/5/13 + * 冷风如刀,以大地为砧板,视众生为鱼肉。 + * 万里飞雪,将穹苍作烘炉,熔万物为白银。 + **/ +class FluwxFileProvider: FileProvider() \ No newline at end of file diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/FluwxPlugin.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/FluwxPlugin.kt new file mode 100644 index 00000000..e43c4f51 --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/FluwxPlugin.kt @@ -0,0 +1,541 @@ +package com.jarvan.fluwx + +import android.content.Context +import android.content.Intent +import com.jarvan.fluwx.handlers.FluwxAuthHandler +import com.jarvan.fluwx.handlers.FluwxRequestHandler +import com.jarvan.fluwx.handlers.FluwxShareHandler +import com.jarvan.fluwx.handlers.FluwxShareHandlerEmbedding +import com.jarvan.fluwx.handlers.WXAPiHandler +import com.jarvan.fluwx.utils.WXApiUtils +import com.jarvan.fluwx.utils.readWeChatCallbackIntent +import com.tencent.mm.opensdk.modelbase.BaseReq +import com.tencent.mm.opensdk.modelbase.BaseResp +import com.tencent.mm.opensdk.modelbiz.ChooseCardFromWXCardPackage +import com.tencent.mm.opensdk.modelbiz.OpenRankList +import com.tencent.mm.opensdk.modelbiz.OpenWebview +import com.tencent.mm.opensdk.modelbiz.SubscribeMessage +import com.tencent.mm.opensdk.modelbiz.WXLaunchMiniProgram +import com.tencent.mm.opensdk.modelbiz.WXOpenBusinessView +import com.tencent.mm.opensdk.modelbiz.WXOpenBusinessWebview +import com.tencent.mm.opensdk.modelbiz.WXOpenCustomerServiceChat +import com.tencent.mm.opensdk.modelmsg.LaunchFromWX +import com.tencent.mm.opensdk.modelmsg.SendAuth +import com.tencent.mm.opensdk.modelmsg.SendMessageToWX +import com.tencent.mm.opensdk.modelmsg.ShowMessageFromWX +import com.tencent.mm.opensdk.modelpay.PayReq +import com.tencent.mm.opensdk.modelpay.PayResp +import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler +import com.tencent.mm.opensdk.openapi.SendReqCallback +import com.tencent.mm.opensdk.utils.ILog +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry +import java.util.concurrent.atomic.AtomicBoolean + + +/** FluwxPlugin */ +class FluwxPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, + PluginRegistry.NewIntentListener, IWXAPIEventHandler { + companion object { + // 主动获取的启动参数 + var extMsg: String? = null + } + + private val errStr = "errStr" + private val errCode = "errCode" + private val openId = "openId" + private val type = "type" + + private val weChatLogger = object : ILog { + + override fun d(p0: String?, p1: String?) { + logToFlutter(p0, p1) + } + + override fun i(p0: String?, p1: String?) { + logToFlutter(p0, p1) + } + + override fun e(p0: String?, p1: String?) { + logToFlutter(p0, p1) + } + + override fun v(p0: String?, p1: String?) { + logToFlutter(p0, p1) + } + + override fun w(p0: String?, p1: String?) { + logToFlutter(p0, p1) + } + } + private var shareHandler: FluwxShareHandler? = null + + private var authHandler: FluwxAuthHandler? = null + + private var fluwxChannel: MethodChannel? = null + + private var context: Context? = null + private val attemptToResumeMsgFromWxFlag = AtomicBoolean(false) + + private var activityPluginBinding: ActivityPluginBinding? = null + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + val channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.jarvanmo/fluwx") + channel.setMethodCallHandler(this) + fluwxChannel = channel + context = flutterPluginBinding.applicationContext + authHandler = FluwxAuthHandler(channel) + shareHandler = FluwxShareHandlerEmbedding( + flutterPluginBinding.flutterAssets, flutterPluginBinding.applicationContext + ) + } + + override fun onMethodCall(call: MethodCall, result: Result) { + when { + call.method == "registerApp" -> { + WXAPiHandler.registerApp(call, result, context) + if (FluwxConfigurations.enableLogging) { + WXAPiHandler.wxApi?.setLogImpl(weChatLogger) + } + } + + call.method == "sendAuth" -> authHandler?.sendAuth(call, result) + call.method == "authByQRCode" -> authHandler?.authByQRCode(call, result) + call.method == "stopAuthByQRCode" -> authHandler?.stopAuthByQRCode(result) + call.method == "payWithFluwx" -> pay(call, result) + call.method == "payWithHongKongWallet" -> payWithHongKongWallet(call, result) + call.method == "launchMiniProgram" -> launchMiniProgram(call, result) + call.method == "subscribeMsg" -> subScribeMsg(call, result) + call.method == "autoDeduct" -> signAutoDeduct(call, result) + call.method == "autoDeductV2" -> autoDeductV2(call, result) + call.method == "openWXApp" -> openWXApp(result) + call.method.startsWith("share") -> shareHandler?.share(call, result) + call.method == "isWeChatInstalled" -> WXAPiHandler.checkWeChatInstallation(result) + call.method == "getExtMsg" -> getExtMsg(result) + call.method == "openWeChatCustomerServiceChat" -> openWeChatCustomerServiceChat( + call, result + ) + + call.method == "checkSupportOpenBusinessView" -> WXAPiHandler.checkSupportOpenBusinessView( + result + ) + + call.method == "openBusinessView" -> openBusinessView(call, result) + + call.method == "openWeChatInvoice" -> openWeChatInvoice(call, result) + call.method == "openUrl" -> openUrl(call, result) + call.method == "openRankList" -> openRankList(result) + call.method == "attemptToResumeMsgFromWx" -> attemptToResumeMsgFromWx(result) + call.method == "selfCheck" -> result.success(null) + else -> result.notImplemented() + } + } + + private fun attemptToResumeMsgFromWx(result: Result) { + if (attemptToResumeMsgFromWxFlag.compareAndSet(false, true)) { + activityPluginBinding?.activity?.intent?.let { + letWeChatHandleIntent(it) + } + + } + result.success(null) + } + + private fun openWeChatInvoice(call: MethodCall, result: Result) { + if (WXAPiHandler.wxApi == null) { + result.error("Unassigned WxApi", "please config wxapi first", null) + return + } else { + //android 只有ChooseCard, IOS直接有ChooseInvoice + val request = ChooseCardFromWXCardPackage.Req() + request.cardType = call.argument("cardType") + request.appId = call.argument("appId") + request.locationId = call.argument("locationId") + request.cardId = call.argument("cardId") + request.canMultiSelect = call.argument("canMultiSelect") + + request.timeStamp = System.currentTimeMillis().toString() + request.nonceStr = System.currentTimeMillis().toString() + request.signType = "SHA1" + request.cardSign = WXApiUtils.createSign( + request.appId, request.nonceStr, request.timeStamp, request.cardType + ) + val done = WXAPiHandler.wxApi?.sendReq(request) + result.success(done) + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + shareHandler?.onDestroy() + authHandler?.removeAllListeners() + activityPluginBinding = null + } + + override fun onDetachedFromActivity() { + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + onAttachedToActivity(binding) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { +// WXAPiHandler.setContext(binding.activity.applicationContext) + activityPluginBinding = binding + binding.addOnNewIntentListener(this) + } + + override fun onDetachedFromActivityForConfigChanges() { + } + + + private fun getExtMsg(result: Result) { + result.success(extMsg) + extMsg = null + } + + private fun pay(call: MethodCall, result: Result) { + + if (WXAPiHandler.wxApi == null) { + result.error("Unassigned WxApi", "please config wxapi first", null) + return + } else { + // 将该app注册到微信 + val request = PayReq() + request.appId = call.argument("appId") + request.partnerId = call.argument("partnerId") + request.prepayId = call.argument("prepayId") + request.packageValue = call.argument("packageValue") + request.nonceStr = call.argument("nonceStr") + request.timeStamp = call.argument("timeStamp").toString() + request.sign = call.argument("sign") + request.signType = call.argument("signType") + request.extData = call.argument("extData") + val done = WXAPiHandler.wxApi?.sendReq(request) + result.success(done) + } + } + + private fun payWithHongKongWallet(call: MethodCall, result: Result) { + val prepayId = call.argument("prepayId") ?: "" + val request = WXOpenBusinessWebview.Req() + request.businessType = 1 + request.queryInfo = hashMapOf( + "token" to prepayId + ) + result.success(WXAPiHandler.wxApi?.sendReq(request)) + } + + private fun openWeChatCustomerServiceChat(call: MethodCall, result: Result) { + val url = call.argument("url") ?: "" + val corpId = call.argument("corpId") ?: "" + val request = WXOpenCustomerServiceChat.Req() + request.corpId = corpId // 企业ID + + request.url = url + result.success(WXAPiHandler.wxApi?.sendReq(request)) + } + + private fun openBusinessView(call: MethodCall, result: Result) { + val request = WXOpenBusinessView.Req() + request.businessType = call.argument("businessType") ?: "" + request.query = call.argument("query") ?: "" + request.extInfo = "{\"miniProgramType\": 0}" + result.success(WXAPiHandler.wxApi?.sendReq(request)) + } + + private fun signAutoDeduct(call: MethodCall, result: Result) { + val appId: String = call.argument("appid") ?: "" + val mchId = call.argument("mch_id") ?: "" + val planId = call.argument("plan_id") ?: "" + val contractCode = call.argument("contract_code") ?: "" + val requestSerial = call.argument("request_serial") ?: "" + val contractDisplayAccount = call.argument("contract_display_account") ?: "" + val notifyUrl = call.argument("notify_url") ?: "" + val version = call.argument("version") ?: "" + val sign = call.argument("sign") ?: "" + val timestamp = call.argument("timestamp") ?: "" + val returnApp = call.argument("return_app") ?: "" + val businessType = call.argument("businessType") ?: 12 + + val req = WXOpenBusinessWebview.Req() + req.businessType = businessType + req.queryInfo = hashMapOf( + "appid" to appId, + "mch_id" to mchId, + "plan_id" to planId, + "contract_code" to contractCode, + "request_serial" to requestSerial, + "contract_display_account" to contractDisplayAccount, + "notify_url" to notifyUrl, + "version" to version, + "sign" to sign, + "timestamp" to timestamp, + "return_app" to returnApp + ) + result.success(WXAPiHandler.wxApi?.sendReq(req)) + } + + private fun autoDeductV2(call: MethodCall, result: Result) { + val businessType = call.argument("businessType") ?: 12 + + val req = WXOpenBusinessWebview.Req() + req.businessType = businessType + req.queryInfo = call.argument>("queryInfo") ?: hashMapOf() + + result.success(WXAPiHandler.wxApi?.sendReq(req)) + } + + private fun subScribeMsg(call: MethodCall, result: Result) { + val appId = call.argument("appId") + val scene = call.argument("scene") + val templateId = call.argument("templateId") + val reserved = call.argument("reserved") + + val req = SubscribeMessage.Req() + req.openId = appId + req.scene = scene!! + req.reserved = reserved + req.templateID = templateId + val b = WXAPiHandler.wxApi?.sendReq(req) + result.success(b) + } + + private fun launchMiniProgram(call: MethodCall, result: Result) { + val req = WXLaunchMiniProgram.Req() + req.userName = call.argument("userName") // 填小程序原始id + req.path = call.argument("path") ?: "" //拉起小程序页面的可带参路径,不填默认拉起小程序首页 + val type = call.argument("miniProgramType") ?: 0 + req.miniprogramType = when (type) { + 1 -> WXLaunchMiniProgram.Req.MINIPROGRAM_TYPE_TEST + 2 -> WXLaunchMiniProgram.Req.MINIPROGRAM_TYPE_PREVIEW + else -> WXLaunchMiniProgram.Req.MINIPTOGRAM_TYPE_RELEASE + }// 可选打开 开发版,体验版和正式版 + val done = WXAPiHandler.wxApi?.sendReq(req) + result.success(done) + } + + private fun openWXApp(result: Result) = result.success(WXAPiHandler.wxApi?.openWXApp()) + + private fun openUrl(call: MethodCall, result: Result) { + val req = OpenWebview.Req() + req.url = call.argument("url") + WXAPiHandler.wxApi?.sendReq(req, SendReqCallback { + result.success(it) + }) ?: kotlin.run { + result.success(false) + } + } + + private fun openRankList(result: Result) { + val req = OpenRankList.Req() + WXAPiHandler.wxApi?.sendReq(req, SendReqCallback { + result.success(it) + }) ?: kotlin.run { + result.success(false) + } + } + + override fun onNewIntent(intent: Intent): Boolean { + return letWeChatHandleIntent(intent) + } + + private fun letWeChatHandleIntent(intent: Intent): Boolean = + intent.readWeChatCallbackIntent()?.let { + WXAPiHandler.wxApi?.handleIntent(it, this) ?: false + } ?: run { + false + } + + override fun onReq(req: BaseReq?) { + activityPluginBinding?.activity?.let { activity -> + req?.let { + if (FluwxConfigurations.interruptWeChatRequestByFluwx) { + when (req) { + is ShowMessageFromWX.Req -> handleShowMessageFromWX(req) + is LaunchFromWX.Req -> handleLaunchFromWX(req) + else -> {} + } + } else { + FluwxRequestHandler.customOnReqDelegate?.invoke(req, activity) + } + } + } + } + + + private fun handleShowMessageFromWX(req: ShowMessageFromWX.Req) { + val result = mapOf( + "extMsg" to req.message.messageExt, + "messageAction" to req.message.messageAction, + "description" to req.message.description, + "lang" to req.lang, + "description" to req.country, + ) + + extMsg = req.message.messageExt + fluwxChannel?.invokeMethod("onWXShowMessageFromWX", result) + } + + private fun handleLaunchFromWX(req: LaunchFromWX.Req) { + val result = mapOf( + "extMsg" to req.messageExt, + "messageAction" to req.messageAction, + "lang" to req.lang, + "country" to req.country, + ) + extMsg = req.messageExt + + fluwxChannel?.invokeMethod("onWXLaunchFromWX", result) + } + + override fun onResp(response: BaseResp?) { + when (response) { + is SendAuth.Resp -> handleAuthResponse(response) + is SendMessageToWX.Resp -> handleSendMessageResp(response) + is PayResp -> handlePayResp(response) + is WXLaunchMiniProgram.Resp -> handleLaunchMiniProgramResponse(response) + is SubscribeMessage.Resp -> handleSubscribeMessage(response) + is WXOpenBusinessWebview.Resp -> handlerWXOpenBusinessWebviewResponse(response) + is WXOpenCustomerServiceChat.Resp -> handlerWXOpenCustomerServiceChatResponse(response) + is WXOpenBusinessView.Resp -> handleWXOpenBusinessView(response) + is ChooseCardFromWXCardPackage.Resp -> handleWXOpenInvoiceResponse(response) + else -> {} + } + } + + private fun handleWXOpenInvoiceResponse(response: ChooseCardFromWXCardPackage.Resp) { + val result = mapOf( + "cardItemList" to response.cardItemList, + "transaction" to response.transaction, + "openid" to response.openId, + errStr to response.errStr, + type to response.type, + errCode to response.errCode + ) + + fluwxChannel?.invokeMethod("onOpenWechatInvoiceResponse", result) + } + + private fun handleWXOpenBusinessView(response: WXOpenBusinessView.Resp) { + val result = mapOf( + "openid" to response.openId, + "extMsg" to response.extMsg, + "businessType" to response.businessType, + errStr to response.errStr, + type to response.type, + errCode to response.errCode + ) + + fluwxChannel?.invokeMethod("onOpenBusinessViewResponse", result) + } + + private fun handleSubscribeMessage(response: SubscribeMessage.Resp) { + val result = mapOf( + "openid" to response.openId, + "templateId" to response.templateID, + "action" to response.action, + "reserved" to response.reserved, + "scene" to response.scene, + type to response.type + ) + + fluwxChannel?.invokeMethod("onSubscribeMsgResp", result) + } + + private fun handleLaunchMiniProgramResponse(response: WXLaunchMiniProgram.Resp) { + val result = mutableMapOf( + errStr to response.errStr, + type to response.type, + errCode to response.errCode, + openId to response.openId + ) + + response.extMsg?.let { + result["extMsg"] = response.extMsg + } + + fluwxChannel?.invokeMethod("onLaunchMiniProgramResponse", result) + } + + private fun handlePayResp(response: PayResp) { + val result = mapOf( + "prepayId" to response.prepayId, + "returnKey" to response.returnKey, + "extData" to response.extData, + errStr to response.errStr, + type to response.type, + errCode to response.errCode + ) + fluwxChannel?.invokeMethod("onPayResponse", result) + } + + private fun handleSendMessageResp(response: SendMessageToWX.Resp) { + val result = mapOf( + errStr to response.errStr, + type to response.type, + errCode to response.errCode, + openId to response.openId + ) + + fluwxChannel?.invokeMethod("onShareResponse", result) + } + + private fun handleAuthResponse(response: SendAuth.Resp) { + val result = mapOf( + errCode to response.errCode, + "code" to response.code, + "state" to response.state, + "lang" to response.lang, + "country" to response.country, + errStr to response.errStr, + openId to response.openId, + "url" to response.url, + type to response.type + ) + + fluwxChannel?.invokeMethod("onAuthResponse", result) + } + + + private fun handlerWXOpenBusinessWebviewResponse(response: WXOpenBusinessWebview.Resp) { + val result = mapOf( + errCode to response.errCode, + "businessType" to response.businessType, + "resultInfo" to response.resultInfo, + errStr to response.errStr, + openId to response.openId, + type to response.type + ) + + fluwxChannel?.invokeMethod("onWXOpenBusinessWebviewResponse", result) + } + + private fun handlerWXOpenCustomerServiceChatResponse(response: WXOpenCustomerServiceChat.Resp) { + val result = mapOf( + errCode to response.errCode, + errStr to response.errStr, + openId to response.openId, + type to response.type + ) + + fluwxChannel?.invokeMethod("onWXOpenCustomerServiceChatResponse", result) + } + + private fun logToFlutter(tag: String?, message: String?) { + activityPluginBinding?.activity?.runOnUiThread { + fluwxChannel?.invokeMethod( + "wechatLog", mapOf( + "detail" to "$tag : $message" + ) + ) + } + } + + +} diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/FluwxAuthHandler.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/FluwxAuthHandler.kt new file mode 100644 index 00000000..2e70f231 --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/FluwxAuthHandler.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 The OpenFlutter Organization + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jarvan.fluwx.handlers + +import com.tencent.mm.opensdk.diffdev.DiffDevOAuthFactory +import com.tencent.mm.opensdk.diffdev.OAuthErrCode +import com.tencent.mm.opensdk.diffdev.OAuthListener +import com.tencent.mm.opensdk.modelmsg.SendAuth +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +internal class FluwxAuthHandler(private val methodChannel: MethodChannel) { + // private DiffDevOAuthFactory.getDiffDevOAuth() + private val qrCodeAuth by lazy { + DiffDevOAuthFactory.getDiffDevOAuth() + } + + private val qrCodeAuthListener by lazy { + object : OAuthListener { + override fun onAuthFinish(p0: OAuthErrCode, authCode: String?) { + methodChannel.invokeMethod("onAuthByQRCodeFinished", mapOf( + "errCode" to p0.code, + "authCode" to authCode + )) + } + + override fun onAuthGotQrcode(p0: String?, p1: ByteArray) { + methodChannel.invokeMethod("onAuthGotQRCode", mapOf("errCode" to 0, "qrCode" to p1)) + } + + override fun onQrcodeScanned() { + methodChannel.invokeMethod("onQRCodeScanned", mapOf("errCode" to 0)) + } + + } + } + + + fun sendAuth(call: MethodCall, result: MethodChannel.Result) { + val req = SendAuth.Req() + req.scope = call.argument("scope") + req.state = call.argument("state") + val openId = call.argument("openId") + if (!openId.isNullOrBlank()) { + req.openId = call.argument("openId") + } + req.nonAutomatic = call.argument("nonAutomatic") ?: false + result.success(WXAPiHandler.wxApi?.sendReq(req)) + } + + + fun authByQRCode(call: MethodCall, result: MethodChannel.Result) { + val appId = call.argument("appId") ?: "" + val scope = call.argument("scope") ?: "" + val nonceStr = call.argument("nonceStr") ?: "" + val timeStamp = call.argument("timeStamp") ?: "" + val signature = call.argument("signature") ?: "" +// val schemeData = call.argument("schemeData")?:"" + + result.success(qrCodeAuth.auth(appId, scope, nonceStr, timeStamp, signature, qrCodeAuthListener)) + } + + fun stopAuthByQRCode(result: MethodChannel.Result) { + result.success(qrCodeAuth.stopAuth()) + } + + fun removeAllListeners() { + qrCodeAuth.removeAllListeners() + } +} \ No newline at end of file diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/FluwxRequestHandler.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/FluwxRequestHandler.kt new file mode 100644 index 00000000..7968e122 --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/FluwxRequestHandler.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 The OpenFlutter Organization + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jarvan.fluwx.handlers + +import android.app.Activity +import com.tencent.mm.opensdk.modelbase.BaseReq + + +object FluwxRequestHandler { + + var customOnReqDelegate: ((baseReq: BaseReq, activity: Activity) -> Unit)? = null +} \ No newline at end of file diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/FluwxShareHandler.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/FluwxShareHandler.kt new file mode 100644 index 00000000..0df3632c --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/FluwxShareHandler.kt @@ -0,0 +1,400 @@ +package com.jarvan.fluwx.handlers + +import android.content.Context +import android.content.Intent +import android.content.res.AssetFileDescriptor +import android.net.Uri +import androidx.core.content.FileProvider +import com.jarvan.fluwx.io.* +import com.tencent.mm.opensdk.modelbase.BaseReq +import com.tencent.mm.opensdk.modelmsg.* +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlinx.coroutines.* +import java.io.File +import java.io.IOException +import java.util.* +import kotlin.coroutines.CoroutineContext +import android.os.Build +import io.flutter.plugin.common.MethodChannel.Result + + +/*** + * Created by mo on 2020/3/6 + * 冷风如刀,以大地为砧板,视众生为鱼肉。 + * 万里飞雪,将穹苍作烘炉,熔万物为白银。 + **/ +internal class FluwxShareHandlerEmbedding( + private val flutterAssets: FlutterPlugin.FlutterAssets, + override val context: Context +) : FluwxShareHandler { + override val assetFileDescriptor: (String) -> AssetFileDescriptor = { + val uri = Uri.parse(it) + val packageName = uri.getQueryParameter("package") + val subPath = if (packageName.isNullOrBlank()) { + flutterAssets.getAssetFilePathBySubpath(uri.path.orEmpty()) + } else { + flutterAssets.getAssetFilePathBySubpath(uri.path.orEmpty(), packageName) + } + context.assets.openFd(subPath) + } + + override val job: Job = Job() + +} + +internal interface FluwxShareHandler : CoroutineScope { + companion object { + const val SHARE_IMAGE_THUMB_LENGTH = 32 * 1024 + const val SHARE_MINI_PROGRAM_THUMB_LENGTH = 120 * 1024 + private const val keyTitle = "title" + private const val keyThumbnail = "thumbnail" + private const val keyDescription = "description" + } + + fun share(call: MethodCall, result: MethodChannel.Result) { + if (WXAPiHandler.wxApi == null) { + result.error("Unassigned WxApi", "please config wxapi first", null) + return + } + + when (call.method) { + "shareText" -> shareText(call, result) + "shareMiniProgram" -> shareMiniProgram(call, result) + "shareImage" -> shareImage(call, result) + "shareMusic" -> shareMusic(call, result) + "shareVideo" -> shareVideo(call, result) + "shareWebPage" -> shareWebPage(call, result) + "shareFile" -> shareFile(call, result) + "shareEmoji" -> shareEmoji(call, result) + else -> { + result.notImplemented() + } + } + } + + private fun shareText(call: MethodCall, result: MethodChannel.Result) { + val textObj = WXTextObject(call.argument("source")) + val msg = WXMediaMessage() + msg.mediaObject = textObj + val req = SendMessageToWX.Req() + setCommonArguments(call, req, msg) + req.message = msg + result.success(WXAPiHandler.wxApi?.sendReq(req)) + } + + + private fun shareMiniProgram(call: MethodCall, result: MethodChannel.Result) { + val miniProgramObj = WXMiniProgramObject() + miniProgramObj.webpageUrl = call.argument("webPageUrl") // 兼容低版本的网页链接 + miniProgramObj.miniprogramType = call.argument("miniProgramType") ?: 0// 正式版:0,测试版:1,体验版:2 + miniProgramObj.userName = call.argument("userName") // 小程序原始id + miniProgramObj.path = call.argument("path") //小程序页面路径 + miniProgramObj.withShareTicket = call.argument("withShareTicket") ?: true + val msg = WXMediaMessage(miniProgramObj) + msg.title = call.argument(keyTitle) // 小程序消息title + msg.description = call.argument(keyDescription) // 小程序消息desc + + launch { + val req = SendMessageToWX.Req() + setCommonArguments(call, req, msg) + req.message = msg + sendRequestInMain(result, req) + } + } + + private fun shareImage(call: MethodCall, result: MethodChannel.Result) { + launch { + val map: Map = call.argument("source") ?: mapOf() + + val imgHash = call.argument("imgDataHash") + val localImagePath = map["localImagePath"] as? String + val imageObject = localImagePath?.let { + WXImageObject().apply { + if (supportFileProvider && targetHigherThanN) { + if (localImagePath.startsWith("content://")) { + imagePath = localImagePath + } else { + val tempFile = File(localImagePath) + val ecd = context.externalCacheDir ?: return@apply + val desPath = + ecd.absolutePath + File.separator + cachePathName + if (tempFile.exists()) { + val target = if (isFileInDirectory( + file = tempFile, + directory = File(desPath) + ) + ) { + tempFile + } else { + withContext(Dispatchers.IO) { + copyFile(tempFile.absolutePath, desPath) + } + } + + imagePath = getFileContentUri(target) + } + } + } else { + imagePath = localImagePath + } + + imgDataHash = imgHash + } + } ?: run { + WXImageObject().apply { + val uint8List = map["uint8List"] as? ByteArray + uint8List?.let { + imageData = it + imgDataHash = imgHash + } + } + } + + + val msg = WXMediaMessage() + msg.mediaObject = imageObject + + msg.description = call.argument(keyDescription) + + val req = SendMessageToWX.Req() + setCommonArguments(call, req, msg) + req.message = msg + + sendRequestInMain(result, req) + } + } + + + private fun shareMusic(call: MethodCall, result: MethodChannel.Result) { + val music = WXMusicObject() + val musicUrl: String? = call.argument("musicUrl") + val musicLowBandUrl: String? = call.argument("musicLowBandUrl") + if (musicUrl != null && musicUrl.isNotBlank()) { + music.musicUrl = musicUrl + music.musicDataUrl = call.argument("musicDataUrl") + } else { + music.musicLowBandUrl = musicLowBandUrl + music.musicLowBandDataUrl = call.argument("musicLowBandDataUrl") + } + val msg = WXMediaMessage() + msg.mediaObject = music + msg.description = call.argument(keyDescription) + + launch { + val req = SendMessageToWX.Req() + setCommonArguments(call, req, msg) + req.message = msg + sendRequestInMain(result, req) + } + } + + private fun shareVideo(call: MethodCall, result: MethodChannel.Result) { + val video = WXVideoObject() + val videoUrl: String? = call.argument("videoUrl") + val videoLowBandUrl: String? = call.argument("videoLowBandUrl") + if (videoUrl != null && videoUrl.isNotBlank()) { + video.videoUrl = videoUrl + } else { + video.videoLowBandUrl = videoLowBandUrl + } + val msg = WXMediaMessage() + msg.mediaObject = video + msg.description = call.argument(keyDescription) + + launch { + val req = SendMessageToWX.Req() + setCommonArguments(call, req, msg) + req.message = msg + + sendRequestInMain(result, req) + } + } + + private fun shareWebPage(call: MethodCall, result: MethodChannel.Result) { + val webPage = WXWebpageObject() + webPage.webpageUrl = call.argument("webPage") + val msg = WXMediaMessage() + + msg.mediaObject = webPage + msg.description = call.argument(keyDescription) + + launch { + val req = SendMessageToWX.Req() + setCommonArguments(call, req, msg) + req.message = msg + sendRequestInMain(result, req) + } + } + + private fun shareFile(call: MethodCall, result: MethodChannel.Result) { + launch { + + val wxFileObject = WXFileObject() +// val filePath: String? = call.argument("filePath") +// wxFileObject.filePath = filePath + + val msg = WXMediaMessage() + msg.mediaObject = wxFileObject + msg.description = call.argument("description") + + val map: Map = call.argument("source") ?: mapOf() + val sourceFile = WeChatFile.createWeChatFile(map, assetFileDescriptor) + + val sourceByteArray = sourceFile.readByteArray() + + wxFileObject.apply { + if (supportFileProvider && targetHigherThanN) { + setFilePath( + getFileContentUri( + sourceByteArray.toCacheFile( + context, + sourceFile.suffix + ) + ) + ) + } else { + filePath = sourceByteArray.toExternalCacheFile( + context, + sourceFile.suffix + )?.absolutePath + } + } + + val req = SendMessageToWX.Req() + setCommonArguments(call, req, msg) + req.message = msg + sendRequestInMain(result, req) + } + } + + private fun shareEmoji(call: MethodCall, result: Result) { + + val scene = call.argument("scene") ?: 0 + val title = call.argument("title") + val desc = call.argument("description") + val thumbData = call.argument("thumbData") + + val emojiMap = call.argument>("source") + ?: run { result.error("ARG", "emoji is null", null); return } + + val emojiObj = WXEmojiObject().apply { + when { + emojiMap["uint8List"] != null -> emojiData = emojiMap["uint8List"] as ByteArray + emojiMap["path"] != null -> emojiPath = ensurePublicPath(emojiMap["path"] as String) + else -> { result.error("ARG", "gif source missing", null); return } + } + } + + val msg = WXMediaMessage(emojiObj).apply { + this.thumbData = thumbData + this.title = title + this.description = desc + } + + val req = SendMessageToWX.Req().apply { + transaction = "emoji${System.currentTimeMillis()}" + message = msg + this.scene = scene + } + + result.success(WXAPiHandler.wxApi?.sendReq(req)) + } + + private suspend fun sendRequestInMain(result: MethodChannel.Result, request: BaseReq) = + withContext(Dispatchers.Main) { + result.success(WXAPiHandler.wxApi?.sendReq(request)) + } + + private suspend fun compressThumbnail(ioIml: ImagesIO, length: Int) = + ioIml.compressedByteArray(context, length) + + // SESSION, TIMELINE, FAVORITE + private fun setCommonArguments( + call: MethodCall, + req: SendMessageToWX.Req, + msg: WXMediaMessage + ) { + msg.messageAction = call.argument("messageAction") + call.argument("msgSignature")?.let { + msg.msgSignature = it + } + call.argument("thumbData")?.let { + msg.thumbData = it + } + + call.argument("thumbDataHash")?.let { + msg.thumbDataHash = it + } + + msg.messageExt = call.argument("messageExt") + msg.mediaTagName = call.argument("mediaTagName") + msg.title = call.argument(keyTitle) + msg.description = call.argument(keyDescription) + req.transaction = UUID.randomUUID().toString().replace("-", "") + val sceneIndex = call.argument("scene") + req.scene = when (sceneIndex) { + 0 -> SendMessageToWX.Req.WXSceneSession + 1 -> SendMessageToWX.Req.WXSceneTimeline + 2 -> SendMessageToWX.Req.WXSceneFavorite + else -> SendMessageToWX.Req.WXSceneSession + } + } + + private fun getFileContentUri(file: File?): String? { + if (file == null || !file.exists()) + return null + + val contentUri = FileProvider.getUriForFile( + context, + "${context.packageName}.fluwxprovider", // 要与`AndroidManifest.xml`里配置的`authorities`一致,假设你的应用包名为com.example.app + file + ) + + // 授权给微信访问路径 + context.grantUriPermission( + "com.tencent.mm", // 这里填微信包名 + contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + + return contentUri.toString() // contentUri.toString() 即是以"content://"开头的用于共享的路径 + + } + + private fun loadBytesFromFlutterAsset(assetKey: String): ByteArray = + context.assets.open(assetKey.removePrefix("flutterassets/")).use { it.readBytes() } + + private fun ensurePublicPath(original: String): String { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return original + val src = File(original) + val dst = File(context.externalCacheDir, src.name) + if (!dst.exists()) src.copyTo(dst, overwrite = true) + return dst.path + } + + private val supportFileProvider: Boolean + get() = (WXAPiHandler.wxApi?.wxAppSupportAPI ?: 0) >= 0x27000D00 + private val targetHigherThanN: Boolean get() = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N + + val context: Context + + val assetFileDescriptor: (String) -> AssetFileDescriptor + + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + job + + val job: Job + + fun onDestroy() = job.cancel() +} + +private fun isFileInDirectory(file: File, directory: File): Boolean { + return try { + val filePath = file.canonicalPath + val dirPath = directory.canonicalPath + filePath.startsWith(dirPath) + } catch (e: IOException) { + false + } +} \ No newline at end of file diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/PermissionHandler.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/PermissionHandler.kt new file mode 100644 index 00000000..e359bfe9 --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/PermissionHandler.kt @@ -0,0 +1,39 @@ +package com.jarvan.fluwx.handlers + +import android.Manifest +import android.app.Activity +import android.app.Fragment +import android.os.Build + +/*** + * Created by mo on 2020/3/27 + * 冷风如刀,以大地为砧板,视众生为鱼肉。 + * 万里飞雪,将穹苍作烘炉,熔万物为白银。 + **/ +class PermissionHandler(private val activity: Activity?) { + private val tag = "Fragment_TAG" + private val fragment: Fragment = Fragment() + + fun requestStoragePermission() { + if (oldFragment != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + oldFragment?.requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 12121) + } + } else { + activity?.run { + val ft = fragmentManager.beginTransaction() + ft.add(fragment, tag) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + ft.commitNow() + } else { + ft.commit() + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + fragment.requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 12121) + } + } + } + } + + private val oldFragment get() = activity?.fragmentManager?.findFragmentByTag(tag) +} \ No newline at end of file diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/WXAPiHandler.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/WXAPiHandler.kt new file mode 100644 index 00000000..700e29bd --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/handlers/WXAPiHandler.kt @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2020. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.jarvan.fluwx.handlers + +import android.content.Context +import android.content.pm.PackageManager +import android.util.Log +import com.jarvan.fluwx.FluwxPlugin +import com.tencent.mm.opensdk.constants.Build +import com.tencent.mm.opensdk.openapi.IWXAPI +import com.tencent.mm.opensdk.openapi.WXAPIFactory +import com.tencent.mm.opensdk.utils.ILog +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +object WXAPiHandler { + + var wxApi: IWXAPI? = null + +// private var context: Context? = null + + private var registered: Boolean = false + + val wxApiRegistered get() = registered + + //是否为冷启动 + var coolBoot: Boolean = false + + fun setupWxApi(appId: String, context: Context, force: Boolean = true): Boolean { + if (force || !registered) { +// setContext(context) + registerWxAPIInternal(appId, context) + } + return registered + } +// +// fun setContext(context: Context?) { +// WXAPiHandler.context = context +// } + + fun registerApp(call: MethodCall, result: MethodChannel.Result, context: Context?) { + + if (call.argument("android") == false) { + return + } + + if (wxApi != null) { + result.success(true) + return + } + + val appId: String? = call.argument("appId") + if (appId.isNullOrBlank()) { + result.error("invalid app id", "are you sure your app id is correct ?", appId) + return + } + + context?.let { + registerWxAPIInternal(appId, it) + } + result.success(registered) + } + + fun checkWeChatInstallation(result: MethodChannel.Result) { + if (wxApi == null) { + result.error("Unassigned WxApi", "please config wxapi first", null) + return + } else { + result.success(wxApi?.isWXAppInstalled) + } + } + + fun checkSupportOpenBusinessView(result: MethodChannel.Result) { + when { + wxApi == null -> { + result.error("Unassigned WxApi", "please config wxapi first", null) + } + + wxApi?.isWXAppInstalled != true -> { + result.error("WeChat Not Installed", "Please install the WeChat first", null) + } + + (wxApi?.wxAppSupportAPI ?: 0) < Build.OPEN_BUSINESS_VIEW_SDK_INT -> { + result.error("WeChat Not Supported", "Please upgrade the WeChat version", null) + } + + else -> { + result.success(true) + } + } + } + + private fun registerWxAPIInternal(appId: String, context: Context) { + val api = WXAPIFactory.createWXAPI(context.applicationContext, appId) + registered = api.registerApp(appId) + wxApi = api + } + +} + + diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/io/ByteArrayToFile.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/io/ByteArrayToFile.kt new file mode 100644 index 00000000..bbe8a474 --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/io/ByteArrayToFile.kt @@ -0,0 +1,83 @@ +package com.jarvan.fluwx.io + +import android.content.Context +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okio.* +import java.io.* +import java.io.IOException +import java.util.* + +/*** + * Created by mo on 2020/5/13 + * 冷风如刀,以大地为砧板,视众生为鱼肉。 + * 万里飞雪,将穹苍作烘炉,熔万物为白银。 + **/ + +internal const val cachePathName = "fluwxSharedData" + +internal suspend fun ByteArray.toExternalCacheFile(context: Context, suffix: String): File? { + var file: File? = null + val externalFile = context.externalCacheDir ?: return file + val dir = File(externalFile.absolutePath + File.separator + cachePathName).apply { + if (!exists()) { + mkdirs() + } + } + file = File(dir.absolutePath + File.separator + UUID.randomUUID().toString() + suffix) + return saveToLocal(this, file) +} + +internal suspend fun ByteArray.toCacheFile(context: Context, suffix: String): File? { + var file: File? = null + val externalFile = context.cacheDir ?: return file + val dir = File(externalFile.absolutePath + File.separator + cachePathName).apply { + if (!exists()) { + mkdirs() + } + } + file = File(dir.absolutePath + File.separator + UUID.randomUUID().toString() + suffix) + return saveToLocal(this, file) +} + +private suspend fun saveToLocal(byteArray: ByteArray, file: File): File? { + return withContext(Dispatchers.IO) { + + var sink: BufferedSink? = null + var source: Source? = null + var outputStream: OutputStream? = null + + try { + + + outputStream = FileOutputStream(file) + sink = outputStream.sink().buffer() + source = ByteArrayInputStream(byteArray).source() + sink.writeAll(source) + sink.flush() + + } catch (e: IOException) { + Log.w("Fluwx", "failed to create cache files") + } finally { + sink?.close() + source?.close() + outputStream?.close() + } + + file + } +} + +@Throws(IOException::class) +internal fun copyFile(sourceFilePath: String, destinationDirPath: String):File { + val sourceFile = File(sourceFilePath) + val destinationDir = File(destinationDirPath) + + if (!destinationDir.exists()) { + destinationDir.mkdirs() + } + + val destinationFile = File(destinationDir, sourceFile.name) + return sourceFile.copyTo(destinationFile, overwrite = true) +} diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/io/ImagesIO.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/io/ImagesIO.kt new file mode 100644 index 00000000..1405379f --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/io/ImagesIO.kt @@ -0,0 +1,131 @@ +package com.jarvan.fluwx.io + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Bitmap.CompressFormat +import android.graphics.BitmapFactory +import id.zelory.compressor.Compressor +import id.zelory.compressor.constraint.size +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okio.* +import java.io.* +import java.io.IOException +import java.util.* +import kotlin.math.sqrt + +/*** + * Created by mo on 2020/3/7 + * 冷风如刀,以大地为砧板,视众生为鱼肉。 + * 万里飞雪,将穹苍作烘炉,熔万物为白银。 + **/ +class ImagesIOIml(override val image: WeChatFile) : ImagesIO { + + override suspend fun readByteArray(): ByteArray = image.readByteArray() + + override suspend fun compressedByteArray(context: Context, maxSize: Int): ByteArray = + withContext(Dispatchers.IO) { + val originalByteArray = readByteArray() + if (originalByteArray.isEmpty()) + return@withContext originalByteArray + + val originFile = inputStreamToFile(ByteArrayInputStream(originalByteArray)) + if (image.suffix.contains("gif")) { + return@withContext originalByteArray + } + val compressedFile = Compressor.compress(context, originFile) { + size(maxFileSize = maxSize * 1024L) + } + + if (compressedFile.length() < maxSize) { + val source = compressedFile.source() + val bufferedSource = source.buffer() + val bytes = bufferedSource.readByteArray() + source.close() + bufferedSource.close() + bytes + } else { + createScaledBitmapWithRatio(compressedFile, maxSize) + } + } + + private fun inputStreamToFile(inputStream: InputStream): File { + val file = File.createTempFile(UUID.randomUUID().toString(), image.suffix) + + val outputStream: OutputStream = FileOutputStream(file) + val sink = outputStream.sink().buffer() + val source = inputStream.source() + sink.writeAll(source) + source.close() + sink.close() + + return file + } + + private fun createScaledBitmapWithRatio(file: File, maxSize: Int): ByteArray { + val originBitmap = BitmapFactory.decodeFile(file.absolutePath) + val result: Bitmap? = createScaledBitmapWithRatio(originBitmap, maxSize, true) + result ?: return byteArrayOf() + + return bmpToByteArray(result, image.suffix) ?: byteArrayOf() + } + + private fun createScaledBitmapWithRatio( + bitmap: Bitmap, + maxLength: Int, + recycle: Boolean + ): Bitmap? { + var result = bitmap + while (true) { + val ratio = maxLength.toDouble() / result.byteCount + val width = result.width * sqrt(ratio) + val height = result.height * sqrt(ratio) + val tmp = Bitmap.createScaledBitmap(result, width.toInt(), height.toInt(), true) + if (result != bitmap) { + result.recycle() + } + result = tmp + if (result.byteCount <= maxLength) { + break + } + } + if (recycle) { + bitmap.recycle() + } + return result + } + + private fun bmpToByteArray( + bitmap: Bitmap, + suffix: String + ): ByteArray? { // int bytes = bitmap.getByteCount(); + + val byteArrayOutputStream = ByteArrayOutputStream() + var format = CompressFormat.PNG + if (suffix.lowercase(Locale.US) == ".jpg" || suffix.lowercase(Locale.US) == ".jpeg") { + format = CompressFormat.JPEG + } + bitmap.compress(format, 100, byteArrayOutputStream) + val inputStream: InputStream = ByteArrayInputStream(byteArrayOutputStream.toByteArray()) + var result: ByteArray? = null + + bitmap.recycle() + + val source = inputStream.source() + val bufferedSource = source.buffer() + try { + result = bufferedSource.readByteArray() + source.close() + bufferedSource.close() + } catch (e: IOException) { + e.printStackTrace() + } + return result + } +} + +interface ImagesIO { + val image: WeChatFile + suspend fun readByteArray(): ByteArray + suspend fun compressedByteArray(context: Context, maxSize: Int): ByteArray +} diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/io/WeChatFiles.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/io/WeChatFiles.kt new file mode 100644 index 00000000..1aa76505 --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/io/WeChatFiles.kt @@ -0,0 +1,141 @@ +package com.jarvan.fluwx.io + +import android.content.res.AssetFileDescriptor +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import okio.BufferedSource +import okio.buffer +import okio.source +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException + +/*** + * Created by mo on 2020/5/13 + * 冷风如刀,以大地为砧板,视众生为鱼肉。 + * 万里飞雪,将穹苍作烘炉,熔万物为白银。 + **/ + + +class WeChatFileFile(override val source: Any, override val suffix: String) : WeChatFile { + private var internalSource: File + + init { + if (source !is File) + throw IllegalArgumentException("source should be File but it's ${source::class.java.name}") + else + internalSource = source + } + + override suspend fun readByteArray(): ByteArray = withContext(Dispatchers.IO) { + var source: BufferedSource? = null + try { + source = internalSource.source().buffer() + val array = source.readByteArray() + array + } catch (e: FileNotFoundException) { + byteArrayOf() + } catch (io: IOException) { + byteArrayOf() + } finally { + source?.close() + } + } +} + +private class WeChatAssetFile(override val source: Any, override val suffix: String) : WeChatFile { + private var internalSource: AssetFileDescriptor + + init { + if (source !is AssetFileDescriptor) + throw IllegalArgumentException("source should be AssetFileDescriptor but it's ${source::class.java.name}") + else + internalSource = source + } + + override suspend fun readByteArray(): ByteArray = withContext(Dispatchers.IO) { + var source: BufferedSource? = null + try { + source = internalSource.createInputStream().source().buffer() + val array = source.readByteArray() + array + } catch (e: FileNotFoundException) { + byteArrayOf() + } catch (io: IOException) { + byteArrayOf() + } finally { + source?.close() + } + } +} + +private class WeChatNetworkFile(override val source: Any, override val suffix: String) : WeChatFile { + private var internalSource: String + + init { + if (source !is String) + throw IllegalArgumentException("source should be String but it's ${source::class.java.name}") + else + internalSource = source + } + + override suspend fun readByteArray(): ByteArray = withContext(Dispatchers.IO) { + val okHttpClient = OkHttpClient.Builder().build() + val request: Request = Request.Builder().url(internalSource).get().build() + try { + val response = okHttpClient.newCall(request).execute() + val responseBody = response.body + if (response.isSuccessful && responseBody != null) { + responseBody.bytes() + } else { + byteArrayOf() + } + } catch (e: IOException) { + Log.w("Fluwx", "reading file from $internalSource failed") + byteArrayOf() + } + } +} + +private class WeChatMemoryFile(override val source: Any, override val suffix: String) : WeChatFile { + private var internalSource: ByteArray + + init { + if (source !is ByteArray) + throw IllegalArgumentException("source should be String but it's ${source::class.java.name}") + else + internalSource = source + } + + override suspend fun readByteArray(): ByteArray = internalSource +} + +interface WeChatFile { + val source: Any + val suffix: String + + suspend fun readByteArray(): ByteArray + + companion object { + // NETWORK, +// ASSET, +// FILE, +// BINARY, + fun createWeChatFile(params: Map, assetFileDescriptor: (String) -> AssetFileDescriptor): WeChatFile { +// Map toMap() => {"source": source, "schema": schema.index, "suffix": suffix}; + val suffix = (params["suffix"] as String?) ?: ".jpeg" + return when ((params["schema"] as? Int) ?: 0) { + 0 -> WeChatNetworkFile(source = (params["source"] as? String).orEmpty(), suffix = suffix) + 1 -> WeChatAssetFile(source = assetFileDescriptor(((params["source"] as? String).orEmpty())), suffix = suffix) + 2 -> WeChatFileFile(source = File((params["source"] as? String).orEmpty()), suffix = suffix) + 3 -> WeChatMemoryFile(source = (params["source"] as? ByteArray) + ?: byteArrayOf(), suffix = suffix) + else -> WeChatNetworkFile(source = (params["source"] as? String).orEmpty(), suffix = suffix) + } + } + } +} + diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/utils/FluwxExtensions.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/utils/FluwxExtensions.kt new file mode 100644 index 00000000..51fb1a84 --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/utils/FluwxExtensions.kt @@ -0,0 +1,54 @@ +package com.jarvan.fluwx.utils + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.util.Log +import com.jarvan.fluwx.FluwxConfigurations + +internal const val KEY_FLUWX_REQUEST_INFO_EXT_MSG = "KEY_FLUWX_REQUEST_INFO_EXT_MSG" +internal const val KEY_FLUWX_REQUEST_INFO_BUNDLE = "KEY_FLUWX_REQUEST_INFO_BUNDLE" +internal const val KEY_FLUWX_EXTRA = "KEY_FLUWX_EXTRA" +internal const val FLAG_PAYLOAD_FROM_WECHAT = "FLAG_PAYLOAD_FROM_WECHAT" + +internal fun Activity.startFlutterActivity( + extra: Intent, +) { + flutterActivityIntent()?.also { intent -> + intent.addFluwxExtras() + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + intent.putExtra(KEY_FLUWX_EXTRA, extra) + intent.putExtra(FLAG_PAYLOAD_FROM_WECHAT, true) + try { + startActivity(intent) + } catch (e: ActivityNotFoundException) { + Log.w("fluwx", "Can not start activity for Intent: $intent") + } + } +} + + +internal fun Context.flutterActivityIntent(): Intent? { + return if (FluwxConfigurations.flutterActivity.isBlank()) { + packageManager.getLaunchIntentForPackage(packageName) + } else { + Intent().also { + it.setClassName(this, "${packageName}.${FluwxConfigurations.flutterActivity}") + } + } +} + +internal fun Intent.addFluwxExtras() { + putExtra("fluwx_payload_from_fluwx", true) +} + +internal fun Intent.readWeChatCallbackIntent(): Intent? { + return if (getBooleanExtra(FLAG_PAYLOAD_FROM_WECHAT, false)) { + getParcelableExtra(KEY_FLUWX_EXTRA) + } else { + null + } +} diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/utils/WXApiUtils.java b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/utils/WXApiUtils.java new file mode 100644 index 00000000..915bab90 --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/utils/WXApiUtils.java @@ -0,0 +1,66 @@ +package com.jarvan.fluwx.utils; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +public class WXApiUtils { + + public static String createSign(String appId, String nonceStr, String timestamp, String card_type) { + SortedMap parameters = new TreeMap<>(); + parameters.put("app_id", appId); + parameters.put("nonce_str", nonceStr); + parameters.put("card_type", timestamp); + parameters.put("time_stamp", card_type); + StringBuffer sb = new StringBuffer(); + Set es = parameters.entrySet(); + // 所有参与传参的参数按照accsii排序(升序) + Iterator it = es.iterator(); + while (it.hasNext()) { + @SuppressWarnings("rawtypes") + Map.Entry entry = (Map.Entry) it.next(); + String k = (String) entry.getKey(); + Object v = entry.getValue(); + if (null != v && !"".equals(v) && !"sign".equals(k) + && !"key".equals(k)) { + sb.append(k + "=" + v + "&"); + } + } + String sign = shaEncode(sb.toString()).toUpperCase(); + return sign; + } + + + public static String shaEncode(String inStr) { + MessageDigest sha = null; + try { + sha = MessageDigest.getInstance("SHA"); + } catch (Exception e) { + System.out.println(e.toString()); + e.printStackTrace(); + return ""; + } + + byte[] byteArray = new byte[0]; + try { + byteArray = inStr.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + byte[] md5Bytes = sha.digest(byteArray); + StringBuffer hexValue = new StringBuffer(); + for (int i = 0; i < md5Bytes.length; i++) { + int val = ((int) md5Bytes[i]) & 0xff; + if (val < 16) { + hexValue.append("0"); + } + hexValue.append(Integer.toHexString(val)); + } + return hexValue.toString(); + } + +} diff --git a/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/wxapi/FluwxWXEntryActivity.kt b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/wxapi/FluwxWXEntryActivity.kt new file mode 100644 index 00000000..d1ef0071 --- /dev/null +++ b/packages/_shared/android/src/main/kotlin/com/jarvan/fluwx/wxapi/FluwxWXEntryActivity.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The OpenFlutter Organization + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jarvan.fluwx.wxapi + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import com.jarvan.fluwx.utils.startFlutterActivity + + +open class FluwxWXEntryActivity : Activity() { + + // IWXAPI 是第三方app和微信通信的openapi接口 + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + startFlutterActivity(intent) + finish() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + startFlutterActivity(intent) + finish() + } + +} \ No newline at end of file diff --git a/packages/_shared/android/src/main/res/values/styles.xml b/packages/_shared/android/src/main/res/values/styles.xml new file mode 100644 index 00000000..c64332ba --- /dev/null +++ b/packages/_shared/android/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/packages/_shared/android/src/main/res/xml/fluwx_file_provider_paths.xml b/packages/_shared/android/src/main/res/xml/fluwx_file_provider_paths.xml new file mode 100644 index 00000000..70e60b4f --- /dev/null +++ b/packages/_shared/android/src/main/res/xml/fluwx_file_provider_paths.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/_shared/android/src/test/kotlin/com/jarvan/fluwx/FluwxPluginTest.kt b/packages/_shared/android/src/test/kotlin/com/jarvan/fluwx/FluwxPluginTest.kt new file mode 100644 index 00000000..0f5faf68 --- /dev/null +++ b/packages/_shared/android/src/test/kotlin/com/jarvan/fluwx/FluwxPluginTest.kt @@ -0,0 +1,14 @@ +package com.jarvan.fluwx + + +/* + * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +internal class FluwxPluginTest { + +} diff --git a/example/images/logo.png b/packages/_shared/example/images/logo.png similarity index 100% rename from example/images/logo.png rename to packages/_shared/example/images/logo.png diff --git a/example/lib/main.dart b/packages/_shared/example/lib/main.dart similarity index 99% rename from example/lib/main.dart rename to packages/_shared/example/lib/main.dart index 1fbc0fb5..b4d98a53 100644 --- a/example/lib/main.dart +++ b/packages/_shared/example/lib/main.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; import 'package:fluwx_example/pages/cold_boot_page.dart'; import 'pages/auth_by_qr_code_page.dart'; diff --git a/example/lib/pages/auth_by_qr_code_page.dart b/packages/_shared/example/lib/pages/auth_by_qr_code_page.dart similarity index 100% rename from example/lib/pages/auth_by_qr_code_page.dart rename to packages/_shared/example/lib/pages/auth_by_qr_code_page.dart diff --git a/example/lib/pages/cold_boot_page.dart b/packages/_shared/example/lib/pages/cold_boot_page.dart similarity index 96% rename from example/lib/pages/cold_boot_page.dart rename to packages/_shared/example/lib/pages/cold_boot_page.dart index ce4d9402..1ea06801 100644 --- a/example/lib/pages/cold_boot_page.dart +++ b/packages/_shared/example/lib/pages/cold_boot_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class ColdBootPage extends StatefulWidget { const ColdBootPage({Key? key}) : super(key: key); diff --git a/example/lib/pages/launch_mini_program_page.dart b/packages/_shared/example/lib/pages/launch_mini_program_page.dart similarity index 96% rename from example/lib/pages/launch_mini_program_page.dart rename to packages/_shared/example/lib/pages/launch_mini_program_page.dart index 43a791c5..61dcc09c 100644 --- a/example/lib/pages/launch_mini_program_page.dart +++ b/packages/_shared/example/lib/pages/launch_mini_program_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class LaunchMiniProgramPage extends StatefulWidget { const LaunchMiniProgramPage({Key? key}) : super(key: key); diff --git a/example/lib/pages/pay_page.dart b/packages/_shared/example/lib/pages/pay_page.dart similarity index 97% rename from example/lib/pages/pay_page.dart rename to packages/_shared/example/lib/pages/pay_page.dart index fd293126..4507fc2d 100644 --- a/example/lib/pages/pay_page.dart +++ b/packages/_shared/example/lib/pages/pay_page.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'dart:io' as H; import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class PayPage extends StatefulWidget { const PayPage({Key? key}) : super(key: key); diff --git a/example/lib/pages/send_auth_page.dart b/packages/_shared/example/lib/pages/send_auth_page.dart similarity index 96% rename from example/lib/pages/send_auth_page.dart rename to packages/_shared/example/lib/pages/send_auth_page.dart index d7929aa4..6456377e 100644 --- a/example/lib/pages/send_auth_page.dart +++ b/packages/_shared/example/lib/pages/send_auth_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class SendAuthPage extends StatefulWidget { const SendAuthPage({Key? key}) : super(key: key); diff --git a/example/lib/pages/share_image_page.dart b/packages/_shared/example/lib/pages/share_image_page.dart similarity index 98% rename from example/lib/pages/share_image_page.dart rename to packages/_shared/example/lib/pages/share_image_page.dart index c55fcbd5..90f2cf41 100644 --- a/example/lib/pages/share_image_page.dart +++ b/packages/_shared/example/lib/pages/share_image_page.dart @@ -1,7 +1,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; import 'package:fluwx_example/utils.dart'; class ShareImagePage extends StatefulWidget { diff --git a/example/lib/pages/share_mini_program_page.dart b/packages/_shared/example/lib/pages/share_mini_program_page.dart similarity index 98% rename from example/lib/pages/share_mini_program_page.dart rename to packages/_shared/example/lib/pages/share_mini_program_page.dart index 4e00f351..c04e6004 100644 --- a/example/lib/pages/share_mini_program_page.dart +++ b/packages/_shared/example/lib/pages/share_mini_program_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class ShareMiniProgramPage extends StatefulWidget { const ShareMiniProgramPage({Key? key}) : super(key: key); diff --git a/example/lib/pages/share_music_page.dart b/packages/_shared/example/lib/pages/share_music_page.dart similarity index 98% rename from example/lib/pages/share_music_page.dart rename to packages/_shared/example/lib/pages/share_music_page.dart index c3a83feb..a68a16a1 100644 --- a/example/lib/pages/share_music_page.dart +++ b/packages/_shared/example/lib/pages/share_music_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class ShareMusicPage extends StatefulWidget { const ShareMusicPage({Key? key}) : super(key: key); diff --git a/example/lib/pages/share_text_page.dart b/packages/_shared/example/lib/pages/share_text_page.dart similarity index 98% rename from example/lib/pages/share_text_page.dart rename to packages/_shared/example/lib/pages/share_text_page.dart index 2cf9ab31..e153fef4 100644 --- a/example/lib/pages/share_text_page.dart +++ b/packages/_shared/example/lib/pages/share_text_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class ShareTextPage extends StatefulWidget { const ShareTextPage({Key? key}) : super(key: key); diff --git a/example/lib/pages/share_video_page.dart b/packages/_shared/example/lib/pages/share_video_page.dart similarity index 98% rename from example/lib/pages/share_video_page.dart rename to packages/_shared/example/lib/pages/share_video_page.dart index b0db093f..1c5e1a09 100644 --- a/example/lib/pages/share_video_page.dart +++ b/packages/_shared/example/lib/pages/share_video_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class ShareVideoPage extends StatefulWidget { const ShareVideoPage({Key? key}) : super(key: key); diff --git a/example/lib/pages/share_web_page.dart b/packages/_shared/example/lib/pages/share_web_page.dart similarity index 98% rename from example/lib/pages/share_web_page.dart rename to packages/_shared/example/lib/pages/share_web_page.dart index e9a295e7..1bf5c0fb 100644 --- a/example/lib/pages/share_web_page.dart +++ b/packages/_shared/example/lib/pages/share_web_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class ShareWebPagePage extends StatefulWidget { const ShareWebPagePage({Key? key}) : super(key: key); diff --git a/example/lib/pages/sign_auto_deduct_page.dart b/packages/_shared/example/lib/pages/sign_auto_deduct_page.dart similarity index 98% rename from example/lib/pages/sign_auto_deduct_page.dart rename to packages/_shared/example/lib/pages/sign_auto_deduct_page.dart index 56a49265..c7e59185 100644 --- a/example/lib/pages/sign_auto_deduct_page.dart +++ b/packages/_shared/example/lib/pages/sign_auto_deduct_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class SignAutoDeductPage extends StatefulWidget { const SignAutoDeductPage({Key? key}) : super(key: key); diff --git a/example/lib/pages/subscribe_message_page.dart b/packages/_shared/example/lib/pages/subscribe_message_page.dart similarity index 98% rename from example/lib/pages/subscribe_message_page.dart rename to packages/_shared/example/lib/pages/subscribe_message_page.dart index 35e472b7..c0d8ea81 100644 --- a/example/lib/pages/subscribe_message_page.dart +++ b/packages/_shared/example/lib/pages/subscribe_message_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:fluwx/fluwx.dart'; +import 'package:fluwx_example/fluwx_compat.dart'; class SubscribeMessagePage extends StatefulWidget { const SubscribeMessagePage({Key? key}) : super(key: key); diff --git a/example/lib/utils.dart b/packages/_shared/example/lib/utils.dart similarity index 100% rename from example/lib/utils.dart rename to packages/_shared/example/lib/utils.dart diff --git a/packages/_shared/ios/Sources/FluwxDelegate.m b/packages/_shared/ios/Sources/FluwxDelegate.m new file mode 100644 index 00000000..e60e8c3a --- /dev/null +++ b/packages/_shared/ios/Sources/FluwxDelegate.m @@ -0,0 +1,27 @@ +// +// FluwxDelegate.m +// fluwx +// +// Created by Mo on 2022/3/6. +// + +#import +#import "FluwxDelegate.h" +#import + +@implementation FluwxDelegate + ++ (instancetype)defaultManager { + static dispatch_once_t onceToken; + static FluwxDelegate *instance; + dispatch_once(&onceToken, ^{ + instance = [[FluwxDelegate alloc] init]; + }); + return instance; +} + +- (void)registerWxAPI:(NSString *)appId universalLink:(NSString *)universalLink { + [WXApi registerApp:appId universalLink:universalLink]; +} + +@end diff --git a/packages/_shared/ios/Sources/FluwxPlugin.m b/packages/_shared/ios/Sources/FluwxPlugin.m new file mode 100644 index 00000000..5933c801 --- /dev/null +++ b/packages/_shared/ios/Sources/FluwxPlugin.m @@ -0,0 +1,1595 @@ +#import "FluwxPlugin.h" +#import "FluwxStringUtil.h" +#import "FluwxDelegate.h" +#import "ThumbnailHelper.h" +#import "NSStringWrapper.h" +#import +#import +#import + +NSString *const fluwxKeyTitle = @"title"; +NSString *const fluwxKeyImage = @ "image"; +NSString *const fluwxKeyImageData = @ "imageData"; +NSString *const fluwxKeyDescription = @"description"; +NSString *const fluwxKeyMsgSignature = @"msgSignature"; +NSString *const fluwxKeyThumbData = @"thumbData"; +NSString *const fluwxKeyThumbDataHash = @"thumbDataHash"; + +NSString *const fluwxKeyPackage = @"?package="; + +NSString *const fluwxKeyMessageExt = @"messageExt"; +NSString *const fluwxKeyMediaTagName = @"mediaTagName"; +NSString *const fluwxKeyMessageAction = @"messageAction"; + +NSString *const fluwxKeyScene = @"scene"; +NSString *const fluwxKeyTimeline = @"timeline"; +NSString *const fluwxKeySession = @"session"; +NSString *const fluwxKeyFavorite = @"favorite"; + +NSString *const keySource = @"source"; +NSString *const keySuffix = @"suffix"; + +CGFloat thumbnailWidth; + +NSUInteger defaultThumbnailSize = 32 * 1024; + +@interface FluwxPlugin() + +@property (strong, nonatomic)NSString *extMsg; + +@end + +typedef void(^FluwxWXReqRunnable)(void); + +@implementation FluwxPlugin { + FlutterMethodChannel *_channel; + WechatAuthSDK *_qrauth; + BOOL _isRunning; + BOOL _attemptToResumeMsgFromWxFlag; + FluwxWXReqRunnable _attemptToResumeMsgFromWxRunnable; + // cache open url request when WXApi is not registered, and handle it once WXApi is registered + FluwxWXReqRunnable _cachedOpenUrlRequest; +} + +const NSString *errStr = @"errStr"; +const NSString *errCode = @"errCode"; +const NSString *openId = @"openId"; +const NSString *fluwxType = @"type"; +const NSString *lang = @"lang"; +const NSString *country = @"country"; +const NSString *description = @"description"; + +BOOL handleOpenURLByFluwx = YES; + +NSObject *_fluwxRegistrar; + ++ (void)registerWithRegistrar:(NSObject *)registrar { + _fluwxRegistrar = registrar; + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:@"com.jarvanmo/fluwx" + binaryMessenger:[registrar messenger]]; + FluwxPlugin *instance = [[FluwxPlugin alloc] initWithChannel:channel]; + [registrar addApplicationDelegate:instance]; + [registrar addSceneDelegate:instance]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (instancetype)initWithChannel:(FlutterMethodChannel *)channel { + self = [super init]; + if (self) { + _channel = channel; + _qrauth = [[WechatAuthSDK alloc] init]; + _qrauth.delegate = self; + _isRunning = NO; + thumbnailWidth = 150; + _attemptToResumeMsgFromWxFlag = NO; +#if WECHAT_LOGGING + [WXApi startLogByLevel:WXLogLevelDetail logBlock:^(NSString *log) { + [self logToFlutterWithDetail:log]; + }]; +#endif + } + return self; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"registerApp" isEqualToString:call.method]) { + [self registerApp:call result:result]; + } else if ([@"isWeChatInstalled" isEqualToString:call.method]) { + [self checkWeChatInstallation:call result:result]; + } else if ([@"sendAuth" isEqualToString:call.method]) { + [self handleAuth:call result:result]; + } else if ([@"authByQRCode" isEqualToString:call.method]) { + [self authByQRCode:call result:result]; + } else if ([@"stopAuthByQRCode" isEqualToString:call.method]) { + [self stopAuthByQRCode:call result:result]; + } else if ([@"openWXApp" isEqualToString:call.method]) { + result(@([WXApi openWXApp])); + } else if ([@"launchMiniProgram" isEqualToString:call.method]) { + [self handleLaunchMiniProgram:call result:result]; + } else if ([@"subscribeMsg" isEqualToString:call.method]) { + [self handleSubscribeWithCall:call result:result]; + } else if ([@"autoDeduct" isEqualToString:call.method]) { + [self handleAutoDeductWithCall:call result:result]; + } else if ([@"autoDeductV2" isEqualToString:call.method]) { + [self handleautoDeductV2:call result:result]; + } else if ([@"openBusinessView" isEqualToString:call.method]) { + [self handleOpenBusinessView:call result:result]; + } else if ([@"authByPhoneLogin" isEqualToString:call.method]) { + [self handleAuthByPhoneLogin:call result:result]; + } else if ([@"getExtMsg" isEqualToString:call.method]) { + [self handelGetExtMsgWithCall:call result:result]; + } else if ([call.method hasPrefix:@"share"]) { + [self handleShare:call result:result]; + } else if ([@"openWeChatCustomerServiceChat" isEqualToString:call.method]) { + [self openWeChatCustomerServiceChat:call result:result]; + } else if ([@"checkSupportOpenBusinessView" isEqualToString:call.method]) { + [self checkSupportOpenBusinessView:call result:result]; + } else if ([@"openRankList" isEqualToString:call.method]) { + [self handleOpenRankListCall:call result:result]; + } else if ([@"openUrl" isEqualToString:call.method]) { + [self handleOpenUrlCall:call result:result]; + } else if ([@"openWeChatInvoice" isEqualToString:call.method]) { + [self openWeChatInvoice:call result:result]; + } else if ([@"selfCheck" isEqualToString:call.method]) { +#ifndef __OPTIMIZE__ + [WXApi checkUniversalLinkReady:^(WXULCheckStep step, WXCheckULStepResult *result) { + NSString *log = [NSString stringWithFormat:@"%@, %u, %@, %@", @(step), result.success, result.errorInfo, result.suggestion]; + [self logToFlutterWithDetail:log]; + }]; +#endif + result(nil); + } else if ([@"attemptToResumeMsgFromWx" isEqualToString:call.method]) { + if (_attemptToResumeMsgFromWxRunnable != nil) { + _attemptToResumeMsgFromWxRunnable(); + _attemptToResumeMsgFromWxRunnable = nil; + } + result(nil); + } else if ([@"payWithFluwx" isEqualToString:call.method]) { +#ifdef FLUWX_NO_PAY + result(@NO); +#else + [self handlePayment:call result:result]; +#endif + } else if ([@"payWithHongKongWallet" isEqualToString:call.method]) { +#ifdef FLUWX_NO_PAY + result(@NO); +#else + [self handleHongKongWalletPayment:call result:result]; +#endif + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)openWeChatInvoice:(FlutterMethodCall *)call result:(FlutterResult)result { + + NSString *appId = call.arguments[@"appId"]; + + if ([FluwxStringUtil isBlank:appId]) { + result([FlutterError + errorWithCode:@"invalid app id" + message:@"are you sure your app id is correct ? " + details:appId]); + return; + } + + WXChooseInvoiceReq *chooseInvoiceReq = [[WXChooseInvoiceReq alloc] init]; + chooseInvoiceReq.appID = appId; + chooseInvoiceReq.timeStamp = [[NSDate date] timeIntervalSince1970]; + chooseInvoiceReq.signType = @"SHA1"; + chooseInvoiceReq.cardSign = @""; + chooseInvoiceReq.nonceStr = @""; + [WXApi sendReq:chooseInvoiceReq completion:^(BOOL done) { + result(@(done)); + }]; +} + +- (void)registerApp:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *doOnIOS = call.arguments[@"iOS"]; + + if (![doOnIOS boolValue]) { + result(@NO); + return; + } + + NSString *appId = call.arguments[@"appId"]; + if ([FluwxStringUtil isBlank:appId]) { + result([FlutterError + errorWithCode:@"invalid app id" + message:@"are you sure your app id is correct ? " + details:appId]); + return; + } + + NSString *universalLink = call.arguments[@"universalLink"]; + + if ([FluwxStringUtil isBlank:universalLink]) { + result([FlutterError + errorWithCode:@"invalid universal link" + message:@"are you sure your universal link is correct ? " + details:universalLink]); + return; + } + + BOOL isWeChatRegistered = [WXApi registerApp:appId universalLink:universalLink]; + + // If registration fails, we can return immediately + if (!isWeChatRegistered) { + result(@(isWeChatRegistered)); + _isRunning = NO; + return; + } + + // Otherwise, since WXApi is now registered successfully, + // we can (and should) immediately handle the previously cached `app:openURL` event (if any) + if (_cachedOpenUrlRequest != nil) { + _cachedOpenUrlRequest(); + _cachedOpenUrlRequest = nil; + } + + // Set `_isRunning` after calling `_cachedOpenUrlRequest` to ensure that + // the `onReq` triggered by this call to `_cachedOpenUrlRequest` will + // be stored in `_attemptToResumeMsgFromWxRunnable` which can be obtained + // by triggering `attemptToResumeMsgFromWx`. + // + // At the same time, this also coincides with the approach on the Android side: + // cold start events are cached and triggered through `attemptToResumeMsgFromWx` + _isRunning = isWeChatRegistered; + + result(@(isWeChatRegistered)); +} + +- (void)checkWeChatInstallation:(FlutterMethodCall *)call result:(FlutterResult)result { + result(@([WXApi isWXAppInstalled])); +} + +- (void)openWeChatCustomerServiceChat:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *url = call.arguments[@"url"]; + NSString *corpId = call.arguments[@"corpId"]; + + WXOpenCustomerServiceReq *req = [[WXOpenCustomerServiceReq alloc] init]; + req.corpid = corpId; //企业ID + req.url = url; //客服URL + return [WXApi sendReq:req completion:^(BOOL success) { + result(@(success)); + }]; +} + +- (void)checkSupportOpenBusinessView:(FlutterMethodCall *)call result:(FlutterResult)result { + if (![WXApi isWXAppInstalled]) { + result([FlutterError errorWithCode:@"WeChat Not Installed" message:@"Please install the WeChat first" details:nil]); + } else { + result(@(YES)); + } +} + +#ifndef FLUWX_NO_PAY +- (void)handlePayment:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *timestamp = call.arguments[@"timeStamp"]; + + NSString *partnerId = call.arguments[@"partnerId"]; + NSString *prepayId = call.arguments[@"prepayId"]; + NSString *packageValue = call.arguments[@"packageValue"]; + NSString *nonceStr = call.arguments[@"nonceStr"]; + UInt32 timeStamp = [timestamp unsignedIntValue]; + NSString *sign = call.arguments[@"sign"]; + [FluwxDelegate defaultManager].extData = call.arguments[@"extData"]; + + NSString *appId = call.arguments[@"appId"]; + PayReq *req = [[PayReq alloc] init]; + req.openID = (appId == (id) [NSNull null]) ? nil : appId; + req.partnerId = partnerId; + req.prepayId = prepayId; + req.nonceStr = nonceStr; + req.timeStamp = timeStamp; + req.package = packageValue; + req.sign = sign; + + [WXApi sendReq:req completion:^(BOOL done) { + result(@(done)); + }]; + +} + +- (void)handleHongKongWalletPayment:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *partnerId = call.arguments[@"prepayId"]; + + WXOpenBusinessWebViewReq *req = [[WXOpenBusinessWebViewReq alloc] init]; + req.businessType = 1; + NSMutableDictionary *queryInfoDic = [NSMutableDictionary dictionary]; + [queryInfoDic setObject:partnerId forKey:@"token"]; + req.queryInfoDic = queryInfoDic; + [WXApi sendReq:req completion:^(BOOL done) { + result(@(done)); + }]; +} +#endif + +- (void)handleLaunchMiniProgram:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *userName = call.arguments[@"userName"]; + NSString *path = call.arguments[@"path"]; + //WXMiniProgramType *miniProgramType = call.arguments[@"miniProgramType"]; + + NSNumber *typeInt = call.arguments[@"miniProgramType"]; + WXMiniProgramType miniProgramType = WXMiniProgramTypeRelease; + if ([typeInt isEqualToNumber:@1]) { + miniProgramType = WXMiniProgramTypeTest; + } else if ([typeInt isEqualToNumber:@2]) { + miniProgramType = WXMiniProgramTypePreview; + } + + WXLaunchMiniProgramReq *launchMiniProgramReq = [WXLaunchMiniProgramReq object]; + launchMiniProgramReq.userName = userName; + launchMiniProgramReq.path = (path == (id) [NSNull null]) ? nil : path; + launchMiniProgramReq.miniProgramType = miniProgramType; + + [WXApi sendReq:launchMiniProgramReq completion:^(BOOL done) { + result(@(done)); + }]; +} + + +- (void)handleSubscribeWithCall:(FlutterMethodCall *)call result:(FlutterResult)result { + NSDictionary *params = call.arguments; + NSString *appId = [params valueForKey:@"appId"]; + NSNumber *scene = [params valueForKey:@"scene"]; + NSString *templateId = [params valueForKey:@"templateId"]; + NSString *reserved = [params valueForKey:@"reserved"]; + + WXSubscribeMsgReq *req = [WXSubscribeMsgReq new]; +#if __LP64__ + req.scene = [scene unsignedIntValue]; +#else + req.scene = [scene unsignedLongValue]; +#endif + req.templateId = templateId; + req.reserved = reserved; + req.openID = appId; + + [WXApi sendReq:req completion:^(BOOL done) { + result(@(done)); + }]; +} + +- (void)handleAutoDeductWithCall:(FlutterMethodCall *)call result:(FlutterResult)result { + NSMutableDictionary *paramsFromDart = [NSMutableDictionary dictionaryWithDictionary:call.arguments]; + [paramsFromDart removeObjectForKey:@"businessType"]; + WXOpenBusinessWebViewReq *req = [[WXOpenBusinessWebViewReq alloc] init]; + NSNumber *businessType = call.arguments[@"businessType"]; + req.businessType = [businessType unsignedIntValue]; + req.queryInfoDic = paramsFromDart; + [WXApi sendReq:req completion:^(BOOL done) { + result(@(done)); + }]; +} + +- (void)handleautoDeductV2:(FlutterMethodCall *)call result:(FlutterResult)result { + NSMutableDictionary *paramsFromDart = call.arguments[@"queryInfo"]; + // [paramsFromDart removeObjectForKey:@"businessType"]; + WXOpenBusinessWebViewReq *req = [[WXOpenBusinessWebViewReq alloc] init]; + NSNumber *businessType = call.arguments[@"businessType"]; + req.businessType = [businessType unsignedIntValue]; + req.queryInfoDic = paramsFromDart; + [WXApi sendReq:req completion:^(BOOL done) { + result(@(done)); + }]; +} + +- (void)handleOpenBusinessView:(FlutterMethodCall *)call result:(FlutterResult)result { + NSDictionary *params = call.arguments; + + WXOpenBusinessViewReq *req = [WXOpenBusinessViewReq object]; + NSString *businessType = [params valueForKey:@"businessType"]; + NSString *query = [params valueForKey:@"query"]; + req.businessType = businessType; + req.query = query; + req.extInfo = @"{\"miniProgramType\":0}"; + [WXApi sendReq:req completion:^(BOOL done) { + result(@(done)); + }]; +} + +- (void)handelGetExtMsgWithCall:(FlutterMethodCall *)call result:(FlutterResult)result { + result([FluwxDelegate defaultManager].extMsg); + [FluwxDelegate defaultManager].extMsg = nil; +} + + +// Deprecated since iOS 9 +// See https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623073-application?language=objc +// Use `application:openURL:options:` instead. +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation { + // Since flutter has minimum iOS version requirement of 11.0, we don't need to change the implementation here. + return [WXApi handleOpenURL:url delegate:self]; +} + +// Deprecated since iOS 9 +// See https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622964-application?language=objc +// Use `application:openURL:options:` instead. +//- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url { +// // Since flutter has minimum iOS version requirement of 11.0, we don't need to change the implementation here. +// return [WXApi handleOpenURL:url delegate:self]; +//} + + +// Available on iOS 9.0 and later +// See https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application?language=objc +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + // ↓ previous solution -- according to document, this may fail if the WXApi hasn't registered yet. + // return [WXApi handleOpenURL:url delegate:self]; + + if (_isRunning) { + // registered -- directly handle open url request by WXApi + return [WXApi handleOpenURL:url delegate:self]; + } else { + // unregistered -- cache open url request and handle it once WXApi is registered + __weak typeof(self) weakSelf = self; + _cachedOpenUrlRequest = ^() { + __strong typeof(weakSelf) strongSelf = weakSelf; + [WXApi handleOpenURL:url delegate:strongSelf]; + }; + // Let's hold this until the PR contributor send feedback. + //return [url.absoluteString contains:[self fetchWeChatAppId]]; + + // simply return YES to indicate that we can handle open url request later + return NO; + } +} + +- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *_Nonnull))restorationHandler{ + // TODO: (if need) cache userActivity and handle it once WXApi is registered + return [WXApi handleOpenUniversalLink:userActivity delegate:self]; +} + +- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity API_AVAILABLE(ios(13.0)) { + // TODO: (if need) cache userActivity and handle it once WXApi is registered + [WXApi handleOpenUniversalLink:userActivity delegate:self]; +} + +- (void)handleOpenUrlCall:(FlutterMethodCall *)call + result:(FlutterResult)result { + OpenWebviewReq *req = [[OpenWebviewReq alloc] init]; + req.url = call.arguments[@"url"]; + [WXApi sendReq:req + completion:^(BOOL success) { + result(@(success)); + }]; +} + +- (void)handleOpenRankListCall:(FlutterMethodCall *)call + result:(FlutterResult)result { + OpenRankListReq *req = [[OpenRankListReq alloc] init]; + [WXApi sendReq:req + completion:^(BOOL success) { + result(@(success)); + }]; +} + +- (BOOL)handleOpenURL:(NSNotification *)aNotification { + if (handleOpenURLByFluwx) { + NSString *aURLString = [aNotification userInfo][@"url"]; + NSURL *aURL = [NSURL URLWithString:aURLString]; + return [WXApi handleOpenURL:aURL delegate:self]; + } else { + return NO; + } +} + +- (void)logToFlutterWithDetail:(NSString *) detail { + if (_channel != nil) { + NSDictionary *result = @{ + @"detail":detail + }; + [_channel invokeMethod:@"wechatLog" arguments:result]; + } +} + +- (void)handleShare:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"shareText" isEqualToString:call.method]) { + [self shareText:call result:result]; + } else if ([@"shareImage" isEqualToString:call.method]) { + [self shareImage:call result:result]; + } else if ([@"shareWebPage" isEqualToString:call.method]) { + [self shareWebPage:call result:result]; + } else if ([@"shareMusic" isEqualToString:call.method]) { + [self shareMusic:call result:result]; + } else if ([@"shareVideo" isEqualToString:call.method]) { + [self shareVideo:call result:result]; + } else if ([@"shareMiniProgram" isEqualToString:call.method]) { + [self shareMiniProgram:call result:result]; + } else if ([@"shareFile" isEqualToString:call.method]) { + [self shareFile:call result:result]; + } else if ([@"shareEmoji" isEqualToString:call.method]) { + [self shareEmoji:call result:result]; + } +} + +- (void)shareText:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *text = call.arguments[@"source"]; + NSNumber *scene = call.arguments[fluwxKeyScene]; + + [self sendText:text InScene:[self intToWeChatScene:scene] completion:^(BOOL done) { + result(@(done)); + }]; +} + +- (void)shareImage:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *scene = call.arguments[fluwxKeyScene]; + + NSDictionary *sourceImage = call.arguments[keySource]; + + FlutterStandardTypedData *flutterImageData = sourceImage[@"uint8List"]; + NSData *imageData = nil; + if (flutterImageData != nil) { + imageData = flutterImageData.data; + } + + NSString *imageDataHash = sourceImage[@"imgDataHash"]; + + FlutterStandardTypedData *flutterThumbData = call.arguments[fluwxKeyThumbData]; + NSData *thumbData = nil; + if (![flutterThumbData isKindOfClass:[NSNull class]]) { + thumbData = flutterThumbData.data; + } + + dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); + dispatch_async(globalQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self sendImageData:imageData + ImgDataHash:imageDataHash + TagName:call.arguments[fluwxKeyMediaTagName] + MessageExt:call.arguments[fluwxKeyMessageExt] + Action:call.arguments[fluwxKeyMessageAction] + InScene:[self intToWeChatScene:scene] + title:call.arguments[fluwxKeyTitle] + description:call.arguments[fluwxKeyDescription] + MsgSignature:call.arguments[fluwxKeyMsgSignature] + ThumbData:thumbData + ThumbDataHash:call.arguments[fluwxKeyThumbDataHash] + completion:^(BOOL done) { + result(@(done)); + }]; + }); + + }); +} + +- (void)shareWebPage:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *webPageUrl = call.arguments[@"webPage"]; + NSNumber *scene = call.arguments[fluwxKeyScene]; + + FlutterStandardTypedData *flutterThumbData = call.arguments[fluwxKeyThumbData]; + NSData *thumbData = nil; + if (![flutterThumbData isKindOfClass:[NSNull class]]) { + thumbData = flutterThumbData.data; + } + + dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); + dispatch_async(globalQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self sendLinkURL:webPageUrl + TagName:call.arguments[fluwxKeyMediaTagName] + Title:call.arguments[fluwxKeyTitle] + Description:call.arguments[fluwxKeyDescription] + MessageExt:call.arguments[fluwxKeyMessageExt] + MessageAction:call.arguments[fluwxKeyMessageAction] + InScene:[self intToWeChatScene:scene] + MsgSignature:call.arguments[fluwxKeyMsgSignature] + ThumbData:thumbData + ThumbDataHash:call.arguments[fluwxKeyThumbDataHash] + completion:^(BOOL done) { + result(@(done)); + }]; + }); + + }); +} + +- (void)shareMusic:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *scene = call.arguments[fluwxKeyScene]; + + FlutterStandardTypedData *flutterThumbData = call.arguments[fluwxKeyThumbData]; + NSData *thumbData = nil; + if (![flutterThumbData isKindOfClass:[NSNull class]]) { + thumbData = flutterThumbData.data; + } + + dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); + dispatch_async(globalQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self sendMusicURL:call.arguments[@"musicUrl"] + dataURL:call.arguments[@"musicDataUrl"] + MusicLowBandUrl:call.arguments[@"musicLowBandUrl"] + MusicLowBandDataUrl:call.arguments[@"musicLowBandDataUrl"] + Title:call.arguments[fluwxKeyTitle] + Description:call.arguments[fluwxKeyDescription] + MessageExt:call.arguments[fluwxKeyMessageExt] + MessageAction:call.arguments[fluwxKeyMessageAction] + TagName:call.arguments[fluwxKeyMediaTagName] + InScene:[self intToWeChatScene:scene] + MsgSignature:call.arguments[fluwxKeyMsgSignature] + ThumbData:thumbData + ThumbDataHash:call.arguments[fluwxKeyThumbDataHash] + completion:^(BOOL done) { + result(@(done)); + }]; + }); + }); +} + +- (void)shareVideo:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *scene = call.arguments[fluwxKeyScene]; + + FlutterStandardTypedData *flutterThumbData = call.arguments[fluwxKeyThumbData]; + NSData *thumbData = nil; + if (![flutterThumbData isKindOfClass:[NSNull class]]) { + thumbData = flutterThumbData.data; + } + + dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); + dispatch_async(globalQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self sendVideoURL:call.arguments[@"videoUrl"] + VideoLowBandUrl:call.arguments[@"videoLowBandUrl"] + Title:call.arguments[fluwxKeyTitle] + Description:call.arguments[fluwxKeyDescription] + MessageExt:call.arguments[fluwxKeyMessageExt] + MessageAction:call.arguments[fluwxKeyMessageAction] + TagName:call.arguments[fluwxKeyMediaTagName] + InScene:[self intToWeChatScene:scene] + MsgSignature:call.arguments[fluwxKeyMsgSignature] + ThumbData:thumbData + ThumbDataHash:call.arguments[fluwxKeyThumbDataHash] + completion:^(BOOL done) { + result(@(done)); + }]; + }); + }); +} + +- (void)shareFile:(FlutterMethodCall *)call result:(FlutterResult)result { + NSDictionary *sourceFile = call.arguments[keySource]; + NSString *fileExtension; + NSString *suffix = sourceFile[keySuffix]; + fileExtension = suffix; + if ([suffix hasPrefix:@"."]) { + NSRange range = NSMakeRange(0, 1); + fileExtension = [suffix stringByReplacingCharactersInRange:range withString:@""]; + } + + NSData *data = [self getNsDataFromWeChatFile:sourceFile]; + NSNumber *scene = call.arguments[fluwxKeyScene]; + + FlutterStandardTypedData *flutterThumbData = call.arguments[fluwxKeyThumbData]; + NSData *thumbData = nil; + if (![flutterThumbData isKindOfClass:[NSNull class]]) { + thumbData = flutterThumbData.data; + } + + dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); + dispatch_async(globalQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self sendFileData:data + fileExtension:fileExtension + Title:call.arguments[fluwxKeyTitle] + Description:call.arguments[fluwxKeyDescription] + InScene:[self intToWeChatScene:scene] + MsgSignature:call.arguments[fluwxKeyMsgSignature] + ThumbData:thumbData + ThumbDataHash:call.arguments[fluwxKeyThumbDataHash] + completion:^(BOOL success) { + result(@(success)); + }]; + }); + }); +} + +- (void)shareMiniProgram:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *scene = call.arguments[fluwxKeyScene]; + + FlutterStandardTypedData *flutterThumbData = call.arguments[fluwxKeyThumbData]; + NSData *thumbData = nil; + if (![flutterThumbData isKindOfClass:[NSNull class]]) { + thumbData = flutterThumbData.data; + } + + FlutterStandardTypedData *hdImageDataPayload = call.arguments[@"hdImageData"]; + NSData *hdImageData = nil; + if (![hdImageDataPayload isKindOfClass:[NSNull class]]) { + hdImageData = hdImageDataPayload.data; + } + + NSNumber *typeInt = call.arguments[@"miniProgramType"]; + WXMiniProgramType miniProgramType = WXMiniProgramTypeRelease; + if ([typeInt isEqualToNumber:@1]) { + miniProgramType = WXMiniProgramTypeTest; + } else if ([typeInt isEqualToNumber:@2]) { + miniProgramType = WXMiniProgramTypePreview; + } + dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); + dispatch_async(globalQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self sendMiniProgramWebpageUrl:call.arguments[@"webPageUrl"] + userName:call.arguments[@"userName"] + path:call.arguments[@"path"] + title:call.arguments[fluwxKeyTitle] + Description:call.arguments[fluwxKeyDescription] + withShareTicket:[call.arguments[@"withShareTicket"] boolValue] + miniProgramType:miniProgramType + MessageExt:call.arguments[fluwxKeyMessageExt] + MessageAction:call.arguments[fluwxKeyMessageAction] + TagName:call.arguments[fluwxKeyMediaTagName] + InScene:[self intToWeChatScene:scene] + MsgSignature:call.arguments[fluwxKeyMsgSignature] + HdImageData:hdImageData + ThumbData:thumbData + ThumbDataHash:call.arguments[fluwxKeyThumbDataHash] + completion:^(BOOL done) { + result(@(done)); + }]; + }); + }); +} + +- (void)shareEmoji:(FlutterMethodCall *)call result:(FlutterResult)result { + + NSNumber *sceneNum = call.arguments[fluwxKeyScene]; + enum WXScene scene = [self intToWeChatScene:sceneNum]; + + NSDictionary *sourceEmoji = call.arguments[keySource]; + FlutterStandardTypedData *flutterEmojiData = sourceEmoji[@"uint8List"]; + NSData *emojiData = flutterEmojiData != nil ? flutterEmojiData.data : nil; + + FlutterStandardTypedData *flutterThumbData = call.arguments[fluwxKeyThumbData]; + NSData *thumbData = ![flutterThumbData isKindOfClass:[NSNull class]] ? flutterThumbData.data : nil; + + NSString *msgSignature = call.arguments[fluwxKeyMsgSignature]; + NSString *thumbHash = call.arguments[fluwxKeyThumbDataHash]; + + dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); + dispatch_async(globalQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + + [self sendEmotionData:emojiData + InScene:scene + MsgSignature:msgSignature + ThumbData:thumbData + ThumbDataHash:thumbHash + completion:^(BOOL success) { + result(@(success)); + }]; + }); + }); +} + +- (NSData *)getNsDataFromWeChatFile:(NSDictionary *)weChatFile { + NSNumber *schema = weChatFile[@"schema"]; + + if ([schema isEqualToNumber:@0]) { + NSString *source = weChatFile[keySource]; + NSURL *imageURL = [NSURL URLWithString:source]; + //下载图片 + return [NSData dataWithContentsOfURL:imageURL]; + } else if ([schema isEqualToNumber:@1]) { + NSString *source = weChatFile[keySource]; + return [NSData dataWithContentsOfFile:[self readFileFromAssets:source]]; + } else if ([schema isEqualToNumber:@2]) { + NSString *source = weChatFile[keySource]; + return [NSData dataWithContentsOfFile:source]; + } else if ([schema isEqualToNumber:@3]) { + FlutterStandardTypedData *imageData = weChatFile[@"source"]; + return imageData.data; + } else { + return nil; + } +} + +- (UIImage *)getThumbnailFromNSData:(NSData *)data size:(NSUInteger)size isPNG:(BOOL)isPNG compress:(BOOL)compress { + UIImage *uiImage = [UIImage imageWithData:data]; + if (compress) { + return [ThumbnailHelper compressImage:uiImage toByte:size isPNG:isPNG]; + } else { + return uiImage; + } +} + +- (NSData *)getThumbnailDataFromNSData:(NSData *)data size:(NSUInteger)size compress:(BOOL)compress { + if (compress) { + return [ThumbnailHelper compressImageData:data toByte:size]; + } else { + return data; + } +} + +- (NSString *)readFileFromAssets:(NSString *)imagePath { + NSArray *array = [self formatAssets:imagePath]; + NSString *key; + if ([FluwxStringUtil isBlank:array[1]]) { + key = [_fluwxRegistrar lookupKeyForAsset:array[0]]; + } else { + key = [_fluwxRegistrar lookupKeyForAsset:array[0] fromPackage:array[1]]; + } + + return [[NSBundle mainBundle] pathForResource:key ofType:nil]; +} + +- (NSArray *)formatAssets:(NSString *)originPath { + NSString *path = nil; + NSString *packageName = @""; + NSString *pathWithoutSchema = originPath; + NSInteger indexOfPackage = [pathWithoutSchema lastIndexOfString:@"?package="]; + + if (indexOfPackage != JavaNotFound) { + path = [pathWithoutSchema substringFromIndex:0 toIndex:indexOfPackage]; + NSInteger begin = indexOfPackage + [fluwxKeyPackage length]; + packageName = [pathWithoutSchema substringFromIndex:begin toIndex:[pathWithoutSchema length]]; + } else { + path = pathWithoutSchema; + } + + return @[path, packageName]; +} + +- (BOOL)isPNG:(NSString *)suffix { + return [@".png" equals:suffix]; +} + +- (enum WXScene)intToWeChatScene:(NSNumber *)value { + // enum WeChatScene { SESSION, TIMELINE, FAVORITE } + if ([value isEqual:@0]) { + return WXSceneSession; + } else if ([value isEqual:@1]) { + return WXSceneTimeline; + } else if ([value isEqual:@2]) { + return WXSceneFavorite; + } else { + return WXSceneSession; + } +} + +- (void)managerDidRecvLaunchFromWXReq:(LaunchFromWXReq *)request { + [FluwxDelegate defaultManager].extMsg = request.message.messageExt; + // LaunchFromWXReq *launchFromWXReq = (LaunchFromWXReq *)request; + // + // if (_isRunning) { + // [FluwxDelegate defaultManager].extMsg = request.message.messageExt; + // } else { + // __weak typeof(self) weakSelf = self; + // _initialWXReqRunnable = ^() { + // __strong typeof(weakSelf) strongSelf = weakSelf; + // [FluwxDelegate defaultManager].extMsg = request.message.messageExt + // }; + // } +} + +- (void)onResp:(BaseResp *)resp { + if ([resp isKindOfClass:[SendMessageToWXResp class]]) { + SendMessageToWXResp *messageResp = (SendMessageToWXResp *) resp; + NSDictionary *result = @{ + description: messageResp.description == nil ? @"" : messageResp.description, + errStr: messageResp.errStr == nil ? @"" : messageResp.errStr, + errCode: @(messageResp.errCode), + fluwxType: @(messageResp.type), + country: messageResp.country == nil ? @"" : messageResp.country, + lang: messageResp.lang == nil ? @"" : messageResp.lang}; + if (_channel != nil) { + [_channel invokeMethod:@"onShareResponse" arguments:result]; + } + } else if ([resp isKindOfClass:[SendAuthResp class]]) { + SendAuthResp *authResp = (SendAuthResp *) resp; + NSDictionary *result = @{ + description: authResp.description == nil ? @"" : authResp.description, + errStr: authResp.errStr == nil ? @"" : authResp.errStr, + errCode: @(authResp.errCode), + fluwxType: @(authResp.type), + country: authResp.country == nil ? @"" : authResp.country, + lang: authResp.lang == nil ? @"" : authResp.lang, + @"code": [FluwxStringUtil nilToEmpty:authResp.code], + @"state": [FluwxStringUtil nilToEmpty:authResp.state] + + }; + + if (_channel != nil) { + [_channel invokeMethod:@"onAuthResponse" arguments:result]; + } + } else if ([resp isKindOfClass:[AddCardToWXCardPackageResp class]]) { + // pass + } else if ([resp isKindOfClass:[WXChooseCardResp class]]) { + // pass + } else if ([resp isKindOfClass:[WXChooseInvoiceResp class]]) { + //TODO 处理发票返回,并回调Dart + + WXChooseInvoiceResp *chooseInvoiceResp = (WXChooseInvoiceResp *) resp; + NSArray *array = chooseInvoiceResp.cardAry; + NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:array.count]; + for (int i = 0; i< array.count; i++) { + WXInvoiceItem *item = array[i]; + NSDictionary *dict = @{@"app_id":item.appID, @"encrypt_code":item.encryptCode, @"card_id":item.cardId}; + [mutableArray addObject:dict]; + } + + NSError *error = nil; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:mutableArray + options:NSJSONWritingPrettyPrinted + error: &error]; + NSString *cardItemList = @""; + if ([jsonData length] && error == nil) { + cardItemList = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + } + + NSDictionary *result = @{ + description: chooseInvoiceResp.description == nil ? @"" : chooseInvoiceResp.description, + errStr: chooseInvoiceResp.errStr == nil ? @"" : chooseInvoiceResp.errStr, + errCode: @(chooseInvoiceResp.errCode), + fluwxType: @(chooseInvoiceResp.type), + @"cardItemList":cardItemList + }; + + if (_channel != nil) { + [_channel invokeMethod:@"onOpenWechatInvoiceResponse" arguments:result]; + } + } else if ([resp isKindOfClass:[WXSubscribeMsgResp class]]) { + WXSubscribeMsgResp *subscribeMsgResp = (WXSubscribeMsgResp *) resp; + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + NSString *openid = subscribeMsgResp.openId; + if (openid != nil && openid != NULL && ![openid isKindOfClass:[NSNull class]]) { + result[@"openid"] = openid; + } + + NSString *templateId = subscribeMsgResp.templateId; + if (templateId != nil && templateId != NULL && ![templateId isKindOfClass:[NSNull class]]) { + result[@"templateId"] = templateId; + } + + NSString *action = subscribeMsgResp.action; + if (action != nil && action != NULL && ![action isKindOfClass:[NSNull class]]) { + result[@"action"] = action; + } + + NSString *reserved = subscribeMsgResp.action; + if (reserved != nil && reserved != NULL && ![reserved isKindOfClass:[NSNull class]]) { + result[@"reserved"] = reserved; + } + + UInt32 scene = subscribeMsgResp.scene; + result[@"scene"] = @(scene); + if (_channel != nil) { + [_channel invokeMethod:@"onSubscribeMsgResp" arguments:result]; + } + } else if ([resp isKindOfClass:[WXLaunchMiniProgramResp class]]) { + WXLaunchMiniProgramResp *miniProgramResp = (WXLaunchMiniProgramResp *) resp; + NSDictionary *commonResult = @{ + description: miniProgramResp.description == nil ? @"" : miniProgramResp.description, + errStr: miniProgramResp.errStr == nil ? @"" : miniProgramResp.errStr, + errCode: @(miniProgramResp.errCode), + fluwxType: @(miniProgramResp.type), + }; + + NSMutableDictionary *result = [NSMutableDictionary dictionaryWithDictionary:commonResult]; + if (miniProgramResp.extMsg != nil) { + result[@"extMsg"] = miniProgramResp.extMsg; + } + if (_channel != nil) { + [_channel invokeMethod:@"onLaunchMiniProgramResponse" arguments:result]; + } + } else if ([resp isKindOfClass:[WXInvoiceAuthInsertResp class]]) { + // pass + } else if ([resp isKindOfClass:[WXOpenBusinessWebViewResp class]]) { + WXOpenBusinessWebViewResp *businessResp = (WXOpenBusinessWebViewResp *) resp; + NSDictionary *result = @{ + description: [FluwxStringUtil nilToEmpty:businessResp.description], + errStr: [FluwxStringUtil nilToEmpty:resp.errStr], + errCode: @(businessResp.errCode), + fluwxType: @(businessResp.type), + @"resultInfo": [FluwxStringUtil nilToEmpty:businessResp.result], + @"businessType": @(businessResp.businessType), + }; + if (_channel != nil) { + [_channel invokeMethod:@"onWXOpenBusinessWebviewResponse" arguments:result]; + } + } else if ([resp isKindOfClass:[WXOpenCustomerServiceResp class]]) { + WXOpenCustomerServiceResp *customerResp = (WXOpenCustomerServiceResp *) resp; + NSDictionary *result = @{ + description: [FluwxStringUtil nilToEmpty:customerResp.description], + errStr: [FluwxStringUtil nilToEmpty:resp.errStr], + errCode: @(customerResp.errCode), + fluwxType: @(customerResp.type), + @"extMsg":[FluwxStringUtil nilToEmpty:customerResp.extMsg] + }; + if (_channel != nil) { + [_channel invokeMethod:@"onWXOpenBusinessWebviewResponse" arguments:result]; + } + // 相关错误信息 + } else if ([resp isKindOfClass:[WXOpenBusinessViewResp class]]) { + WXOpenBusinessViewResp *openBusinessViewResp = (WXOpenBusinessViewResp *) resp; + NSDictionary *result = @{ + description: [FluwxStringUtil nilToEmpty:openBusinessViewResp.description], + errStr: [FluwxStringUtil nilToEmpty:resp.errStr], + errCode: @(openBusinessViewResp.errCode), + @"businessType":openBusinessViewResp.businessType, + fluwxType: @(openBusinessViewResp.type), + @"extMsg":[FluwxStringUtil nilToEmpty:openBusinessViewResp.extMsg] + }; + if (_channel != nil) { + [_channel invokeMethod:@"onOpenBusinessViewResponse" arguments:result]; + } + // 相关错误信息 + } +#ifndef FLUWX_NO_PAY + else if ([resp isKindOfClass:[WXPayInsuranceResp class]]) { + // pass + } else if ([resp isKindOfClass:[PayResp class]]) { + PayResp *payResp = (PayResp *) resp; + NSDictionary *result = @{ + description: [FluwxStringUtil nilToEmpty:payResp.description], + errStr: [FluwxStringUtil nilToEmpty:resp.errStr], + errCode: @(payResp.errCode), + fluwxType: @(payResp.type), + @"extData": [FluwxStringUtil nilToEmpty:[FluwxDelegate defaultManager].extData], + @"returnKey": [FluwxStringUtil nilToEmpty:payResp.returnKey], + }; + [FluwxDelegate defaultManager].extData = nil; + if (_channel != nil) { + [_channel invokeMethod:@"onPayResponse" arguments:result]; + } + } else if ([resp isKindOfClass:[WXNontaxPayResp class]]) { + // pass + } +#endif +} + +- (void)onReq:(BaseReq *)req { + if ([req isKindOfClass:[GetMessageFromWXReq class]]) { + // pass + } else if ([req isKindOfClass:[ShowMessageFromWXReq class]]) { + // ShowMessageFromWXReq -- android spec + ShowMessageFromWXReq *showMessageFromWXReq = (ShowMessageFromWXReq *) req; + WXMediaMessage *wmm = showMessageFromWXReq.message; + + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + [result setValue:wmm.messageAction forKey:@"messageAction"]; + [result setValue:wmm.messageExt forKey:@"extMsg"]; + [result setValue:showMessageFromWXReq.lang forKey:@"lang"]; + [result setValue:showMessageFromWXReq.country forKey:@"country"]; + + // Cache extMsg for later use (by calling 'getExtMsg') + [FluwxDelegate defaultManager].extMsg = wmm.messageExt; + + if (_isRunning) { + [_channel invokeMethod:@"onWXShowMessageFromWX" arguments:result]; + } else { + __weak typeof(self) weakSelf = self; + _attemptToResumeMsgFromWxRunnable = ^() { + __strong typeof(weakSelf) strongSelf = weakSelf; + [strongSelf->_channel invokeMethod:@"onWXShowMessageFromWX" arguments:result]; + }; + } + } else if ([req isKindOfClass:[LaunchFromWXReq class]]) { + // ShowMessageFromWXReq -- ios spec + LaunchFromWXReq *launchFromWXReq = (LaunchFromWXReq *) req; + WXMediaMessage *wmm = launchFromWXReq.message; + + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + [result setValue:wmm.messageAction forKey:@"messageAction"]; + [result setValue:wmm.messageExt forKey:@"extMsg"]; + [result setValue:launchFromWXReq.lang forKey:@"lang"]; + [result setValue:launchFromWXReq.country forKey:@"country"]; + + // Cache extMsg for later use (by calling 'getExtMsg') + [FluwxDelegate defaultManager].extMsg = wmm.messageExt; + + if (_isRunning) { + [_channel invokeMethod:@"onWXLaunchFromWX" arguments:result]; + } else { + __weak typeof(self) weakSelf = self; + _attemptToResumeMsgFromWxRunnable = ^() { + __strong typeof(weakSelf) strongSelf = weakSelf; + [strongSelf->_channel invokeMethod:@"onWXLaunchFromWX" arguments:result]; + }; + } + } +} + +- (void)sendText:(NSString *)text + InScene:(enum WXScene)scene + completion:(void (^ __nullable)(BOOL success))completion { + SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init]; + req.scene = scene; + req.bText = YES; + req.text = text; + [WXApi sendReq:req completion:completion]; +} + +- (void)sendImageData:(NSData *)imageData + ImgDataHash:(NSString *) imgDataHash + TagName:(NSString *)tagName + MessageExt:(NSString *)messageExt + Action:(NSString *)action + InScene:(enum WXScene)scene + title:(NSString *)title + description:(NSString *)description + MsgSignature:(NSString *)msgSignature + ThumbData:(NSData *)thumbData + ThumbDataHash:(NSString*)thumbDataHash + completion:(void (^ __nullable)(BOOL success))completion { + WXImageObject *ext = [WXImageObject object]; + ext.imageData = imageData; + ext.imgDataHash = (imgDataHash == (id) [NSNull null]) ? nil : imgDataHash; + + WXMediaMessage *message = [self messageWithTitle:(title == (id) [NSNull null]) ? nil : title + Description:(description == (id) [NSNull null]) ? nil : description + Object:ext + MessageExt:(messageExt == (id) [NSNull null]) ? nil : messageExt + MessageAction:(action == (id) [NSNull null]) ? nil : action + MediaTag:(tagName == (id) [NSNull null]) ? nil : tagName + MsgSignature:(msgSignature == (id) [NSNull null]) ? nil : msgSignature + ThumbData: thumbData + ThumbDataHash:(thumbDataHash == (id) [NSNull null]) ? nil : thumbDataHash + + ];; + + SendMessageToWXReq *req = [self requestWithText:nil + OrMediaMessage:message + bText:NO + InScene:scene]; + + [WXApi sendReq:req completion:completion]; +} + +- (void)sendLinkURL:(NSString *)urlString + TagName:(NSString *)tagName + Title:(NSString *)title + Description:(NSString *)description + MessageExt:(NSString *)messageExt + MessageAction:(NSString *)messageAction + InScene:(enum WXScene)scene + MsgSignature:(NSString *)msgSignature + ThumbData:(NSData *)thumbData + ThumbDataHash:(NSString*)thumbDataHash + completion:(void (^ __nullable)(BOOL success))completion { + WXWebpageObject *ext = [WXWebpageObject object]; + ext.webpageUrl = urlString; + + WXMediaMessage *message = [self messageWithTitle:(title == (id) [NSNull null]) ? nil :title + Description:(description == (id) [NSNull null]) ? nil : description + Object:ext + MessageExt:(messageExt == (id) [NSNull null]) ? nil : messageExt + MessageAction:(messageAction == (id) [NSNull null]) ? nil : messageAction + MediaTag:(tagName == (id) [NSNull null]) ? nil : tagName + MsgSignature:(msgSignature == (id) [NSNull null]) ? nil : msgSignature + ThumbData: thumbData + ThumbDataHash:(thumbDataHash == (id) [NSNull null]) ? nil : thumbDataHash + ]; + + SendMessageToWXReq *req = [self requestWithText:nil + OrMediaMessage:message + bText:NO + InScene:scene]; + [WXApi sendReq:req completion:completion]; +} + +- (void)sendMusicURL:(NSString *)musicURL + dataURL:(NSString *)dataURL + MusicLowBandUrl:(NSString *)musicLowBandUrl + MusicLowBandDataUrl:(NSString *)musicLowBandDataUrl + Title:(NSString *)title + Description:(NSString *)description + MessageExt:(NSString *)messageExt + MessageAction:(NSString *)messageAction + TagName:(NSString *)tagName + InScene:(enum WXScene)scene + MsgSignature:(NSString *)msgSignature + ThumbData:(NSData *)thumbData + ThumbDataHash:(NSString*)thumbDataHash + completion:(void (^ __nullable)(BOOL success))completion { + WXMusicObject *ext = [WXMusicObject object]; + + if ([FluwxStringUtil isBlank:musicURL]) { + ext.musicLowBandUrl = musicLowBandUrl; + ext.musicLowBandDataUrl = (musicLowBandDataUrl == (id) [NSNull null]) ? nil : musicLowBandDataUrl; + } else { + ext.musicUrl = musicURL; + ext.musicDataUrl = (dataURL == (id) [NSNull null]) ? nil : dataURL; + } + + + WXMediaMessage *message = [self messageWithTitle:(title == (id) [NSNull null]) ? nil : title + Description:description + Object:ext + MessageExt:(messageExt == (id) [NSNull null]) ? nil : messageExt + MessageAction:(messageAction == (id) [NSNull null]) ? nil : messageAction + MediaTag:(tagName == (id) [NSNull null]) ? nil : tagName + MsgSignature:(msgSignature == (id) [NSNull null]) ? nil : msgSignature + ThumbData: thumbData + ThumbDataHash:(thumbDataHash == (id) [NSNull null]) ? nil : thumbDataHash + ]; + + SendMessageToWXReq *req = [self requestWithText:nil + OrMediaMessage:message + bText:NO + InScene:scene]; + + [WXApi sendReq:req completion:completion]; +} + +- (void)sendVideoURL:(NSString *)videoURL + VideoLowBandUrl:(NSString *)videoLowBandUrl + Title:(NSString *)title + Description:(NSString *)description + MessageExt:(NSString *)messageExt + MessageAction:(NSString *)messageAction + TagName:(NSString *)tagName + InScene:(enum WXScene)scene + MsgSignature:(NSString *)msgSignature + ThumbData:(NSData *)thumbData + ThumbDataHash:(NSString*)thumbDataHash + completion:(void (^ __nullable)(BOOL success))completion { + WXMediaMessage *message = [WXMediaMessage message]; + message.title = (title == (id) [NSNull null]) ? nil : title; + message.description = (description == (id) [NSNull null]) ? nil : description; + message.messageExt = (messageExt == (id) [NSNull null]) ? nil : messageExt; + message.messageAction = (messageAction == (id) [NSNull null]) ? nil : messageAction; + message.mediaTagName = (tagName == (id) [NSNull null]) ? nil : tagName; + message.thumbData = (thumbData == (id) [NSNull null]) ? nil : thumbData; + message.thumbDataHash = (thumbDataHash == (id) [NSNull null]) ? nil : thumbDataHash; + + WXVideoObject *ext = [WXVideoObject object]; + if ([FluwxStringUtil isBlank:videoURL]) { + ext.videoLowBandUrl = videoLowBandUrl; + } else { + ext.videoUrl = videoURL; + } + message.mediaObject = ext; + + SendMessageToWXReq *req = [self requestWithText:nil OrMediaMessage:message bText:NO InScene:scene]; + [WXApi sendReq:req completion:completion]; +} + +- (void)sendEmotionData:(NSData *)emotionData + InScene:(enum WXScene)scene + MsgSignature:(NSString *)msgSignature + ThumbData:(NSData *)thumbData + ThumbDataHash:(NSString*)thumbDataHash + completion:(void (^ __nullable)(BOOL success))completion { + WXMediaMessage *message = [WXMediaMessage message]; + message.thumbData = (thumbData == (id) [NSNull null]) ? nil : thumbData; + message.thumbDataHash = (thumbDataHash == (id) [NSNull null]) ? nil : thumbDataHash; + + WXEmoticonObject *ext = [WXEmoticonObject object]; + ext.emoticonData = emotionData; + message.mediaObject = ext; + + NSString *signature = (msgSignature == (id) [NSNull null]) ? nil : msgSignature; + if (signature != nil) { + message.msgSignature = signature; + } + + SendMessageToWXReq *req = [self requestWithText:nil OrMediaMessage:message bText:NO InScene:scene]; + [WXApi sendReq:req completion:completion]; +} + +- (void)sendFileData:(NSData *)fileData + fileExtension:(NSString *)extension + Title:(NSString *)title + Description:(NSString *)description + InScene:(enum WXScene)scene + MsgSignature:(NSString *)msgSignature + ThumbData:(NSData *)thumbData + ThumbDataHash:(NSString*)thumbDataHash + completion:(void (^ __nullable)(BOOL success))completion { + WXMediaMessage *message = [WXMediaMessage message]; + message.title = title; + message.description = description; + message.thumbData = (thumbData == (id) [NSNull null]) ? nil : thumbData; + message.thumbDataHash = (thumbDataHash == (id) [NSNull null]) ? nil : thumbDataHash; + + WXFileObject *ext = [WXFileObject object]; + ext.fileExtension = extension; + ext.fileData = fileData; + message.mediaObject = ext; + + NSString *signature = (msgSignature == (id) [NSNull null]) ? nil : msgSignature; + if (signature != nil) { + message.msgSignature = signature; + } + + SendMessageToWXReq *req = [self requestWithText:nil OrMediaMessage:message bText:NO InScene:scene]; + [WXApi sendReq:req completion:completion]; +} + +- (void)sendMiniProgramWebpageUrl:(NSString *)webpageUrl + userName:(NSString *)userName + path:(NSString *)path + title:(NSString *)title + Description:(NSString *)description + withShareTicket:(BOOL)withShareTicket + miniProgramType:(WXMiniProgramType)programType + MessageExt:(NSString *)messageExt + MessageAction:(NSString *)messageAction + TagName:(NSString *)tagName + InScene:(enum WXScene)scene + MsgSignature:(NSString *)msgSignature + HdImageData:(NSData *)hdImageData + ThumbData:(NSData *)thumbData + ThumbDataHash:(NSString*)thumbDataHash + completion:(void (^ __nullable)(BOOL success))completion { + WXMiniProgramObject *ext = [WXMiniProgramObject object]; + ext.webpageUrl = (webpageUrl == (id) [NSNull null]) ? nil : webpageUrl; + ext.userName = (userName == (id) [NSNull null]) ? nil : userName; + ext.path = (path == (id) [NSNull null]) ? nil : path; + ext.withShareTicket = withShareTicket; + ext.hdImageData = hdImageData; + ext.miniProgramType = programType; + + WXMediaMessage *message = [self messageWithTitle:(title == (id) [NSNull null]) ? nil : title + Description:(description == (id) [NSNull null]) ? nil : description + Object:ext + MessageExt:(messageExt == (id) [NSNull null]) ? nil : messageExt + MessageAction:(messageAction == (id) [NSNull null]) ? nil : messageAction + MediaTag:(tagName == (id) [NSNull null]) ? nil : tagName + MsgSignature:(msgSignature == (id) [NSNull null]) ? nil : msgSignature + ThumbData:(thumbData == (id) [NSNull null] ? nil : thumbData) + ThumbDataHash:(thumbDataHash == (id) [NSNull null]) ? nil : thumbDataHash + ]; + + SendMessageToWXReq *req = [self requestWithText:nil OrMediaMessage:message bText:NO InScene:scene]; + [WXApi sendReq:req completion:completion]; +} + +- (void)sendAppContentData:(NSData *)data + ExtInfo:(NSString *)info + ExtURL:(NSString *)url + Title:(NSString *)title + Description:(NSString *)description + MessageExt:(NSString *)messageExt + MessageAction:(NSString *)action + InScene:(enum WXScene)scene + MsgSignature:(NSString *)msgSignature + ThumbData:(NSData *)thumbData + ThumbDataHash:(NSString*)thumbDataHash + completion:(void (^ __nullable)(BOOL success))completion { + WXAppExtendObject *ext = [WXAppExtendObject object]; + ext.extInfo = info; + ext.url = url; + ext.fileData = data; + + WXMediaMessage *message = [self messageWithTitle:title + Description:description + Object:ext + MessageExt:messageExt + MessageAction:action + MediaTag:nil + MsgSignature:(msgSignature == (id) [NSNull null]) ? nil : msgSignature + ThumbData:(thumbData == (id) [NSNull null]) ? nil : thumbData + ThumbDataHash:(thumbDataHash == (id) [NSNull null]) ? nil : thumbDataHash + ]; + + SendMessageToWXReq *req = [self requestWithText:nil OrMediaMessage:message bText:NO InScene:scene]; + [WXApi sendReq:req completion:completion]; +} + +- (void)addCardsToCardPackage:(NSArray *)cardIds + cardExts:(NSArray *)cardExts + completion:(void (^ __nullable)(BOOL success))completion { + NSMutableArray *cardItems = [NSMutableArray array]; + for (NSString *cardId in cardIds) { + WXCardItem *item = [[WXCardItem alloc] init]; + item.cardId = cardId; + item.appID = @"wxf8b4f85f3a794e77"; + [cardItems addObject:item]; + } + + for (NSInteger index = 0; index < cardItems.count; index++) { + WXCardItem *item = cardItems[index]; + NSString *ext = cardExts[index]; + item.extMsg = ext; + } + + AddCardToWXCardPackageReq *req = [[AddCardToWXCardPackageReq alloc] init]; + req.cardAry = cardItems; + [WXApi sendReq:req completion:completion]; +} + +- (void)chooseCard:(NSString *)appid + cardSign:(NSString *)cardSign + nonceStr:(NSString *)nonceStr + signType:(NSString *)signType + timestamp:(UInt32)timestamp + completion:(void (^ __nullable)(BOOL success))completion { + WXChooseCardReq *chooseCardReq = [[WXChooseCardReq alloc] init]; + chooseCardReq.appID = appid; + chooseCardReq.cardSign = cardSign; + chooseCardReq.nonceStr = nonceStr; + chooseCardReq.signType = signType; + chooseCardReq.timeStamp = timestamp; + [WXApi sendReq:chooseCardReq completion:completion]; +} + +- (void)sendAuthRequestScope:(NSString *)scope + State:(NSString *)state + OpenID:(NSString *)openID + InViewController:(UIViewController *)viewController + completion:(void (^ __nullable)(BOOL success))completion { + SendAuthReq *req = [[SendAuthReq alloc] init]; + req.scope = scope; // @"post_timeline,sns" + req.state = state; + req.openID = openID; + + return [WXApi sendAuthReq:req + viewController:viewController + delegate:self + completion:completion]; +} + + +- (void)sendAuthRequestScope:(NSString *)scope + State:(NSString *)state + OpenID:(NSString *)openID + NonAutomatic:(BOOL)nonAutomatic + completion:(void (^)(BOOL))completion { + SendAuthReq *req = [[SendAuthReq alloc] init]; + req.scope = scope; // @"post_timeline,sns" + req.state = state; + req.openID = openID; + req.nonautomatic = nonAutomatic; + + [WXApi sendReq:req completion:completion]; + +} + + +- (void)openUrl:(NSString *)url + completion:(void (^ __nullable)(BOOL success))completion { + OpenWebviewReq *req = [[OpenWebviewReq alloc] init]; + req.url = url; + [WXApi sendReq:req completion:completion]; +} + +- (void)chooseInvoice:(NSString *)appid + cardSign:(NSString *)cardSign + nonceStr:(NSString *)nonceStr + signType:(NSString *)signType + timestamp:(UInt32)timestamp + completion:(void (^ __nullable)(BOOL success))completion { + WXChooseInvoiceReq *chooseInvoiceReq = [[WXChooseInvoiceReq alloc] init]; + chooseInvoiceReq.appID = appid; + chooseInvoiceReq.cardSign = cardSign; + chooseInvoiceReq.nonceStr = nonceStr; + chooseInvoiceReq.signType = signType; + // chooseCardReq.cardType = @"INVOICE"; + chooseInvoiceReq.timeStamp = timestamp; + // chooseCardReq.canMultiSelect = 1; + [WXApi sendReq:chooseInvoiceReq completion:completion]; +} + + +- (void)openCustomerService:(NSString *)url CorpId:(NSString *)corpId completion:(void (^)(BOOL))completion { + WXOpenCustomerServiceReq *req = [[WXOpenCustomerServiceReq alloc] init]; + req.corpid = corpId; //企业ID + req.url = url; //客服URL + [WXApi sendReq:req completion:completion]; +} + +- (void)handleAuthByPhoneLogin:(FlutterMethodCall *)call result:(FlutterResult)result { + UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController; + SendAuthReq *authReq = [[SendAuthReq alloc] init]; + authReq.scope = call.arguments[@"scope"]; + authReq.state = (call.arguments[@"state"] == (id) [NSNull null]) ? nil : call.arguments[@"state"]; + [WXApi sendAuthReq:authReq viewController:vc delegate:self completion:^(BOOL success) { + result(@(success)); + }]; +} + +- (void)handleAuth:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *openId = call.arguments[@"openId"]; + + [self sendAuthRequestScope:call.arguments[@"scope"] + State:(call.arguments[@"state"] == (id) [NSNull null]) ? nil : call.arguments[@"state"] + OpenID:(openId == (id) [NSNull null]) ? nil : openId + NonAutomatic:[call.arguments[@"nonAutomatic"] boolValue] + completion:^(BOOL done) { + result(@(done)); + }]; +} + +- (void)authByQRCode:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *appId = call.arguments[@"appId"]; + NSString *scope = call.arguments[@"scope"]; + NSString *nonceStr = call.arguments[@"nonceStr"]; + NSString *timeStamp = call.arguments[@"timeStamp"]; + NSString *signature = call.arguments[@"signature"]; + NSString *schemeData = (call.arguments[@"schemeData"] == (id) [NSNull null]) ? nil : call.arguments[@"schemeData"]; + + BOOL done = [_qrauth Auth:appId nonceStr:nonceStr timeStamp:timeStamp scope:scope signature:signature schemeData:schemeData]; + result(@(done)); +} + +- (void)stopAuthByQRCode:(FlutterMethodCall *)call result:(FlutterResult)result { + BOOL done = [_qrauth StopAuth]; + result(@(done)); +} + +- (void)onQrcodeScanned { + if (_channel != nil) { + [_channel invokeMethod:@"onQRCodeScanned" arguments:@{@"errCode": @0}]; + } +} + +- (void)onAuthGotQrcode:(UIImage *)image { + NSData *imageData = UIImagePNGRepresentation(image); + // if (imageData == nil) { + // imageData = UIImageJPEGRepresentation(image, 1); + // } + if (_channel != nil) { + [_channel invokeMethod:@"onAuthGotQRCode" arguments:@{@"errCode": @0, @"qrCode": imageData}]; + } +} + +- (void)onAuthFinish:(int)errCode AuthCode:(nullable NSString *)authCode { + NSDictionary *errorCode = @{@"errCode": @(errCode)}; + NSMutableDictionary *result = [NSMutableDictionary dictionaryWithDictionary:errorCode]; + if (authCode != nil) { + result[@"authCode"] = authCode; + } + if (_channel != nil) { + [_channel invokeMethod:@"onAuthByQRCodeFinished" arguments:result]; + } +} + +- (WXMediaMessage *)messageWithTitle:(NSString *)title + Description:(NSString *)description + Object:(id)mediaObject + MessageExt:(NSString *)messageExt + MessageAction:(NSString *)action + MediaTag:(NSString *)tagName + MsgSignature:(NSString *)msgSignature + ThumbData:(NSData *)thumbData + ThumbDataHash:(NSString*)thumbDataHash { + WXMediaMessage *message = [WXMediaMessage message]; + message.title = title; + message.description = description; + message.mediaObject = mediaObject; + message.messageExt = messageExt; + message.messageAction = action; + message.mediaTagName = tagName; + message.thumbData = thumbData; + message.thumbDataHash = thumbDataHash; + if (msgSignature != nil) { + message.msgSignature = msgSignature; + } + return message; +} + +- (SendMessageToWXReq *)requestWithText:(NSString *)text + OrMediaMessage:(WXMediaMessage *)message + bText:(BOOL)bText + InScene:(enum WXScene)scene { + SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init]; + req.bText = bText; + req.scene = scene; + if (bText) { + req.text = text; + } else { + req.message = message; + } + return req; +} + +- (NSString*)fetchWeChatAppId { + NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; + NSArray *types = infoDict[@"CFBundleURLTypes"]; + for (NSDictionary *dict in types) { + if ([@"weixin" isEqualToString:dict[@"CFBundleURLName"]]) { + return dict[@"CFBundleURLSchemes"][0]; + } + } + return nil; +} + +@end \ No newline at end of file diff --git a/packages/_shared/ios/Sources/FluwxStringUtil.h b/packages/_shared/ios/Sources/FluwxStringUtil.h new file mode 100644 index 00000000..35f49f2e --- /dev/null +++ b/packages/_shared/ios/Sources/FluwxStringUtil.h @@ -0,0 +1,12 @@ +// +// Created by mo on 2020/3/7. +// + +#import + + +@interface FluwxStringUtil : NSObject ++ (BOOL)isBlank:(NSString *)string; + ++ (NSString *)nilToEmpty:(NSString *)string; +@end \ No newline at end of file diff --git a/packages/_shared/ios/Sources/FluwxStringUtil.m b/packages/_shared/ios/Sources/FluwxStringUtil.m new file mode 100644 index 00000000..b6c87ed1 --- /dev/null +++ b/packages/_shared/ios/Sources/FluwxStringUtil.m @@ -0,0 +1,24 @@ +// +// Created by mo on 2020/3/7. +// + +#import "FluwxStringUtil.h" + + +@implementation FluwxStringUtil + ++ (BOOL)isBlank:(NSString *)string { + if (string == nil) { + return YES; + } + if ([string isKindOfClass:[NSNull class]]) { + return YES; + } + return [[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0; +} + ++ (NSString *)nilToEmpty:(NSString *)string { + return string == nil ? @"" : string; +} + +@end diff --git a/packages/_shared/ios/Sources/NSStringWrapper.h b/packages/_shared/ios/Sources/NSStringWrapper.h new file mode 100644 index 00000000..bfd571b7 --- /dev/null +++ b/packages/_shared/ios/Sources/NSStringWrapper.h @@ -0,0 +1,64 @@ +// +// Created by mo on 2020/3/8. +// + + +#import + +#define JavaNotFound -1 + +@interface NSString (Wrapper) + +/** Return the char value at the specified index. */ +- (unichar)charAt:(int)index; + +/** + * Compares two strings lexicographically. + * the value 0 if the argument string is equal to this string; + * a value less than 0 if this string is lexicographically less than the string argument; + * and a value greater than 0 if this string is lexicographically greater than the string argument. + */ +- (int)compareTo:(NSString *)anotherString; + +- (int)compareToIgnoreCase:(NSString *)str; + +- (BOOL)contains:(NSString *)str; + +- (BOOL)startsWith:(NSString *)prefix; + +- (BOOL)endsWith:(NSString *)suffix; + +- (BOOL)equals:(NSString *)anotherString; + +- (BOOL)equalsIgnoreCase:(NSString *)anotherString; + +- (int)indexOfChar:(unichar)ch; + +- (int)indexOfChar:(unichar)ch fromIndex:(int)index; + +- (int)indexOfString:(NSString *)str; + +- (int)indexOfString:(NSString *)str fromIndex:(int)index; + +- (int)lastIndexOfChar:(unichar)ch; + +- (int)lastIndexOfChar:(unichar)ch fromIndex:(int)index; + +- (int)lastIndexOfString:(NSString *)str; + +- (int)lastIndexOfString:(NSString *)str fromIndex:(int)index; + +- (NSString *)substringFromIndex:(NSInteger)beginIndex + toIndex:(NSInteger)endIndex; + +- (NSString *)toLowerCase; + +- (NSString *)toUpperCase; + +- (NSString *)trim; + +- (NSString *)replaceAll:(NSString *)origin with:(NSString *)replacement; + +- (NSArray *)split:(NSString *)separator; + +@end diff --git a/packages/_shared/ios/Sources/NSStringWrapper.m b/packages/_shared/ios/Sources/NSStringWrapper.m new file mode 100644 index 00000000..0fea3503 --- /dev/null +++ b/packages/_shared/ios/Sources/NSStringWrapper.m @@ -0,0 +1,153 @@ +// +// Created by mo on 2020/3/8. +// + +#import "NSStringWrapper.h" + +@implementation NSString (Wrapper) + + +/** Java-like method. Returns the char value at the specified index. */ +- (unichar)charAt:(int)index { + return [self characterAtIndex:index]; +} + +/** + * Java-like method. Compares two strings lexicographically. + * the value 0 if the argument string is equal to this string; + * a value less than 0 if this string is lexicographically less than the string argument; + * and a value greater than 0 if this string is lexicographically greater than the string argument. + */ +- (int)compareTo:(NSString *)anotherString { + return (int)[self compare:anotherString]; +} + +/** Java-like method. Compares two strings lexicographically, ignoring case differences. */ +- (int)compareToIgnoreCase:(NSString *)str { + return (int)[self compare:str options:NSCaseInsensitiveSearch]; +} + +/** Java-like method. Returns true if and only if this string contains the specified sequence of char values. */ +- (BOOL)contains:(NSString *)str { + NSRange range = [self rangeOfString:str]; + return (range.location != NSNotFound); +} + +- (BOOL)startsWith:(NSString *)prefix { + return [self hasPrefix:prefix]; +} + +- (BOOL)endsWith:(NSString *)suffix { + return [self hasSuffix:suffix]; +} + +- (BOOL)equals:(NSString *)anotherString { + return [self isEqualToString:anotherString]; +} + +- (BOOL)equalsIgnoreCase:(NSString *)anotherString { + return [[self toLowerCase] equals:[anotherString toLowerCase]]; +} + +- (int)indexOfChar:(unichar)ch { + return [self indexOfChar:ch fromIndex:0]; +} + +- (int)indexOfChar:(unichar)ch fromIndex:(int)index { + int len = (int)self.length; + for (int i = index; i < len; ++i) { + if (ch == [self charAt:i]) { + return i; + } + } + return JavaNotFound; +} + +- (int)indexOfString:(NSString *)str { + NSRange range = [self rangeOfString:str]; + if (range.location == NSNotFound) { + return JavaNotFound; + } + return (int)range.location; +} + +- (int)indexOfString:(NSString *)str fromIndex:(int)index { + NSRange fromRange = NSMakeRange(index, self.length - index); + NSRange range = [self rangeOfString:str options:NSLiteralSearch range:fromRange]; + if (range.location == NSNotFound) { + return JavaNotFound; + } + return (int)range.location; +} + +- (int)lastIndexOfChar:(unichar)ch { + int len = (int)self.length; + for (int i = len - 1; i >= 0; --i) { + if ([self charAt:i] == ch) { + return i; + } + } + return JavaNotFound; +} + +- (int)lastIndexOfChar:(unichar)ch fromIndex:(int)index { + int len = (int)self.length; + if (index >= len) { + index = len - 1; + } + for (int i = index; i >= 0; --i) { + if ([self charAt:i] == ch) { + return index; + } + } + return JavaNotFound; +} + +- (int)lastIndexOfString:(NSString *)str { + NSRange range = [self rangeOfString:str options:NSBackwardsSearch]; + if (range.location == NSNotFound) { + return JavaNotFound; + } + return (int)range.location; +} + +- (int)lastIndexOfString:(NSString *)str fromIndex:(int)index { + NSRange fromRange = NSMakeRange(0, index); + NSRange range = [self rangeOfString:str options:NSBackwardsSearch range:fromRange]; + if (range.location == NSNotFound) { + return JavaNotFound; + } + return (int)range.location; +} + +- (NSString *)substringFromIndex:(NSInteger)beginIndex + toIndex:(NSInteger)endIndex { + if (endIndex <= beginIndex) { + return @""; + } + NSRange range = NSMakeRange(beginIndex, endIndex - beginIndex); + return [self substringWithRange:range]; +} + +- (NSString *)toLowerCase { + return [self lowercaseString]; +} + +- (NSString *)toUpperCase { + return [self uppercaseString]; +} + +- (NSString *)trim { + return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; +} + +- (NSString *)replaceAll:(NSString *)origin with:(NSString *)replacement { + return [self stringByReplacingOccurrencesOfString:origin withString:replacement]; +} + +- (NSArray *)split:(NSString *)separator { + return [self componentsSeparatedByString:separator]; +} + + +@end diff --git a/packages/_shared/ios/Sources/Resources/PrivacyInfo.xcprivacy b/packages/_shared/ios/Sources/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..80f7c45e --- /dev/null +++ b/packages/_shared/ios/Sources/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,23 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/packages/_shared/ios/Sources/ThumbnailHelper.h b/packages/_shared/ios/Sources/ThumbnailHelper.h new file mode 100644 index 00000000..6ac96a79 --- /dev/null +++ b/packages/_shared/ios/Sources/ThumbnailHelper.h @@ -0,0 +1,18 @@ +// +// Created by mo on 2020/3/7. +// + +#import +#import + + +@interface ThumbnailHelper : NSObject + ++ (UIImage *)compressImage:(UIImage *)image toByte:(NSUInteger)maxLength isPNG:(BOOL)isPNG; + +/// NSData 压缩后转NSData +/// @param imageData 来源data +/// @param maxLength 压缩目标值,压缩结果在maxLength的0.9~1之间 ++ (NSData *)compressImageData:(NSData *)imageData toByte:(NSUInteger)maxLength; + +@end diff --git a/packages/_shared/ios/Sources/ThumbnailHelper.m b/packages/_shared/ios/Sources/ThumbnailHelper.m new file mode 100644 index 00000000..3645a022 --- /dev/null +++ b/packages/_shared/ios/Sources/ThumbnailHelper.m @@ -0,0 +1,135 @@ +// +// Created by mo on 2020/3/7. +// + +#import "ThumbnailHelper.h" +#import + + +@implementation ThumbnailHelper + ++ (NSData *)compressImageData:(NSData *)imageData toByte:(NSUInteger)maxLength { + // Compress by quality + CGFloat compression = 1; + NSData *data = imageData; + NSLog(@"压缩前 %lu %lu", (unsigned long)data.length,maxLength); + if (data.length < maxLength) return data; + + UIImage *image = [UIImage imageWithData:imageData]; + CGFloat max = 1; + CGFloat min = 0; + for (int i = 0; i < 6; ++i) { + compression = (max + min) / 2; + data = UIImageJPEGRepresentation(image, compression); + if (data.length < maxLength * 0.9) { + min = compression; + } else if (data.length > maxLength) { + max = compression; + } else { + break; + } + } + + NSLog(@"压缩第一次 %lu %lu", (unsigned long)data.length,maxLength); + if (data.length < maxLength) return data; + + UIImage *resultImage; + + resultImage = [UIImage imageWithData:data]; + + // Compress by size + NSUInteger lastDataLength = 0; + while (data.length > maxLength && data.length != lastDataLength) { + lastDataLength = data.length; + CGFloat ratio = (CGFloat) maxLength / data.length; + CGSize size = CGSizeMake((NSUInteger) (resultImage.size.width * sqrtf(ratio)), + (NSUInteger) (resultImage.size.height * sqrtf(ratio))); // Use NSUInteger to prevent white blank + UIGraphicsBeginImageContext(size); + [resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; + resultImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + data = UIImageJPEGRepresentation(resultImage, compression); + } + + NSLog(@"压缩第二次 %lu %lu", (unsigned long)data.length,maxLength); + return data; +} + ++ (UIImage *)compressImage:(UIImage *)image toByte:(NSUInteger)maxLength isPNG:(BOOL)isPNG { + // Compress by quality + CGFloat compression = 1; + NSData *data = UIImageJPEGRepresentation(image, compression); + if (data.length < maxLength) return image; + + CGFloat max = 1; + CGFloat min = 0; + for (int i = 0; i < 6; ++i) { + compression = (max + min) / 2; + data = UIImageJPEGRepresentation(image, compression); + if (data.length < maxLength * 0.9) { + min = compression; + } else if (data.length > maxLength) { + max = compression; + } else { + break; + } + } + + UIImage *resultImage; + if (isPNG) { + NSData *tmp = UIImagePNGRepresentation([UIImage imageWithData:data]); + resultImage = [UIImage imageWithData:tmp]; + } else { + resultImage = [UIImage imageWithData:data]; + } + + + if (data.length < maxLength) return resultImage; + + // Compress by size + NSUInteger lastDataLength = 0; + while (data.length > maxLength && data.length != lastDataLength) { + lastDataLength = data.length; + CGFloat ratio = (CGFloat) maxLength / data.length; + CGSize size = CGSizeMake((NSUInteger) (resultImage.size.width * sqrtf(ratio)), + (NSUInteger) (resultImage.size.height * sqrtf(ratio))); // Use NSUInteger to prevent white blank + UIGraphicsBeginImageContext(size); + [resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; + resultImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + data = UIImageJPEGRepresentation(resultImage, compression); + } + + return resultImage; +} + + +- (UIImage *)scaleFromImage:(UIImage *)image width:(CGSize)newSize { + CGSize imageSize = image.size; + CGFloat width = imageSize.width; + CGFloat height = imageSize.height; + + if (width <= newSize.width && height <= newSize.height) { + return image; + } + + if (width == 0 || height == 0) { + return image; + } + + CGFloat widthFactor = newSize.width / width; + CGFloat heightFactor = newSize.height / height; + CGFloat scaleFactor = (widthFactor < heightFactor ? widthFactor : heightFactor); + + CGFloat scaledWidth = width * scaleFactor; + CGFloat scaledHeight = height * scaleFactor; + CGSize targetSize = CGSizeMake(scaledWidth, scaledHeight); + + UIGraphicsBeginImageContext(targetSize); + [image drawInRect:CGRectMake(0, 0, scaledWidth, scaledHeight)]; + UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return newImage; +} + +@end diff --git a/packages/_shared/ios/Sources/include/FluwxDelegate.h b/packages/_shared/ios/Sources/include/FluwxDelegate.h new file mode 100644 index 00000000..fa9d1df0 --- /dev/null +++ b/packages/_shared/ios/Sources/include/FluwxDelegate.h @@ -0,0 +1,19 @@ +// +// FluwxDelegate.h +// Pods +// +// Created by Mo on 2022/3/6. +// +#import + +@interface FluwxDelegate : NSObject + +@property (strong,nonatomic)NSString *extMsg; + +@property (strong,nonatomic)NSString *extData; + ++ (instancetype)defaultManager; + +- (void)registerWxAPI:(NSString *)appId universalLink:(NSString *)universalLink; + +@end diff --git a/packages/_shared/ios/Sources/include/FluwxPlugin.h b/packages/_shared/ios/Sources/include/FluwxPlugin.h new file mode 100644 index 00000000..4b60454e --- /dev/null +++ b/packages/_shared/ios/Sources/include/FluwxPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface FluwxPlugin : NSObject +@end diff --git a/packages/_shared/ohos/build-profile.json5 b/packages/_shared/ohos/build-profile.json5 new file mode 100644 index 00000000..79961f96 --- /dev/null +++ b/packages/_shared/ohos/build-profile.json5 @@ -0,0 +1,10 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "targets": [ + { + "name": "default" + } + ] +} diff --git a/packages/_shared/ohos/hvigorfile.ts b/packages/_shared/ohos/hvigorfile.ts new file mode 100644 index 00000000..47e6e1f8 --- /dev/null +++ b/packages/_shared/ohos/hvigorfile.ts @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { harTasks } from '@ohos/hvigor-ohos-plugin'; \ No newline at end of file diff --git a/example/ohos/hvigor/hvigor-config.json5 b/packages/_shared/ohos/index.ets similarity index 86% rename from example/ohos/hvigor/hvigor-config.json5 rename to packages/_shared/ohos/index.ets index 541ba357..d6659ad4 100644 --- a/example/ohos/hvigor/hvigor-config.json5 +++ b/packages/_shared/ohos/index.ets @@ -13,8 +13,5 @@ * limitations under the License. */ -{ - "modelVersion": "5.0.0", - "dependencies": { - } -} \ No newline at end of file +import FluwxPlugin from './src/main/ets/components/plugin/FluwxPlugin'; +export default FluwxPlugin; diff --git a/packages/_shared/ohos/oh-package.json5 b/packages/_shared/ohos/oh-package.json5 new file mode 100644 index 00000000..07abd25a --- /dev/null +++ b/packages/_shared/ohos/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "fluwx", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/flutter_ohos": "file:./har/flutter.har", + "@tencent/wechat_open_sdk": "1.0.15" + } +} diff --git a/packages/_shared/ohos/src/main/ets/components/plugin/FluwxPlugin.ets b/packages/_shared/ohos/src/main/ets/components/plugin/FluwxPlugin.ets new file mode 100644 index 00000000..e69de29b diff --git a/packages/_shared/ohos/src/main/ets/components/plugin/handlers/FluwxAuthHandler.ets b/packages/_shared/ohos/src/main/ets/components/plugin/handlers/FluwxAuthHandler.ets new file mode 100644 index 00000000..e69de29b diff --git a/packages/_shared/ohos/src/main/ets/components/plugin/handlers/FluwxShareHandler.ets b/packages/_shared/ohos/src/main/ets/components/plugin/handlers/FluwxShareHandler.ets new file mode 100644 index 00000000..e69de29b diff --git a/packages/_shared/ohos/src/main/ets/components/plugin/handlers/WXAPiHandler.ets b/packages/_shared/ohos/src/main/ets/components/plugin/handlers/WXAPiHandler.ets new file mode 100644 index 00000000..e69de29b diff --git a/packages/fluwx/.pubignore b/packages/fluwx/.pubignore new file mode 100644 index 00000000..6b6a748f --- /dev/null +++ b/packages/fluwx/.pubignore @@ -0,0 +1,41 @@ +example/android/gradle/wrapper/gradle-wrapper.jar +example/build + +android/.classpath +android/.project +android/.settings/org.eclipse.buildship.core.prefs +example/android/.project +example/android/.settings/org.eclipse.buildship.core.prefs +example/android/app/.classpath +example/android/app/.project +example/android/app/.settings/org.eclipse.buildship.core.prefs +example/ios/.symlinks +example/Podfile.lock +.last_build_id + +example/build +example/.dart_tool + +example/.flutter-plugins +example/.flutter-plugins-dependencies +example/.packages +example/.pub-cache/ +example/.pub/ +example/build/ + +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ +*.iml +.idea/ +.vscode/ + +.flutter-plugins-dependencies +flutter_export_environment.sh + +.last_build_id + diff --git a/packages/fluwx/CHANGELOG.md b/packages/fluwx/CHANGELOG.md new file mode 100644 index 00000000..a599646a --- /dev/null +++ b/packages/fluwx/CHANGELOG.md @@ -0,0 +1,841 @@ +# 6.0.0-preview.3 +* 修复iOS编译错误 + +# 6.0.0-preview.2 +* iOS旧的回调加回来了 + +# 6.0.0-preview.1 +* Flutter 版本要求升级到3.38.0或更高 +* Dart 版本要求升级到3.10.0或更高 +* 支持UISceneDelegate。 + +# 5.7.5 +* 更新:鸿蒙端支持分享文件 +* 更新:鸿蒙端支持打开客服会话 +* 修复: 鸿蒙端分享小程序类型错误的问题 +* 完善鸿蒙端调试文档 + +# 5.7.4 +* 鸿蒙SDK升级至1.0.15 +* Fix #735 + +# 5.7.3 +* Kotlin 升级至2.1.0 + +# 5.7.2 +* Fix #723 + +# 5.7.1 +* iOS SDK升级至2.0.5 +* Android SDK升级至6.8.34 + +# 5.7.0 +* 修复debug_logging在Android端不生效的问题 +* Fix #716 + +# 5.6.0 +* Android和iOS支持Emoj分享 +* 文档完善 + +# 5.5.5 +* 鸿蒙SDK升级到1.0.14 +* 鸿蒙端支持分享视频 + +# 5.5.4 +* 分享到小程序时,增加scene字段,尽管微信只支持分享小程序到会话. + +# 5.5.3 +* Fix [#706](https://github.com/OpenFlutter/fluwx/issues/706) + +# 5.5.2 +* Fix [#703](https://github.com/OpenFlutter/fluwx/issues/703) + +# 5.5.1 +* 鸿蒙端bug修复,详见[PR](https://github.com/OpenFlutter/fluwx/pull/700) + +# 5.5.0 +* 更新鸿蒙sdk,支持openBusinessView,onWXLaunchFromWX + +# 5.4.2 +* 更新一下API文档 + +# 5.4.1 +* Android替换过时方法toLowerCase()为lowercase() + +# 5.4.0 +* 增加鸿蒙分享网页和小程序,微信sdk升级1.0.6 + +# 5.3.1 +* JVM target降低至11 + +# 5.3.0 +* 升级kotlin、gradle相关 +* 更新progurad规则(#672) + +# 5.2.7 +* 鸿蒙sdk升级到1.0.3 +* 鸿蒙支持分享至小程序 + +# 5.2.6 +* iOS脚本优化 + +# 5.2.5 +* 测试自动发布脚本 + +# 5.2.4 +* Fix #663 + +# 5.2.3 +* Fix #661 + +# 5.2.2 +* Fix #659,优化iOS脚本,使其更加友好 + +# 5.2.1 +* Merge #658 + +# 5.2.0 +* 为iOS分享小程序增加hdImageData选项 +* 优化Android图片分享逻辑,优先把LocalImagePath以最大限度保证图片质量 + +# 5.1.0 +* 试验性支持harmonyOS,目前受限于native sdk,只支持部分功能 + +# 5.0.3 +* 优化localImagePath处理,减轻非Android开发者的上手难度:localImagePath如果是以content://开头,则默认你已经挂载了相关路径的权限, +否则请务必保证该路径是文件实际保存路径,即Android层可以直接读取到该文件,以方便fluwx将文件拷贝到指定可用目录。 + +# 5.0.2 +* 删除subscribeResponse, unsubscribeResponse + +# 5.0.1 +* Fix #642 + +# 5.0.0 +* 使用了多端统一API,详情请点击[这里](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Share_and_Favorites/Android.html) +* 分享时所有的缩略图请使用`thumbData`字段 +* Fluwx不会再对任何图片进行压缩处理 + +# 4.6.0 +* Android SDK => 6.8.30 +* 分享新增基础字段:thumbData +* 分享新增基础字段:thumbDataHash + +# 4.5.6 +* Merge #639 + +# 4.5.5 +* iOS 最小版本支持为12.0 + +# 4.5.4 +* Merge #606 + +# 4.5.3 +* Merge #605 + +# 4.5.2 +* Android WechatSdk->6.8.28 +* iOS WeChatSdk->2.0.4 + +# 4.5.1 +* Merge #602: 增加iOS privacy manifest + +# 4.5.0 +* Fix #599 +* 破坏性更新:从4.5.0起,当分享图片到微信时,如果不支持FileProvider方式分享,Fluwx不再尝试申请WRITE_EXTERNAL_STORAGE权限,这意味着你需要自己处理权限问题。 + +# 4.4.10 +* Merge #596 + +# 4.4.9 +* Merge #591 +* + +# 4.4.8 +* Fix #543 + +# 4.4.7 +* Response里asRecord更名为toRecord. +* Response相关实体类的内容比较应该对比toRecord结果 + +# 4.4.6 +* Response里asString更名为asRecord. + +# 4.4.5 +* Response相关实体类增加asString方法,以方便debug. + +# 4.4.4 +* Android重写onReattachedToActivityForConfigChanges + +# 4.4.3 +* Fix #584 缩略图不显示问题 + +# 4.4.2 +* 修复iOS回调会与其他插件冲突的问题 + +# 4.4.1 +* Fix #575 + +# 4.4.0 +* universal_link 不再是必选项 + +# 4.3.2 +* iOS新增ignore_security选项,详见#576 + +# 4.3.1 +* Merge #574 + +# 4.3.0 +* Android minSdkVersion升级至19 +* 主要解决了 iOS 端的冷启动参数传递问题 + +# 4.2.7 + +* 修复在Flutter module中编译不过的问题 + +# 4.2.6 + +* Fix #549 + +# 4.2.5 + +* iOS支持解析包含Anchors & Alias的Yaml + +# 4.2.4+1 + +* 优化Android代码生成逻辑 + +# 4.2.4 + +* 修复iOS extMsg的问题 + +# 4.2.3 + +* 修复iOS冷启动extMsg的问题 + +# 4.2.2+1 + +* 更新iOS冷启动处理 + +# 4.2.2 + +* 删除iOS在registerApi并且开启debug_logging时自动自检 +* 为iOS增加自检方法selfCheck +* subscribeResponse, unsubscribeResponse已废弃。 +* 新增addSubscriber, removeSubscriber +* addSubscriber会返回Cancelable对象,可以直接调用cancel()函数。 + +# 4.2.1 + +* 支持选用scene_delegate. + +# 4.2.0 + +* 修复ios上微信唤醒app崩溃的问题 +* Android端重构 +* app_id不再必填 + +# 4.1.1+1 + +* Fix Android compile issue + +# 4.1.1 + +* 重构Android app_id处理 + +# 4.1.0 + +* 修复冷启动问题 +* iOS端代码清理 + +# 4.0.1+1 + +* 更新iOS引用方式 + +# 4.0.1 + +* Fix #531 + +# 4.0.0+2 + +* flutter: ">=3.3.0" + +# 4.0.0+1 + +* Fix iOS compile issue + +# 4.0.0 + +* 重构Flutter端,现在需要`Fluwx fluwx = Fluwx();`调用fluwx实例 +* 支持取消回传值的监听 +* 枚举例按照dart语言规范进行了重命名 +* 一些包含`WeChat`的方法删除了`WeChat` +* 部分类改为sealed class +* No_pay现已合并入Fluwx +* 将一些设置移到pubspec.yaml,具体可以参看`example/pubspec.yaml` +* 删除了log相关操作,因为现在可以通过yaml配置 +* 新增一些open功能 + +# 4.0.0-pre.3 + +* `Fluwx`接口优化。合并了一些函数以优化使用体验。 +* 修复Logging在iOS端不好的问题。 + +# 4.0.0-pre.2 + +* No_pay现已合并入Fluwx +* 将一些设置移到pubspec.yaml,具体可以参看`example/pubspec.yaml` +* 删除了log相关操作,因为现在可以通过yaml配置 +* 增加了open()方法并删除了openWeChatApp + +# 4.0.0-pre.1 + +* 重构Flutter端,现在需要`Fluwx fluwx = Fluwx();`调用fluwx实例 +* 支持取消回传值的监听 +* 枚举例按照dart语言规范进行了重命名 +* 一些包含`WeChat`的方法删除了`WeChat` +* 部分类改为sealed class +* 最低dart版本>=3.1.0-26.0.dev + +# 3.13.1 + +* 分享到小程序的thumbnail为必填 + +# 3.13.0 + +* Android SDK升级到6.8.24 +* Kotlin升级到1.7.10 +* iOS切换到WechatOpenSDK-XCFramework + +# 3.12.2 + +* Fix #509 + +# 3.12.1 + +* 升级AGP +* Fix #512 + +# 3.12.0 + +* 授权登录支持关闭自动授权 +* 分享支持添加签名,防止篡改 + +# 3.11.0+1 + +* Fix #506 + +# 3.11.0 + +* Fix #504 + +# 3.10.0 + +* 更新微信SDK + +# 3.9.2 + +* 修复分享图片会导致Android无反应问题 + +# 3.9.1 + +* Fix issue getting extData on iOS + +# 3.9.0+2 + +* Merge #485 + +# 3.9.0+1 + +* Merge #482 + +# 3.9.0 + +* 支持微信卡包 + +# 3.8.5 + +* Fix #471 + +# 3.8.4+3 + +* Fix #478 #466 #470 #472 + +# 3.8.4+2 + +* Fix #471 +* 更换pod源 + +# 3.8.4+1 + +* Fix #471 + +# 3.8.4 + +* 增加微信的日志开关 + +# 3.8.2+1 + +* 升级kotlin-coroutine + +# 3.8.2 + +* 新加自动订阅续费功能 + +# 3.8.1+1 + +* Just update docs + +# 3.8.1 + +* 在iOS中增加FluwxDelegate +* 尝试修复iOS冷启动获取extMsg问题 + +# 3.8.0+2 + +* Fix #461 + +# 3.8.0 + +* APP调起支付分-订单详情 + +# 3.7.0 + +* Fix #453 + +# 3.6.1+4 + +* Android P support + +# 3.6.1+3 + +* Fix #431 + +# 3.6.1+2 + +* Fix #422 + +# 3.6.1+1 + +* Fix #414 + +# 3.6.1 + +* Fix #415 + +# 3.6.0 + +* APP拉起微信客服功能 + +## 3.5.1 + +* 自动释放extMsg + +## 3.5.0 + +* update compileSdkVersion + +## 3.4.3 + +* update Android SDK version + +## 3.4.2 + +* Merge #370 + +## 3.4.1 + +* 修复热启动传值问题 + +## 3.4.0 + +* 修复从外部拉起App白屏问题 +* 修复从外部拉起App无法传值问题 + +## 3.3.2 + +* Fix #357 + +## 3.3.1 + +* Fix #354 + +## 3.3.0 + +* Null-safety support +* Fix #350 + +## 2.6.2 + +* Fix #338 on Android + +## 2.6.1 + +* Fix #338 + +## 2.6.0+2 + +* Remove trailing + +## 2.6.0+1 + +* Nothing + +## 2.6.0 + +* Android支持通过H5冷启动app传递中的extinfo数据 +* Android新加handleWeChatRequestByFluwx。 + +## 2.5.0+1 + +* Fix trailing , issue. + +## 2.5.0 + +* App获取开放标签中的extinfo数据 + +## 2.4.2 + +* Fix #317 + +## 2.4.1 + +* 修复Android 11无法分享图片的问题 + +## 2.4.0 + +* 支持compressThumbnail +* 升级OkHttp + +## 2.3.0 + +* 适配Flutter 1.20 +* 升级Android的Gradle以及更库的版本 + +## 2.2.0 + +* Merged #249 + +## 2.1.0 + +* Specifying supported platforms +* Fix: Android分享小程序时,缩略图压缩过重的问题 +* 更改分享文件的实现形式 + +## 2.0.9 + +* Android SDK 升级到6.6.4 +* iOS SDK升级到1.8.7.1 +* Kotlin->1.3.72 + +## 2.0.8+3 + +* Merge #218 + +## 2.0.8+2 + +* Merge #218 + +## 2.0.8+1 + +* 修复ios编译错误 + +## 2.0.8 + +* Fix #212 + +## 2.0.7 + +* Fix #207 + +## 2.0.6+2 + +* Fix: Android分享大图时存储权限问题 + +## 2.0.6 + +* Fix: Android请求权限崩溃的问题 + +## 2.0.5+1 + +* 升级 + +## 2.0.5 + +* Fix:Android分享file文件时,会crash + +## 2.0.4 + +* Fix:hdImage为空时,ios会crash + +## 2.0.3 + +* 添加混淆文件 + +## 2.0.2 + +* Fix #199 + +## 2.0.1 + +* 修复Android没有回调的问题 + +## 2.0.0+1 + +* 按照pub建议改进 + +## 2.0.0 + +* 代码重构,现在代码结构更清晰 +* 所有图片由WeChatImage构建 +* 现在iOS对分享微信小程序的高清图也会压缩 +* 微信回调监听形式变更 +* Android增加新的Action以防微信打开小程序出错不会返回原app的问题 +* iOS改用Pod引用微信SDK +* iOS隐藏一些header +* kotlin 1.3.70 + +## 1.2.1+2 + +* iOS的StringUtil重命名了 + +## 1.2.1+1 + +* Fix #178 + +## 1.2.1 + +* Fix #175 + +## 1.2.0 + +* 分享文件 +* compileSdkVersion 29 + +## 1.1.4 + +* 注册微信时会对universal link进行简单校验 + +## 1.1.3 + +* Fix #146 + +## 1.1.2 + +* Fix #122 + +## 1.1.1+1 + +* Android CompileSDKVersion 提升到28 + +### 1.1.1 + +* registerWxApi + +## 1.1.0 + +* iOS SDK升级至1.8.6.1,本版本开始支持universal link。 +* Android SDK更换至without-mat:5.4.3 +* Android配置升级 +* 移除MTA选项 + +## 1.0.6 + +* Fix #110 + +## 1.0.5 + +* 增加分享内存图片 + +## 1.0.4 + +* 解决Android上打开小程序返回白屏问题(非官方解决方案) + +## 1.0.3 + +* 修复一些小问题 + +## 1.0.2 + +* 修复无法Android上分享大图的问题 + +## 1.0.1 + +* 修复一些小问题 + +## 1.0.0 + +* ios不必再重写AppDelegate + +## 0.6.3 + +* 免密支付 +* 支持打开微信App了 +* 升级了Android + +## 0.6.2 + +* 对android进行了升级 + +## 0.6.1 + +* 支持二维码登录 + +## 0.6.0 + +* kotlin升级至1.3.21。 +* ios SDK升级至1.8.4。 +* android SDK升级至5.3.6。 + +## 0.5.7 + +* 修复问题43。 + +## 0.5.6 + +## 0.5.5 + +* 修复ios分享小程序标题不正确的问题。 + +## 0.5.4 + +* 增加一次性订阅消息功能。 + +## 0.5.3 + +* 修复唤起小程序返回值类型不一致的问题。 + +## 0.5.2 + +* 修复ios上sendAuth无返回的问题。 +* kotlin升级至1.3.10 +* android WeChatSDK升级到5.1.6 + +## 0.5.1 + +* Kotlin升级到了1.3.0 +* 代码格化 + +## 0.5.0 + +* 增加了对拉起小程序的支持 +* 删除了一些不必要的类 +* 发送Auth验证Api调整 + +## 0.4.1 + +* 修复iOS与其他库共存时,会有重复的错误 + +## 0.4.0 + +* 移除WeChatPayModel +* 移除ios最小支持。 +* 优化*Android*微信回调。 +* *build.gradle*升级到了*3.2.1*。 + +## 0.3.2 + +* *build.gradle*升级到了*3.2.0* +* *kotlin*升级到了*1.2.71* + +## 0.3.1 + +* 修复了由于Flutter-dev-0.9.7-pre在android中添加了*@Nullable*注解而引起的编译问题 + +## 0.3.0 + +* 回调方式发生变化,由Map变更为实体类。 +* iOS的WeChatSDK更换为内部依赖,并升级到了1.8.3。 +* 修复iOS支付返回结果缺少*returnKey*的问题。 +* API现在更加友善了。 +* 对swift支持更友好了。 + +## 0.2.1 + +* 修复在Android处理网络图片后缀不对的问题。 + +## 0.2.0 + +* iOS支持Swift了。 + +## 0.1.9 + +* 修复了不传*thumbnail*在Android上会崩溃的bug。 + +## 0.1.8 + +* `WeChatPayModel`里的字段不再是`dynamic`。 +* 修复了iOS对支付功能中timestamp处理不正确的问题。 + +## 0.1.7 + +* 删除`Fluwx.registerApp(RegisterModel)`,现在使用`Fluwx.register()`。 + +## 0.1.6 + +* 修复transitive dependencies。 + +## 0.1.5 + +* 增加了本地图片的支持 + +## 0.1.4 + +* 修复了iOS分享去处错误的问题 + +## 0.1.3 + +* `ResponseType` 更名为`WeChatResponseType` + +## 0.1.2 + +* 修复iOS中FluwxShareHandler.h的导入问题 + +## 0.1.1 + +* 修复iOS分享去处错误的bug + +## 0.1.0 + +* 增加了MTA选项 +* Android部分的微信SDK提供方式由implementation更换为api + +## 0.0.8 + +* 修复了iOS无法分享小程序的bug +* 修复了iOS分享音乐崩溃的问题 +* 修复了iOS发送Auth偶尔会崩溃的问题 + +## 0.0.7 + +* 修复了iOS回调崩溃的bug + +## 0.0.6 + +* 修复iOS拉起支付崩溃的问题 + +## 0.0.5 + +* 格式化代码 + +## 0.0.4 + +* 支付 +* demo + +## 0.0.3 + +* 发送Auth认证。 + +## 0.0.2 + +* 文本分享。 +* 网站分享。 +* 图片分享。 +* 音乐分享。 +* 视频分享。 +* 小程序分享。 + +## 0.0.1 + +* Android部分的分享已完成. diff --git a/packages/fluwx/LICENSE b/packages/fluwx/LICENSE new file mode 120000 index 00000000..30cff740 --- /dev/null +++ b/packages/fluwx/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/fluwx/README.md b/packages/fluwx/README.md new file mode 100644 index 00000000..3714ebdc --- /dev/null +++ b/packages/fluwx/README.md @@ -0,0 +1,203 @@ +# Fluwx + +[![pub package](https://img.shields.io/pub/v/fluwx.svg)](https://pub.dev/packages/fluwx) +![Build status](https://github.com/OpenFlutter/fluwx/actions/workflows/build_test.yml/badge.svg) +[![GitHub stars](https://img.shields.io/github/stars/OpenFlutter/fluwx)](https://github.com/OpenFlutter/fluwx/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/OpenFlutter/fluwx)](https://github.com/OpenFlutter/fluwx/network) +[![GitHub license](https://img.shields.io/github/license/OpenFlutter/fluwx)](https://github.com/OpenFlutter/fluwx/blob/master/LICENSE) +[![GitHub issues](https://img.shields.io/github/issues/OpenFlutter/fluwx)](https://github.com/OpenFlutter/fluwx/issues) +OpenFlutter + +--- + +![logo](https://gitee.com/OpenFlutter/resoures-repository/raw/master/fluwx/fluwx_logo.png) + +[中文请移步此处](./README_CN.md) + +## What's Fluwx + +`Fluwx` is flutter plugin for [WeChatSDK](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html) which allows developers to call +[WeChatSDK](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html) native APIs. + +> Join QQ Group now: 1003811176 + +![QQGroup](https://gitee.com/OpenFlutter/resoures-repository/raw/master/common/flutter.png) + +## Capability + +- Share images, texts, music and so on to WeChat, including session, favorite and timeline. +- Payment with WeChat. +- Get auth code before you login in with WeChat. +- Launch mini program in WeChat. +- Subscribe Message. +- Just open WeChat app. +- Launch app From wechat link. +- Open Customer Service + +## Preparation + +[Migrate to V4 now](./doc/MIGRATE_TO_V4_CN.md) + +> Breaking changes :*Fluwx* won't request permission(WRITE_EXTERNAL_STORAGE) since 4.5.0. That means you will need to handle permission when sharing images, if FileProvider is not supported. + +`Fluwx` is good but not God. You'd better read [official documents](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html) before +integrating `Fluwx`. Then you'll understand how to generate Android signature, what's universal link for iOS, how to add URL schema for iOS and so on. + +## Install + +Add the `fluwx` package (with payment feature by default) in your `pubspec.yaml` file: + +`fluwx` with pay: + +```yaml +dependencies: + fluwx: ^${latestVersion} +``` + +![pub package](https://img.shields.io/pub/v/fluwx.svg) + +> [!WARNING] +> Never forget to replace ^${latestVersion} with an actual version!
+> (See the above version, or go to [versions](https://pub.dev/packages/fluwx/versions) on pub.dev) + +> [!NOTE] +> `fluwx` without pay:
+> Developers who need to exclude payment for iOS can set `no_pay: true` in the `fluwx` section of `pubspec.yaml`.
+> See the example: [example/pubspec.yaml](./example/pubspec.yaml#L19)
+ +## Configurations + +`Fluwx` enables multiple configurations in the section `fluwx` of `pubspec.yaml` from v4, you can reference [pubspec.yaml](./example/pubspec.yaml#L10) +for more details. + +> For iOS, some configurations, such as url_scheme,universal_link, LSApplicationQueriesSchemes, can be configured by `fluwx`, +> what you need to do is to fill configurations in `pubspec.yaml` + +- app_id. Recommend. It'll be used to generate scheme on iOS。This is not used to init WeChat SDK so you still need to call `fluwx.registerApi` manually. +- debug_logging. Optional. Enable logs by setting it `true`. +- flutter_activity. Optional. This is usually used by cold boot from WeChat on Android. `Fluwx` will try to launch launcher activity if not set. +- universal_link. Recommend for iOS. It'll be used to generate universal link on your projects. +- scene_delegate. Optional. Use `AppDelegate` or `SceneDelegate`. See [official documents](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html) for more details. + +- For iOS + If you are failing `cannot load such file -- plist` on iOS, please do the following steps: + +```shell +# step.1 install missing dependencies +sudo gem install plist +# step.2 enter iOS folder(example/ios/,ios/) +cd example/ios/ +# step.3 execute +pod install +``` + +- On OpenHarmony, to check if WeChat is installed, add the following to the module.json5 in your project + +```json5 +{ + "module": { + "querySchemes": [ + "weixin" + ], + } +} +``` + +> HarmonyOS Debugging Notice: Do not use the IDE's automatic signing. You must manually apply for a debug certificate for signing and debugging. + +## Register WxAPI + +Register your app via `fluwx` if necessary. + +```dart +Fluwx fluwx = Fluwx(); +final success = fluwx.registerApi(appId: "wxd930ea5d5a228f5f",universalLink: "https://your.univerallink.com/link/"); +print("register API success: $success"); +``` +### iOS +The parameter `universalLink` only works with iOS. You can read [this document](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html) to learn +how to create universalLink and what additional configuration is needed on iOS. In what follows is a summary: + +1. A valid iOS universal link for this app is needed. + +2. Make sure the app has the entitlement Associated domains enabled + +3. Add or modify `LSApplicationQueriesSchemes` in Info.plist in your iOS project. This is essential. The following strings should be added: `weixin`, `wechat`, `weixinULAPI` and `weixinURLParamsAPI`. + +4. Add or modify `CFBundleURLTypes` in Info.plist in your iOS project. Add a URL type with name `weixin` and role `editor`. Put your WeChat App ID in URL Schemes. +Example how this looks like in Info.plist after modifying via XCode: +```xml +CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + weixin + CFBundleURLSchemes + + wx123456789 + + + +``` + + +### Android +Make sure the MD5 fingerprint of the signature of your app +is registered at WeChat. You can extract this signature from your `keystore` using Java's keytool. + +In debug mode your app is signed with a debug key from the development machine which will not be recognized by WeChat and you'll get `errCode = -1`. If you want to test in debug mode you will have to modify your debug key + +You can read more about it on [this page](https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html). + + +It's better to register your API as early as possible. + +## Capability Document + +- [Basic knowledge](./doc/BASIC_KNOWLEDGE.md) +- [Share](./doc/SHARE.md) +- [Payment](./doc/PAYMENT.md) +- [Auth](./doc/AUTH.md) +- [Launch app from h5](./doc/LAUNCH_APP_FROM_H5.md) +- [Open Customer Service](/doc/Customer_Service.md) + +For more capabilities, you can read the public functions of `fluwx`. + +## QA + +[These questions maybe help](./doc/QA_CN.md) + +## Donate + +Buy the writer a cup of coffee。 + + + +## Subscribe Us On WeChat + +![subscribe](https://gitee.com/OpenFlutter/resoures-repository/raw/master/fluwx/wx_subscription.png) + +## Star history + +![stars](https://starchart.cc/OpenFlutter/fluwx.svg) + +## LICENSE + + Copyright 2023 OpenFlutter Project + + Licensed to the Apache Software Foundation (ASF) under one or more contributor + license agreements. See the NOTICE file distributed with this work for + additional information regarding copyright ownership. The ASF licenses this + file to you under the Apache License, Version 2.0 (the "License"); you may not + use this file except in compliance with the License. You may obtain a copy of + the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations under + the License. diff --git a/packages/fluwx/README_CN.md b/packages/fluwx/README_CN.md new file mode 100644 index 00000000..211a9a78 --- /dev/null +++ b/packages/fluwx/README_CN.md @@ -0,0 +1,165 @@ +# Fluwx + +[![pub package](https://img.shields.io/pub/v/fluwx.svg)](https://pub.dartlang.org/packages/fluwx) +![Build status](https://github.com/OpenFlutter/fluwx/actions/workflows/build_test.yml/badge.svg) +[![GitHub stars](https://img.shields.io/github/stars/OpenFlutter/fluwx)](https://github.com/OpenFlutter/fluwx/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/OpenFlutter/fluwx)](https://github.com/OpenFlutter/fluwx/network) +[![GitHub license](https://img.shields.io/github/license/OpenFlutter/fluwx)](https://github.com/OpenFlutter/fluwx/blob/master/LICENSE) +[![GitHub issues](https://img.shields.io/github/issues/OpenFlutter/fluwx)](https://github.com/OpenFlutter/fluwx/issues) +OpenFlutter + +--- + +![logo](https://gitee.com/OpenFlutter/resoures-repository/raw/master/fluwx/fluwx_logo.png) + +## 什么是Fluwx + +`Fluwx` 是一个[微信SDK](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html)插件,它允许开发者调用 +[微信原生SDK](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html). + +> 加入我们的QQ群: 1003811176 + +![QQGroup](https://gitee.com/OpenFlutter/resoures-repository/raw/master/common/flutter.png) + +## 能力 + +- 分享图片,文本,音乐,视频等。支持分享到会话,朋友圈以及收藏. +- 微信支付. +- 在微信登录时,获取Auth Code. +- 拉起小程序. +- 订阅消息. +- 打开微信. +- 从微信标签打开应用 +- APP拉起客服微信 + +## 准备 + +[迁移到V4指南](./doc/MIGRATE_TO_V4_CN.md) + +> 破坏性更新 :从4.5.0起,当分享图片到微信时,如果不支持FileProvider方式分享,Fluwx不再尝试申请WRITE_EXTERNAL_STORAGE权限,这意味着你需要自己处理权限问题。 + +`Fluwx` 可以做很多工作但不是所有. 在集成之前,最好读一下[官方文档](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html). + 然后你才知道怎么生成签名,怎么使用universal link以及怎么添加URL schema等. + +## 安装 + +在`pubspec.yaml` 文件中添加 `fluwx` 依赖(默认带支付功能): + +```yaml +dependencies: + fluwx: ^${latestVersion} +``` + +![pub package](https://img.shields.io/pub/v/fluwx.svg) + +> [!WARNING] +> 别忘记替换 ^${latestVersion} !为 `fluwx` 的发布版本!
+> (参考上面的版本号,或pub.dev 上的 [versions](https://pub-web.flutter-io.cn/packages/fluwx/versions)) + +> [!NOTE] +> 不带支付的 `fluwx`:
+> 一些开发者并不需要在 iOS 端使用支付能力,此时您只需要在 pubspec.yaml 的 `fluwx` 部分设置 `no_pay: true` 来去除支付能力。
+> 参考文件:[example/pubspec.yaml](./example/pubspec.yaml#L19)
+ +## 配置 + +`Fluwx` 从v4开始可以在`pubspec.yaml`的`fluwx`进行一些配置。具体可以参考[pubspec.yaml](./example/pubspec.yaml#L10)。 + +> V4开始,iOS中的url_scheme,universal_link, LSApplicationQueriesSchemes可以不必开发者手动配动。只需在`pubspec.yaml` +> 中填写即可。 + +- app_id. 推荐. 它将用于生成iOS的url_scheme。这并不会替你初始化微信SDK,所以你还是自己调用`fluwx.registerApi`。 +- debug_logging. 可选. 把它设置成`true`可以开启日志。 +- flutter_activity. 可选. 这个通常是用于Android的冷启动。如果不设置任何值,`Fluwx`将尝试启动launcher activity. +- universal_link. iOS 推荐. 它将用自动配置universal_link。 +- scene_delegate. iOS 可选. 使用 `AppDelegate` 还是使用 `SceneDelegate`. 查阅[官方文档](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html)了解更多. + +- For iOS +如果你在iOS上遇到了 `cannot load such file -- plist`, 请按照以下步骤进行操作: + +```shell +# step.1 安装必要依赖 +sudo gem install plist +# step.2 进行iOS文件夹(example/ios/,ios/) +cd example/ios/ +# step.3 执行脚本 +pod install +``` + +- 在 OpenHarmony 上,要检查微信是否已安装,请在项目的 module.json5 中添加以下内容 + +```json5 +{ + "module": { + "querySchemes": [ + "weixin" + ], + } +} +``` + +> HarmonyOS 调试须知:不要使用 IDE 的自动签名,务必手动申请调试证书进行签名并调试 + +## 注册 WxAPI + +通过 `fluwx` 注册WxApi. + +```dart +Fluwx fluwx = Fluwx(); +fluwx.registerApi(appId: "wxd930ea5d5a228f5f",universalLink: "https://your.univerallink.com/link/"); +``` + +参数 `universalLink` 只在iOS上有用. 查看[文档](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html) 以便了解如何生成通用链接. + 你也可以学习到怎么在iOS工程中添加URL schema,怎么添加`LSApplicationQueriesSchemes`。这很重要。 + +对于Android, 可以查看[本文](https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html)以便了解怎么获取app签名. +然后你需要知道release和debug时,app签名有什么区别。如果签名不对,你会得一个错误 `errCode = -1`. + +建议越早注册越好。 + +## 能力文档 + +- [基础知识](./doc/BASIC_KNOWLEDGE_CN.md) +- [分享](./doc/SHARE_CN.md) +- [支付](./doc/PAYMENT_CN.md) +- [登录](./doc/AUTH_CN.md) +- [从微信标签打开应用](./doc/LAUNCH_APP_FROM_H5_CN.md) +- [APP拉起客服微信](/doc/Customer_Service_CN.md) +对于更多功能,可以查看源码。 + +## QA + +[这些问题可能对你有帮助](./doc/QA_CN.md) + +## 捐助 + +开源不易,请作者喝杯咖啡。 + + + +## 关注公众号 + +![subscribe](https://gitee.com/OpenFlutter/resoures-repository/raw/master/fluwx/wx_subscription.png) + +## 关注趋势 + +![stars](https://starchart.cc/OpenFlutter/fluwx.svg) + +## LICENSE + + Copyright 2018 OpenFlutter Project + + Licensed to the Apache Software Foundation (ASF) under one or more contributor + license agreements. See the NOTICE file distributed with this work for + additional information regarding copyright ownership. The ASF licenses this + file to you under the Apache License, Version 2.0 (the "License"); you may not + use this file except in compliance with the License. You may obtain a copy of + the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations under + the License. diff --git a/packages/fluwx/analysis_options.yaml b/packages/fluwx/analysis_options.yaml new file mode 100644 index 00000000..513f957b --- /dev/null +++ b/packages/fluwx/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options +analyzer: + exclude: + - CHANGELOG.md diff --git a/packages/fluwx/android/build.gradle b/packages/fluwx/android/build.gradle new file mode 100644 index 00000000..2bae6799 --- /dev/null +++ b/packages/fluwx/android/build.gradle @@ -0,0 +1,150 @@ +import org.yaml.snakeyaml.Yaml + +group 'com.jarvan.fluwx' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '2.1.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:8.9.1" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.yaml:snakeyaml:2.0" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + if (project.android.hasProperty("namespace")) { + namespace 'com.jarvan.fluwx' + } + + compileSdk 34 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = '11' + } + + sourceSets { + // src/main/kotlin 是指向 _shared 的 symlink,Gradle 通过它直接找到源文件 + // 只需额外声明生成代码目录和 res / test 路径 + main.java.srcDirs += ["${buildDir}/generated/src/kotlin"] + main.res.srcDirs += ['../../_shared/android/src/main/res'] + test.java.srcDirs += ['../../_shared/android/src/test/kotlin'] + } + + defaultConfig { + minSdkVersion 24 + consumerProguardFiles 'consumer-proguard-rules.txt' + } + + dependencies { + api 'com.tencent.mm.opensdk:wechat-sdk-android:6.8.34' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2' + implementation 'id.zelory:compressor:3.0.1' + implementation 'com.squareup.okhttp3:okhttp:5.2.1' + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-core:5.0.0' + } + + testOptions { + unitTests.all { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen { false } + showStandardStreams = true + } + } + } +} + +Map loadPubspec() { + File pubspecFile + def yamlDir = rootProject.hasProperty('yamlDir') ? rootProject.ext.yamlDir : '' + def pubspecPath = rootProject.projectDir.parent + File.separator + yamlDir + "pubspec.yaml" + if (file(pubspecPath).exists()) { + pubspecFile = new File(pubspecPath) + } else { + // fallback:build.gradle 所在目录的父目录(即 packages/fluwx/) + def currentGradleFileDir = file(".").absolutePath + def parentDir = new File(currentGradleFileDir).getParentFile() + pubspecFile = new File(parentDir, "pubspec.yaml") + } + InputStream input = new FileInputStream(pubspecFile) + Yaml yaml = new Yaml() + return (Map) yaml.load(input) +} + +tasks.register("generateFluwxHelperFile") { + doFirst { + Map config = loadPubspec() + Map fluwx = (Map) config.get("fluwx") + String enableLogging = "false" + String interruptWeChatRequestByFluwx = "true" + String flutterActivity = "" + if (fluwx) { + Map android = (Map) fluwx.get("android") + if (android) { + def iwr = android.get("interrupt_wx_request") + if (iwr && iwr == "true" || iwr == "false") { + interruptWeChatRequestByFluwx = (String) iwr + } + def activity = android.get("flutter_activity") + if (activity) { + flutterActivity = (String) activity + } + } + def logging = fluwx.get("debug_logging") + if ("${logging}" == "true" || "${logging}" == "false") { + enableLogging = "${logging}" + } + } + generateFluwxConfigurations(interruptWeChatRequestByFluwx, flutterActivity, enableLogging) + } +} + +def generateFluwxConfigurations(String interruptWeChatRequestByFluwx, String flutterActivity, String enableLogging) { + File generateFolder = new File("${buildDir}/generated/src/kotlin/com/jarvan/fluwx") + String template = "package com.jarvan.fluwx\n\n" + + "// auto generated\n" + + "internal object FluwxConfigurations {\n" + + " val flutterActivity: String = \"&&flutterActivity&&\"\n" + + " val enableLogging: Boolean = &&enableLogging&&\n" + + " val interruptWeChatRequestByFluwx: Boolean = &&interruptWeChatRequestByFluwx&&\n" + + "}" + if (!generateFolder.exists()) { + generateFolder.mkdirs() + } + String source = template + .replace("&&interruptWeChatRequestByFluwx&&", interruptWeChatRequestByFluwx) + .replace("&&flutterActivity&&", flutterActivity) + .replace("&&enableLogging&&", enableLogging) + file("${generateFolder.absolutePath}/FluwxConfigurations.kt").text = source +} + +android.libraryVariants.configureEach { + it.registerGeneratedResFolders( + project.files(new File("${buildDir}/generated/src/kotlin/com/jarvan/fluwx")) + .builtBy(generateFluwxHelperFile) + ) +} diff --git a/packages/fluwx/android/consumer-proguard-rules.txt b/packages/fluwx/android/consumer-proguard-rules.txt new file mode 100644 index 00000000..f1897d89 --- /dev/null +++ b/packages/fluwx/android/consumer-proguard-rules.txt @@ -0,0 +1,38 @@ + +# 微信 + +-keep class com.tencent.mm.opensdk.** {*;} +-keep class com.tencent.wxop.** {*;} +-keep class com.tencent.mm.sdk.** {*;} + +## Kotlin + +# ServiceLoader support +-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} +-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} +-keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {} +-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {} + +# Most of volatile fields are updated with AFU and should not be mangled +-keepclassmembernames class kotlinx.** { + volatile ; +} + +## OkHttp + +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keeppackagenames okhttp3.internal.publicsuffix.* +-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt and other security providers are available. +-dontwarn okhttp3.internal.platform.** +-dontwarn org.conscrypt.** +-dontwarn org.bouncycastle.** +-dontwarn org.openjsse.** + diff --git a/packages/fluwx/android/gen.gradle.kts b/packages/fluwx/android/gen.gradle.kts new file mode 100644 index 00000000..09009352 --- /dev/null +++ b/packages/fluwx/android/gen.gradle.kts @@ -0,0 +1,32 @@ +abstract class GenFluwxHelperTask : DefaultTask() { + @get:Incremental + @get:PathSensitive(PathSensitivity.NAME_ONLY) + @get:InputDirectory + abstract val inputDir: DirectoryProperty + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @get:Input + abstract val inputProperty: Property + + @TaskAction + fun execute(inputChanges: InputChanges) { + println( + if (inputChanges.isIncremental) "Executing incrementally" + else "Executing non-incrementally" + ) + + inputChanges.getFileChanges(inputDir).forEach { change -> + if (change.fileType == FileType.DIRECTORY) return@forEach + + println("${change.changeType}: ${change.normalizedPath}") + val targetFile = outputDir.file(change.normalizedPath).get().asFile +// if (change.changeType == ChangeType.REMOVED) { +// targetFile.delete() +// } else { +// targetFile.writeText(change.file.readText().reversed()) +// } + } + } +} \ No newline at end of file diff --git a/packages/fluwx/android/settings.gradle b/packages/fluwx/android/settings.gradle new file mode 100644 index 00000000..15566533 --- /dev/null +++ b/packages/fluwx/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'fluwx' diff --git a/packages/fluwx/android/src/main/AndroidManifest.xml b/packages/fluwx/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..aae46b12 --- /dev/null +++ b/packages/fluwx/android/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fluwx/android/src/main/kotlin b/packages/fluwx/android/src/main/kotlin new file mode 120000 index 00000000..7971bc2b --- /dev/null +++ b/packages/fluwx/android/src/main/kotlin @@ -0,0 +1 @@ +../../../../_shared/android/src/main/kotlin \ No newline at end of file diff --git a/example/.gitignore b/packages/fluwx/example/.gitignore similarity index 96% rename from example/.gitignore rename to packages/fluwx/example/.gitignore index 6c319542..3820a95c 100644 --- a/example/.gitignore +++ b/packages/fluwx/example/.gitignore @@ -27,12 +27,11 @@ migrate_working_dir/ **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ -.flutter-plugins .flutter-plugins-dependencies -.packages .pub-cache/ .pub/ /build/ +/coverage/ # Symbolication related app.*.symbols diff --git a/packages/fluwx/example/.metadata b/packages/fluwx/example/.metadata new file mode 100644 index 00000000..7cd7e6ae --- /dev/null +++ b/packages/fluwx/example/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "cc0734ac716fbb8b90f3f9db8020958b1553afa7" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: android + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: ios + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: linux + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: macos + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: web + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: windows + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + + # 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/example/README.md b/packages/fluwx/example/README.md similarity index 59% rename from example/README.md rename to packages/fluwx/example/README.md index 5319fd07..a26620ea 100644 --- a/example/README.md +++ b/packages/fluwx/example/README.md @@ -1,6 +1,6 @@ # fluwx_example -Demonstrates how to use the fluwx plugin. +A new Flutter project. ## Getting Started @@ -8,8 +8,9 @@ This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) +- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) +- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, diff --git a/example/analysis_options.yaml b/packages/fluwx/example/analysis_options.yaml similarity index 93% rename from example/analysis_options.yaml rename to packages/fluwx/example/analysis_options.yaml index 61b6c4de..0d290213 100644 --- a/example/analysis_options.yaml +++ b/packages/fluwx/example/analysis_options.yaml @@ -13,8 +13,7 @@ linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. + # and their documentation is published at https://dart.dev/lints. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code diff --git a/example/android/.gitignore b/packages/fluwx/example/android/.gitignore similarity index 69% rename from example/android/.gitignore rename to packages/fluwx/example/android/.gitignore index 6f568019..be3943c9 100644 --- a/example/android/.gitignore +++ b/packages/fluwx/example/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/fluwx/example/android/app/build.gradle.kts b/packages/fluwx/example/android/app/build.gradle.kts new file mode 100644 index 00000000..31ac922e --- /dev/null +++ b/packages/fluwx/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.jarvan.fluwx_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.jarvan.fluwx_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/packages/fluwx/example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from example/android/app/src/debug/AndroidManifest.xml rename to packages/fluwx/example/android/app/src/debug/AndroidManifest.xml diff --git a/example/android/app/src/main/AndroidManifest.xml b/packages/fluwx/example/android/app/src/main/AndroidManifest.xml similarity index 50% rename from example/android/app/src/main/AndroidManifest.xml rename to packages/fluwx/example/android/app/src/main/AndroidManifest.xml index cce786e5..234ff79f 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/packages/fluwx/example/android/app/src/main/AndroidManifest.xml @@ -1,32 +1,28 @@ - - - - - - - - - + android:icon="@mipmap/ic_launcher"> + + - - + + + + + + + + diff --git a/packages/fluwx/example/android/app/src/main/kotlin/com/jarvan/fluwx_example/MainActivity.kt b/packages/fluwx/example/android/app/src/main/kotlin/com/jarvan/fluwx_example/MainActivity.kt new file mode 100644 index 00000000..2c100dbc --- /dev/null +++ b/packages/fluwx/example/android/app/src/main/kotlin/com/jarvan/fluwx_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.jarvan.fluwx_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/fluwx/example/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from example/android/app/src/main/res/drawable-v21/launch_background.xml rename to packages/fluwx/example/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/packages/fluwx/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from example/android/app/src/main/res/drawable/launch_background.xml rename to packages/fluwx/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/fluwx/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/fluwx/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/fluwx/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/fluwx/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/fluwx/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/fluwx/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/fluwx/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/fluwx/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/fluwx/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/fluwx/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/example/android/app/src/main/res/values-night/styles.xml b/packages/fluwx/example/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from example/android/app/src/main/res/values-night/styles.xml rename to packages/fluwx/example/android/app/src/main/res/values-night/styles.xml diff --git a/example/android/app/src/main/res/values/styles.xml b/packages/fluwx/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from example/android/app/src/main/res/values/styles.xml rename to packages/fluwx/example/android/app/src/main/res/values/styles.xml diff --git a/example/android/app/src/profile/AndroidManifest.xml b/packages/fluwx/example/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from example/android/app/src/profile/AndroidManifest.xml rename to packages/fluwx/example/android/app/src/profile/AndroidManifest.xml diff --git a/packages/fluwx/example/android/build.gradle.kts b/packages/fluwx/example/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/packages/fluwx/example/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/fluwx/example/android/gradle.properties b/packages/fluwx/example/android/gradle.properties new file mode 100644 index 00000000..fbee1d8c --- /dev/null +++ b/packages/fluwx/example/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/fluwx/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 92% rename from example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/fluwx/example/android/gradle/wrapper/gradle-wrapper.properties index e496849c..e4ef43fb 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/fluwx/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/packages/fluwx/example/android/settings.gradle.kts b/packages/fluwx/example/android/settings.gradle.kts new file mode 100644 index 00000000..ca7fe065 --- /dev/null +++ b/packages/fluwx/example/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/packages/fluwx/example/images b/packages/fluwx/example/images new file mode 120000 index 00000000..8d39e19d --- /dev/null +++ b/packages/fluwx/example/images @@ -0,0 +1 @@ +../../_shared/example/images \ No newline at end of file diff --git a/example/ios/.gitignore b/packages/fluwx/example/ios/.gitignore similarity index 100% rename from example/ios/.gitignore rename to packages/fluwx/example/ios/.gitignore diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/packages/fluwx/example/ios/Flutter/AppFrameworkInfo.plist similarity index 93% rename from example/ios/Flutter/AppFrameworkInfo.plist rename to packages/fluwx/example/ios/Flutter/AppFrameworkInfo.plist index 1dc6cf76..391a902b 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/fluwx/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/packages/fluwx/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from example/ios/Flutter/Debug.xcconfig rename to packages/fluwx/example/ios/Flutter/Debug.xcconfig diff --git a/example/ios/Flutter/Release.xcconfig b/packages/fluwx/example/ios/Flutter/Release.xcconfig similarity index 100% rename from example/ios/Flutter/Release.xcconfig rename to packages/fluwx/example/ios/Flutter/Release.xcconfig diff --git a/example/ios/Podfile b/packages/fluwx/example/ios/Podfile similarity index 98% rename from example/ios/Podfile rename to packages/fluwx/example/ios/Podfile index 6eafd7e2..620e46eb 100644 --- a/example/ios/Podfile +++ b/packages/fluwx/example/ios/Podfile @@ -28,6 +28,8 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do + use_frameworks! + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths diff --git a/packages/fluwx/example/ios/Podfile.lock b/packages/fluwx/example/ios/Podfile.lock new file mode 100644 index 00000000..64cf43cf --- /dev/null +++ b/packages/fluwx/example/ios/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - Flutter (1.0.0) + +DEPENDENCIES: + - Flutter (from `Flutter`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + +SPEC CHECKSUMS: + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/packages/fluwx/example/ios/Runner.xcodeproj/project.pbxproj similarity index 78% rename from example/ios/Runner.xcodeproj/project.pbxproj rename to packages/fluwx/example/ios/Runner.xcodeproj/project.pbxproj index a745e568..132ae7d0 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/fluwx/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,20 +8,20 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 331C80F4294D02FB00263BE5 /* RunnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 331C80F3294D02FB00263BE5 /* RunnerTests.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 5255F9316848A30ED1F84FF5 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 61BB2832514DC5AC842E583C /* libPods-RunnerTests.a */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 7F92E66569D6BADC48553CE7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 389EEAC0E032DEA2F26AE4B7 /* Pods_Runner.framework */; }; + 877F65CDE05F977525B07A73 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E501F8DE1B8A222ABABD76E /* Pods_RunnerTests.framework */; }; 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 */; }; - F3F2D014A64FDD073F05B4CA /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F1790AB3CDE0ED1C0E115C2 /* libPods-Runner.a */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 331C80F5294D02FB00263BE5 /* PBXContainerItemProxy */ = { + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; @@ -44,40 +44,39 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 06B1F1CBB0F5E70CD45DBD70 /* Runner.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; - 1201E93D5A4AF06F8B2154F7 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 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 = ""; }; - 1F1790AB3CDE0ED1C0E115C2 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 331C80F1294D02FB00263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 331C80F3294D02FB00263BE5 /* RunnerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerTests.m; sourceTree = ""; }; - 3796AEEE99D23C2985F27594 /* 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 = ""; }; + 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; }; + 389EEAC0E032DEA2F26AE4B7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3CDE7FB3ADAC9EC781FA9BD4 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 517551098246B7C71A7D63D8 /* 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 = ""; }; - 61BB2832514DC5AC842E583C /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5A7B6CAB9CEA0738B6709DD1 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 7C3462A6C438B8F6D89E1918 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 8E501F8DE1B8A222ABABD76E /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F2AB3D3CD61402024BFB92C /* 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 = ""; }; 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; }; - 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 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 = ""; }; - A9DDFA5760AD388C7AE67D68 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - F071110615569824B74AF901 /* 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 = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 99F5AC8A34D399D690D545EB /* 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 = ""; }; + BFD6918593E5703EAB228D37 /* 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 = ""; }; + E8660B83C5F392CDA8617FD3 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 331C80EE294D02FB00263BE5 /* Frameworks */ = { + 632BD047812488B297C6A151 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5255F9316848A30ED1F84FF5 /* libPods-RunnerTests.a in Frameworks */, + 877F65CDE05F977525B07A73 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -86,21 +85,44 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - F3F2D014A64FDD073F05B4CA /* libPods-Runner.a in Frameworks */, + 7F92E66569D6BADC48553CE7 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 331C80F2294D02FB00263BE5 /* RunnerTests */ = { + 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( - 331C80F3294D02FB00263BE5 /* RunnerTests.m */, + 331C807B294A618700263BE5 /* RunnerTests.swift */, ); path = RunnerTests; sourceTree = ""; }; + 771BCC3BF0FD3B5D582F2881 /* Pods */ = { + isa = PBXGroup; + children = ( + BFD6918593E5703EAB228D37 /* Pods-Runner.debug.xcconfig */, + 8F2AB3D3CD61402024BFB92C /* Pods-Runner.release.xcconfig */, + 99F5AC8A34D399D690D545EB /* Pods-Runner.profile.xcconfig */, + E8660B83C5F392CDA8617FD3 /* Pods-RunnerTests.debug.xcconfig */, + 7C3462A6C438B8F6D89E1918 /* Pods-RunnerTests.release.xcconfig */, + 5A7B6CAB9CEA0738B6709DD1 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 8E75DE885488032C253EF12D /* Frameworks */ = { + isa = PBXGroup; + children = ( + 389EEAC0E032DEA2F26AE4B7 /* Pods_Runner.framework */, + 8E501F8DE1B8A222ABABD76E /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -118,10 +140,10 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, - 331C80F2294D02FB00263BE5 /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, - B7024FD9E7130F00288DF469 /* Pods */, - C05958523A4B26DE07C628DD /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 771BCC3BF0FD3B5D582F2881 /* Pods */, + 8E75DE885488032C253EF12D /* Frameworks */, ); sourceTree = ""; }; @@ -129,7 +151,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, - 331C80F1294D02FB00263BE5 /* RunnerTests.xctest */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -137,93 +159,61 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 06B1F1CBB0F5E70CD45DBD70 /* Runner.entitlements */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 97C146F21CF9000F007C117D /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - B7024FD9E7130F00288DF469 /* Pods */ = { - isa = PBXGroup; - children = ( - F071110615569824B74AF901 /* Pods-Runner.debug.xcconfig */, - 3796AEEE99D23C2985F27594 /* Pods-Runner.release.xcconfig */, - 517551098246B7C71A7D63D8 /* Pods-Runner.profile.xcconfig */, - 1201E93D5A4AF06F8B2154F7 /* Pods-RunnerTests.debug.xcconfig */, - A9DDFA5760AD388C7AE67D68 /* Pods-RunnerTests.release.xcconfig */, - 3CDE7FB3ADAC9EC781FA9BD4 /* Pods-RunnerTests.profile.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; - C05958523A4B26DE07C628DD /* Frameworks */ = { - isa = PBXGroup; - children = ( - 1F1790AB3CDE0ED1C0E115C2 /* libPods-Runner.a */, - 61BB2832514DC5AC842E583C /* libPods-RunnerTests.a */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 331C80F0294D02FB00263BE5 /* RunnerTests */ = { + 331C8080294A63A400263BE5 /* RunnerTests */ = { isa = PBXNativeTarget; - buildConfigurationList = 331C80F7294D02FB00263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - A4A7D39886C9A1DA97227C83 /* [CP] Check Pods Manifest.lock */, - 331C80ED294D02FB00263BE5 /* Sources */, - 331C80EE294D02FB00263BE5 /* Frameworks */, - 331C80EF294D02FB00263BE5 /* Resources */, + E7E53A7DEB878E271D2A1F83 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 632BD047812488B297C6A151 /* Frameworks */, ); buildRules = ( ); dependencies = ( - 331C80F6294D02FB00263BE5 /* PBXTargetDependency */, + 331C8086294A63A400263BE5 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; - productReference = 331C80F1294D02FB00263BE5 /* RunnerTests.xctest */; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 97C146ED1CF9000F007C117D /* Runner */ = { - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 6284E94FC7A5514593210205 /* [CP] Check Pods Manifest.lock */, + 256B2F7CC8B22A1AC1001C2A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 8C5E9C755A0435676AA211B3 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -232,23 +222,19 @@ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { - packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, - ); isa = PBXProject; attributes = { - KnownAssetTags = ( - New, - ); + BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { - 331C80F0294D02FB00263BE5 = { + 331C8080294A63A400263BE5 = { CreatedOnToolsVersion = 14.0; TestTargetID = 97C146ED1CF9000F007C117D; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; }; }; }; @@ -261,18 +247,21 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, - 331C80F0294D02FB00263BE5 /* RunnerTests */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 331C80EF294D02FB00263BE5 /* Resources */ = { + 331C807F294A63A400263BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -293,23 +282,7 @@ /* 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"; - }; - 6284E94FC7A5514593210205 /* [CP] Check Pods Manifest.lock */ = { + 256B2F7CC8B22A1AC1001C2A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -331,22 +304,21 @@ 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; }; - 8C5E9C755A0435676AA211B3 /* [CP] Copy Pods Resources */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + name = "Thin Binary"; + outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; @@ -363,7 +335,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - A4A7D39886C9A1DA97227C83 /* [CP] Check Pods Manifest.lock */ = { + E7E53A7DEB878E271D2A1F83 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -388,11 +360,11 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 331C80ED294D02FB00263BE5 /* Sources */ = { + 331C807D294A63A400263BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 331C80F4294D02FB00263BE5 /* RunnerTests.m in Sources */, + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -400,19 +372,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, - 97C146F31CF9000F007C117D /* main.m in Sources */, + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 331C80F6294D02FB00263BE5 /* PBXTargetDependency */ = { + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = 331C80F5294D02FB00263BE5 /* PBXContainerItemProxy */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -440,6 +412,7 @@ 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++"; @@ -469,6 +442,7 @@ 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; @@ -491,9 +465,9 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 8JJXUFV6F7; + DEVELOPMENT_TEAM = R7Y4583RU2; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -502,48 +476,58 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.jarvan.fluwxExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; - 331C80F8294D02FB00263BE5 /* Debug */ = { + 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1201E93D5A4AF06F8B2154F7 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = E8660B83C5F392CDA8617FD3 /* Pods-RunnerTests.debug.xcconfig */; 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.jarvan.fluwxExample.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; }; - 331C80F9294D02FB00263BE5 /* Release */ = { + 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A9DDFA5760AD388C7AE67D68 /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 7C3462A6C438B8F6D89E1918 /* Pods-RunnerTests.release.xcconfig */; 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.jarvan.fluwxExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; name = Release; }; - 331C80FA294D02FB00263BE5 /* Profile */ = { + 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3CDE7FB3ADAC9EC781FA9BD4 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 5A7B6CAB9CEA0738B6709DD1 /* Pods-RunnerTests.profile.xcconfig */; 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.jarvan.fluwxExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; name = Profile; @@ -552,6 +536,7 @@ 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++"; @@ -581,6 +566,7 @@ 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; @@ -607,6 +593,7 @@ 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++"; @@ -636,6 +623,7 @@ 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; @@ -648,6 +636,8 @@ 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; }; @@ -658,9 +648,9 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 8JJXUFV6F7; + DEVELOPMENT_TEAM = R7Y4583RU2; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -669,6 +659,9 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.jarvan.fluwxExample; 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; @@ -678,9 +671,9 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 8JJXUFV6F7; + DEVELOPMENT_TEAM = R7Y4583RU2; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -689,6 +682,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.jarvan.fluwxExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -696,12 +691,12 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 331C80F7294D02FB00263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 331C80F8294D02FB00263BE5 /* Debug */, - 331C80F9294D02FB00263BE5 /* Release */, - 331C80FA294D02FB00263BE5 /* Profile */, + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -727,12 +722,14 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; /* End XCLocalSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { isa = XCSwiftPackageProductDependency; diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/fluwx/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/fluwx/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/fluwx/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/fluwx/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/fluwx/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/fluwx/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/fluwx/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/fluwx/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..af6c028a --- /dev/null +++ b/packages/fluwx/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "wechatopensdk-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JarvanMo/WechatOpenSDK-SPM", + "state" : { + "revision" : "8175b246203fc5f70664588046e29675d914ac44", + "version" : "2.0.5" + } + } + ], + "version" : 2 +} diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/fluwx/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 98% rename from example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/fluwx/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1f70c354..c3fedb29 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/fluwx/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -61,7 +61,7 @@ parallelizable = "YES"> diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/fluwx/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/fluwx/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/fluwx/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/fluwx/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/fluwx/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/fluwx/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/fluwx/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/fluwx/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..af6c028a --- /dev/null +++ b/packages/fluwx/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "wechatopensdk-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JarvanMo/WechatOpenSDK-SPM", + "state" : { + "revision" : "8175b246203fc5f70664588046e29675d914ac44", + "version" : "2.0.5" + } + } + ], + "version" : 2 +} diff --git a/packages/fluwx/example/ios/Runner/AppDelegate.swift b/packages/fluwx/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..c30b367e --- /dev/null +++ b/packages/fluwx/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/fluwx/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/fluwx/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/fluwx/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/fluwx/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/fluwx/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/fluwx/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/fluwx/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/fluwx/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/packages/fluwx/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from example/ios/Runner/Base.lproj/Main.storyboard rename to packages/fluwx/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/example/ios/Runner/Info.plist b/packages/fluwx/example/ios/Runner/Info.plist similarity index 72% rename from example/ios/Runner/Info.plist rename to packages/fluwx/example/ios/Runner/Info.plist index 71b1ccab..7a9a96f1 100644 --- a/example/ios/Runner/Info.plist +++ b/packages/fluwx/example/ios/Runner/Info.plist @@ -7,11 +7,9 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Fluwx + Fluwx Example CFBundleExecutable $(EXECUTABLE_NAME) - CFBundleGetInfoString - CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -24,38 +22,10 @@ $(FLUTTER_BUILD_NAME) CFBundleSignature ???? - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - weixin - CFBundleURLSchemes - - 123456 - - - CFBundleVersion $(FLUTTER_BUILD_NUMBER) - LSApplicationCategoryType - - LSApplicationQueriesSchemes - - weixin - weixinULAPI - weixinURLParamsAPI - LSRequiresIPhoneOS - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSAllowsArbitraryLoadsInWebContent - - UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -70,7 +40,7 @@ UISceneConfigurationName flutter UISceneDelegateClassName - FlutterSceneDelegate + $(PRODUCT_MODULE_NAME).SceneDelegate UISceneStoryboardFile Main @@ -96,7 +66,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - diff --git a/packages/fluwx/example/ios/Runner/Runner-Bridging-Header.h b/packages/fluwx/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/packages/fluwx/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/fluwx/example/ios/Runner/SceneDelegate.swift b/packages/fluwx/example/ios/Runner/SceneDelegate.swift new file mode 100644 index 00000000..b9ce8ea2 --- /dev/null +++ b/packages/fluwx/example/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/packages/fluwx/example/ios/RunnerTests/RunnerTests.swift b/packages/fluwx/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/lib/fluwx_compat.dart b/packages/fluwx/example/lib/fluwx_compat.dart new file mode 100644 index 00000000..5017a2cf --- /dev/null +++ b/packages/fluwx/example/lib/fluwx_compat.dart @@ -0,0 +1,2 @@ +// fluwx 适配层 — 共享 example 代码通过此文件引入正确的包 +export 'package:fluwx/fluwx.dart'; diff --git a/packages/fluwx/example/lib/main.dart b/packages/fluwx/example/lib/main.dart new file mode 120000 index 00000000..142dab37 --- /dev/null +++ b/packages/fluwx/example/lib/main.dart @@ -0,0 +1 @@ +../../../_shared/example/lib/main.dart \ No newline at end of file diff --git a/packages/fluwx/example/lib/pages b/packages/fluwx/example/lib/pages new file mode 120000 index 00000000..769f19de --- /dev/null +++ b/packages/fluwx/example/lib/pages @@ -0,0 +1 @@ +../../../_shared/example/lib/pages \ No newline at end of file diff --git a/packages/fluwx/example/lib/utils.dart b/packages/fluwx/example/lib/utils.dart new file mode 120000 index 00000000..e7b59f92 --- /dev/null +++ b/packages/fluwx/example/lib/utils.dart @@ -0,0 +1 @@ +../../../_shared/example/lib/utils.dart \ No newline at end of file diff --git a/packages/fluwx/example/linux/.gitignore b/packages/fluwx/example/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/packages/fluwx/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/fluwx/example/linux/CMakeLists.txt b/packages/fluwx/example/linux/CMakeLists.txt new file mode 100644 index 00000000..038daaa5 --- /dev/null +++ b/packages/fluwx/example/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "fluwx_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.jarvan.fluwx_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/fluwx/example/linux/flutter/CMakeLists.txt b/packages/fluwx/example/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/packages/fluwx/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/fluwx/example/linux/flutter/generated_plugin_registrant.cc b/packages/fluwx/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/packages/fluwx/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/packages/fluwx/example/linux/flutter/generated_plugin_registrant.h b/packages/fluwx/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/packages/fluwx/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/fluwx/example/linux/flutter/generated_plugins.cmake b/packages/fluwx/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..2e1de87a --- /dev/null +++ b/packages/fluwx/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/fluwx/example/linux/runner/CMakeLists.txt b/packages/fluwx/example/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/linux/runner/main.cc b/packages/fluwx/example/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/linux/runner/my_application.cc b/packages/fluwx/example/linux/runner/my_application.cc new file mode 100644 index 00000000..9bb73426 --- /dev/null +++ b/packages/fluwx/example/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, "fluwx_example"); + 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, "fluwx_example"); + } + + 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/packages/fluwx/example/linux/runner/my_application.h b/packages/fluwx/example/linux/runner/my_application.h new file mode 100644 index 00000000..db16367a --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/macos/.gitignore b/packages/fluwx/example/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/packages/fluwx/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/packages/fluwx/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/fluwx/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/packages/fluwx/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/fluwx/example/macos/Flutter/Flutter-Release.xcconfig b/packages/fluwx/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/packages/fluwx/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/fluwx/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/fluwx/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..cccf817a --- /dev/null +++ b/packages/fluwx/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/packages/fluwx/example/macos/Podfile b/packages/fluwx/example/macos/Podfile new file mode 100644 index 00000000..ff5ddb3b --- /dev/null +++ b/packages/fluwx/example/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/packages/fluwx/example/macos/Runner.xcodeproj/project.pbxproj b/packages/fluwx/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..19af8ad9 --- /dev/null +++ b/packages/fluwx/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,729 @@ +// !$*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 */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; +/* 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 */ + 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 /* fluwx_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "fluwx_example.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 = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage 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 */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* fluwx_example.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 = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 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 = ( + ); + name = Frameworks; + 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 = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* fluwx_example.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; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + 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 */ + 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"; + }; +/* 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.jarvan.fluwxExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/fluwx_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/fluwx_example"; + }; + 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.jarvan.fluwxExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/fluwx_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/fluwx_example"; + }; + 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.jarvan.fluwxExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/fluwx_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/fluwx_example"; + }; + 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 */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/example/ios/Runner/Runner.entitlements b/packages/fluwx/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 62% rename from example/ios/Runner/Runner.entitlements rename to packages/fluwx/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index a310474d..18d98100 100644 --- a/example/ios/Runner/Runner.entitlements +++ b/packages/fluwx/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -2,9 +2,7 @@ - com.apple.developer.associated-domains - - applinks:testdomain.com - + IDEDidComputeMac32BitWarning + diff --git a/packages/fluwx/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/fluwx/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..740c6e68 --- /dev/null +++ b/packages/fluwx/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fluwx/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/fluwx/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/packages/fluwx/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/fluwx/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/fluwx/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/fluwx/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/fluwx/example/macos/Runner/AppDelegate.swift b/packages/fluwx/example/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/packages/fluwx/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/packages/fluwx/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/fluwx/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/fluwx/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/packages/fluwx/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fluwx/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/fluwx/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..12bdabcd --- /dev/null +++ b/packages/fluwx/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = fluwx_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.jarvan.fluwxExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.jarvan. All rights reserved. diff --git a/packages/fluwx/example/macos/Runner/Configs/Debug.xcconfig b/packages/fluwx/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/packages/fluwx/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/fluwx/example/macos/Runner/Configs/Release.xcconfig b/packages/fluwx/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/packages/fluwx/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/fluwx/example/macos/Runner/Configs/Warnings.xcconfig b/packages/fluwx/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/packages/fluwx/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/fluwx/example/macos/Runner/DebugProfile.entitlements b/packages/fluwx/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/packages/fluwx/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/packages/fluwx/example/macos/Runner/Info.plist b/packages/fluwx/example/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/packages/fluwx/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/fluwx/example/macos/Runner/MainFlutterWindow.swift b/packages/fluwx/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/packages/fluwx/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/fluwx/example/macos/Runner/Release.entitlements b/packages/fluwx/example/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/packages/fluwx/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/fluwx/example/macos/RunnerTests/RunnerTests.swift b/packages/fluwx/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/pubspec.yaml b/packages/fluwx/example/pubspec.yaml new file mode 100644 index 00000000..9de32152 --- /dev/null +++ b/packages/fluwx/example/pubspec.yaml @@ -0,0 +1,42 @@ +name: fluwx_example +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.11.5 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.8 + + fluwx: + path: ../ + http: ^1.6.0 + + +#flutter: +# config: +# enable-swift-package-manager: false \ No newline at end of file diff --git a/example/test/widget_test.dart b/packages/fluwx/example/test/widget_test.dart similarity index 56% rename from example/test/widget_test.dart rename to packages/fluwx/example/test/widget_test.dart index 880e2f96..dc13770e 100644 --- a/example/test/widget_test.dart +++ b/packages/fluwx/example/test/widget_test.dart @@ -11,17 +11,20 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:fluwx_example/main.dart'; void main() { - testWidgets('Verify Platform version', (WidgetTester tester) async { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(const MyApp()); - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => widget is Text && - widget.data!.startsWith('Running on:'), - ), - findsOneWidget, - ); + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); }); } diff --git a/example/web/favicon.png b/packages/fluwx/example/web/favicon.png similarity index 100% rename from example/web/favicon.png rename to packages/fluwx/example/web/favicon.png diff --git a/example/web/icons/Icon-192.png b/packages/fluwx/example/web/icons/Icon-192.png similarity index 100% rename from example/web/icons/Icon-192.png rename to packages/fluwx/example/web/icons/Icon-192.png diff --git a/example/web/icons/Icon-512.png b/packages/fluwx/example/web/icons/Icon-512.png similarity index 100% rename from example/web/icons/Icon-512.png rename to packages/fluwx/example/web/icons/Icon-512.png diff --git a/example/web/icons/Icon-maskable-192.png b/packages/fluwx/example/web/icons/Icon-maskable-192.png similarity index 100% rename from example/web/icons/Icon-maskable-192.png rename to packages/fluwx/example/web/icons/Icon-maskable-192.png diff --git a/example/web/icons/Icon-maskable-512.png b/packages/fluwx/example/web/icons/Icon-maskable-512.png similarity index 100% rename from example/web/icons/Icon-maskable-512.png rename to packages/fluwx/example/web/icons/Icon-maskable-512.png diff --git a/example/web/index.html b/packages/fluwx/example/web/index.html similarity index 56% rename from example/web/index.html rename to packages/fluwx/example/web/index.html index 408bd1e5..35e47f90 100644 --- a/example/web/index.html +++ b/packages/fluwx/example/web/index.html @@ -18,10 +18,10 @@ - + - + @@ -31,29 +31,16 @@ fluwx_example - - - - - + + diff --git a/example/web/manifest.json b/packages/fluwx/example/web/manifest.json similarity index 93% rename from example/web/manifest.json rename to packages/fluwx/example/web/manifest.json index a17e3d05..e15330e7 100644 --- a/example/web/manifest.json +++ b/packages/fluwx/example/web/manifest.json @@ -5,7 +5,7 @@ "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", - "description": "Demonstrates how to use the fluwx plugin.", + "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ diff --git a/packages/fluwx/example/windows/.gitignore b/packages/fluwx/example/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/windows/CMakeLists.txt b/packages/fluwx/example/windows/CMakeLists.txt new file mode 100644 index 00000000..90773820 --- /dev/null +++ b/packages/fluwx/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(fluwx_example 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 "fluwx_example") + +# 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) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# 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/packages/fluwx/example/windows/flutter/CMakeLists.txt b/packages/fluwx/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/windows/flutter/generated_plugin_registrant.cc b/packages/fluwx/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..8b6d4680 --- /dev/null +++ b/packages/fluwx/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/packages/fluwx/example/windows/flutter/generated_plugin_registrant.h b/packages/fluwx/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/windows/flutter/generated_plugins.cmake b/packages/fluwx/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..b93c4c30 --- /dev/null +++ b/packages/fluwx/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/fluwx/example/windows/runner/CMakeLists.txt b/packages/fluwx/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/windows/runner/Runner.rc b/packages/fluwx/example/windows/runner/Runner.rc new file mode 100644 index 00000000..1a0918be --- /dev/null +++ b/packages/fluwx/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.jarvan" "\0" + VALUE "FileDescription", "fluwx_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "fluwx_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.jarvan. All rights reserved." "\0" + VALUE "OriginalFilename", "fluwx_example.exe" "\0" + VALUE "ProductName", "fluwx_example" "\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/packages/fluwx/example/windows/runner/flutter_window.cpp b/packages/fluwx/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/windows/runner/flutter_window.h b/packages/fluwx/example/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/windows/runner/main.cpp b/packages/fluwx/example/windows/runner/main.cpp new file mode 100644 index 00000000..b22a41e9 --- /dev/null +++ b/packages/fluwx/example/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"fluwx_example", 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/packages/fluwx/example/windows/runner/resource.h b/packages/fluwx/example/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/windows/runner/resources/app_icon.ico b/packages/fluwx/example/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/packages/fluwx/example/windows/runner/resources/app_icon.ico differ diff --git a/packages/fluwx/example/windows/runner/runner.exe.manifest b/packages/fluwx/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /dev/null +++ b/packages/fluwx/example/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/packages/fluwx/example/windows/runner/utils.cpp b/packages/fluwx/example/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/windows/runner/utils.h b/packages/fluwx/example/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/windows/runner/win32_window.cpp b/packages/fluwx/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/packages/fluwx/example/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/packages/fluwx/example/windows/runner/win32_window.h b/packages/fluwx/example/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/packages/fluwx/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/fluwx/ios/fluwx.podspec b/packages/fluwx/ios/fluwx.podspec new file mode 100644 index 00000000..1c8914cb --- /dev/null +++ b/packages/fluwx/ios/fluwx.podspec @@ -0,0 +1,34 @@ +Pod::Spec.new do |s| + s.name = 'fluwx' + s.module_name = 'fluwx' + s.version = '2.0.5' + s.summary = 'WeChat SDK Flutter plugin without payment — passes App Store payment compliance review.' + s.description = <<-DESC + fluwx provides the same API as fluwx but the iOS binary contains NO WechatOpenSDK symbols. + Calling payment methods returns MissingPluginException at runtime. + Use this package when your app must pass App Store payment compliance review. + DESC + s.homepage = 'https://github.com/OpenFlutter/fluwx' + s.license = { :file => '../LICENSE' } + s.author = { 'OpenFlutter' => 'jarvan.mo@gmail.com' } + s.source = { :path => '.' } + s.source_files = 'fluwx/CocoaPodsSources/fluwx/**/*' + s.public_header_files = 'fluwx/CocoaPodsSources/fluwx/include/**/*.h' + s.dependency 'Flutter' + s.dependency 'WechatOpenSDK-XCFramework','~> 2.0.5' + s.platform = :ios, '12.0' + s.static_framework = true + s.resource_bundles = { + 'fluwx_privacy' => ['fluwx/CocoaPodsSources/fluwx/Resources/PrivacyInfo.xcprivacy'] + } + s.swift_version = '5.0' + + s.frameworks = 'CoreGraphics', 'Security', 'WebKit' + s.libraries = 'c++', 'z', 'sqlite3.0' + + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + 'OTHER_LDFLAGS' => '$(inherited) -ObjC -all_load' + } +end diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxDelegate.m b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxDelegate.m new file mode 120000 index 00000000..be46ba54 --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxDelegate.m @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/FluwxDelegate.m \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxPlugin.m b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxPlugin.m new file mode 120000 index 00000000..6cc919b2 --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxPlugin.m @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/FluwxPlugin.m \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxStringUtil.h b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxStringUtil.h new file mode 120000 index 00000000..f8d9504f --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxStringUtil.h @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/FluwxStringUtil.h \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxStringUtil.m b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxStringUtil.m new file mode 120000 index 00000000..c932288a --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/FluwxStringUtil.m @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/FluwxStringUtil.m \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/NSStringWrapper.h b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/NSStringWrapper.h new file mode 120000 index 00000000..998fff9a --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/NSStringWrapper.h @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/NSStringWrapper.h \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/NSStringWrapper.m b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/NSStringWrapper.m new file mode 120000 index 00000000..4d66a933 --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/NSStringWrapper.m @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/NSStringWrapper.m \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/Resources/PrivacyInfo.xcprivacy b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/Resources/PrivacyInfo.xcprivacy new file mode 120000 index 00000000..80534207 --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1 @@ +../../../../../../_shared/ios/Sources/Resources/PrivacyInfo.xcprivacy \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/ThumbnailHelper.h b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/ThumbnailHelper.h new file mode 120000 index 00000000..1a2d3445 --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/ThumbnailHelper.h @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/ThumbnailHelper.h \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/ThumbnailHelper.m b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/ThumbnailHelper.m new file mode 120000 index 00000000..2b26083e --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/ThumbnailHelper.m @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/ThumbnailHelper.m \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/include/FluwxDelegate.h b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/include/FluwxDelegate.h new file mode 120000 index 00000000..59235176 --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/include/FluwxDelegate.h @@ -0,0 +1 @@ +../../../../../../_shared/ios/Sources/include/FluwxDelegate.h \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/include/FluwxPlugin.h b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/include/FluwxPlugin.h new file mode 120000 index 00000000..7d25db4c --- /dev/null +++ b/packages/fluwx/ios/fluwx/CocoaPodsSources/fluwx/include/FluwxPlugin.h @@ -0,0 +1 @@ +../../../../../../_shared/ios/Sources/include/FluwxPlugin.h \ No newline at end of file diff --git a/packages/fluwx/ios/fluwx/Package.swift b/packages/fluwx/ios/fluwx/Package.swift new file mode 100644 index 00000000..90169fb3 --- /dev/null +++ b/packages/fluwx/ios/fluwx/Package.swift @@ -0,0 +1,43 @@ +// swift-tools-version: 5.9 +import PackageDescription + + +let package = Package( + name: "fluwx", + platforms: [.iOS(.v13)], + products: [ + .library(name: "fluwx", targets: ["fluwx"]) + ], + dependencies: [ + .package(name: "FlutterFramework", path: "../FlutterFramework"), + .package( + url: "https://github.com/JarvanMo/WechatOpenSDK-SPM", // + from: "2.0.5" + ) + ], + targets: [ + .target( + name: "fluwx", + dependencies: [ + .product(name: "FlutterFramework", package: "FlutterFramework"), + .product(name: "WechatOpenSDK", package: "WechatOpenSDK-SPM") + ], + resources: [ + .process("Resources/PrivacyInfo.xcprivacy") + ], + cSettings: [ + .define("FLUWX_WITH_PAY"), + .headerSearchPath("include") + ], + swiftSettings: [ + .define("FLUWX_WITH_PAY") + ], + linkerSettings: [ + .linkedFramework("CoreGraphics"), + .linkedFramework("Security"), + .linkedFramework("WebKit"), + .unsafeFlags(["-ObjC", "-all_load"]) + ] + ) + ] +) diff --git a/packages/fluwx/ios/fluwx/Sources/fluwx b/packages/fluwx/ios/fluwx/Sources/fluwx new file mode 120000 index 00000000..ad0ef0fc --- /dev/null +++ b/packages/fluwx/ios/fluwx/Sources/fluwx @@ -0,0 +1 @@ +../../../../_shared/ios/Sources \ No newline at end of file diff --git a/packages/fluwx/ios/wechat_setup.rb b/packages/fluwx/ios/wechat_setup.rb new file mode 100644 index 00000000..364c7d18 --- /dev/null +++ b/packages/fluwx/ios/wechat_setup.rb @@ -0,0 +1,168 @@ +# +# Reference documentations +# https://github.com/firebase/flutterfire/blob/master/packages/firebase_crashlytics/firebase_crashlytics/ios/crashlytics_add_upload_symbols +# https://github.com/MagicalWater/Base-APP-Env/blob/master/fastlane/actions/xcode_parse.rb +# + +require 'xcodeproj' +require 'plist' +require 'optparse' +require 'uri' + +# Dictionary to hold command line arguments +options_dict = {} + +# Parse command line arguments into options_dict +OptionParser.new do |options| + options.banner = "Setup the Wechat to an Xcode target." + + options.on("-p", "--projectDirectory=DIRECTORY", String, "Directory of the Xcode project") do |dir| + options_dict[:project_dir] = dir + end + + options.on("-n", "--projectName=NAME", String, "Name of the Xcode project (ex: Runner.xcodeproj)") do |name| + options_dict[:project_name] = name + end + + options.on("-i", "--ignoreSecurity", "Ignore modifying NSAppTransportSecurity") do |opts| + options_dict[:ignore_security] = true + end + + options.on("-a", "--appId=APPID", String, "App ID for Wechat") do |opts| + options_dict[:app_id] = opts + end + + options.on("-u", "--universalLink=UNIVERSALLINK", String, "Universal Link for Wechat") do |opts| + options_dict[:universal_link] = opts + end +end.parse! + +# Minimum required arguments are a project directory and project name +unless (options_dict[:project_dir] and options_dict[:project_name]) + abort("Must provide a project directory and project name.\n") +end + +# Path to the Xcode project to modify +project_path = File.join(options_dict[:project_dir], options_dict[:project_name]) + +unless (File.exist?(project_path)) + abort("Project at #{project_path} does not exist. Please check paths manually.\n"); +end + +# Actually open and modify the project +project = Xcodeproj::Project.open(project_path) +project.targets.each do |target| + if target.name == "Runner" + app_id = options_dict[:app_id] + universal_link = options_dict[:universal_link] + + applinks = nil + if (!app_id.nil? && !app_id.empty? && !universal_link.nil? && !universal_link.empty?) + begin + applinks = "applinks:#{URI.parse(universal_link).host}" + rescue URI::InvalidURIError + applinks = nil + end + end + + sectionObject = {} + project.objects.each do |object| + if object.uuid == target.uuid + sectionObject = object + break + end + end + sectionObject.build_configurations.each do |config| + infoplist = config.build_settings["INFOPLIST_FILE"] + if !infoplist + abort("INFOPLIST_FILE is not exist\n") + end + infoplistFile = File.join(options_dict[:project_dir], infoplist) + if !File.exist?(infoplistFile) + abort("#{infoplist} is not exist\n") + end + result = Plist.parse_xml(infoplistFile, marshal: false) + if !result + result = {} + end + urlTypes = result["CFBundleURLTypes"] + if !urlTypes + urlTypes = [] + result["CFBundleURLTypes"] = urlTypes + end + isUrlTypeExist = urlTypes.any? { |urlType| urlType["CFBundleURLSchemes"] && (urlType["CFBundleURLSchemes"].include? app_id) } + if !app_id.nil? && !app_id.empty? && !isUrlTypeExist + print("writing app id\n ") + urlTypes << { + "CFBundleTypeRole": "Editor", + "CFBundleURLName": "weixin", + "CFBundleURLSchemes": [ app_id ] + } + File.write(infoplistFile, Plist::Emit.dump(result)) + end + + queriesSchemes = result["LSApplicationQueriesSchemes"] + if !queriesSchemes + queriesSchemes = [] + result["LSApplicationQueriesSchemes"] = queriesSchemes + end + wechatQueriesSchemes = ["weixin", "weixinULAPI", "weixinURLParamsAPI"] + if wechatQueriesSchemes.any? { |queriesScheme| !(queriesSchemes.include? queriesScheme) } + wechatQueriesSchemes.each do |queriesScheme| + if !(queriesSchemes.include? queriesScheme) + queriesSchemes << queriesScheme + end + end + File.write(infoplistFile, Plist::Emit.dump(result)) + end + if !options_dict[:ignore_security] + security = result["NSAppTransportSecurity"] + if !security + security = {} + result["NSAppTransportSecurity"] = security + end + if security["NSAllowsArbitraryLoads"] != true + security["NSAllowsArbitraryLoads"] = true + File.write(infoplistFile, Plist::Emit.dump(result)) + end + if security["NSAllowsArbitraryLoadsInWebContent"] != true + security["NSAllowsArbitraryLoadsInWebContent"] = true + File.write(infoplistFile, Plist::Emit.dump(result)) + end + end + end + sectionObject.build_configurations.each do |config| + codeSignEntitlements = config.build_settings["CODE_SIGN_ENTITLEMENTS"] + if !codeSignEntitlements + codeSignEntitlements = "Runner/Runner.entitlements" + config.build_settings["CODE_SIGN_ENTITLEMENTS"] = codeSignEntitlements + project.save() + end + codeSignEntitlementsFile = File.join(options_dict[:project_dir], codeSignEntitlements) + if !File.exist?(codeSignEntitlementsFile) + content = Plist::Emit.dump({}) + File.write(codeSignEntitlementsFile, content) + end + runnerTargetMainGroup = project.main_group.find_subpath('Runner', false) + isRefExist = runnerTargetMainGroup.files.any? { |file| file.path.include? File.basename(codeSignEntitlementsFile) } + if !isRefExist + runnerTargetMainGroup.new_reference(File.basename(codeSignEntitlementsFile)) + project.save() + end + result = Plist.parse_xml(codeSignEntitlementsFile, marshal: false) + if !result + result = {} + end + domains = result["com.apple.developer.associated-domains"] + if !domains + domains = [] + result["com.apple.developer.associated-domains"] = domains + end + isApplinksExist = domains.include? applinks + if !isApplinksExist && !applinks.nil? + domains << applinks + File.write(codeSignEntitlementsFile, Plist::Emit.dump(result)) + end + end + end +end diff --git a/packages/fluwx/lib/fluwx.dart b/packages/fluwx/lib/fluwx.dart new file mode 100644 index 00000000..3b9cc21b --- /dev/null +++ b/packages/fluwx/lib/fluwx.dart @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/// Fluwx is a powerful plugin for WeChatSDK. +/// easy to use. +/// +/// A open sou;rce project authorized by [OpenFlutter](https://github.com/OpenFlutter). +library fluwx; + +export 'src/fluwx.dart'; +export 'src/foundation/arguments.dart'; +export 'src/foundation/cancelable.dart' hide FluwxCancelableImpl; +export 'src/response/wechat_response.dart'; +export 'src/wechat_enums.dart'; +export 'src/wechat_file.dart' hide FileSchema; diff --git a/packages/fluwx/lib/src/fluwx.dart b/packages/fluwx/lib/src/fluwx.dart new file mode 100644 index 00000000..7a2a7041 --- /dev/null +++ b/packages/fluwx/lib/src/fluwx.dart @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import 'dart:async'; + +import 'foundation/arguments.dart'; +import 'foundation/cancelable.dart'; +import 'method_channel/fluwx_platform_interface.dart'; +import 'response/wechat_response.dart'; + +class Fluwx { + Fluwx() { + _responseSubscription = FluwxPlatform.instance.responseEventHandler.listen( + _responseEventListener, + onDone: () { + _responseSubscription?.cancel(); + }, + ); + } + + final _responseListeners = []; + StreamSubscription? _responseSubscription; + + void _responseEventListener(WeChatResponse event) { + for (final listener in _responseListeners.toList()) { + listener(event); + } + } + + Future get isWeChatInstalled => + FluwxPlatform.instance.isWeChatInstalled; + + /// Open given target. See [OpenType] for more details + Future open({required OpenType target}) { + return FluwxPlatform.instance.open(target); + } + + /// It's ok if you register multi times. + /// [appId] is not necessary. + /// if [doOnIOS] is true ,fluwx will register WXApi on iOS. + /// if [doOnAndroid] is true, fluwx will register WXApi on Android. + /// [universalLink] is required if you want to register on iOS. + Future registerApi({ + required String appId, + bool doOnIOS = true, + bool doOnAndroid = true, + String? universalLink, + }) async { + return FluwxPlatform.instance.registerApi( + appId: appId, + doOnAndroid: doOnAndroid, + doOnIOS: doOnIOS, + universalLink: universalLink); + } + + /// Share your requests to WeChat. + /// This depends on the actual type of [what]. + Future share(WeChatShareModel what) async { + return FluwxPlatform.instance.share(what); + } + + /// Login by WeChat.See [AuthType] for more details. + Future authBy({required AuthType which}) async { + return FluwxPlatform.instance.authBy(which); + } + + /// Stop QR service + Future stopAuthByQRCode() => FluwxPlatform.instance.stopAuthByQRCode(); + + /// please read * [official docs](https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_2.shtml). + Future autoDeduct({required AutoDeduct data}) async { + return FluwxPlatform.instance.autoDeduct(data); + } + + Future get isSupportOpenBusinessView async => + await FluwxPlatform.instance.isSupportOpenBusinessView; + + Future getExtMsg() async { + return FluwxPlatform.instance.getExtMsg(); + } + + /// Pay with WeChat. See [PayType] + Future pay({required PayType which}) async { + return FluwxPlatform.instance.pay(which); + } + + /// Try to reload data from cold boot. For example, the app is launched by mini program and + /// we can get ext message by calling this. + Future attemptToResumeMsgFromWx() async { + return FluwxPlatform.instance.attemptToResumeMsgFromWx(); + } + + /// Only works on iOS in debug mode. + /// Check if your app can work with WeChat correctly. + /// Please make sure [registerApi] returns true before self check. + Future selfCheck() async { + return FluwxPlatform.instance.selfCheck(); + } + + /// Add a subscriber to subscribe responses from WeChat + FluwxCancelable addSubscriber(WeChatResponseSubscriber listener) { + _responseListeners.add(listener); + return FluwxCancelableImpl(onCancel: () { + removeSubscriber(listener); + }); + } + + /// remove your subscriber from WeChat + void removeSubscriber(WeChatResponseSubscriber listener) { + _responseListeners.remove(listener); + } + + /// remove all existing + void clearSubscribers() { + _responseListeners.clear(); + } +} diff --git a/packages/fluwx/lib/src/foundation/arguments.dart b/packages/fluwx/lib/src/foundation/arguments.dart new file mode 100644 index 00000000..0555bc4a --- /dev/null +++ b/packages/fluwx/lib/src/foundation/arguments.dart @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import 'dart:io'; +import 'dart:typed_data'; + +import '../wechat_enums.dart'; +import '../wechat_file.dart'; + +part 'auth_type.dart'; + +part 'auto_deduct.dart'; + +part 'open_type.dart'; + +part 'pay_type.dart'; + +part 'share_models.dart'; + +mixin _Argument { + Map get arguments => {}; +} diff --git a/packages/fluwx/lib/src/foundation/auth_type.dart b/packages/fluwx/lib/src/foundation/auth_type.dart new file mode 100644 index 00000000..cbe2ffd2 --- /dev/null +++ b/packages/fluwx/lib/src/foundation/auth_type.dart @@ -0,0 +1,76 @@ +part of 'arguments.dart'; + +/// The base class for all AuthType +sealed class AuthType with _Argument {} + +/// The WeChat-Login is under Auth-2.0 +/// This method login with native WeChat app. +/// For users without WeChat app, please use [QRCode] instead +/// This method only supports getting AuthCode,this is first step to login with WeChat +/// Once AuthCode got, you need to request Access_Token +/// For more information please visit: +/// * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419317851&token= +class NormalAuth extends AuthType { + final String scope; + final String state; + final bool nonAutomatic; + + NormalAuth( + {required this.scope, this.state = 'state', this.nonAutomatic = false}) + : assert(scope.trim().isNotEmpty); + + @override + Map get arguments => + {'scope': scope, 'state': state, 'nonAutomatic': nonAutomatic}; +} + +/// Sometimes WeChat is not installed on users's devices.However we can +/// request a QRCode so that we can get AuthCode by scanning the QRCode +/// All required params must not be null or empty +/// [schemeData] only works on iOS +/// see * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=215238808828h4XN&token=&lang=zh_CN +class QRCode extends AuthType { + final String appId; + final String scope; + final String nonceStr; + final String timestamp; + final String signature; + final String? schemeData; + + QRCode({ + required this.appId, + required this.scope, + required this.nonceStr, + required this.timestamp, + required this.signature, + this.schemeData, + }) : assert(appId.isNotEmpty), + assert(scope.isNotEmpty), + assert(nonceStr.isNotEmpty), + assert(timestamp.isNotEmpty), + assert(signature.isNotEmpty); + + @override + Map get arguments => { + 'appId': appId, + 'scope': scope, + 'nonceStr': nonceStr, + 'timeStamp': timestamp, + 'signature': signature, + 'schemeData': schemeData + }; +} + +/// Currently only support iOS +class PhoneLogin extends AuthType { + final String scope; + final String state; + + PhoneLogin({ + required this.scope, + this.state = 'state', + }); + + @override + Map get arguments => {'scope': scope, 'state': state}; +} diff --git a/packages/fluwx/lib/src/foundation/auto_deduct.dart b/packages/fluwx/lib/src/foundation/auto_deduct.dart new file mode 100644 index 00000000..120a7b3d --- /dev/null +++ b/packages/fluwx/lib/src/foundation/auto_deduct.dart @@ -0,0 +1,47 @@ +part of 'arguments.dart'; + +/// Auto Deduct +class AutoDeduct with _Argument { + final Map queryInfo; + final int businessType; + final Map _detailData; + + bool get isV2 => _detailData.isEmpty; + + AutoDeduct.detail({ + required String appId, + required String mchId, + required String planId, + required String contractCode, + required String requestSerial, + required String contractDisplayAccount, + required String notifyUrl, + required String version, + required String sign, + required String timestamp, + String returnApp = '3', + this.businessType = 12, + }) : queryInfo = {}, + _detailData = { + 'appid': appId, + 'mch_id': mchId, + 'plan_id': planId, + 'contract_code': contractCode, + 'request_serial': requestSerial, + 'contract_display_account': contractDisplayAccount, + 'notify_url': notifyUrl, + 'version': version, + 'sign': sign, + 'timestamp': timestamp, + 'return_app': returnApp, + 'businessType': businessType + }; + + AutoDeduct.custom({required this.queryInfo, this.businessType = 12}) + : _detailData = {}; + + @override + Map get arguments => isV2 + ? {'queryInfo': queryInfo, 'businessType': businessType} + : _detailData; +} diff --git a/packages/fluwx/lib/src/foundation/cancelable.dart b/packages/fluwx/lib/src/foundation/cancelable.dart new file mode 100644 index 00000000..255d2964 --- /dev/null +++ b/packages/fluwx/lib/src/foundation/cancelable.dart @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import 'package:flutter/foundation.dart'; + +/// A mixin that provides a method to cancel an operation. +mixin FluwxCancelable { + void cancel(); +} + +class FluwxCancelableImpl implements FluwxCancelable { + const FluwxCancelableImpl({required this.onCancel}); + + final VoidCallback onCancel; + + @override + void cancel() { + onCancel(); + } +} diff --git a/packages/fluwx/lib/src/foundation/open_type.dart b/packages/fluwx/lib/src/foundation/open_type.dart new file mode 100644 index 00000000..b03d2f65 --- /dev/null +++ b/packages/fluwx/lib/src/foundation/open_type.dart @@ -0,0 +1,112 @@ +part of 'arguments.dart'; + +/// The target you want to open. +sealed class OpenType with _Argument {} + +/// Just open WeChat app. +class WeChatApp extends OpenType {} + +/// Open WeChat browser with given url. +class Browser extends OpenType { + final String url; + + Browser(this.url); + + @override + Map get arguments => {'url': url}; +} + +/// Rank list +class RankList extends OpenType {} + +/// see * https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter6_2_1.shtml +class BusinessView extends OpenType { + final String businessType; + final String query; + + BusinessView({required this.businessType, required this.query}); + + @override + Map get arguments => + {"businessType": businessType, "query": query}; +} + +/// Inovice +class Invoice extends OpenType { + final String appId; + final String cardType; + final String locationId; + final String cardId; + final String canMultiSelect; + + Invoice( + {required this.appId, + required this.cardType, + this.locationId = "", + this.cardId = "", + this.canMultiSelect = "1"}); + + @override + Map get arguments => { + "appId": appId, + "cardType": cardType, + "locationId": locationId, + "cardId": cardId, + "canMultiSelect": canMultiSelect + }; +} + +/// Customer service chat, known as "微信客服" in WeChat +class CustomerServiceChat extends OpenType { + final String corpId; + final String url; + + CustomerServiceChat({required this.corpId, required this.url}); + + @override + Map get arguments => {"corpId": corpId, "url": url}; +} + +/// open mini-program +/// see [WXMiniProgramType] +class MiniProgram extends OpenType { + final String username; + final String? path; + final WXMiniProgramType miniProgramType; + + MiniProgram({ + required this.username, + this.path, + this.miniProgramType = WXMiniProgramType.release, + }) : assert(username.trim().isNotEmpty); + + @override + Map get arguments => { + 'userName': username, + 'path': path, + 'miniProgramType': miniProgramType.value + }; +} + +/// See *https://developers.weixin.qq.com/doc/oplatform/Mobile_App/One-time_subscription_info.html for more detail +class SubscribeMessage extends OpenType { + final String appId; + final int scene; + final String templateId; + final String? reserved; + + SubscribeMessage({ + required this.appId, + required this.scene, + required this.templateId, + this.reserved, + }); + + @override + Map get arguments => { + 'appId': appId, + 'scene': scene, + 'templateId': templateId, + 'reserved': reserved, + }; +} diff --git a/packages/fluwx/lib/src/foundation/pay_type.dart b/packages/fluwx/lib/src/foundation/pay_type.dart new file mode 100644 index 00000000..d1d2e397 --- /dev/null +++ b/packages/fluwx/lib/src/foundation/pay_type.dart @@ -0,0 +1,56 @@ +part of 'arguments.dart'; + +sealed class PayType with _Argument {} + +/// request payment with WeChat. +/// Read the official document for more detail. +/// [timestamp] is int because [timestamp] will be mapped to Unit32. +class Payment extends PayType { + final String appId; + final String partnerId; + final String prepayId; + final String packageValue; + final String nonceStr; + final int timestamp; + final String sign; + final String? signType; + final String? extData; + + Payment({ + required this.appId, + required this.partnerId, + required this.prepayId, + required this.packageValue, + required this.nonceStr, + required this.timestamp, + required this.sign, + this.signType, + this.extData, + }); + + @override + Map get arguments => { + 'appId': appId, + 'partnerId': partnerId, + 'prepayId': prepayId, + 'packageValue': packageValue, + 'nonceStr': nonceStr, + 'timeStamp': timestamp, + 'sign': sign, + 'signType': signType, + 'extData': extData, + }; +} + +/// request Hong Kong Wallet payment with WeChat. +/// Read the official document for more detail. +class HongKongWallet extends PayType { + final String prepayId; + + HongKongWallet({required this.prepayId}); + + @override + Map get arguments => { + 'prepayId': prepayId, + }; +} diff --git a/packages/fluwx/lib/src/foundation/share_models.dart b/packages/fluwx/lib/src/foundation/share_models.dart new file mode 100644 index 00000000..86216211 --- /dev/null +++ b/packages/fluwx/lib/src/foundation/share_models.dart @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2023. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +part of 'arguments.dart'; + +const String _scene = "scene"; +const String _source = "source"; +const String _title = "title"; +const String _description = "description"; +const String _messageExt = "messageExt"; +const String _mediaTagName = "mediaTagName"; +const String _messageAction = "messageAction"; +const String _msgSignature = "msgSignature"; +const String _thumbData = "thumbData"; +const String _thumbDataHash = "thumbDataHash"; + +/// Base class for all share models +sealed class WeChatShareModel with _Argument { + final String? title; + final String? description; + final Uint8List? thumbData; + final String? thumbDataHash; + final String? msgSignature; + + WeChatShareModel( + {required this.title, + required this.description, + required this.thumbData, + required this.thumbDataHash, + required this.msgSignature}); +} + +/// [source] the text you want to send to WeChat +/// [scene] the target you want to send +class WeChatShareTextModel extends WeChatShareModel { + WeChatShareTextModel( + this.source, { + this.scene = WeChatScene.session, + this.mediaTagName, + this.messageAction, + this.messageExt, + super.msgSignature, + super.thumbData, + super.thumbDataHash, + String? description, + String? title, + }) : super( + title: title ?? source, + description: description ?? source, + ); + + final String source; + final WeChatScene scene; + final String? messageExt; + final String? messageAction; + final String? mediaTagName; + + @override + Map get arguments => { + _scene: scene.index, + _source: source, + _messageExt: messageExt, + _messageAction: messageAction, + _mediaTagName: mediaTagName, + _title: title, + _description: description, + _msgSignature: msgSignature, + _thumbData: thumbData, + _thumbDataHash: thumbDataHash, + }; +} + +/// [hdImageData] only works on iOS, not sure the relationship +/// the default value is [MINI_PROGRAM_TYPE_RELEASE] +/// [scene] on supports [WeChatScene.session] for mini program sharing. +class WeChatShareMiniProgramModel extends WeChatShareModel { + WeChatShareMiniProgramModel({ + required this.webPageUrl, + this.miniProgramType = WXMiniProgramType.release, + required this.userName, + this.path = "/", + super.title, + super.description, + this.withShareTicket = false, + this.mediaTagName, + this.messageAction, + this.messageExt, + this.hdImageData, + this.scene = WeChatScene.session, + super.msgSignature, + super.thumbData, + super.thumbDataHash, + }) : assert(webPageUrl.isNotEmpty), + assert(userName.isNotEmpty), + assert(path.isNotEmpty); + + final Uint8List? hdImageData; + final String webPageUrl; + final WXMiniProgramType miniProgramType; + final WeChatScene scene; + final String userName; + final String path; + final bool withShareTicket; + final String? messageExt; + final String? messageAction; + final String? mediaTagName; + + @override + Map get arguments => { + 'webPageUrl': webPageUrl, + "miniProgramType": miniProgramType.value, + "userName": userName, + "path": path, + "title": title, + _description: description, + "withShareTicket": withShareTicket, + _messageAction: messageAction, + _mediaTagName: mediaTagName, + _msgSignature: msgSignature, + _thumbData: thumbData, + _thumbDataHash: thumbDataHash, + "hdImageData": hdImageData, + _scene: scene.index, + }; +} + +/// [source] the image you want to send to WeChat +/// [scene] the target you want to send +class WeChatShareImageModel extends WeChatShareModel { + WeChatShareImageModel( + this.source, { + this.entranceMiniProgramPath, + this.entranceMiniProgramUsername, + super.title, + this.scene = WeChatScene.session, + super.description, + this.mediaTagName, + this.messageAction, + this.messageExt, + super.msgSignature, + super.thumbData, + super.thumbDataHash, + }); + + final WeChatImageToShare source; + final WeChatScene scene; + final String? messageExt; + final String? messageAction; + final String? mediaTagName; + final String? entranceMiniProgramUsername; + final String? entranceMiniProgramPath; + + @override + Map get arguments => { + _scene: scene.index, + _source: source.arguments, + _title: title, + _description: description, + _messageAction: messageAction, + _mediaTagName: mediaTagName, + _msgSignature: msgSignature, + _thumbData: thumbData, + _thumbDataHash: thumbDataHash, + "entranceMiniProgramUsername": entranceMiniProgramUsername, + "entranceMiniProgramPath": entranceMiniProgramPath, + }; +} + +/// if [musicUrl] and [musicLowBandUrl] are both provided, +/// only [musicUrl] will be used. +class WeChatShareMusicModel extends WeChatShareModel { + WeChatShareMusicModel( + {this.musicUrl, + this.musicLowBandUrl, + super.title = "", + super.description = "", + this.musicDataUrl, + this.musicLowBandDataUrl, + this.mediaTagName, + this.messageAction, + this.messageExt, + this.scene = WeChatScene.session, + super.msgSignature, + super.thumbData, + super.thumbDataHash}) + : assert(musicUrl != null || musicLowBandUrl != null); + + final String? musicUrl; + final String? musicDataUrl; + final String? musicLowBandUrl; + final String? musicLowBandDataUrl; + final WeChatScene scene; + final String? messageExt; + final String? messageAction; + final String? mediaTagName; + + @override + Map get arguments => { + _scene: scene.index, + "musicUrl": musicUrl, + "musicDataUrl": musicDataUrl, + "musicLowBandUrl": musicLowBandUrl, + "musicLowBandDataUrl": musicLowBandDataUrl, + _title: title, + _description: description, + _messageAction: messageAction, + _mediaTagName: mediaTagName, + _msgSignature: msgSignature, + _thumbData: thumbData, + _thumbDataHash: thumbDataHash, + }; +} + +/// if [videoUrl] and [videoLowBandUrl] are both provided, +/// only [videoUrl] will be used. +class WeChatShareVideoModel extends WeChatShareModel { + WeChatShareVideoModel({ + this.scene = WeChatScene.session, + this.videoUrl, + this.videoLowBandUrl, + super.title = "", + super.description = "", + this.mediaTagName, + this.messageAction, + this.messageExt, + super.msgSignature, + super.thumbData, + super.thumbDataHash, + }) : assert(videoUrl != null || videoLowBandUrl != null); + + final String? videoUrl; + final String? videoLowBandUrl; + final WeChatScene scene; + final String? messageExt; + final String? messageAction; + final String? mediaTagName; + + @override + Map get arguments => { + _scene: scene.index, + "videoUrl": videoUrl, + "videoLowBandUrl": videoLowBandUrl, + _title: title, + _description: description, + _messageAction: messageAction, + _mediaTagName: mediaTagName, + _msgSignature: msgSignature, + _thumbData: thumbData, + _thumbDataHash: thumbDataHash, + }; +} + +/// [webPage] url you want to send to wechat +/// [thumbnail] logo of your website +class WeChatShareWebPageModel extends WeChatShareModel { + WeChatShareWebPageModel( + this.webPage, { + super.title = "", + super.description, + this.scene = WeChatScene.session, + this.mediaTagName, + this.messageAction, + this.messageExt, + super.msgSignature, + super.thumbData, + super.thumbDataHash, + }) : assert(webPage.isNotEmpty); + + final String webPage; + final WeChatScene scene; + final String? messageExt; + final String? messageAction; + final String? mediaTagName; + + @override + Map get arguments => { + _scene: scene.index, + "webPage": webPage, + _title: title, + _messageAction: messageAction, + _mediaTagName: mediaTagName, + _description: description, + _msgSignature: msgSignature, + _thumbData: thumbData, + _thumbDataHash: thumbDataHash, + }; +} + +/// [source] the file you want to share, [source.suffix] is necessary on iOS. +/// [scene] can't be [WeChatScene.TIMELINE], otherwise, sharing nothing. +/// send files to WeChat +class WeChatShareFileModel extends WeChatShareModel { + WeChatShareFileModel( + this.source, { + super.title = "", + super.description = "", + this.scene = WeChatScene.session, + this.mediaTagName, + this.messageAction, + this.messageExt, + super.msgSignature, + super.thumbData, + super.thumbDataHash, + }); + + final WeChatFile source; + final WeChatScene scene; + final String? messageExt; + final String? messageAction; + final String? mediaTagName; + + @override + Map get arguments => { + _scene: scene.index, + _source: source.toMap(), + _title: title, + _description: description, + _messageAction: messageAction, + _mediaTagName: mediaTagName, + _msgSignature: msgSignature, + _thumbData: thumbData, + _thumbDataHash: thumbDataHash, + }; +} + +class WeChatShareEmojiModel extends WeChatShareModel { + final WeChatImageToShare emoji; + final WeChatScene scene; + + WeChatShareEmojiModel( + this.emoji, { + this.scene = WeChatScene.session, + super.title, + super.description, + super.msgSignature, + super.thumbData, + super.thumbDataHash, + }); + + @override + Map get arguments => { + _scene: scene.index, + _source: emoji.arguments, + _title: title, + _description: description, + _msgSignature: msgSignature, + _thumbData: thumbData, + _thumbDataHash: thumbDataHash, + }; +} + +class WeChatImageToShare with _Argument { + final Uint8List? uint8List; + final String? localImagePath; + final String? imgDataHash; + + /// [uint8List] is available on both iOS and Android + /// [localImagePath] only available on Android, if [uint8List] is null, [localImagePath] must not be null; + /// if [uint8List] and [localImagePath] are both provided on android, [uint8List] will be used. + /// If [localImagePath] starts with content://, it will be treated as a content provider uri and please ensure + /// you have already granted the app the permission to read the content. Otherwise, please ensure the file is + /// accessible by the app. + WeChatImageToShare({this.uint8List, this.localImagePath, this.imgDataHash}) { + if (Platform.isIOS) { + assert(uint8List != null); + } + + if (Platform.isAndroid) { + assert(uint8List != null || localImagePath != null); + } + } + + @override + Map get arguments => { + 'uint8List': uint8List, + 'localImagePath': localImagePath, + 'imgDataHash': imgDataHash, + }; +} diff --git a/packages/fluwx/lib/src/method_channel/fluwx_method_channel.dart b/packages/fluwx/lib/src/method_channel/fluwx_method_channel.dart new file mode 100644 index 00000000..41bb39ab --- /dev/null +++ b/packages/fluwx/lib/src/method_channel/fluwx_method_channel.dart @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2023. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the 'License'); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import '../foundation/arguments.dart'; +import '../response/wechat_response.dart'; +import 'fluwx_platform_interface.dart'; + +/// An implementation of [FluwxPlatform] that uses method channels. +class MethodChannelFluwx extends FluwxPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('com.jarvanmo/fluwx'); + + MethodChannelFluwx() { + methodChannel.setMethodCallHandler(_methodHandler); + } + + final Map _shareModelMethodMapper = { + WeChatShareTextModel: 'shareText', + WeChatShareImageModel: 'shareImage', + WeChatShareMusicModel: 'shareMusic', + WeChatShareVideoModel: 'shareVideo', + WeChatShareWebPageModel: 'shareWebPage', + WeChatShareMiniProgramModel: 'shareMiniProgram', + WeChatShareFileModel: 'shareFile', + WeChatShareEmojiModel: 'shareEmoji', + }; + + final StreamController _responseEventHandler = + StreamController.broadcast(); + + /// Response answers from WeChat after sharing, payment etc. + @override + Stream get responseEventHandler => + _responseEventHandler.stream; + + Future _methodHandler(MethodCall methodCall) { + if (methodCall.method == "wechatLog") { + _printLog(methodCall.arguments); + } else { + final response = WeChatResponse.create( + methodCall.method, + methodCall.arguments, + ); + _responseEventHandler.add(response); + } + + return Future.value(); + } + + _printLog(Map data) { + debugPrint("FluwxLog: ${data["detail"]}"); + } + + /// [true] if WeChat installed, otherwise [false]. + /// Please add WeChat to the white list in order use this method on IOS. + @override + Future get isWeChatInstalled async { + return await methodChannel.invokeMethod('isWeChatInstalled'); + } + + /// The OpenType has various types, which are: + /// WeChatApp + /// Browser + /// RankList + /// BusinessView + /// Invoice + /// CustomerServiceChat + /// MiniProgram + /// SubscribeMessage + /// How to use: + ///  Fluwx().open( + ///  target: CustomerServiceChat( + ///    url: 'URL', + ///    corpId: 'ID', + ///    ), + /// ); + @override + Future open(OpenType target) async { + switch (target) { + case WeChatApp(): + return await methodChannel.invokeMethod( + 'openWXApp', target.arguments) ?? + false; + case Browser(): + return await methodChannel.invokeMethod('openUrl', target.arguments) ?? + false; + case RankList(): + return await methodChannel.invokeMethod("openRankList") ?? false; + case BusinessView(): + return await methodChannel.invokeMethod( + "openBusinessView", target.arguments) ?? + false; + case Invoice(): + return await methodChannel.invokeMethod( + "openWeChatInvoice", target.arguments) ?? + false; + case CustomerServiceChat(): + return await methodChannel.invokeMethod( + "openWeChatCustomerServiceChat", target.arguments) ?? + false; + case MiniProgram(): + return await methodChannel.invokeMethod( + 'launchMiniProgram', target.arguments) ?? + false; + case SubscribeMessage(): + return await methodChannel.invokeMethod( + 'subscribeMsg', target.arguments); + } + } + + @override + Future registerApi({ + required String appId, + bool doOnIOS = true, + bool doOnAndroid = true, + String? universalLink, + }) async { + if (doOnIOS && Platform.isIOS) { + if (universalLink == null || + universalLink.trim().isEmpty || + !universalLink.startsWith('https')) { + throw ArgumentError.value( + universalLink, + "You're trying to use illegal universal link, see " + 'https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html ' + 'for more detail', + ); + } + } + return await methodChannel.invokeMethod('registerApp', { + 'appId': appId, + 'iOS': doOnIOS, + 'android': doOnAndroid, + 'universalLink': universalLink + }); + } + + /// Get ext Message + @override + Future getExtMsg() { + return methodChannel.invokeMethod('getExtMsg'); + } + + /// see [_shareModelMethodMapper] for detail. + @override + Future share(WeChatShareModel what) async { + if (_shareModelMethodMapper.containsKey(what.runtimeType)) { + final channelName = _shareModelMethodMapper[what.runtimeType]; + + if (channelName == null) { + throw ArgumentError.value( + '${what.runtimeType} method channel not found', + ); + } + return await methodChannel.invokeMethod(channelName, what.arguments); + } + return Future.error('no method mapper found[${what.runtimeType}]'); + } + + @override + Future authBy(AuthType which) async { + switch (which) { + case NormalAuth(): + return await methodChannel.invokeMethod( + 'sendAuth', + which.arguments, + ); + case QRCode(): + return await methodChannel.invokeMethod( + 'authByQRCode', which.arguments) ?? + false; + case PhoneLogin(): + return await methodChannel.invokeMethod( + 'authByPhoneLogin', which.arguments); + } + } + + @override + Future pay(PayType which) async { + switch (which) { + case Payment(): + return await methodChannel.invokeMethod( + 'payWithFluwx', which.arguments); + case HongKongWallet(): + return await methodChannel.invokeMethod( + 'payWithHongKongWallet', which.arguments); + } + } + + @override + Future autoDeduct(AutoDeduct data) async { + return await methodChannel.invokeMethod( + data.isV2 ? "autoDeductV2" : 'autoDeduct', data.arguments) ?? + false; + } + + /// stop [authWeChatByQRCode] + @override + Future stopAuthByQRCode() async { + return await methodChannel.invokeMethod('stopAuthByQRCode'); + } + + ///Only works on iOS in debug mode. + @override + Future selfCheck() async { + return await methodChannel.invokeMethod('selfCheck'); + } + + @override + Future attemptToResumeMsgFromWx() async { + return await methodChannel.invokeMethod("attemptToResumeMsgFromWx"); + } + + @override + Future get isSupportOpenBusinessView async => + await methodChannel.invokeMethod("checkSupportOpenBusinessView"); +} diff --git a/packages/fluwx/lib/src/method_channel/fluwx_platform_interface.dart b/packages/fluwx/lib/src/method_channel/fluwx_platform_interface.dart new file mode 100644 index 00000000..e3f28344 --- /dev/null +++ b/packages/fluwx/lib/src/method_channel/fluwx_platform_interface.dart @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the 'License'); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../../fluwx.dart'; +import 'fluwx_method_channel.dart'; + +abstract class FluwxPlatform extends PlatformInterface { + /// Constructs a FluwxPlatform. + FluwxPlatform() : super(token: _token); + + static final Object _token = Object(); + + static FluwxPlatform _instance = MethodChannelFluwx(); + + /// The default instance of [FluwxPlatform] to use. + /// + /// Defaults to [MethodChannelFluwx]. + static FluwxPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [FluwxPlatform] when + /// they register themselves. + static set instance(FluwxPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Stream get responseEventHandler { + throw UnimplementedError('responseEventHandler has not been implemented.'); + } + + Future get isWeChatInstalled { + throw UnimplementedError('isWeChatInstalled has not been implemented.'); + } + + Future open(OpenType target) { + throw UnimplementedError('open() has not been implemented.'); + } + + Future registerApi({ + required String appId, + bool doOnIOS = true, + bool doOnAndroid = true, + String? universalLink, + }) { + throw UnimplementedError('registerWxApi() has not been implemented.'); + } + + Future getExtMsg() { + throw UnimplementedError('getExtMsg() has not been implemented.'); + } + + Future share(WeChatShareModel what) { + throw UnimplementedError('share() has not been implemented.'); + } + + Future sendAuth( + {required String scope, + String state = 'state', + bool nonAutomatic = false}) { + throw UnimplementedError('sendAuth() has not been implemented.'); + } + + Future authByPhoneLogin({ + required String scope, + String state = 'state', + }) async { + throw UnimplementedError('authByPhoneLogin() has not been implemented.'); + } + + Future authByQRCode({ + required String appId, + required String scope, + required String nonceStr, + required String timestamp, + required String signature, + String? schemeData, + }) async { + throw UnimplementedError('authByQRCode() has not been implemented.'); + } + + Future stopAuthByQRCode() async { + throw UnimplementedError( + 'stopWeChatAuthByQRCode() has not been implemented.'); + } + + Future pay(PayType which) { + throw UnimplementedError('pay() has not been implemented.'); + } + + Future autoDeduct(AutoDeduct data) { + throw UnimplementedError('autoDeduct() has not been implemented.'); + } + + Future authBy(AuthType which) { + throw UnimplementedError('authBy() has not been implemented.'); + } + + Future attemptToResumeMsgFromWx() { + throw UnimplementedError( + 'attemptToResumeMsgFromWx() has not been implemented.'); + } + + Future selfCheck() { + throw UnimplementedError('selfCheck() has not been implemented.'); + } + + Future get isSupportOpenBusinessView async { + throw UnimplementedError( + 'isSupportOpenBusinessView() has not been implemented.'); + } +} diff --git a/packages/fluwx/lib/src/response/wechat_response.dart b/packages/fluwx/lib/src/response/wechat_response.dart new file mode 100644 index 00000000..12bc8ab7 --- /dev/null +++ b/packages/fluwx/lib/src/response/wechat_response.dart @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2020. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import 'dart:typed_data'; + +const String _errCode = 'errCode'; +const String _errStr = 'errStr'; + +typedef WeChatResponseSubscriber = void Function(WeChatResponse response); +typedef _WeChatResponseInvoker = WeChatResponse Function(Map argument); + +Map _nameAndResponseMapper = { + 'onShareResponse': (Map argument) => WeChatShareResponse.fromMap(argument), + 'onAuthResponse': (Map argument) => WeChatAuthResponse.fromMap(argument), + 'onLaunchMiniProgramResponse': (Map argument) => + WeChatLaunchMiniProgramResponse.fromMap(argument), + 'onPayResponse': (Map argument) => WeChatPaymentResponse.fromMap(argument), + 'onSubscribeMsgResp': (Map argument) => + WeChatSubscribeMsgResponse.fromMap(argument), + 'onWXOpenBusinessWebviewResponse': (Map argument) => + WeChatOpenBusinessWebviewResponse.fromMap(argument), + 'onAuthByQRCodeFinished': (Map argument) => + WeChatAuthByQRCodeFinishedResponse.fromMap(argument), + 'onAuthGotQRCode': (Map argument) => + WeChatAuthGotQRCodeResponse.fromMap(argument), + 'onQRCodeScanned': (Map argument) => + WeChatQRCodeScannedResponse.fromMap(argument), + 'onWXShowMessageFromWX': (Map argument) => + WeChatShowMessageFromWXRequest.fromMap(argument), + 'onWXOpenCustomerServiceChatResponse': (Map argument) => + WeChatOpenCustomerServiceChatResponse.fromMap(argument), + "onOpenBusinessViewResponse": (Map argument) => + WeChatOpenBusinessViewResponse.fromMap(argument), + "onOpenWechatInvoiceResponse": (Map argument) => + WeChatOpenInvoiceResponse.fromMap(argument), + "onWXLaunchFromWX": (Map argument) => + WeChatLaunchFromWXRequest.fromMap(argument), +}; + +sealed class WeChatResponse { + WeChatResponse._(this.errCode, this.errStr); + + /// Create response from the response pool. + factory WeChatResponse.create(String name, Map argument) { + var result = _nameAndResponseMapper[name]; + if (result == null) { + throw ArgumentError("Can't found instance of $name"); + } + return result(argument); + } + + final int? errCode; + final String? errStr; + + bool get isSuccessful => errCode == 0; + + Record toRecord() { + return (); + } +} + +class WeChatOpenInvoiceResponse extends WeChatResponse { + String? cardItemList; + + WeChatOpenInvoiceResponse.fromMap(Map map) + : cardItemList = map["cardItemList"], + super._(map[_errCode], map[_errStr]); + + @override + Record toRecord() { + return (errCode: errCode, errStr: errStr, cardItemList: cardItemList); + } +} +/// The response when sharing finished. +class WeChatShareResponse extends WeChatResponse { + WeChatShareResponse.fromMap(Map map) + : type = map['type'], + super._(map[_errCode], map[_errStr]); + + final int type; + + @override + Record toRecord() { + return (errCode: errCode, errStr: errStr, type: type); + } +} + +/// The response when auth finished. +class WeChatAuthResponse extends WeChatResponse { + WeChatAuthResponse.fromMap(Map map) + : type = map['type'], + country = map['country'], + lang = map['lang'], + code = map['code'], + state = map['state'], + super._(map[_errCode], map[_errStr]); + + final int type; + final String? country; + final String? lang; + final String? code; + final String? state; + + @override + Record toRecord() { + return ( + errCode: errCode, + errStr: errStr, + type: type, + country: country, + lang: lang, + code: code, + state: state + ); + } +} + +/// The response when launching mini program finished. +class WeChatLaunchMiniProgramResponse extends WeChatResponse { + WeChatLaunchMiniProgramResponse.fromMap(Map map) + : type = map['type'], + extMsg = map['extMsg'], + super._(map[_errCode], map[_errStr]); + + final int? type; + final String? extMsg; + + @override + Record toRecord() { + return (errCode: errCode, errStr: errStr, type: type, extMsg: extMsg); + } +} + +/// The response when payment finished. +class WeChatPaymentResponse extends WeChatResponse { + WeChatPaymentResponse.fromMap(Map map) + : type = map['type'], + extData = map['extData'], + super._(map[_errCode], map[_errStr]); + + final int type; + final String? extData; + + @override + Record toRecord() { + return (errCode: errCode, errStr: errStr, type: type, extData: extData); + } +} + +/// The response when customer service chat finished. +class WeChatOpenCustomerServiceChatResponse extends WeChatResponse { + WeChatOpenCustomerServiceChatResponse.fromMap(Map map) + : extMsg = map['extMsg'], + super._(map[_errCode], map[_errStr]); + + final String? extMsg; + + @override + Record toRecord() { + return (errCode: errCode, errStr: errStr, extMsg: extMsg); + } +} + + +/// The response when open business view finished. +class WeChatOpenBusinessViewResponse extends WeChatResponse { + final String? extMsg; + final String? openid; + final String? businessType; + final int? type; + + WeChatOpenBusinessViewResponse.fromMap(Map map) + : extMsg = map["extMsg"], + openid = map["openid"], + businessType = map["businessType"], + type = map["type"], + super._(map[_errCode], map[_errStr]); + + @override + Record toRecord() { + return ( + errCode: errCode, + errStr: errStr, + type: type, + extMsg: extMsg, + openid: openid, + businessType: businessType + ); + } +} + +/// The response when subscribe message finished. +class WeChatSubscribeMsgResponse extends WeChatResponse { + WeChatSubscribeMsgResponse.fromMap(Map map) + : openid = map['openid'], + templateId = map['templateId'], + action = map['action'], + reserved = map['reserved'], + scene = map['scene'], + super._(map[_errCode], map[_errStr]); + + final String? openid; + final String? templateId; + final String? action; + final String? reserved; + final int scene; + + @override + Record toRecord() { + return ( + errCode: errCode, + errStr: errStr, + openid: openid, + templateId: templateId, + action: action, + reserved: reserved, + scene: scene + ); + } +} + +/// The response when open business webview finished. +class WeChatOpenBusinessWebviewResponse extends WeChatResponse { + WeChatOpenBusinessWebviewResponse.fromMap(Map map) + : type = map['type'], + businessType = map['businessType'], + resultInfo = map['resultInfo'], + super._(map[_errCode], map[_errStr]); + + final int? type; + final int? businessType; + final String resultInfo; + + @override + Record toRecord() { + return ( + errCode: errCode, + errStr: errStr, + type: type, + businessType: businessType, + resultInfo: resultInfo + ); + } +} +/// The response when auth by QRCode finished. +class WeChatAuthByQRCodeFinishedResponse extends WeChatResponse { + WeChatAuthByQRCodeFinishedResponse.fromMap(Map map) + : authCode = map['authCode'], + qrCodeErrorCode = (_authByQRCodeErrorCodes[map[_errCode]] ?? + AuthByQRCodeErrorCode.unknown), + super._(map[_errCode], map[_errStr]); + + final String? authCode; + final AuthByQRCodeErrorCode? qrCodeErrorCode; + + @override + Record toRecord() { + return ( + errCode: errCode, + errStr: errStr, + authCode: authCode, + qrCodeErrorCode: qrCodeErrorCode, + ); + } +} + +///[qrCode] in memory. +class WeChatAuthGotQRCodeResponse extends WeChatResponse { + WeChatAuthGotQRCodeResponse.fromMap(Map map) + : qrCode = map['qrCode'], + super._(map[_errCode], map[_errStr]); + + final Uint8List? qrCode; + + @override + Record toRecord() { + return (qrCode: qrCode); + } +} + +class WeChatQRCodeScannedResponse extends WeChatResponse { + WeChatQRCodeScannedResponse.fromMap(Map map) + : super._(map[_errCode], map[_errStr]); + + @override + Record toRecord() { + return (errCode: errCode, errStr: errStr); + } +} + +/// 获取微信打开App时携带的参数 +class WeChatShowMessageFromWXRequest extends WeChatResponse { + final String? country; + final String? lang; + final String? messageAction; + final String? description; + + WeChatShowMessageFromWXRequest.fromMap(Map map) + : extMsg = map['extMsg'], + country = map['country'], + messageAction = map['messageAction'], + description = map["description"], + lang = map["lang"], + super._(0, ''); + + final String? extMsg; + + @override + Record toRecord() { + return ( + errCode: errCode, + errStr: errStr, + country: country, + lang: lang, + messageAction: messageAction, + description: description, + extMsg: extMsg, + ); + } +} + +/// 获取微信打开App时携带的参数 +class WeChatLaunchFromWXRequest extends WeChatResponse { + final String? country; + final String? lang; + final String? messageAction; + + WeChatLaunchFromWXRequest.fromMap(Map map) + : extMsg = map['extMsg'], + country = map['country'], + messageAction = map['messageAction'], + lang = map["lang"], + super._(0, ''); + + final String? extMsg; + + @override + Record toRecord() { + return ( + errCode: errCode, + errStr: errStr, + country: country, + lang: lang, + messageAction: messageAction, + extMsg: extMsg, + ); + } +} + +/// The error code returned by Wechat SDK. +enum AuthByQRCodeErrorCode { + ok, // WechatAuth_Err_OK(0) + normalErr, // WechatAuth_Err_NormalErr(-1) + networkErr, // WechatAuth_Err_NetworkErr(-2) + jsonDecodeErr, // WechatAuth_Err_JsonDecodeErr(-3), WechatAuth_Err_GetQrcodeFailed on iOS + cancel, // WechatAuth_Err_Cancel(-4) + timeout, // WechatAuth_Err_Timeout(-5) + authStopped, // WechatAuth_Err_Auth_Stopped(-6), Android only + unknown +} + +const Map _authByQRCodeErrorCodes = { + 0: AuthByQRCodeErrorCode.ok, + -1: AuthByQRCodeErrorCode.normalErr, + -2: AuthByQRCodeErrorCode.networkErr, + -3: AuthByQRCodeErrorCode.jsonDecodeErr, + -4: AuthByQRCodeErrorCode.cancel, + -5: AuthByQRCodeErrorCode.authStopped +}; diff --git a/packages/fluwx/lib/src/wechat_enums.dart b/packages/fluwx/lib/src/wechat_enums.dart new file mode 100644 index 00000000..c3d58870 --- /dev/null +++ b/packages/fluwx/lib/src/wechat_enums.dart @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/// [WXMiniProgramType.release]正式版 +/// [WXMiniProgramType.test]测试版 +/// [WXMiniProgramType.preview]预览版 +enum WXMiniProgramType { + release(0), + test(1), + preview(2); + + final int value; + + const WXMiniProgramType(this.value); +} + +/// [WeChatScene.session]会话 +/// [WeChatScene.timeline]朋友圈 +/// [WeChatScene.favorite]收藏 +enum WeChatScene { session, timeline, favorite } diff --git a/packages/fluwx/lib/src/wechat_file.dart b/packages/fluwx/lib/src/wechat_file.dart new file mode 100644 index 00000000..97460fa1 --- /dev/null +++ b/packages/fluwx/lib/src/wechat_file.dart @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023. OpenFlutter Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. The ASF licenses this + * file to you under the Apache License, Version 2.0 (the 'License'); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +import 'dart:io'; +import 'dart:typed_data'; + +const String defaultSuffixJpeg = '.jpeg'; +const String defaultSuffixTxt = '.txt'; + +class WeChatFile { + /// [source] must begin with http or https + WeChatFile.network( + String this.source, { + String? suffix, + }) : assert(source.startsWith('http')), + schema = FileSchema.network, + suffix = source.readSuffix(suffix, defaultSuffixTxt); + + ///[source] path of the image, like '/asset/image.pdf?package=flutter', + ///the query param package in [source] only available when you want to specify the package of image + WeChatFile.asset( + String this.source, { + String? suffix, + }) : assert(source.trim().isNotEmpty), + schema = FileSchema.asset, + suffix = source.readSuffix(suffix, defaultSuffixTxt); + + WeChatFile.file( + File source, { + String suffix = defaultSuffixTxt, + }) : source = source.path, + schema = FileSchema.file, + suffix = source.path.readSuffix(suffix, defaultSuffixTxt); + + WeChatFile.binary( + Uint8List this.source, { + this.suffix = defaultSuffixTxt, + }) : assert(suffix.trim().isNotEmpty), + schema = FileSchema.binary; + + final dynamic source; + final FileSchema schema; + final String suffix; + + Map toMap() => {'source': source, 'schema': schema.index, 'suffix': suffix}; +} + +/// Types of image, usually there are for types listed below. +/// +/// [network] is online images. +/// [asset] is flutter asset image. +/// [binary] is binary image, shall be be [Uint8List] +/// [file] is local file, usually not comes from flutter asset. +enum FileSchema { network, asset, file, binary } + +extension _FileSuffix on String { + /// returns [suffix] if [suffix] not blank. + /// If [suffix] is blank, then try to read from url + /// if suffix in url not found, then return jpg as default. + String readSuffix(String? suffix, String defaultSuffix) { + if (suffix != null && suffix.trim().isNotEmpty) { + if (suffix.startsWith('.')) { + return suffix; + } else { + return '.$suffix'; + } + } + + var path = Uri.parse(this).path; + var index = path.lastIndexOf('.'); + + if (index >= 0) { + return path.substring(index); + } + return defaultSuffix; + } +} diff --git a/packages/fluwx/ohos b/packages/fluwx/ohos new file mode 120000 index 00000000..74fecdfa --- /dev/null +++ b/packages/fluwx/ohos @@ -0,0 +1 @@ +../_shared/ohos \ No newline at end of file diff --git a/packages/fluwx/pubspec.yaml b/packages/fluwx/pubspec.yaml new file mode 100644 index 00000000..7a2bd7df --- /dev/null +++ b/packages/fluwx/pubspec.yaml @@ -0,0 +1,76 @@ +name: fluwx +description: The capability of implementing WeChat SDKs in Flutter. With Fluwx, developers can use WeChatSDK easily, such as sharing, payment, lanuch mini program and etc. +version: 6.0.0-preview.4 +homepage: https://github.com/OpenFlutter/fluwx + +environment: + sdk: ^3.11.0 + flutter: ">=3.41.0" + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: com.jarvan.fluwx + pluginClass: FluwxPlugin + ios: + pluginClass: FluwxPlugin + ohos: + pluginClass: FluwxPlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/fluwx_no_pay/LICENSE b/packages/fluwx_no_pay/LICENSE new file mode 120000 index 00000000..30cff740 --- /dev/null +++ b/packages/fluwx_no_pay/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/fluwx_no_pay/android/build.gradle b/packages/fluwx_no_pay/android/build.gradle new file mode 100644 index 00000000..e8d5b80a --- /dev/null +++ b/packages/fluwx_no_pay/android/build.gradle @@ -0,0 +1,149 @@ +import org.yaml.snakeyaml.Yaml + +group 'com.jarvan.fluwx' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '2.1.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:8.9.1" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.yaml:snakeyaml:2.0" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + if (project.android.hasProperty("namespace")) { + namespace 'com.jarvan.fluwx' + } + + compileSdk 34 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = '11' + } + + sourceSets { + // src/main/kotlin 是指向 _shared 的 symlink,Gradle 通过它直接找到源文件 + main.java.srcDirs += ["${buildDir}/generated/src/kotlin"] + main.res.srcDirs += ['../../_shared/android/src/main/res'] + test.java.srcDirs += ['../../_shared/android/src/test/kotlin'] + } + + defaultConfig { + minSdkVersion 24 + consumerProguardFiles 'consumer-proguard-rules.txt' + } + + // Android 端 no_pay 无区别,完整引入微信 SDK + dependencies { + api 'com.tencent.mm.opensdk:wechat-sdk-android:6.8.34' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2' + implementation 'id.zelory:compressor:3.0.1' + implementation 'com.squareup.okhttp3:okhttp:5.2.1' + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-core:5.0.0' + } + + testOptions { + unitTests.all { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen { false } + showStandardStreams = true + } + } + } +} + +Map loadPubspec() { + File pubspecFile + def yamlDir = rootProject.hasProperty('yamlDir') ? rootProject.ext.yamlDir : '' + def pubspecPath = rootProject.projectDir.parent + File.separator + yamlDir + "pubspec.yaml" + if (file(pubspecPath).exists()) { + pubspecFile = new File(pubspecPath) + } else { + def currentGradleFileDir = file(".").absolutePath + def parentDir = new File(currentGradleFileDir).getParentFile() + pubspecFile = new File(parentDir, "pubspec.yaml") + } + InputStream input = new FileInputStream(pubspecFile) + Yaml yaml = new Yaml() + return (Map) yaml.load(input) +} + +tasks.register("generateFluwxHelperFile") { + doFirst { + Map config = loadPubspec() + Map fluwx = (Map) config.get("fluwx") + String enableLogging = "false" + String interruptWeChatRequestByFluwx = "true" + String flutterActivity = "" + if (fluwx) { + Map android = (Map) fluwx.get("android") + if (android) { + def iwr = android.get("interrupt_wx_request") + if (iwr && iwr == "true" || iwr == "false") { + interruptWeChatRequestByFluwx = (String) iwr + } + def activity = android.get("flutter_activity") + if (activity) { + flutterActivity = (String) activity + } + } + def logging = fluwx.get("debug_logging") + if ("${logging}" == "true" || "${logging}" == "false") { + enableLogging = "${logging}" + } + } + generateFluwxConfigurations(interruptWeChatRequestByFluwx, flutterActivity, enableLogging) + } +} + +def generateFluwxConfigurations(String interruptWeChatRequestByFluwx, String flutterActivity, String enableLogging) { + File generateFolder = new File("${buildDir}/generated/src/kotlin/com/jarvan/fluwx") + String template = "package com.jarvan.fluwx\n\n" + + "// auto generated\n" + + "internal object FluwxConfigurations {\n" + + " val flutterActivity: String = \"&&flutterActivity&&\"\n" + + " val enableLogging: Boolean = &&enableLogging&&\n" + + " val interruptWeChatRequestByFluwx: Boolean = &&interruptWeChatRequestByFluwx&&\n" + + "}" + if (!generateFolder.exists()) { + generateFolder.mkdirs() + } + String source = template + .replace("&&interruptWeChatRequestByFluwx&&", interruptWeChatRequestByFluwx) + .replace("&&flutterActivity&&", flutterActivity) + .replace("&&enableLogging&&", enableLogging) + file("${generateFolder.absolutePath}/FluwxConfigurations.kt").text = source +} + +android.libraryVariants.configureEach { + it.registerGeneratedResFolders( + project.files(new File("${buildDir}/generated/src/kotlin/com/jarvan/fluwx")) + .builtBy(generateFluwxHelperFile) + ) +} diff --git a/packages/fluwx_no_pay/android/consumer-proguard-rules.txt b/packages/fluwx_no_pay/android/consumer-proguard-rules.txt new file mode 100644 index 00000000..f1897d89 --- /dev/null +++ b/packages/fluwx_no_pay/android/consumer-proguard-rules.txt @@ -0,0 +1,38 @@ + +# 微信 + +-keep class com.tencent.mm.opensdk.** {*;} +-keep class com.tencent.wxop.** {*;} +-keep class com.tencent.mm.sdk.** {*;} + +## Kotlin + +# ServiceLoader support +-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} +-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} +-keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {} +-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {} + +# Most of volatile fields are updated with AFU and should not be mangled +-keepclassmembernames class kotlinx.** { + volatile ; +} + +## OkHttp + +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keeppackagenames okhttp3.internal.publicsuffix.* +-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt and other security providers are available. +-dontwarn okhttp3.internal.platform.** +-dontwarn org.conscrypt.** +-dontwarn org.bouncycastle.** +-dontwarn org.openjsse.** + diff --git a/packages/fluwx_no_pay/android/settings.gradle b/packages/fluwx_no_pay/android/settings.gradle new file mode 100644 index 00000000..85499872 --- /dev/null +++ b/packages/fluwx_no_pay/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'fluwx_no_pay' diff --git a/packages/fluwx_no_pay/android/src/main/AndroidManifest.xml b/packages/fluwx_no_pay/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..aae46b12 --- /dev/null +++ b/packages/fluwx_no_pay/android/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fluwx_no_pay/android/src/main/kotlin b/packages/fluwx_no_pay/android/src/main/kotlin new file mode 120000 index 00000000..7971bc2b --- /dev/null +++ b/packages/fluwx_no_pay/android/src/main/kotlin @@ -0,0 +1 @@ +../../../../_shared/android/src/main/kotlin \ No newline at end of file diff --git a/packages/fluwx_no_pay/example/.gitignore b/packages/fluwx_no_pay/example/.gitignore new file mode 100644 index 00000000..3820a95c --- /dev/null +++ b/packages/fluwx_no_pay/example/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/fluwx_no_pay/example/.metadata b/packages/fluwx_no_pay/example/.metadata new file mode 100644 index 00000000..7cd7e6ae --- /dev/null +++ b/packages/fluwx_no_pay/example/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "cc0734ac716fbb8b90f3f9db8020958b1553afa7" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: android + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: ios + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: linux + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: macos + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: web + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: windows + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + + # 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/packages/fluwx_no_pay/example/README.md b/packages/fluwx_no_pay/example/README.md new file mode 100644 index 00000000..a26620ea --- /dev/null +++ b/packages/fluwx_no_pay/example/README.md @@ -0,0 +1,17 @@ +# fluwx_example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) +- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/fluwx_no_pay/example/analysis_options.yaml b/packages/fluwx_no_pay/example/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/packages/fluwx_no_pay/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/fluwx_no_pay/example/android/.gitignore b/packages/fluwx_no_pay/example/android/.gitignore new file mode 100644 index 00000000..be3943c9 --- /dev/null +++ b/packages/fluwx_no_pay/example/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/packages/fluwx_no_pay/example/android/app/build.gradle.kts b/packages/fluwx_no_pay/example/android/app/build.gradle.kts new file mode 100644 index 00000000..31ac922e --- /dev/null +++ b/packages/fluwx_no_pay/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.jarvan.fluwx_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.jarvan.fluwx_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/fluwx_no_pay/example/android/app/src/debug/AndroidManifest.xml b/packages/fluwx_no_pay/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/packages/fluwx_no_pay/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/fluwx_no_pay/example/android/app/src/main/AndroidManifest.xml b/packages/fluwx_no_pay/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..234ff79f --- /dev/null +++ b/packages/fluwx_no_pay/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fluwx_no_pay/example/android/app/src/main/kotlin/com/jarvan/fluwx_example/MainActivity.kt b/packages/fluwx_no_pay/example/android/app/src/main/kotlin/com/jarvan/fluwx_example/MainActivity.kt new file mode 100644 index 00000000..2c100dbc --- /dev/null +++ b/packages/fluwx_no_pay/example/android/app/src/main/kotlin/com/jarvan/fluwx_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.jarvan.fluwx_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/fluwx_no_pay/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/fluwx_no_pay/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/packages/fluwx_no_pay/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/fluwx_no_pay/example/android/app/src/main/res/drawable/launch_background.xml b/packages/fluwx_no_pay/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/packages/fluwx_no_pay/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/packages/fluwx_no_pay/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/fluwx_no_pay/example/android/app/src/main/res/values-night/styles.xml b/packages/fluwx_no_pay/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/packages/fluwx_no_pay/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/fluwx_no_pay/example/android/app/src/main/res/values/styles.xml b/packages/fluwx_no_pay/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/packages/fluwx_no_pay/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/fluwx_no_pay/example/android/app/src/profile/AndroidManifest.xml b/packages/fluwx_no_pay/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/packages/fluwx_no_pay/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/fluwx_no_pay/example/android/build.gradle.kts b/packages/fluwx_no_pay/example/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/packages/fluwx_no_pay/example/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/fluwx_no_pay/example/android/gradle.properties b/packages/fluwx_no_pay/example/android/gradle.properties new file mode 100644 index 00000000..fbee1d8c --- /dev/null +++ b/packages/fluwx_no_pay/example/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/packages/fluwx_no_pay/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/fluwx_no_pay/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e4ef43fb --- /dev/null +++ b/packages/fluwx_no_pay/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/packages/fluwx_no_pay/example/android/settings.gradle.kts b/packages/fluwx_no_pay/example/android/settings.gradle.kts new file mode 100644 index 00000000..ca7fe065 --- /dev/null +++ b/packages/fluwx_no_pay/example/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/packages/fluwx_no_pay/example/images b/packages/fluwx_no_pay/example/images new file mode 120000 index 00000000..8d39e19d --- /dev/null +++ b/packages/fluwx_no_pay/example/images @@ -0,0 +1 @@ +../../_shared/example/images \ No newline at end of file diff --git a/packages/fluwx_no_pay/example/ios/.gitignore b/packages/fluwx_no_pay/example/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/fluwx_no_pay/example/ios/Flutter/AppFrameworkInfo.plist b/packages/fluwx_no_pay/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..391a902b --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/packages/fluwx_no_pay/example/ios/Flutter/Debug.xcconfig b/packages/fluwx_no_pay/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/fluwx_no_pay/example/ios/Flutter/Release.xcconfig b/packages/fluwx_no_pay/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/fluwx_no_pay/example/ios/Podfile b/packages/fluwx_no_pay/example/ios/Podfile new file mode 100644 index 00000000..620e46eb --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/fluwx_no_pay/example/ios/Podfile.lock b/packages/fluwx_no_pay/example/ios/Podfile.lock new file mode 100644 index 00000000..64cf43cf --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - Flutter (1.0.0) + +DEPENDENCIES: + - Flutter (from `Flutter`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + +SPEC CHECKSUMS: + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.pbxproj b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..dfc7f351 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,741 @@ +// !$*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 */; }; + 6ED68A4F0C52E262DB8A1A3B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46BE7387A65668B6034B596E /* Pods_RunnerTests.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 9D4E7471DFD6C4B6E4D143CB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 19316FEE0F0FEBDFC0C35D4B /* Pods_Runner.framework */; }; +/* 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 = ""; }; + 19316FEE0F0FEBDFC0C35D4B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; + 46BE7387A65668B6034B596E /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B230EE159056D8843878D1A /* 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 = ""; }; + 544DED0D23C29EDA78A74364 /* 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 = ""; }; + 60D9311FE7860FE9CB57A85F /* 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 = ""; }; + 6EFEC56BA1AC2AEACB4D5C30 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 87B6AABDF4961CDBE082419D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 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 = ""; }; + F8F2960B3E74EAFE2218CDE6 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D88BF26B8BE54D4FC79125A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6ED68A4F0C52E262DB8A1A3B /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + 9D4E7471DFD6C4B6E4D143CB /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 5A3DB72B476B36D00EB266A5 /* Pods */ = { + isa = PBXGroup; + children = ( + 60D9311FE7860FE9CB57A85F /* Pods-Runner.debug.xcconfig */, + 4B230EE159056D8843878D1A /* Pods-Runner.release.xcconfig */, + 544DED0D23C29EDA78A74364 /* Pods-Runner.profile.xcconfig */, + F8F2960B3E74EAFE2218CDE6 /* Pods-RunnerTests.debug.xcconfig */, + 87B6AABDF4961CDBE082419D /* Pods-RunnerTests.release.xcconfig */, + 6EFEC56BA1AC2AEACB4D5C30 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 5A3DB72B476B36D00EB266A5 /* Pods */, + F1943A4BEE454ADEBFE8267A /* Frameworks */, + ); + 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 = ""; + }; + F1943A4BEE454ADEBFE8267A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 19316FEE0F0FEBDFC0C35D4B /* Pods_Runner.framework */, + 46BE7387A65668B6034B596E /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + E4FD2E1A0FD9F5BE2528EC61 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 1D88BF26B8BE54D4FC79125A /* Frameworks */, + ); + 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 = ( + D4326D6F62EF44432C720662 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + D4326D6F62EF44432C720662 /* [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; + }; + E4FD2E1A0FD9F5BE2528EC61 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 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)"; + DEVELOPMENT_TEAM = R7Y4583RU2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.jarvan.fluwxExample; + 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; + baseConfigurationReference = F8F2960B3E74EAFE2218CDE6 /* Pods-RunnerTests.debug.xcconfig */; + 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.jarvan.fluwxExample.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; + baseConfigurationReference = 87B6AABDF4961CDBE082419D /* Pods-RunnerTests.release.xcconfig */; + 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.jarvan.fluwxExample.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; + baseConfigurationReference = 6EFEC56BA1AC2AEACB4D5C30 /* Pods-RunnerTests.profile.xcconfig */; + 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.jarvan.fluwxExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = R7Y4583RU2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.jarvan.fluwxExample; + 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)"; + DEVELOPMENT_TEAM = R7Y4583RU2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.jarvan.fluwxExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..e47381e9 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "wechatopensdk-nopay-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JarvanMo/WechatOpenSDK-NoPay-SPM", + "state" : { + "revision" : "9d497373a7423e0a6cae7513590f20e62e7c37c8", + "version" : "2.0.5" + } + } + ], + "version" : 2 +} diff --git a/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..c3fedb29 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..e47381e9 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "wechatopensdk-nopay-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JarvanMo/WechatOpenSDK-NoPay-SPM", + "state" : { + "revision" : "9d497373a7423e0a6cae7513590f20e62e7c37c8", + "version" : "2.0.5" + } + } + ], + "version" : 2 +} diff --git a/packages/fluwx_no_pay/example/ios/Runner/AppDelegate.swift b/packages/fluwx_no_pay/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..c30b367e --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..0467bf12 Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/fluwx_no_pay/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/fluwx_no_pay/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fluwx_no_pay/example/ios/Runner/Base.lproj/Main.storyboard b/packages/fluwx_no_pay/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fluwx_no_pay/example/ios/Runner/Info.plist b/packages/fluwx_no_pay/example/ios/Runner/Info.plist new file mode 100644 index 00000000..7a9a96f1 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Fluwx Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + fluwx_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/packages/fluwx_no_pay/example/ios/Runner/Runner-Bridging-Header.h b/packages/fluwx_no_pay/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/fluwx_no_pay/example/ios/Runner/SceneDelegate.swift b/packages/fluwx_no_pay/example/ios/Runner/SceneDelegate.swift new file mode 100644 index 00000000..b9ce8ea2 --- /dev/null +++ b/packages/fluwx_no_pay/example/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/packages/fluwx_no_pay/example/ios/RunnerTests/RunnerTests.swift b/packages/fluwx_no_pay/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/lib/fluwx_compat.dart b/packages/fluwx_no_pay/example/lib/fluwx_compat.dart new file mode 100644 index 00000000..a688379b --- /dev/null +++ b/packages/fluwx_no_pay/example/lib/fluwx_compat.dart @@ -0,0 +1,2 @@ +// fluwx_no_pay 适配层 — 共享 example 代码通过此文件引入正确的包 +export 'package:fluwx_no_pay/fluwx.dart'; diff --git a/packages/fluwx_no_pay/example/lib/main.dart b/packages/fluwx_no_pay/example/lib/main.dart new file mode 120000 index 00000000..142dab37 --- /dev/null +++ b/packages/fluwx_no_pay/example/lib/main.dart @@ -0,0 +1 @@ +../../../_shared/example/lib/main.dart \ No newline at end of file diff --git a/packages/fluwx_no_pay/example/lib/pages b/packages/fluwx_no_pay/example/lib/pages new file mode 120000 index 00000000..769f19de --- /dev/null +++ b/packages/fluwx_no_pay/example/lib/pages @@ -0,0 +1 @@ +../../../_shared/example/lib/pages \ No newline at end of file diff --git a/packages/fluwx_no_pay/example/lib/utils.dart b/packages/fluwx_no_pay/example/lib/utils.dart new file mode 120000 index 00000000..e7b59f92 --- /dev/null +++ b/packages/fluwx_no_pay/example/lib/utils.dart @@ -0,0 +1 @@ +../../../_shared/example/lib/utils.dart \ No newline at end of file diff --git a/packages/fluwx_no_pay/example/linux/.gitignore b/packages/fluwx_no_pay/example/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/packages/fluwx_no_pay/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/fluwx_no_pay/example/linux/CMakeLists.txt b/packages/fluwx_no_pay/example/linux/CMakeLists.txt new file mode 100644 index 00000000..038daaa5 --- /dev/null +++ b/packages/fluwx_no_pay/example/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "fluwx_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.jarvan.fluwx_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/fluwx_no_pay/example/linux/flutter/CMakeLists.txt b/packages/fluwx_no_pay/example/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/packages/fluwx_no_pay/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/fluwx_no_pay/example/linux/flutter/generated_plugin_registrant.cc b/packages/fluwx_no_pay/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/packages/fluwx_no_pay/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/packages/fluwx_no_pay/example/linux/flutter/generated_plugin_registrant.h b/packages/fluwx_no_pay/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/packages/fluwx_no_pay/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/fluwx_no_pay/example/linux/flutter/generated_plugins.cmake b/packages/fluwx_no_pay/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..2e1de87a --- /dev/null +++ b/packages/fluwx_no_pay/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/fluwx_no_pay/example/linux/runner/CMakeLists.txt b/packages/fluwx_no_pay/example/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/linux/runner/main.cc b/packages/fluwx_no_pay/example/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/linux/runner/my_application.cc b/packages/fluwx_no_pay/example/linux/runner/my_application.cc new file mode 100644 index 00000000..9bb73426 --- /dev/null +++ b/packages/fluwx_no_pay/example/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, "fluwx_example"); + 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, "fluwx_example"); + } + + 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/packages/fluwx_no_pay/example/linux/runner/my_application.h b/packages/fluwx_no_pay/example/linux/runner/my_application.h new file mode 100644 index 00000000..db16367a --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/macos/.gitignore b/packages/fluwx_no_pay/example/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/packages/fluwx_no_pay/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/fluwx_no_pay/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/fluwx_no_pay/example/macos/Flutter/Flutter-Release.xcconfig b/packages/fluwx_no_pay/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/fluwx_no_pay/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/fluwx_no_pay/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..cccf817a --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/packages/fluwx_no_pay/example/macos/Podfile b/packages/fluwx_no_pay/example/macos/Podfile new file mode 100644 index 00000000..ff5ddb3b --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/packages/fluwx_no_pay/example/macos/Runner.xcodeproj/project.pbxproj b/packages/fluwx_no_pay/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..19af8ad9 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,729 @@ +// !$*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 */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; +/* 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 */ + 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 /* fluwx_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "fluwx_example.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 = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage 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 */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* fluwx_example.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 = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 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 = ( + ); + name = Frameworks; + 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 = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* fluwx_example.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; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + 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 */ + 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"; + }; +/* 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.jarvan.fluwxExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/fluwx_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/fluwx_example"; + }; + 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.jarvan.fluwxExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/fluwx_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/fluwx_example"; + }; + 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.jarvan.fluwxExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/fluwx_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/fluwx_example"; + }; + 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 */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/fluwx_no_pay/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/fluwx_no_pay/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/fluwx_no_pay/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/fluwx_no_pay/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..740c6e68 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fluwx_no_pay/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/fluwx_no_pay/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/fluwx_no_pay/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/fluwx_no_pay/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/fluwx_no_pay/example/macos/Runner/AppDelegate.swift b/packages/fluwx_no_pay/example/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/packages/fluwx_no_pay/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/fluwx_no_pay/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/fluwx_no_pay/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/fluwx_no_pay/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/fluwx_no_pay/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..12bdabcd --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = fluwx_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.jarvan.fluwxExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.jarvan. All rights reserved. diff --git a/packages/fluwx_no_pay/example/macos/Runner/Configs/Debug.xcconfig b/packages/fluwx_no_pay/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/fluwx_no_pay/example/macos/Runner/Configs/Release.xcconfig b/packages/fluwx_no_pay/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/fluwx_no_pay/example/macos/Runner/Configs/Warnings.xcconfig b/packages/fluwx_no_pay/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/fluwx_no_pay/example/macos/Runner/DebugProfile.entitlements b/packages/fluwx_no_pay/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/packages/fluwx_no_pay/example/macos/Runner/Info.plist b/packages/fluwx_no_pay/example/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/fluwx_no_pay/example/macos/Runner/MainFlutterWindow.swift b/packages/fluwx_no_pay/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/fluwx_no_pay/example/macos/Runner/Release.entitlements b/packages/fluwx_no_pay/example/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/packages/fluwx_no_pay/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/fluwx_no_pay/example/macos/RunnerTests/RunnerTests.swift b/packages/fluwx_no_pay/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/pubspec.yaml b/packages/fluwx_no_pay/example/pubspec.yaml new file mode 100644 index 00000000..91ddcf15 --- /dev/null +++ b/packages/fluwx_no_pay/example/pubspec.yaml @@ -0,0 +1,41 @@ +name: fluwx_example +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.11.5 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.8 + + fluwx_no_pay: + path: ../ + http: ^1.6.0 + +#flutter: +# config: +# enable-swift-package-manager: false \ No newline at end of file diff --git a/packages/fluwx_no_pay/example/test/widget_test.dart b/packages/fluwx_no_pay/example/test/widget_test.dart new file mode 100644 index 00000000..dc13770e --- /dev/null +++ b/packages/fluwx_no_pay/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:fluwx_example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/packages/fluwx_no_pay/example/web/favicon.png b/packages/fluwx_no_pay/example/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/packages/fluwx_no_pay/example/web/favicon.png differ diff --git a/packages/fluwx_no_pay/example/web/icons/Icon-192.png b/packages/fluwx_no_pay/example/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/packages/fluwx_no_pay/example/web/icons/Icon-192.png differ diff --git a/packages/fluwx_no_pay/example/web/icons/Icon-512.png b/packages/fluwx_no_pay/example/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/packages/fluwx_no_pay/example/web/icons/Icon-512.png differ diff --git a/packages/fluwx_no_pay/example/web/icons/Icon-maskable-192.png b/packages/fluwx_no_pay/example/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/packages/fluwx_no_pay/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/fluwx_no_pay/example/web/icons/Icon-maskable-512.png b/packages/fluwx_no_pay/example/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/packages/fluwx_no_pay/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/fluwx_no_pay/example/web/index.html b/packages/fluwx_no_pay/example/web/index.html new file mode 100644 index 00000000..35e47f90 --- /dev/null +++ b/packages/fluwx_no_pay/example/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + fluwx_example + + + + + + + diff --git a/packages/fluwx_no_pay/example/web/manifest.json b/packages/fluwx_no_pay/example/web/manifest.json new file mode 100644 index 00000000..e15330e7 --- /dev/null +++ b/packages/fluwx_no_pay/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "fluwx_example", + "short_name": "fluwx_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/fluwx_no_pay/example/windows/.gitignore b/packages/fluwx_no_pay/example/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/windows/CMakeLists.txt b/packages/fluwx_no_pay/example/windows/CMakeLists.txt new file mode 100644 index 00000000..90773820 --- /dev/null +++ b/packages/fluwx_no_pay/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(fluwx_example 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 "fluwx_example") + +# 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) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# 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/packages/fluwx_no_pay/example/windows/flutter/CMakeLists.txt b/packages/fluwx_no_pay/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/windows/flutter/generated_plugin_registrant.cc b/packages/fluwx_no_pay/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..8b6d4680 --- /dev/null +++ b/packages/fluwx_no_pay/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/packages/fluwx_no_pay/example/windows/flutter/generated_plugin_registrant.h b/packages/fluwx_no_pay/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/windows/flutter/generated_plugins.cmake b/packages/fluwx_no_pay/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..b93c4c30 --- /dev/null +++ b/packages/fluwx_no_pay/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/fluwx_no_pay/example/windows/runner/CMakeLists.txt b/packages/fluwx_no_pay/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/windows/runner/Runner.rc b/packages/fluwx_no_pay/example/windows/runner/Runner.rc new file mode 100644 index 00000000..1a0918be --- /dev/null +++ b/packages/fluwx_no_pay/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.jarvan" "\0" + VALUE "FileDescription", "fluwx_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "fluwx_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.jarvan. All rights reserved." "\0" + VALUE "OriginalFilename", "fluwx_example.exe" "\0" + VALUE "ProductName", "fluwx_example" "\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/packages/fluwx_no_pay/example/windows/runner/flutter_window.cpp b/packages/fluwx_no_pay/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/windows/runner/flutter_window.h b/packages/fluwx_no_pay/example/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/windows/runner/main.cpp b/packages/fluwx_no_pay/example/windows/runner/main.cpp new file mode 100644 index 00000000..b22a41e9 --- /dev/null +++ b/packages/fluwx_no_pay/example/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"fluwx_example", 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/packages/fluwx_no_pay/example/windows/runner/resource.h b/packages/fluwx_no_pay/example/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/windows/runner/resources/app_icon.ico b/packages/fluwx_no_pay/example/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/packages/fluwx_no_pay/example/windows/runner/resources/app_icon.ico differ diff --git a/packages/fluwx_no_pay/example/windows/runner/runner.exe.manifest b/packages/fluwx_no_pay/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /dev/null +++ b/packages/fluwx_no_pay/example/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/packages/fluwx_no_pay/example/windows/runner/utils.cpp b/packages/fluwx_no_pay/example/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/windows/runner/utils.h b/packages/fluwx_no_pay/example/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/windows/runner/win32_window.cpp b/packages/fluwx_no_pay/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/packages/fluwx_no_pay/example/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/packages/fluwx_no_pay/example/windows/runner/win32_window.h b/packages/fluwx_no_pay/example/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/packages/fluwx_no_pay/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay.podspec b/packages/fluwx_no_pay/ios/fluwx_no_pay.podspec new file mode 100644 index 00000000..e5289852 --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay.podspec @@ -0,0 +1,37 @@ + +Pod::Spec.new do |s| + s.name = 'fluwx_no_pay' + s.module_name = 'fluwx_no_pay' + s.version = '2.0.5' + s.summary = 'WeChat SDK Flutter plugin without payment — passes App Store payment compliance review.' + s.description = <<-DESC + fluwx_no_pay provides the same API as fluwx but the iOS binary contains NO WechatOpenSDK symbols. + Calling payment methods returns MissingPluginException at runtime. + Use this package when your app must pass App Store payment compliance review. + DESC + s.homepage = 'https://github.com/OpenFlutter/fluwx' + s.license = { :file => '../LICENSE' } + s.author = { 'OpenFlutter' => 'jarvan.mo@gmail.com' } + s.source = { :path => '.' } + s.source_files = 'fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/**/*' + s.public_header_files = 'fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/include/**/*.h' + s.dependency 'Flutter' + s.dependency 'OpenWeChatSDKNoPay', '~> 2.0.5' + s.platform = :ios, '13.0' + s.static_framework = true + s.resource_bundles = { + 'fluwx_no_pay_privacy' => ['fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/Resources/PrivacyInfo.xcprivacy'] + } + s.swift_version = '5.0' + + # ✅ 依赖 OpenWeChatSDKNoPay(无支付符号版),通过 FLUWX_NO_PAY 宏屏蔽支付代码 + s.frameworks = 'CoreGraphics', 'Security', 'WebKit' + s.libraries = 'c++', 'z', 'sqlite3.0' + + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) FLUWX_NO_PAY=1', + 'OTHER_LDFLAGS' => '$(inherited) -ObjC -all_load' + } +end diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxDelegate.m b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxDelegate.m new file mode 120000 index 00000000..be46ba54 --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxDelegate.m @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/FluwxDelegate.m \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxPlugin.m b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxPlugin.m new file mode 120000 index 00000000..6cc919b2 --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxPlugin.m @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/FluwxPlugin.m \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxStringUtil.h b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxStringUtil.h new file mode 120000 index 00000000..f8d9504f --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxStringUtil.h @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/FluwxStringUtil.h \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxStringUtil.m b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxStringUtil.m new file mode 120000 index 00000000..c932288a --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/FluwxStringUtil.m @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/FluwxStringUtil.m \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/NSStringWrapper.h b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/NSStringWrapper.h new file mode 120000 index 00000000..998fff9a --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/NSStringWrapper.h @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/NSStringWrapper.h \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/NSStringWrapper.m b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/NSStringWrapper.m new file mode 120000 index 00000000..4d66a933 --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/NSStringWrapper.m @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/NSStringWrapper.m \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/Resources/PrivacyInfo.xcprivacy b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/Resources/PrivacyInfo.xcprivacy new file mode 120000 index 00000000..80534207 --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1 @@ +../../../../../../_shared/ios/Sources/Resources/PrivacyInfo.xcprivacy \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/ThumbnailHelper.h b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/ThumbnailHelper.h new file mode 120000 index 00000000..1a2d3445 --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/ThumbnailHelper.h @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/ThumbnailHelper.h \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/ThumbnailHelper.m b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/ThumbnailHelper.m new file mode 120000 index 00000000..2b26083e --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/ThumbnailHelper.m @@ -0,0 +1 @@ +../../../../../_shared/ios/Sources/ThumbnailHelper.m \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/include/FluwxDelegate.h b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/include/FluwxDelegate.h new file mode 120000 index 00000000..59235176 --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/include/FluwxDelegate.h @@ -0,0 +1 @@ +../../../../../../_shared/ios/Sources/include/FluwxDelegate.h \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/include/FluwxPlugin.h b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/include/FluwxPlugin.h new file mode 120000 index 00000000..7d25db4c --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/CocoaPodsSources/fluwx_no_pay/include/FluwxPlugin.h @@ -0,0 +1 @@ +../../../../../../_shared/ios/Sources/include/FluwxPlugin.h \ No newline at end of file diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/Package.swift b/packages/fluwx_no_pay/ios/fluwx_no_pay/Package.swift new file mode 100644 index 00000000..e255cdcf --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/Package.swift @@ -0,0 +1,42 @@ +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "fluwx_no_pay", + platforms: [.iOS(.v13)], + products: [ + .library(name: "fluwx-no-pay", targets: ["fluwx_no_pay"]) + ], + dependencies: [ + .package(name: "FlutterFramework", path: "../FlutterFramework"), + .package( + url: "https://github.com/JarvanMo/WechatOpenSDK-NoPay-SPM", // + from: "2.0.5" + ) + ], + targets: [ + .target( + name: "fluwx_no_pay", + dependencies: [ + .product(name: "FlutterFramework", package: "FlutterFramework"), + .product(name: "WechatOpenSDK", package: "WechatOpenSDK-NoPay-SPM") + ], + resources: [ + .process("Resources/PrivacyInfo.xcprivacy") + ], + cSettings: [ + .define("FLUWX_NO_PAY"), + .headerSearchPath("include") + ], + swiftSettings: [ + .define("FLUWX_NO_PAY") + ], + linkerSettings: [ + .linkedFramework("CoreGraphics"), + .linkedFramework("Security"), + .linkedFramework("WebKit") + ] + // ⚠️ 无 -ObjC -all_load:没有静态 SDK 需要强制加载 + ) + ] +) diff --git a/packages/fluwx_no_pay/ios/fluwx_no_pay/Sources/fluwx_no_pay b/packages/fluwx_no_pay/ios/fluwx_no_pay/Sources/fluwx_no_pay new file mode 120000 index 00000000..ad0ef0fc --- /dev/null +++ b/packages/fluwx_no_pay/ios/fluwx_no_pay/Sources/fluwx_no_pay @@ -0,0 +1 @@ +../../../../_shared/ios/Sources \ No newline at end of file diff --git a/packages/fluwx_no_pay/lib b/packages/fluwx_no_pay/lib new file mode 120000 index 00000000..92869342 --- /dev/null +++ b/packages/fluwx_no_pay/lib @@ -0,0 +1 @@ +../fluwx/lib \ No newline at end of file diff --git a/packages/fluwx_no_pay/ohos b/packages/fluwx_no_pay/ohos new file mode 120000 index 00000000..74fecdfa --- /dev/null +++ b/packages/fluwx_no_pay/ohos @@ -0,0 +1 @@ +../_shared/ohos \ No newline at end of file diff --git a/packages/fluwx_no_pay/pubspec.yaml b/packages/fluwx_no_pay/pubspec.yaml new file mode 100644 index 00000000..929f823b --- /dev/null +++ b/packages/fluwx_no_pay/pubspec.yaml @@ -0,0 +1,33 @@ +name: fluwx_no_pay +description: > + WeChat SDK Flutter plugin without payment — passes App Store payment compliance review. + API is identical to fluwx; calling payment methods returns MissingPluginException. +version: 6.0.0-preview.4 +homepage: https://github.com/OpenFlutter/fluwx + +environment: + sdk: ^3.11.0 + flutter: ">=3.41.0" + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + plugin: + platforms: + android: + package: com.jarvan.fluwx + pluginClass: FluwxPlugin + ios: + pluginClass: FluwxPlugin + ohos: + pluginClass: FluwxPlugin diff --git a/pubspec.yaml b/pubspec.yaml index aabf2da5..d75450a9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,76 +1,11 @@ +# Monorepo workspace root — 仅包含 dev 依赖,不发布到 pub.dev name: fluwx -description: The capability of implementing WeChat SDKs in Flutter. With Fluwx, developers can use WeChatSDK easily, such as sharing, payment, lanuch mini program and etc. -version: 6.0.0-preview.3 -homepage: https://github.com/OpenFlutter/fluwx +publish_to: none environment: sdk: ^3.10.0 - flutter: ">=3.38.0" - -dependencies: - flutter: - sdk: flutter - flutter_web_plugins: - sdk: flutter - plugin_platform_interface: ^2.0.2 dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^2.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) - # which should be registered in the plugin registry. This is required for - # using method channels. - # The Android 'package' specifies package in which the registered class is. - # This is required for using method channels on Android. - # The 'ffiPlugin' specifies that native code should be built and bundled. - # This is required for using `dart:ffi`. - # All these are used by the tooling to maintain consistency when - # adding or updating assets for this project. - plugin: - platforms: - android: - package: com.jarvan.fluwx - pluginClass: FluwxPlugin - ios: - pluginClass: FluwxPlugin - ohos: - pluginClass: FluwxPlugin - - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # To add custom fonts to your plugin package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages + melos: ^7.5.1 + yaml: ^3.1.2 + yaml_writer: ^2.0.0 diff --git a/tools/setup_example_symlinks.sh b/tools/setup_example_symlinks.sh new file mode 100644 index 00000000..07809190 --- /dev/null +++ b/tools/setup_example_symlinks.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# 在仓库根目录运行:bash tools/setup_example_symlinks.sh +set -e +cd "$(dirname "$0")/.." + +SHARED="packages/_shared/example/lib" + +for PKG in fluwx fluwx_no_pay; do + LIB="packages/$PKG/example/lib" + + echo "▶ $PKG" + + # 删除旧 pages 目录(含 ln -sf 误建的 pages/pages 嵌套) + rm -rf "$LIB/pages" + # 删除旧 main.dart / utils.dart(已被 symlink 替代,但可能还存在实体文件) + rm -f "$LIB/main.dart" "$LIB/utils.dart" + + # 建立 symlink + ln -sf "../../../../_shared/example/lib/pages" "$LIB/pages" + ln -sf "../../../../_shared/example/lib/utils.dart" "$LIB/utils.dart" + ln -sf "../../../../_shared/example/lib/main.dart" "$LIB/main.dart" + + echo " ✅ symlink 完成" + ls -la "$LIB/" + echo "" +done + +echo "🎉 所有 example symlink 建立完毕" +echo "" +echo "验证:" +echo " ls -la packages/fluwx/example/lib/" +echo " ls -la packages/fluwx_no_pay/example/lib/" diff --git a/tools/setup_examples.sh b/tools/setup_examples.sh new file mode 100644 index 00000000..45e0a2de --- /dev/null +++ b/tools/setup_examples.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# 在仓库根目录运行:bash tools/setup_examples.sh +# 依赖:flutter 命令已在 PATH 中 +set -e +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +echo "==========================================" +echo " fluwx monorepo — example 初始化脚本" +echo "==========================================" +echo "" + +# ────────────────────────────────────────────── +# 1. 用 flutter create 生成两个 example 脚手架 +# ────────────────────────────────────────────── +echo "▶ [1/4] 生成 example 脚手架..." + +flutter create \ + --template=app \ + --project-name=fluwx_example \ + --org=com.jarvan \ + packages/fluwx/example + +flutter create \ + --template=app \ + --project-name=fluwx_example \ + --org=com.jarvan \ + packages/fluwx_no_pay/example + +echo " ✅ 脚手架生成完毕" +echo "" + +# ────────────────────────────────────────────── +# 2. 建立 lib/ 下的 symlink +# +# symlink 存放位置:packages//example/lib/ +# 目标位置: packages/_shared/example/lib/ +# +# 从 packages//example/lib/ 出发,向上: +# .. → packages//example/ +# ../.. → packages// +# ../../..→ packages/ +# ../../../_shared → packages/_shared ✓ +# ────────────────────────────────────────────── +echo "▶ [2/4] 建立 lib/ symlink..." + +for PKG in fluwx fluwx_no_pay; do + LIB="packages/$PKG/example/lib" + + # 删除 flutter create 生成的占位内容 + rm -rf "$LIB/main.dart" "$LIB/pages" "$LIB/utils.dart" + + # 正确的相对路径:3 级向上到 packages/,再进 _shared/example/lib/ + ln -sf "../../../_shared/example/lib/pages" "$LIB/pages" + ln -sf "../../../_shared/example/lib/main.dart" "$LIB/main.dart" + ln -sf "../../../_shared/example/lib/utils.dart" "$LIB/utils.dart" + + # 验证 + echo " --- $PKG/example/lib ---" + ls -la "$LIB/" +done +echo "" + +# ────────────────────────────────────────────── +# 3. 写 fluwx_compat.dart(两包唯一不同的文件) +# ────────────────────────────────────────────── +echo "▶ [3/4] 写 fluwx_compat.dart..." + +cat > packages/fluwx/example/lib/fluwx_compat.dart << 'EOF' +// fluwx 适配层 — 共享 example 代码通过此文件引入正确的包 +export 'package:fluwx/fluwx.dart'; +EOF + +cat > packages/fluwx_no_pay/example/lib/fluwx_compat.dart << 'EOF' +// fluwx_no_pay 适配层 — 共享 example 代码通过此文件引入正确的包 +export 'package:fluwx_no_pay/fluwx.dart'; +EOF + +echo " ✅ fluwx_compat.dart 写入完毕" +echo "" + +# ────────────────────────────────────────────── +# 4. 更新 pubspec.yaml + images symlink +# +# images symlink 位置:packages//example/images +# 从 packages//example/ 出发: +# .. → packages// +# ../.. → packages/ +# ../../_shared → packages/_shared ✓ +# ────────────────────────────────────────────── +echo "▶ [4/4] 更新 pubspec.yaml 并建立 images symlink..." + +for PKG in fluwx fluwx_no_pay; do + PUBSPEC="packages/$PKG/example/pubspec.yaml" + + # 用 Python 更新 pubspec.yaml + python3 - "$PUBSPEC" "$PKG" << 'PYEOF' +import sys, re + +pubspec_path = sys.argv[1] +pkg = sys.argv[2] + +content = open(pubspec_path).read() + +# 替换 dependencies 块,加入插件 path 依赖 +dep_block = ( + "dependencies:\n" + " flutter:\n" + " sdk: flutter\n" + " cupertino_icons: ^1.0.8\n" + "\n" + f" {pkg}:\n" + " path: ../\n" +) +content = re.sub( + r'dependencies:.*?(?=\ndev_dependencies:|\Z)', + dep_block + '\n', + content, + flags=re.DOTALL, +) + +# 在顶层追加 fluwx 配置(如果还没有) +cfg_key = f'{pkg}:' +if cfg_key not in content: + cfg = ( + f"{cfg_key}\n" + " app_id: '123456'\n" + " debug_logging: true\n" + " ios:\n" + " universal_link: https://testdomain.com\n" + "\n" + ) + content = cfg + content + +open(pubspec_path, 'w').write(content) +print(f" ✅ {pubspec_path} 更新完毕") +PYEOF + + # images symlink(2 级向上到 packages/,再进 _shared/example/images) + rm -rf "packages/$PKG/example/images" + ln -sf "../../_shared/example/images" "packages/$PKG/example/images" + echo " ✅ $PKG/example/images symlink 完毕" +done + +echo "" +echo "==========================================" +echo " 完成!验证 symlink:" +echo "" +echo " ls -la packages/fluwx/example/lib/" +echo " ls -la packages/fluwx_no_pay/example/lib/" +echo " ls packages/fluwx/example/lib/pages/" +echo "" +echo " 运行 example:" +echo " cd packages/fluwx/example && flutter pub get && flutter run" +echo " cd packages/fluwx_no_pay/example && flutter pub get && flutter run" +echo "==========================================" diff --git a/tools/sync_version.dart b/tools/sync_version.dart new file mode 100644 index 00000000..ff4db444 --- /dev/null +++ b/tools/sync_version.dart @@ -0,0 +1,53 @@ +// ignore_for_file: avoid_print +import 'dart:io'; + +/// 将 fluwx_no_pay 的版本号同步为与 fluwx 一致。 +/// +/// 用法: +/// dart tools/sync_version.dart # 同步写入 +/// dart tools/sync_version.dart --check # 仅校验(供 CI 使用) +void main(List args) { + final checkOnly = args.contains('--check'); + + final fluwxPubspec = File('packages/fluwx/pubspec.yaml'); + final noPayPubspec = File('packages/fluwx_no_pay/pubspec.yaml'); + + if (!fluwxPubspec.existsSync()) { + print('❌ 找不到 packages/fluwx/pubspec.yaml'); + exit(1); + } + if (!noPayPubspec.existsSync()) { + print('❌ 找不到 packages/fluwx_no_pay/pubspec.yaml'); + exit(1); + } + + final fluwxContent = fluwxPubspec.readAsStringSync(); + final versionMatch = + RegExp(r'^version:\s*(.+)$', multiLine: true).firstMatch(fluwxContent); + if (versionMatch == null) { + print('❌ 未在 fluwx/pubspec.yaml 中找到版本号'); + exit(1); + } + final version = versionMatch.group(1)!.trim(); + + if (checkOnly) { + final noPayContent = noPayPubspec.readAsStringSync(); + final noPayVersionMatch = + RegExp(r'^version:\s*(.+)$', multiLine: true).firstMatch(noPayContent); + final noPayVersion = noPayVersionMatch?.group(1)?.trim(); + if (noPayVersion != version) { + print('❌ 版本不同步:fluwx=$version, fluwx_no_pay=$noPayVersion'); + exit(1); + } + print('✅ 版本一致:$version'); + return; + } + + var noPayContent = noPayPubspec.readAsStringSync(); + noPayContent = noPayContent.replaceFirstMapped( + RegExp(r'^version: .+$', multiLine: true), + (_) => 'version: $version', + ); + noPayPubspec.writeAsStringSync(noPayContent); + print('✅ fluwx_no_pay 版本已同步至 $version'); +}