Files
xianyan/iOS_macOS_Developer_Guide.md
Developer 3a38c69521 chore: 完成v1.2.3版本迭代更新
主要变更:
1. 移除摇一摇相关功能代码与依赖
2. 新增自定义频道导入与管理功能
3. 优化iOS/macOS平台配置与适配
4. 重构路由转场逻辑为原生Cupertino风格
5. 修复设备发现与文件传输相关bug
6. 调整深色模式默认值为纯黑AMOLED
7. 新增运行模式标签与Spotlight搜索优化
8. 清理废弃的本地化字符串与设置项
2026-06-10 07:57:58 +08:00

1098 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# iOS / macOS 开发者指南
> 本项目同时支持 iOS、macOS、Android、鸿蒙(HarmonyOS) 四端。
> 鸿蒙端使用定制 Flutter SDK`flutter-ohos`),在 `TargetPlatform` 枚举中新增了 `TargetPlatform.ohos`
> 并对大量三方库做了本地化适配。iOS/macOS 开发者需了解以下关键事项,避免踩坑。
---
## 文档更新日志
| 日期 | 版本 | 变更内容 |
|---|---|---|
<<<<<<< Updated upstream
| 2026-06-07 | v10 | 修正 §2.3 dependency_overrides 行数4→5行/40+→46行修正 §2.6 补丁引用§2.8→§2.9);简化 §2.8.1 pro_image_editor 过时回退建议;删除 §5.4 pro_image_editor 本地包条目和 bitsdojo_window 废弃条目;简化 §3.3 pubspec.yaml 处理策略git stash → 双模板脚本生成);更新 §3.2/§3.5/§6 与双模板机制对齐 |
| 2026-06-06 | v9 | 清理未使用依赖:移除 animations、animate_do、value_layout_builder、flutter_advanced_canvas_editor、flutter_blue_plus、http_cache_file_store、dartx、vector_math删除差异对照表中 flutter_nfc_kit 过时条目 |
| 2026-06-06 | v8 | 新增 `app_tracking_transparency` 差异对照条目;新增 `nearby_connections` 鸿蒙端本地stub包说明新增 §2.10 nearby_connections鸿蒙适配说明 |
=======
| 2026-06-06 | v8 | 移除 `nearby_connections` 库及P2P功能本地stub包影响Android构建更新差异对照表新增 §2.8.7 app_tracking_transparency说明 |
>>>>>>> Stashed changes
| 2026-06-02 | v7 | **重大变更**pubspec.yaml 拆分为双模板pubspec.ohos.yaml + pubspec.macos.yamlpubspec.yaml 不再提交到 Git新增三方库变更通知机制新增 setup_pubspec.ps1 脚本 |
| 2026-06-02 | v6 | 鸿蒙端 pubspec.yaml 同步 bitsdojo_window → window_manager 迁移;更新 file_picker 本地包版本注释(v8.3.7→v11.0.0-ohos.1);更新 speech_to_text(^7.0.0→^7.4.0)、live_activities(^2.0.0→^2.4.9) 远程版本号;补充 dependency_overrides 中 bitsdojo_window_windows 移除说明 |
| 2026-06-01 | v5 | 新增 §2.6 pub cache 补丁说明;标记 bitsdojo_window 迁移完成file_picker 升级到 12.x |
| 2026-05-30 | v4 | 初版完整指南 |
---
## 一、环境准备与项目拉取
### 1.1 Flutter SDK 选择
| 平台 | 推荐SDK | 说明 |
|---|---|---|
| iOS / macOS | **官方 Flutter SDK** | 标准 stable/beta 渠道即可 |
| 鸿蒙 | flutter-ohos 定制SDK | 包含 `TargetPlatform.ohos` 等扩展,仅在鸿蒙开发机上使用 |
| Android | 官方 Flutter SDK | 同 iOS |
> ⚠️ **iOS/macOS 开发使用官方 Flutter SDK 即可**无需安装鸿蒙定制SDK。
> 不要用鸿蒙SDK编译iOS/macOS反之亦然SDK不兼容会导致编译错误。
### 1.2 克隆仓库
```bash
# 克隆项目仓库
git clone <仓库URL> xianyan
cd xianyan
# 查看当前分支
git branch -a
# 切换到开发分支(如需)
git checkout feature/xxx
# 或直接在 main 分支开发
git checkout main
```
### 1.3 生成 pubspec.yaml 并安装依赖
> ⚠️ **`pubspec.yaml` 不再提交到 Git**。项目使用双模板机制:
> - `pubspec.ohos.yaml` — 鸿蒙端模板(使用本地 packages/ 目录)
> - `pubspec.macos.yaml` — MacBook Pro 端模板(使用远程版本号)
> - `pubspec.yaml` — 由脚本自动生成,已加入 `.gitignore`
```bash
# 1. 确认使用官方 Flutter SDK
flutter --version # 应显示官方版本,非 flutter-ohos
# 2. 运行脚本生成 pubspec.yamlMacBook Pro 端)
.\tools\setup_pubspec.ps1 -Platform macos
# 或自动检测平台
.\tools\setup_pubspec.ps1
# 3. 获取依赖
flutter pub get
# 4. iOS 编译验证
flutter build ios --no-codesign
# 5. macOS 编译验证
flutter build macos
```
鸿蒙端开发者:
```bash
# 1. 确认使用 flutter-ohos SDK
flutter --version # 应显示 ohos 版本
# 2. 运行脚本生成 pubspec.yaml鸿蒙端
.\tools\setup_pubspec.ps1 -Platform ohos
# 3. 获取依赖
flutter pub get
```
---
## 二、pubspec.yaml 双模板机制
### 2.1 架构概述
> **`pubspec.yaml` 不再提交到 Git**,两端各自维护独立的模板文件。
```
项目根目录/
├── pubspec.ohos.yaml ← 鸿蒙端模板tracked使用本地 packages/ 目录)
├── pubspec.macos.yaml ← MacBook Pro 端模板tracked使用远程版本号
├── pubspec.yaml ← 本地生成文件(.gitignore不提交
└── tools/
└── setup_pubspec.ps1 ← 自动生成脚本
```
**为什么这样做?**
之前鸿蒙端和 MacBook Pro 端共用一个 `pubspec.yaml`MacBook Pro 端每次 `git pull` 后需要手动替换 82 行本地包引用,容易出错且经常互相覆盖。
现在:
- 鸿蒙端模板 `pubspec.ohos.yaml` 包含所有 `path: packages/` 引用
- MacBook Pro 端模板 `pubspec.macos.yaml` 使用远程版本号
- `pubspec.yaml` 由脚本自动生成,两边都不提交
### 2.2 MacBook Pro 端的核心原则
> **MacBook Pro 端不需要 `packages/` 目录,直接使用远程三方库即可。**
`pubspec.macos.yaml` 已将所有本地包引用替换为远程版本号,无需手动修改。
**为什么不能直接用鸿蒙端的本地包?**
本地包中包含 `TargetPlatform.ohos` 引用鸿蒙SDK新增的枚举值官方 SDK 没有此值,会导致编译报错:
```
Error: The getter 'ohos' isn't defined for the class 'TargetPlatform'
```
受影响的本地包有 5 个:
| 包名 | 引用次数 | 引用类型 |
|---|---|---|
| flex_color_picker | 2 | `case TargetPlatform.ohos:` (switch) |
| flutter_quill | 2 | `case TargetPlatform.ohos:` (switch) |
| flutter_local_notifications | 8 | `== TargetPlatform.ohos` (if比较) |
| mobile_scanner | 3 | `== / != TargetPlatform.ohos` (if比较) |
| audioplayers | 1 | `!= TargetPlatform.ohos` (if比较) |
**解决方案**MacBook Pro 端使用远程版本,远程版本不含 `TargetPlatform.ohos`,编译正常。
### 2.3 双模板差异对照表
| 区域 | pubspec.ohos.yaml鸿蒙端 | pubspec.macos.yamlMacBook Pro端 |
|---|---|---|
| shared_preferences | `path: packages/shared_preferences` | `^2.5.5` |
| flutter_secure_storage | `path: packages/flutter_secure_storage` | `^10.2.0` |
| hive_flutter | `path: packages/hive_flutter` | `^1.1.0` |
| path_provider | `path: packages/path_provider` | `^2.1.5` |
| package_info_plus | `path: packages/package_info_plus` | `^10.1.0` |
| connectivity_plus | `path: packages/connectivity_plus` | `^7.1.1` |
| device_info_plus | `path: packages/device_info_plus` | `^13.1.0` |
| permission_handler | `path: packages/permission_handler` | `^12.0.1` |
| app_tracking_transparency | `^2.0.6` | `^2.0.6` |
| flutter_local_notifications | `path: packages/flutter_local_notifications` | `^21.0.0` |
| url_launcher | `path: packages/url_launcher` | `^6.3.2` |
| app_links | `path: packages/app_links` | `^7.0.0` |
| home_widget | `git: gitcode.com/...` | `^0.9.1` |
| file_picker | `path: packages/file_picker` | `^12.0.0-beta.5` |
| image_picker | `path: packages/image_picker` | `^1.2.2` |
| share_plus | `path: packages/share_plus` | `^13.1.0` |
| gal | `path: packages/gal` | `^2.3.0` |
| flutter_quill | `path: packages/flutter_quill` | `^11.5.0` |
| flex_color_picker | `path: packages/flex_color_picker` | `^3.8.0` |
| flutter_image_compress | `path: packages/flutter_image_compress` | `^2.4.0` |
| wakelock_plus | `path: packages/wakelock_plus` | `^1.4.0` |
| audioplayers | `path: packages/audioplayers` | `^6.5.0` |
| record | `path: packages/record` | `^6.0.0` |
| video_compress | `path: packages/video_compress` | `^3.1.2` |
| video_player | `path: packages/video_player` | `^2.10.0` |
| local_auth | `path: packages/local_auth` | `^3.0.1` |
| battery_plus | `path: packages/battery_plus` | `^7.0.0` |
| network_info_plus | `path: packages/network_info_plus` | `^8.1.0` |
| flutter_webrtc | `path: packages/flutter_webrtc` | `^1.4.0` |
| mobile_scanner | `path: packages/mobile_scanner` | `^7.1.4` |
| wifi_iot | `path: packages/wifi_iot` | `^0.3.19` |
| nearby_service | `path: packages/nearby_service` | `^0.2.1` |
| sqflite | `path: packages/sqflite` | `^2.4.1` |
| workmanager | `path: packages/workmanager` | `^0.9.0` |
| flutter_tts | `path: packages/flutter_tts` | `^4.2.0` |
| speech_to_text | `path: packages/speech_to_text` | `^7.4.0` |
| live_activities | `path: packages/live_activities` | `^2.4.9` |
| dependency_overrides | 46 行(含本地包覆盖 + ohos 子包) | 5 行(仅版本号覆盖 + win32 + quill_native_bridge_windows |
### 2.4 ⚠️ 新增三方库变更流程(必读)
> **铁律:新增三方库时,必须同时更新两个模板 + 本文档。**
```
新增三方库流程:
1. 在 pubspec.ohos.yaml 添加依赖(鸿蒙端优先)
2. 在 pubspec.macos.yaml 添加对应远程版本
3. 在本文档 §2.3 差异对照表添加一行
4. 在本文档顶部更新日志记录变更
5. 在 CHANGELOG.md 记录变更
6. git push 后通知另一端开发者
7. 另一端开发者: git pull → 运行 setup_pubspec.ps1 → flutter pub get
```
**鸿蒙端新增本地化包时额外步骤:**
- 将包放入 `packages/` 目录
-`pubspec.ohos.yaml``dependency_overrides` 中添加覆盖
-`pubspec.macos.yaml` 中使用远程版本号
- 更新本文档 §2.3 差异对照表
**升级三方库版本时:**
- 在对应模板中更新版本号
- 如果是鸿蒙本地包升级,同步更新 `pubspec.ohos.yaml` 中的版本注释
- 更新本文档 §2.3 差异对照表中的版本号
### 2.5 setup_pubspec.ps1 脚本说明
```powershell
# 鸿蒙端
.\tools\setup_pubspec.ps1 -Platform ohos
# MacBook Pro 端
.\tools\setup_pubspec.ps1 -Platform macos
# 自动检测(根据 flutter --version 或 packages/ 目录是否存在)
.\tools\setup_pubspec.ps1
```
脚本功能:
- 将对应模板复制为 `pubspec.yaml`
- 自动备份现有 `pubspec.yaml``pubspec.yaml.bak`
- 验证模板内容(鸿蒙端检查 `path: packages/`MacBook Pro 端检查无本地包引用)
- 输出后续操作提示
### 2.6 MacBook Pro 端完整操作流程
```bash
# 1. 克隆仓库
git clone <仓库URL> xianyan
cd xianyan
# 2. 生成 pubspec.yaml
.\tools\setup_pubspec.ps1 -Platform macos
# 3. 获取依赖
flutter pub get
# 4. 应用 pub cache 补丁(见 §2.9
bash scripts/patch_pub_cache.sh
# 5. 编译验证
flutter build ios --no-codesign
flutter build macos
```
### 2.7 鸿蒙端完整操作流程
```bash
# 1. 克隆仓库
git clone <仓库URL> xianyan
cd xianyan
# 2. 确保 packages/ 目录已准备好(从 zip 解压或 git submodule
# 3. 生成 pubspec.yaml
.\tools\setup_pubspec.ps1 -Platform ohos
# 4. 获取依赖
flutter pub get
```
### 2.8 特殊包说明
#### 2.8.1 pro_image_editor已迁移至远程版本
`pro_image_editor` 已从本地包迁移为远程版本 `^12.4.4`,两端均使用远程版本:
```yaml
# 两端均使用远程版本
pro_image_editor: ^12.4.4
```
#### 2.8.2 file_pickerAPI 变更 + win32 6.x 兼容)
`file_picker` 已从 `^11.0.0` 升级为 `^12.0.0-beta.5`,原因:
1. **API 变更**11.x → 12.x 通用):
```dart
// ❌ 旧版 APIfile_picker ^8.x
final result = await FilePicker.platform.pickFiles();
// ✅ 新版 APIfile_picker ^11.x / ^12.x
final result = await FilePicker.pickFiles();
```
2. **win32 6.x 兼容**12.x 专属):
- `file_picker ^11.0.0` 依赖 `win32: ^5.9.0`,与 `dependency_overrides` 中的 `win32: ^6.0.1` 冲突
- `file_picker ^12.0.0-beta.5` 兼容 `win32 ^6.0.1`,解决 macOS 构建失败
- ⚠️ 12.x 为 beta 版本,如遇问题可回退到 11.x需同时降级 win32 override见§2.6
项目代码已更新为新版 API。鸿蒙端本地包版本已升级为 `11.0.0-ohos.1`(基于 file_picker 11.x 适配),
API 已与远程版本对齐,无需额外处理。
#### 2.8.3 flutter_secure_storage版本差异说明
MacBook Pro 端使用远程版本 `^10.2.0`,其 Windows 平台实现兼容 `win32 ^6.0.1`
解决了之前版本与 `win32 6.x` 的编译冲突。
> **注意**:鸿蒙端本地包版本为 `9.2.4-ohos.1`(基于 9.x 适配),
> 与 MacBook Pro 端远程版本 `10.2.0` 存在主版本号差异。
> 两端 API 兼容,`lib/` 代码无需特殊处理。
#### 2.8.4 receive_sharing_intentgitcode 引用)
`receive_sharing_intent` 在 pub.dev 上的版本与鸿蒙端不兼容MacBook Pro 端需使用 gitcode 引用:
```yaml
# MacBook Pro 端使用 gitcode 引用
receive_sharing_intent:
git:
url: "https://gitcode.com/openharmony-sig/fluttertpc_receive_sharing_intent.git"
ref: "br_v1.8.1_ohos"
```
> **注意**gitcode 版本的 `SharedMediaFile` 构造函数可能包含 `ohosPath` 等鸿蒙特有参数,
> 官方 SDK 不存在这些参数。项目代码中已通过 `pu.isOhos` 条件分支隔离。
#### 2.8.5 flutter_vibrategitcode 引用)
`flutter_vibrate` 在 pub.dev 上的版本不支持鸿蒙MacBook Pro 端需使用 gitcode 引用:
```yaml
# MacBook Pro 端使用 gitcode 引用
flutter_vibrate:
git:
url: https://gitcode.com/openharmony-sig/fluttertpc_flutter_vibrate.git
```
#### 2.8.6 home_widgetpub.dev 远程版本)
`home_widget` 的 gitcode 版本依赖 `path_provider` 的 git 版本,会与远程 `path_provider` 冲突。
MacBook Pro 端使用 pub.dev 版本 `^0.9.1`,鸿蒙端的 `ohosName` 参数通过 `pu.isOhos` + `dynamic` 调用隔离:
```yaml
# MacBook Pro 端使用 pub.dev 远程版本
home_widget: ^0.9.1
```
#### 2.8.7 nearby_connections已移除
`nearby_connections`Google Nearby Connections API已从项目中完全移除。
原因:该库仅支持 Android/iOS鸿蒙端需要本地 stub 包,但本地包会影响 Android 端构建。
近场通信功能由 `nearby_service` 统一承担Android Wi-Fi Direct / iOS MultipeerConnectivity / 鸿蒙)。
> **注意**:移除后,蓝牙 P2P 发现功能不再可用,设备配对通过配对码/扫码/雷达/Wi-Fi Direct 进行。
#### 2.8.8 app_tracking_transparency两端均使用远程版本
`app_tracking_transparency` 是 iOS 专属权限库App Tracking Transparency
两端均使用远程版本 `^2.0.6`,无需本地适配。代码中通过 `Platform.isIOS` 条件守卫,
非 iOS 平台直接返回授权成功,不影响鸿蒙/Android/macOS 编译。
```yaml
# 两端配置相同
app_tracking_transparency: ^2.0.6
```
### 2.9 ⚠️ pub cache 补丁MacBook Pro 端必读)
> **关键问题**`dependency_overrides` 中 `win32: ^6.0.1` 导致部分依赖 `win32 ^5.x` 的三方包编译失败。
> 这些三方包的 Windows 平台代码在 macOS 构建时也会被编译Dart 编译器不区分平台)。
> 需要手动修补 pub cache 中的文件,使它们兼容 win32 6.x API。
#### 2.9.1 需要修补的包
| 包名 | 版本 | 问题 | 修补文件 |
|---|---|---|---|
| quill_native_bridge_windows | 0.0.2 | 使用 win32 5.x API`TEXT()``OpenClipboard(NULL)==FALSE` 等) | `lib/quill_native_bridge_windows.dart` + `lib/src/clipboard_html_format.dart` |
| flutter_vibrate | gitcode | `TARGET_OS_SIMULATOR` 在 Xcode 16+/Swift 6 中不可用 | `ios/Classes/SwiftVibratePlugin.swift` |
#### 2.9.2 补丁应用流程
```bash
# 1. 确保 flutter pub get 已执行
flutter pub get
# 2. 运行补丁脚本(见 §2.6.3
bash scripts/patch_pub_cache.sh
# 3. 验证构建
flutter build macos
flutter build ios --no-codesign
```
> ⚠️ `flutter clean` 或 `flutter pub cache repair` 会清除补丁,需重新执行步骤 2。
#### 2.9.3 补丁脚本
在项目根目录创建 `scripts/patch_pub_cache.sh`(已包含在仓库中),内容如下:
```bash
#!/bin/bash
# patch_pub_cache.sh — 修补 pub cache 中的兼容性问题
# 创建时间: 2026-06-01
# 作用: 修补 quill_native_bridge_windows (win32 6.x) 和 flutter_vibrate (Swift 6)
set -e
echo "🔧 Applying pub cache patches..."
# ── Patch 1: quill_native_bridge_windows — clipboard_html_format.dart ──
QNBW_DIR="$HOME/.pub-cache/hosted/pub.dev/quill_native_bridge_windows-0.0.2"
if [ -d "$QNBW_DIR" ]; then
echo " Patching clipboard_html_format.dart..."
cat > "$QNBW_DIR/lib/src/clipboard_html_format.dart" << 'DART_EOF'
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
import '../quill_native_bridge_windows.dart';
const _kHtmlFormatName = 'HTML Format';
int? _cfHtml;
extension ClipboardHtmlFormatExt on QuillNativeBridgeWindows {
int? get cfHtml {
_cfHtml ??= _registerHtmlFormat();
return _cfHtml;
}
int? _registerHtmlFormat() {
final htmlFormatPointer = _kHtmlFormatName.toPcwstr();
final result = RegisterClipboardFormat(htmlFormatPointer);
free(htmlFormatPointer);
if (result.error.isError) {
return null;
}
return result.value;
}
}
DART_EOF
echo " ✅ clipboard_html_format.dart patched"
else
echo " ⚠️ quill_native_bridge_windows not found in pub cache"
fi
# ── Patch 2: quill_native_bridge_windows — quill_native_bridge_windows.dart ──
if [ -d "$QNBW_DIR" ]; then
echo " Patching quill_native_bridge_windows.dart..."
cat > "$QNBW_DIR/lib/quill_native_bridge_windows.dart" << 'DART_EOF'
// Patched for win32 6.x compatibility (2026-06-01)
// Changes: Win32Result API, HGLOBAL/HANDLE extension types, TEXT() → toPcwstr()
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
import 'package:flutter/foundation.dart';
import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart';
import 'package:win32/win32.dart';
import 'src/clipboard_html_format.dart';
import 'src/html_cleaner.dart';
import 'src/html_formatter.dart';
import 'src/image_saver.dart';
class QuillNativeBridgeWindows extends QuillNativeBridgePlatform {
static void registerWith() {
QuillNativeBridgePlatform.instance = QuillNativeBridgeWindows();
}
@override
Future<bool> isSupported(QuillNativeBridgeFeature feature) async => {
QuillNativeBridgeFeature.getClipboardHtml,
QuillNativeBridgeFeature.copyHtmlToClipboard,
QuillNativeBridgeFeature.saveImage,
}.contains(feature);
@override
Future<String?> getClipboardHtml() async {
final openResult = OpenClipboard(null);
if (openResult.error.isError) {
assert(false, 'Unknown error while opening the clipboard. Error code: ${GetLastError()}');
return null;
}
try {
final htmlFormatId = cfHtml;
if (htmlFormatId == null) {
assert(false, 'Failed to register clipboard HTML format.');
return null;
}
final availableResult = IsClipboardFormatAvailable(htmlFormatId);
if (availableResult.error.isError) {
return null;
}
final getDataResult = GetClipboardData(htmlFormatId);
if (getDataResult.error.isError) {
assert(false, 'Failed to get clipboard data. Error code: ${GetLastError()}');
return null;
}
final clipboardDataHandle = getDataResult.value;
final hglobal = HGLOBAL(clipboardDataHandle);
final lockResult = GlobalLock(hglobal);
if (lockResult.error.isError) {
assert(false, 'Failed to lock global memory. Error code: ${GetLastError()}');
return null;
}
final lockedMemoryPointer = lockResult.value;
final windowsHtmlWithMetadata = lockedMemoryPointer.cast<Utf8>().toDartString();
GlobalUnlock(hglobal);
final cleanedHtml = stripWindowsHtmlDescriptionHeaders(windowsHtmlWithMetadata);
return cleanedHtml;
} finally {
CloseClipboard();
}
}
@override
Future<void> copyHtmlToClipboard(String html) async {
final openResult = OpenClipboard(null);
if (openResult.error.isError) {
assert(false, 'Unknown error while opening the clipboard. Error code: ${GetLastError()}');
return;
}
final windowsClipboardHtml = constructWindowsHtmlDescriptionHeaders(html);
final htmlPointer = windowsClipboardHtml.toNativeUtf8();
try {
final emptyResult = EmptyClipboard();
if (emptyResult.error.isError) {
assert(false, 'Failed to empty the clipboard. Error code: ${GetLastError()}');
return;
}
final htmlFormatId = cfHtml;
if (htmlFormatId == null) {
assert(false, 'Failed to register clipboard HTML format. Error code: ${GetLastError()}');
return;
}
final unitSize = sizeOf<Uint8>();
final htmlSize = (htmlPointer.length + 1) * unitSize;
final allocResult = GlobalAlloc(GMEM_MOVEABLE, htmlSize);
if (allocResult.error.isError) {
assert(false, 'Failed to allocate memory for the clipboard content. Error code: ${GetLastError()}');
return;
}
final clipboardMemoryHandle = allocResult.value;
final lockResult = GlobalLock(clipboardMemoryHandle);
if (lockResult.error.isError) {
GlobalFree(clipboardMemoryHandle);
assert(false, 'Failed to lock global memory. Error code: ${GetLastError()}');
return;
}
final lockedMemoryPointer = lockResult.value;
final targetMemoryPointer = lockedMemoryPointer.cast<Uint8>();
final sourcePointer = htmlPointer.cast<Uint8>();
for (var i = 0; i < htmlPointer.length; i++) {
targetMemoryPointer[i] = (sourcePointer + i).value;
}
(targetMemoryPointer + htmlPointer.length).value = 0;
GlobalUnlock(clipboardMemoryHandle);
final setResult = SetClipboardData(htmlFormatId, HANDLE(clipboardMemoryHandle));
if (setResult.error.isError) {
GlobalFree(clipboardMemoryHandle);
assert(false, 'Failed to set the clipboard data: ${GetLastError()}');
}
} finally {
CloseClipboard();
calloc.free(htmlPointer);
}
}
@visibleForTesting
static ImageSaver imageSaver = ImageSaver();
@override
Future<ImageSaveResult> saveImage(Uint8List imageBytes, {required ImageSaveOptions options}) async {
final typeGroup = XTypeGroup(label: 'Images', extensions: [options.fileExtension]);
final saveLocation = await imageSaver.fileSelector.getSaveLocation(
options: SaveDialogOptions(
suggestedName: '${options.name}.${options.fileExtension}',
initialDirectory: imageSaver.picturesDirectoryPath,
),
acceptedTypeGroups: [typeGroup],
);
final imageFilePath = saveLocation?.path;
if (imageFilePath == null) {
return ImageSaveResult.io(filePath: null);
}
final imageFile = File(imageFilePath);
await imageFile.writeAsBytes(imageBytes);
return ImageSaveResult.io(filePath: imageFile.path);
}
@override
Future<void> openGalleryApp() async {
final uriPtr = 'ms-photos:'.toPcwstr();
final openPtr = 'open'.toPcwstr();
ShellExecute(null, openPtr, uriPtr, null, null, SW_SHOWNORMAL);
free(uriPtr);
free(openPtr);
}
}
DART_EOF
echo " ✅ quill_native_bridge_windows.dart patched"
fi
# ── Patch 3: flutter_vibrate — SwiftVibratePlugin.swift ──
FV_DIR=$(find "$HOME/.pub-cache/git" -path "*/fluttertpc_flutter_vibrate*" -maxdepth 2 -type d 2>/dev/null | head -1)
if [ -n "$FV_DIR" ]; then
echo " Patching SwiftVibratePlugin.swift..."
cat > "$FV_DIR/ios/Classes/SwiftVibratePlugin.swift" << 'SWIFT_EOF'
import Flutter
import UIKit
import AudioToolbox
// Patched for Xcode 16+/Swift 6 compatibility (2026-06-01)
// TARGET_OS_SIMULATOR removed; using #if targetEnvironment(simulator)
#if targetEnvironment(simulator)
private let isDevice = false
#else
private let isDevice = true
#endif
public class SwiftVibratePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "vibrate", binaryMessenger: registrar.messenger())
let instance = SwiftVibratePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch (call.method) {
case "canVibrate":
if isDevice { result(true) } else { result(false) }
case "vibrate":
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
case "impact":
if #available(iOS 10.0, *) {
let impact = UIImpactFeedbackGenerator()
impact.prepare()
impact.impactOccurred()
} else { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) }
case "selection":
if #available(iOS 10.0, *) {
let selection = UISelectionFeedbackGenerator()
selection.prepare()
selection.selectionChanged()
} else { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) }
case "success":
if #available(iOS 10.0, *) {
let notification = UINotificationFeedbackGenerator()
notification.prepare()
notification.notificationOccurred(.success)
} else { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) }
case "warning":
if #available(iOS 10.0, *) {
let notification = UINotificationFeedbackGenerator()
notification.prepare()
notification.notificationOccurred(.warning)
} else { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) }
case "error":
if #available(iOS 10.0, *) {
let notification = UINotificationFeedbackGenerator()
notification.prepare()
notification.notificationOccurred(.error)
} else { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) }
case "heavy":
if #available(iOS 10.0, *) {
let generator = UIImpactFeedbackGenerator(style: .heavy)
generator.prepare()
generator.impactOccurred()
} else { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) }
case "medium":
if #available(iOS 10.0, *) {
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.prepare()
generator.impactOccurred()
} else { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) }
case "light":
if #available(iOS 10.0, *) {
let generator = UIImpactFeedbackGenerator(style: .light)
generator.prepare()
generator.impactOccurred()
} else { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) }
default:
result(FlutterMethodNotImplemented)
}
}
}
SWIFT_EOF
echo " ✅ SwiftVibratePlugin.swift patched"
else
echo " ⚠️ flutter_vibrate not found in pub cache"
fi
echo "🎉 All patches applied!"
echo "⚠️ Note: Run this script again after 'flutter clean' or 'flutter pub cache repair'"
```
#### 2.9.4 win32 5.x → 6.x API 迁移参考
如需修补其他使用 win32 的三方包,以下是关键 API 变更:
| win32 5.x | win32 6.x | 说明 |
|---|---|---|
| `OpenClipboard(NULL) == FALSE` | `OpenClipboard(null).error.isError` | 返回 `Win32Result<bool>` |
| `CloseClipboard()` | `CloseClipboard()` | 返回 `Win32Result<bool>`,忽略即可 |
| `EmptyClipboard() == FALSE` | `EmptyClipboard().error.isError` | 返回 `Win32Result<bool>` |
| `IsClipboardFormatAvailable(id) == FALSE` | `IsClipboardFormatAvailable(id).error.isError` | 返回 `Win32Result<bool>` |
| `GetClipboardData(id) == NULL` | `GetClipboardData(id).error.isError` | 返回 `Win32Result<HANDLE>` |
| `GlobalAlloc(flags, size) == nullptr` | `GlobalAlloc(flags, size).error.isError` | 返回 `Win32Result<HGLOBAL>` |
| `GlobalLock(handle) == nullptr` | `GlobalLock(HGLOBAL(handle)).error.isError` | 返回 `Win32Result<Pointer>` |
| `GlobalUnlock(handle)` | `GlobalUnlock(HGLOBAL(handle))` | 返回 `Win32Result<bool>` |
| `GlobalFree(handle)` | `GlobalFree(HGLOBAL(handle))` | 返回 `Win32Result<HGLOBAL>` |
| `SetClipboardData(id, addr) == NULL` | `SetClipboardData(id, HANDLE(hglobal)).error.isError` | 返回 `Win32Result<HANDLE>` |
| `TEXT('str')` | `'str'.toPcwstr()` | `TEXT()` 已移除 |
| `RegisterClipboardFormat(ptr)` 返回 `int` | `RegisterClipboardFormat(ptr)` 返回 `Win32Result<int>` | 检查 `.error.isError` |
| `ShellExecute(NULL, ...)` | `ShellExecute(null, ...)` | `NULL``null``nullptr``null`PCWSTR? 类型) |
| `HGLOBAL``int` | `HGLOBAL``extension type const HGLOBAL(Pointer _)` | 需类型转换 |
| `HANDLE``int` | `HANDLE``extension type const HANDLE(Pointer _)` | 需类型转换 |
> **类型转换**`HANDLE` 和 `HGLOBAL` 都 `implements Pointer`,可互相转换:
> - `HANDLE → HGLOBAL``HGLOBAL(handle)`
> - `HGLOBAL → HANDLE``HANDLE(hglobal)`
---
## 三、Git 提交与合并规范
### 3.1 分支策略
```
main (受保护) ← 所有平台共用
├── feature/xxx ← 功能开发(所有平台共用)
├── fix/xxx ← Bug修复
└── hotfix/xxx ← 紧急修复
```
> **不需要按平台创建长期分支**。`lib/` 代码所有平台共用,独立平台分支会导致大量合并冲突。
> 平台原生代码已天然隔离(`ios/`、`macos/`、`ohos/` 各自独立),不会互相污染。
### 3.2 iOS/macOS 开发者提交规则
**✅ 应该提交的文件:**
- `lib/` 目录下的所有 Dart 代码
- `ios/` 目录下的原生代码
- `macos/` 目录下的原生代码
- `assets/` 目录下的资源文件
- `test/` 目录下的测试代码
**❌ 不要提交的文件:**
- `pubspec.yaml`MacBook Pro 端已替换为远程版本,**绝不能提交**
- `packages/` 目录(已在 `.gitignore` 中排除)
- `ohos/` 目录下的鸿蒙原生代码
- 鸿蒙SDK特有的配置文件
**⚠️ pubspec.yaml 提交铁律:**
1. **`pubspec.yaml` 已加入 `.gitignore`,不应提交** — 由 `setup_pubspec.ps1` 脚本从模板生成
2. **修改依赖时编辑模板文件**`pubspec.macos.yaml`MacBook Pro 端)或 `pubspec.ohos.yaml`(鸿蒙端)
3. **模板文件必须提交** — 确保两端依赖配置同步
### 3.3 pubspec.yaml 处理策略(⭐ 重点)
> **双模板机制下,`pubspec.yaml` 由脚本生成,不提交到 Git。**
> MacBook Pro 端开发者无需手动替换本地包引用,也无需 git stash 隔离。
#### 3.3.1 日常操作
```bash
# git pull 后,重新生成 pubspec.yaml 即可
.\tools\setup_pubspec.ps1 -Platform macos
flutter pub get
# 编译验证
flutter build ios --no-codesign
```
#### 3.3.2 需要新增依赖时
新增依赖时,必须在**两端模板**分别操作:
```bash
# 1. 在 pubspec.macos.yaml 添加远程版本依赖
# 2. 在 pubspec.ohos.yaml 添加对应依赖(本地包或远程版本)
# 3. 更新本文档 §2.3 差异对照表
# 4. 重新生成 pubspec.yaml
.\tools\setup_pubspec.ps1 -Platform macos
flutter pub get
# 5. 通知鸿蒙开发者评估适配
```
#### 3.3.3 git pull 后 pubspec.yaml 被覆盖
```bash
# pubspec.yaml 在 .gitignore 中git pull 不会覆盖
# 如果误删或需要重新生成:
.\tools\setup_pubspec.ps1 -Platform macos
flutter pub get
```
#### 3.3.4 减少冲突的最佳实践
| 做法 | 说明 |
|---|---|
| **不提交 pubspec.yaml** | 已在 .gitignore从根本上避免冲突 |
| **新增依赖时通知鸿蒙开发者** | 让鸿蒙端同步评估适配 |
| **版本号升级单独提交** | 不要和功能代码混在一起提交 |
| **修改模板而非 pubspec.yaml** | 直接编辑 `pubspec.macos.yaml` / `pubspec.ohos.yaml` |
### 3.4 其他合并注意事项
1. **不要删除 `pu.isOhos` 相关代码**:这些条件分支在 iOS/macOS 上不会执行,但删除会导致鸿蒙端编译失败
2. **不要修改 `OhosAppShell`、`OhosNavBridge` 等鸿蒙专用类**:这些类仅在鸿蒙端使用
3. **新增页面路由时**:需同时在 `app_router.dart`GoRouter路由表`ohos_nav_bridge.dart`(鸿蒙路由映射表)中注册
### 3.5 PR 审查要点
- [ ] 未提交 `pubspec.yaml`(已加入 .gitignore由脚本生成
- [ ] 依赖变更已同步更新 `pubspec.macos.yaml``pubspec.ohos.yaml` 两个模板
- [ ] 未引入 `TargetPlatform` exhaustive switch 问题
- [ ] 新增路由已在 `ohos_nav_bridge.dart` 中同步
- [ ] `lib/` 代码无平台特定硬编码(应使用 `platform_utils.dart`
- [ ] 新增依赖已通知鸿蒙开发者评估适配
---
## 四、TargetPlatform.ohos 处理
### 4.1 问题背景
鸿蒙定制 Flutter SDK 在 `TargetPlatform` 枚举中新增了 `TargetPlatform.ohos`
官方 SDK 不包含此值。本地包中引用了 `TargetPlatform.ohos`,在官方 SDK 下会编译报错。
**MacBook Pro 端的解决方案**:使用远程版本的三方库,远程版本不含 `TargetPlatform.ohos`,无需关心此问题。
### 4.2 lib/ 代码中的平台判断
`lib/` 目录下的项目代码使用 `platform_utils.dart` 进行平台判断,**不依赖 `TargetPlatform.ohos` 枚举**
在官方 SDK 下编译完全正常:
```dart
// lib/ 代码使用 Platform.operatingSystem 字符串比较,不依赖枚举
// platform_io_native.dart:
bool _isOhos() {
try {
return Platform.operatingSystem == 'ohos'; // 字符串比较官方SDK也支持
} catch (_) {
return false;
}
}
```
### 4.3 iOS/macOS 开发者规范
**规则1不要使用 `switch(TargetPlatform)` 穷举匹配**
```dart
// ❌ 错误 — 官方SDK没有 ohos鸿蒙SDK缺少 ohos 会报错
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
// ...
case TargetPlatform.android:
// ...
case TargetPlatform.macOS:
// ...
case TargetPlatform.windows:
// ...
case TargetPlatform.linux:
// ...
case TargetPlatform.fuchsia:
// ...
}
// ✅ 正确 — 使用 if-else 或添加 default
if (defaultTargetPlatform == TargetPlatform.iOS) {
// iOS 逻辑
} else if (defaultTargetPlatform == TargetPlatform.macOS) {
// macOS 逻辑
} else {
// 其他平台
}
// ✅ 正确 — switch 加 default
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
case TargetPlatform.android:
return mobileLayout;
default:
return desktopLayout;
}
```
**规则2使用 `platform_utils.dart` 判断平台**
```dart
import 'package:xianyan/core/utils/platform_utils.dart' as pu;
// ✅ 推荐方式 — 内部使用字符串比较,两端都安全
if (pu.isIOS) { /* iOS */ }
if (pu.isMacOS) { /* macOS */ }
if (pu.isOhos) { /* 鸿蒙 */ }
if (pu.isMobile) { /* 移动端 */ }
if (pu.isDesktop) { /* 桌面端 */ }
```
### 4.4 已知的 TargetPlatform.ohos 适配点(仅本地包)
以下文件在本地包中引用了 `TargetPlatform.ohos`MacBook Pro 端使用远程版本不受影响:
| 文件 | 位置 | 引用类型 |
|---|---|---|
| `packages/flex_color_picker/.../picker_functions.dart` | L49, L65 | `case TargetPlatform.ohos:` |
| `packages/flutter_quill/.../raw_editor_state.dart` | L309 | `case TargetPlatform.ohos:` |
| `packages/flutter_quill/.../link.dart` | L58 | `case TargetPlatform.ohos:` |
| `packages/flutter_local_notifications/.../plugin.dart` | 8处 | `== TargetPlatform.ohos` |
| `packages/mobile_scanner/.../method_channel.dart` | 3处 | `== / != TargetPlatform.ohos` |
| `packages/audioplayers/.../audioplayer.dart` | L178 | `!= TargetPlatform.ohos` |
> 以上文件均在 `packages/` 目录中MacBook Pro 端使用远程版本,不会编译这些文件。
### 4.5 lib/ 代码中的鸿蒙SDK类型桥接
鸿蒙端本地包中某些类有官方 SDK 不存在的额外参数或类型(如 `OhosInitializationSettings``ohosName`)。
项目通过桥接文件和 `dynamic` 调用隔离这些差异,确保两端编译正常。
#### 4.5.1 通知服务桥接
**桥接文件**`lib/core/services/notification/notification_init_stub.dart`
| 方法 | 官方SDK行为 | 鸿蒙端行为 |
|---|---|---|
| `buildNotificationInitSettings()` | 构建 `InitializationSettings`(无 ohos 参数) | 鸿蒙端本地包的 `InitializationSettings` 自带 ohos 参数,官方端不传即可 |
| `requestOhosNotificationPermission()` | 返回 `false`(不执行) | 动态调用 `OhosFlutterLocalNotificationsPlugin` |
**使用此桥接的文件**
- `lib/core/services/notification/notification_service.dart`
- `lib/core/services/notification/local_notification_service.dart`
- `lib/features/file_transfer/services/notification_service.dart`
#### 4.5.2 HomeWidget 桥接
鸿蒙端本地包的 `HomeWidget.updateWidget()``HomeWidget.requestPinWidget()``ohosName` 参数,
官方 SDK 不存在此参数。项目通过 `pu.isOhos` 条件 + `dynamic` 调用隔离:
```dart
// 官方SDK标准调用
await HomeWidget.updateWidget(
androidName: type.androidProviderName,
iOSName: type.iosWidgetKind,
);
// 鸿蒙端dynamic 调用,传入 ohosName
if (pu.isOhos) {
final dynamic updateWidget = HomeWidget.updateWidget;
await updateWidget(
androidName: type.androidProviderName,
iOSName: type.iosWidgetKind,
ohosName: type.ohosFormName,
);
}
```
**涉及文件**
- `lib/core/services/data/home_widget_service.dart`
- `lib/features/widget/providers/widget_provider.dart`
#### 4.5.3 新增鸿蒙SDK特有类型的规范
如果鸿蒙端本地包新增了官方 SDK 不存在的类型或参数,按以下规范处理:
1. **在 `lib/` 代码中不直接 import 鸿蒙专用类型**(如 `OhosInitializationSettings`
2. **使用 `pu.isOhos` 条件分支**:鸿蒙端逻辑仅在 `pu.isOhos` 为 true 时执行
3. **使用 `dynamic` 调用**:绕过官方 SDK 的静态类型检查
4. **优先创建桥接文件**:将鸿蒙特有逻辑封装在独立文件中(如 `notification_init_stub.dart`
---
## 五、其他注意事项
### 5.1 应用入口架构差异
项目在 `app.dart` 中根据平台选择不同的应用架构:
| 平台 | 应用架构 | 导航方式 | 入口Widget |
|---|---|---|---|
| iOS/macOS/Android | `MaterialApp.router` + GoRouter | GoRouter 声明式路由 | `AppShell` |
| 鸿蒙 | `MaterialApp(home:)` + Navigator | `CupertinoTabView` + Navigator.push | `OhosAppShell` |
**原因**:鸿蒙端 `MaterialApp.router` 白屏,无法使用 GoRouter因此使用传统 Navigator 导航。
### 5.2 路由注册双写
新增页面时,必须在两处注册路由:
1. **`lib/core/router/app_router.dart`** — GoRouter 路由表iOS/macOS/Android 使用)
2. **`lib/core/router/ohos_nav_bridge.dart`** — 鸿蒙路由映射表
```dart
// app_router.dart
GoRoute(
path: AppRoutes.newPage,
name: 'new-page',
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) =>
iosSlideTransition(state: state, child: const NewPage()),
),
// ohos_nav_bridge.dart
AppRoutes.newPage: (_) => const NewPage(),
```
### 5.3 平台特性检测
使用 `OhosDeviceCapabilities` 检测鸿蒙设备特性(毛玻璃、液态玻璃、重度动画、折叠屏等),
iOS/macOS 端这些检测不会执行(`isOhos` 为 false无需关心。
### 5.4 packages 目录说明
- `packages/` 目录存放鸿蒙适配的本地三方库,已在 `.gitignore` 中排除(`/packages/`
- MacBook Pro 端使用远程版本,无需 `packages/` 目录
- 鸿蒙开发者需手动维护本地 `packages/` 目录
- ⚠️ `pubspec.yaml` 已加入 `.gitignore`,不再提交到 Git
- 鸿蒙端模板:`pubspec.ohos.yaml`MacBook Pro 端模板:`pubspec.macos.yaml`
- 使用 `tools/setup_pubspec.ps1` 生成 `pubspec.yaml`
### 5.5 MacBook Pro 修改 ios/macos 后,鸿蒙端是否需要同步?
| 修改内容 | 鸿蒙端是否需要同步 | 原因 |
|---|---|---|
| `ios/` 目录 | ❌ 不需要 | 平台原生代码,鸿蒙不使用 |
| `macos/` 目录 | ❌ 不需要 | 平台原生代码,鸿蒙不使用 |
| `lib/` 目录 | ✅ 需要同步 | Dart 代码所有平台共用 |
| `pubspec.ohos.yaml` | ✅ 鸿蒙端模板 | 鸿蒙端依赖配置 |
| `pubspec.macos.yaml` | ✅ MacBook Pro端模板 | MacBook Pro端依赖配置 |
| `pubspec.yaml` | ❌ 不提交 | 已加入 .gitignore本地生成 |
| `assets/` 目录 | ✅ 需要同步 | 资源文件共享 |
> **实际案例**`bitsdojo_window → window_manager` 迁移时MacBook Pro 端先完成代码和依赖替换,
> 鸿蒙端随后同步更新 `pubspec.yaml`(移除 `bitsdojo_window`,添加 `window_manager`
> 并删除 `packages/bitsdojo_window_windows/` 废弃目录。
### 5.6 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| `flutter pub get` 报 packages/xxx 目录不存在 | pubspec.yaml 是鸿蒙端模板 | MacBook Pro端运行 `.\tools\setup_pubspec.ps1 -Platform macos` |
| 编译报 `TargetPlatform.ohos` 不存在 | 使用了含 ohos 引用的本地包 | 确认使用 `pubspec.macos.yaml` 生成的 pubspec.yaml |
| iOS 编译报 ohos 相关错误 | 误用鸿蒙SDK编译iOS | 切换到官方 Flutter SDK |
| GoRouter 路由正常但鸿蒙端白屏 | 鸿蒙端不支持 GoRouter | 检查 OhosNavBridge 路由映射 |
| git pull 后 pubspec.yaml 被覆盖 | pubspec.yaml 已在 .gitignore | 重新运行 `.\tools\setup_pubspec.ps1` |
| 新增依赖后另一端报错 | 只更新了一个模板 | 必须同时更新两个模板 + 文档,参见 §2.4 |
| 新增依赖后鸿蒙端报错 | 新增的三方库未适配鸿蒙 | 通知鸿蒙开发者评估,必要时本地化到 packages/ |
| 编译报 `OhosInitializationSettings` 不存在 | 官方SDK无此类型 | 使用 `notification_init_stub.dart` 桥接,参见 §4.5 |
| 编译报 `ohosName` 参数不存在 | 官方SDK的 HomeWidget 无此参数 | 使用 `dynamic` 调用,参见 §4.5.2 |
| `pro_image_editor``CanvasStyleModel` 不存在 | 远程版本不含魔改内容 | 使用本地包 `path: packages/pro_image_editor`,参见 §2.8.1 |
| `FilePicker.platform` 报错 | file_picker 11.x API 变更 | 使用 `FilePicker.pickFiles()`,参见 §2.8.2 |
---
## 六、快速检查清单
MacBook Pro 开发前,确认以下事项:
- [ ] 使用官方 Flutter SDK非 flutter-ohos
- [ ]`git clone` 拉取最新代码
- [ ] 已运行 `.\tools\setup_pubspec.ps1 -Platform macos` 生成 pubspec.yaml
- [ ] `flutter pub get` 无报错
- [ ] `dart analyze lib/` 无 error
- [ ] 新增代码未使用 `switch(TargetPlatform)` 穷举匹配
- [ ] 新增鸿蒙SDK特有类型已通过桥接文件隔离参见 §4.5
- [ ] 新增路由已在 `app_router.dart``ohos_nav_bridge.dart` 双写
- [ ] 依赖变更已同步更新两个模板文件
- [ ] Git 提交未删除 `pu.isOhos` 相关代码
---
*文档创建时间: 2026-05-21 | 更新时间: 2026-06-07 v10 | 维护者: 闲言APP开发团队*
*更新内容: 修正dependency_overrides行数、补丁引用简化pro_image_editor和pubspec.yaml处理策略与双模板机制对齐*