- 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
319 lines
11 KiB
Bash
Executable File
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'"
|