【契约影响】docs/H5-Native-Contract.md §3.1[2]type==2 分支 WeChat 截图分享之前丢了 H5 description(msext 通过 sendImageData:MessageExt: 写到 WXMediaMessage.description,接收方在 微信聊天预览里能看到)。链接分享之前完全没传 thumb(msext 用 app icon)。统一补齐: - WeChatManager.ShareImage 加 messageExt / mediaTagName 字段; shareImage 内 message.description = messageExt + message.messageExt = messageExt + setThumbImage(thumb) + mediaTagName - WeChatManager.ShareLink 把 thumbnailURL: String? 替换为 thumb: UIImage?;shareLink 内 setThumbImage(thumb) + mediaTagName。drop Sendable 因 UIImage 不可 Sendable(调用都在 MainActor 上无风险) - WechatShare 双分支统一传 appIconImage() 做 thumb;截图分支 messageExt = content.desc + mediaTagName "进贤聚友棋牌游戏截图分享";链接分支 mediaTagName "进贤聚友棋牌游戏分享"(字面对齐 msext WechatShareManager.m) QQ 截图分享原本 4-fallback URL 链不靠谱(file_type=news 给截图场景实际 无意义)。简化为 Douyin 同款模式:截图 → UIPasteboard.image → 弹"立即 打开 QQ / 稍后分享"二选一 Alert。承认 ADR-006 无 QQ SDK 退化的现实, 但 UX 不再让用户摸不着北。
141 lines
5.6 KiB
Swift
141 lines
5.6 KiB
Swift
//
|
||
// QQShare.swift
|
||
// ylgamehall
|
||
//
|
||
// QQ 分享 SharePlatform 实现,对齐 daoqi msext QQShareManager
|
||
// `shareWithContent:completion:`(按 type 分两个分支):
|
||
// - type == "2" → 截图分享:复制截图到 UIPasteboard + 弹引导框("立即打开 QQ")
|
||
// - else → 链接分享:mqqapi://share/to_fri?req_type=1&url=...
|
||
//
|
||
// **不依赖 QQ SDK**(CLAUDE.md ADR-006):原项目走 SDK 弹原生会话列表带
|
||
// 图片预览;无 SDK 路径下退化为"剪贴板 + 引导框",与 Douyin 同款 UX 模式。
|
||
//
|
||
// encodeString 与 msext line 1262-1268 等价:unreserved 字符集(RFC 3986
|
||
// alphanumeric + "-._~"),其它全部 percent encode。
|
||
//
|
||
// fire-and-forget:sharesuccess 已在 FriendsShareHandler 立即触发。
|
||
//
|
||
|
||
import UIKit
|
||
import os.log
|
||
|
||
@MainActor
|
||
public final class QQShare: SharePlatform {
|
||
|
||
public static let shared = QQShare()
|
||
|
||
private static let log = Logger(subsystem: "ylgamehall", category: "QQShare")
|
||
|
||
public let name = "QQ"
|
||
|
||
public var isInstalled: Bool {
|
||
guard let url = URL(string: "mqqapi://") else { return false }
|
||
return UIApplication.shared.canOpenURL(url)
|
||
}
|
||
|
||
public init() {}
|
||
|
||
public func share(_ content: ShareContent, scene: ShareScene) {
|
||
guard isInstalled else { return }
|
||
|
||
if content.type == "2" {
|
||
shareScreenshot()
|
||
} else {
|
||
shareLink(content, scene: scene)
|
||
}
|
||
}
|
||
|
||
// MARK: - 截图分享
|
||
|
||
/// 对齐 msext QQShareManager.m:1876-1906 isScreenshotShare 分支:
|
||
/// 原项目用 QQ SDK shareToQQFriend QQShareTypeImage 直接传 UIImage 给 SDK;
|
||
/// 无 SDK 路径下退化为:截图 → UIPasteboard.image → 弹引导框("立即打开 QQ")。
|
||
/// UX 模式与 DouyinShare 截图分享对齐(都是非 SDK 平台的剪贴板兜底)。
|
||
private func shareScreenshot() {
|
||
do {
|
||
let shot = try ImageProvider.captureScreenshot()
|
||
UIPasteboard.general.image = shot
|
||
Self.log.debug("QQShare 截图已复制到剪贴板")
|
||
} catch {
|
||
Self.log.error("QQShare 截图失败 \(error.localizedDescription, privacy: .public)")
|
||
return
|
||
}
|
||
|
||
let message = """
|
||
✅ 图片已复制到剪贴板
|
||
|
||
🎯 分享到好友或群聊步骤:
|
||
1️⃣ 打开 QQ,选择好友或群聊进入聊天
|
||
2️⃣ 在输入框长按粘贴图片
|
||
3️⃣ 点击发送即可分享
|
||
"""
|
||
showGuidanceAlert(title: "QQ 分享准备完成", message: message)
|
||
}
|
||
|
||
/// 弹"立即打开 QQ / 稍后分享"二选一 Alert,与 DouyinShare 同款 UX。
|
||
private func showGuidanceAlert(title: String, message: String) {
|
||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||
alert.addAction(UIAlertAction(title: "立即打开 QQ", style: .default) { _ in
|
||
if let url = URL(string: "mqqapi://"),
|
||
UIApplication.shared.canOpenURL(url) {
|
||
UIApplication.shared.open(url)
|
||
} else if let url = URL(string: "mqq://"),
|
||
UIApplication.shared.canOpenURL(url) {
|
||
UIApplication.shared.open(url)
|
||
}
|
||
})
|
||
alert.addAction(UIAlertAction(title: "稍后分享", style: .cancel))
|
||
|
||
guard let topVC = Self.topViewController() else { return }
|
||
topVC.present(alert, animated: true)
|
||
}
|
||
|
||
private static func topViewController() -> UIViewController? {
|
||
let scene = UIApplication.shared.connectedScenes
|
||
.compactMap { $0 as? UIWindowScene }
|
||
.first(where: { $0.activationState == .foregroundActive })
|
||
?? UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first
|
||
guard var top = scene?.windows.first(where: \.isKeyWindow)?.rootViewController
|
||
?? scene?.windows.first?.rootViewController else { return nil }
|
||
while let presented = top.presentedViewController { top = presented }
|
||
if let nav = top as? UINavigationController, let visible = nav.visibleViewController {
|
||
return visible
|
||
}
|
||
return top
|
||
}
|
||
|
||
// MARK: - 链接分享
|
||
|
||
/// 对齐 msext QQShareManager.m simpleShareToQQFriend:
|
||
/// mqqapi://share/to_fri?version=1&cflag=0&req_type=1
|
||
/// &url=<encoded webpageUrl>&title=<encoded title>&description=<encoded desc>
|
||
/// scene == .timeline 走 QQ 空间 (to_qzone);.friend 走 QQ 好友 (to_fri)
|
||
private func shareLink(_ content: ShareContent, scene: ShareScene) {
|
||
let host = scene == .timeline ? "share/to_qzone" : "share/to_fri"
|
||
var parts: [String] = ["version=1", "cflag=0"]
|
||
if !content.webpageUrl.isEmpty {
|
||
parts.append("req_type=1")
|
||
parts.append("url=\(Self.encode(content.webpageUrl))")
|
||
} else {
|
||
parts.append("req_type=0")
|
||
}
|
||
if !content.title.isEmpty {
|
||
parts.append("title=\(Self.encode(content.title))")
|
||
}
|
||
if !content.desc.isEmpty {
|
||
parts.append("description=\(Self.encode(content.desc))")
|
||
}
|
||
let urlString = "mqqapi://\(host)?\(parts.joined(separator: "&"))"
|
||
|
||
guard let qqURL = URL(string: urlString),
|
||
UIApplication.shared.canOpenURL(qqURL) else { return }
|
||
UIApplication.shared.open(qqURL)
|
||
}
|
||
|
||
nonisolated static func encode(_ string: String) -> String {
|
||
var allowed = CharacterSet.alphanumerics
|
||
allowed.insert(charactersIn: "-._~")
|
||
return string.addingPercentEncoding(withAllowedCharacters: allowed) ?? ""
|
||
}
|
||
}
|