Files
xianyan/scripts/patch_pub_cache.sh
Developer 22987e4ad1 fix: MacBook Pro端iOS/macOS构建修复 - win32 6.x兼容+Swift 6修复
- file_picker升级到12.x兼容win32 6.x
- 新增scripts/patch_pub_cache.sh补丁脚本(quill_native_bridge_windows+flutter_vibrate)
- bitsdojo_window→window_manager代码迁移
- 更新iOS_macOS_Developer_Guide.md v6(新增§2.6 pub cache补丁文档)
- 更新CHANGELOG.md v6.9.51
2026-06-01 11:15:59 +08:00

319 lines
11 KiB
Bash
Executable File

#!/bin/bash
# ============================================================
# patch_pub_cache.sh — 修补 pub cache 中的兼容性问题
# 创建时间: 2026-06-01
# 更新时间: 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'"