From 0011861e3f9155ba4c28864f9bd7f452fdc2942e Mon Sep 17 00:00:00 2001 From: Innei Date: Sat, 8 Feb 2025 17:50:13 +0800 Subject: [PATCH] feat(mobile): enhance iOS image preview with SDWebImage and improved QuickLook handling - Add SDWebImage dependency to FollowNative.podspec - Refactor ImagePreview and PreviewControllerController for robust image preview - Implement dynamic file extension handling for image preview - Update WebView bridge to support more flexible image preview payload - Improve MarkdownImage component to use Uint8Array for image conversion Signed-off-by: Innei --- apps/mobile/native/ios/FollowNative.podspec | 1 + .../native/ios/Helper/Helper+Image.swift | 94 ++++++++++++++----- .../CustomURLSchemeHandler.swift | 41 ++++---- .../ios/SharedWebView/Injected/at_start.js | 9 +- .../SharedWebView+BridgeData.swift | 15 ++- .../ios/SharedWebView/WebViewManager.swift | 2 +- apps/mobile/web-app/html-renderer/global.d.ts | 2 +- .../html-renderer/src/components/image.tsx | 47 ++++++---- 8 files changed, 137 insertions(+), 74 deletions(-) diff --git a/apps/mobile/native/ios/FollowNative.podspec b/apps/mobile/native/ios/FollowNative.podspec index 1d6f7a650..855b13a56 100644 --- a/apps/mobile/native/ios/FollowNative.podspec +++ b/apps/mobile/native/ios/FollowNative.podspec @@ -20,6 +20,7 @@ Pod::Spec.new do |s| s.dependency 'ExpoModulesCore' s.dependency 'SnapKit', '~> 5.7.0' + s.dependency 'SDWebImage', '~> 5.0' # Swift/Objective-C compatibility s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', diff --git a/apps/mobile/native/ios/Helper/Helper+Image.swift b/apps/mobile/native/ios/Helper/Helper+Image.swift index 86f3f4184..83c26f8c6 100644 --- a/apps/mobile/native/ios/Helper/Helper+Image.swift +++ b/apps/mobile/native/ios/Helper/Helper+Image.swift @@ -5,48 +5,78 @@ // Created by Innei on 2025/2/7. // -import ObjectiveC import QuickLook +import SDWebImage import UIKit -class PreviewControllerClass: NSObject, QLPreviewControllerDataSource, QLPreviewControllerDelegate { - private var imageDataArray: [Data] = [] +private let previewContentDirectory = URL(fileURLWithPath: NSTemporaryDirectory()) + .appendingPathComponent(Bundle.main.bundleIdentifier!) + .appendingPathComponent("image-preview") - init(images: [Data]) { - self.imageDataArray = images - super.init() - } +class PreviewControllerController: QLPreviewController, QLPreviewControllerDataSource, + QLPreviewControllerDelegate +{ + private var imageDataArray: [Data] = [] + private var initialIndex: Int = 0 func numberOfPreviewItems(in controller: QLPreviewController) -> Int { return imageDataArray.count } + func prepareImages(_ images: [Data], initialIndex: Int = 0) { + self.imageDataArray = images + self.initialIndex = initialIndex + } + func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { - let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent( - "preview_image_\(index).jpg") - try? imageDataArray[index].write(to: tempURL) - return tempURL as QLPreviewItem + let imageData = imageDataArray[index] + guard let image = UIImage(data: imageData) else { + return previewContentDirectory as QLPreviewItem + } + try? FileManager.default.createDirectory( + at: previewContentDirectory, withIntermediateDirectories: true) + + let tempLocation = + previewContentDirectory + .appendingPathComponent(UUID().uuidString) + .appendingPathExtension(image.sd_imageFormat.possiblePathExtension) + + try? imageData.write(to: tempLocation) + return tempLocation as QLPreviewItem + } + + override func viewDidLoad() { + debugPrint(previewContentDirectory, "previewContentDirectory") + + super.viewDidLoad() + delegate = self + dataSource = self + view.tintColor = Utils.accentColor + + // Set initial preview index + if initialIndex < imageDataArray.count { + self.currentPreviewItemIndex = initialIndex + } } private func cleanupTempFiles() { - for index in 0.. { + previewImage: (data) => { send({ type: "previewImage", - payload: base64, + payload: { + images: data.images.map((image) => Array.from(image)), + index: data.index, + ext: data.ext, + filename: data.filename, + }, }) }, } diff --git a/apps/mobile/native/ios/SharedWebView/SharedWebView+BridgeData.swift b/apps/mobile/native/ios/SharedWebView/SharedWebView+BridgeData.swift index f3c090b22..456ea78c0 100644 --- a/apps/mobile/native/ios/SharedWebView/SharedWebView+BridgeData.swift +++ b/apps/mobile/native/ios/SharedWebView/SharedWebView+BridgeData.swift @@ -10,16 +10,21 @@ private protocol BasePayload { var type: String { get } } -struct SetContentHeightPayload: Hashable, Codable, BasePayload { +struct SetContentHeightPayload: Codable, BasePayload { var type: String var payload: CGFloat } -struct BridgeDataBasePayload: Hashable, Codable { +struct BridgeDataBasePayload: Codable { var type: String } -struct PreviewImagePayload: Hashable, Codable, BasePayload { - var type: String - var payload: [String] +struct PreviewImagePayloadProps: Codable { + var images: [[UInt8]] + var index: Int = 0 +} + +struct PreviewImagePayload: Codable, BasePayload { + var type: String + var payload: PreviewImagePayloadProps } diff --git a/apps/mobile/native/ios/SharedWebView/WebViewManager.swift b/apps/mobile/native/ios/SharedWebView/WebViewManager.swift index 57fc4ee0b..cac56ac98 100644 --- a/apps/mobile/native/ios/SharedWebView/WebViewManager.swift +++ b/apps/mobile/native/ios/SharedWebView/WebViewManager.swift @@ -219,7 +219,7 @@ private class WebViewDelegate: NSObject, WKNavigationDelegate, WKScriptMessageHa guard let data = data else { return } DispatchQueue.main.async { ImagePreview.quickLookImage( - data.payload.compactMap { Data(base64Encoded: $0) }) + data.payload.images.compactMap { Data($0) }) } default: diff --git a/apps/mobile/web-app/html-renderer/global.d.ts b/apps/mobile/web-app/html-renderer/global.d.ts index 9f0d71064..a35f46442 100644 --- a/apps/mobile/web-app/html-renderer/global.d.ts +++ b/apps/mobile/web-app/html-renderer/global.d.ts @@ -5,7 +5,7 @@ import "../../../../packages/types/global" interface Bridge { measure: () => void setContentHeight: (height: number) => void - previewImage: (base64: string[]) => void + previewImage: (data: { images: Uint8Array[]; index: number }) => void } declare global { diff --git a/apps/mobile/web-app/html-renderer/src/components/image.tsx b/apps/mobile/web-app/html-renderer/src/components/image.tsx index 3e1a1afe0..e63976ce0 100644 --- a/apps/mobile/web-app/html-renderer/src/components/image.tsx +++ b/apps/mobile/web-app/html-renderer/src/components/image.tsx @@ -1,37 +1,44 @@ +import { useRef } from "react" + import type { HTMLProps } from "~/HTML" export const MarkdownImage = (props: HTMLProps<"img">) => { const { src, ...rest } = props + const imageRef = useRef(null) + return ( ) }