4.B 分享逻辑对齐 daoqi msext:调用即触发 sharesuccess

【契约影响】docs/H5-Native-Contract.md §3.1[2]/§3.2[11]

H5 端 sharesuccess 时机改为"调用 friendsShare handler 即立刻发",与
原 msext 行为等价(live path 完全不等 SDK / URL Scheme 回包)。用户
明示有意为之:URL Scheme + 剪贴板路径本就没有可靠回包,乐观策略避免
H5 业务卡住。

3 平台分支严格 1:1 对齐 daoqi msext:

WechatShare(对齐 WechatShareManager shareWithContent)
  - type=="2" → 截图 JPEG 0.6 + sendImageData + app icon thumb
  - else      → 链接分享 sendLinkURL + app icon thumb
  - 移除原"远端图"分支(原项目无此分支)
  - 新增 appIconImage helper(读 Info.plist CFBundleIcons,对齐
    [self getAppIconImage])

QQShare(对齐 QQShareManager shareWithContent + simpleShareToQQFriend)
  - type=="2" → 截图 → UIPasteboard.image → 4 URL Scheme fallback
  - else      → mqqapi://share/to_{fri,qzone}?req_type=1&url=...
                &title=...&description=...(webpageUrl 空时 req_type=0)
  - 不依赖 QQ SDK(CLAUDE.md ADR-006)

DouyinShare(对齐 DouyinShareManager shareWithContent)
  - type=="2" → 截图 → UIPasteboard.image → "立即打开抖音"引导框
  - else      → title+"\n\n"+desc → UIPasteboard.string → 引导框
                双侧空时兜底 "来自进贤聚友棋牌的精彩内容分享"
  - 放弃之前的"存相册 + 自动拉起"激进路径,回到原项目"用户主动点"路径

架构调整:
- SharePlatform.share 改 sync fire-and-forget(去 ShareResult 返回)
- ShareCenter.dispatch / SharePanel.show 去 completion 参数
- FriendsShareHandler:解析后先发 responseCallback("sharefriend")
  与 sharesuccess({success:"2", type:<sharefriend>}),再异步 dispatch
This commit is contained in:
joywayer
2026-06-24 07:34:03 +08:00
parent d471393806
commit 9bd5a81e6c
7 changed files with 259 additions and 219 deletions
+53 -37
View File
@@ -2,21 +2,18 @@
// QQShare.swift
// ylgamehall
//
// QQ SharePlatform Phase 4.C
// QQ SharePlatform daoqi msext QQShareManager
// `shareWithContent:completion:` type
// - type == "2" UIPasteboard + mqqapi://
// - else mqqapi://share/to_fri?req_type=1&url=...
//
// msext QQShareManager.m:716-771 simpleShareToQQFriend URL Scheme
// fallback** QQ SDK**
//
// URL
// mqqapi://share/to_fri?version=1&cflag=0&req_type=1
// &url=<encoded webpageUrl>&title=<encoded title>&description=<encoded desc>
// ** QQ SDK**CLAUDE.md ADR-006 SDK
// URL Scheme + pasteboard UX
//
// encodeString msext line 1262-1268 unreserved RFC 3986
// alphanumeric + "-._~" percent encode
//
// type == "1"type == "2" /
// Phase 4.F UIGraphicsImageRenderer + Photos / +
// mqqapi://share/to_fri?file_type=img
// fire-and-forgetsharesuccess FriendsShareHandler
//
import UIKit
@@ -38,28 +35,57 @@ public final class QQShare: SharePlatform {
public init() {}
public func share(_ content: ShareContent, scene: ShareScene) async -> ShareResult {
guard isInstalled else { return .notInstalled(platform: name) }
public func share(_ content: ShareContent, scene: ShareScene) {
guard isInstalled else { return }
// URL Scheme Phase 4.F
guard content.type == "1" else {
Self.log.warning("QQShare: type=\(content.type, privacy: .public) (非链接) 暂未实现,返回 success 避免 H5 业务卡住")
return .success
if content.type == "2" {
shareScreenshot()
} else {
shareLink(content, scene: scene)
}
}
// MARK: -
/// msext QQShareManager.m:1876-1906 isScreenshotShare
/// QQ SDK shareToQQFriend QQShareTypeImage UIImage SDK
/// SDK 退 pasteboard QQ
private func shareScreenshot() {
do {
let shot = try ImageProvider.captureScreenshot()
UIPasteboard.general.image = shot
} catch {
Self.log.error("QQShare 截图失败 \(error.localizedDescription, privacy: .public)")
}
// URLmqqapi://share/to_fri? mqqapi://share/to_qzone?
// scene == .timeline QQ to_qzone.friend to_friQQ
let host = scene == .timeline ? "share/to_qzone" : "share/to_fri"
var parts: [String] = [
"version=1",
"cflag=0",
"req_type=1"
let candidates = [
"mqqapi://share/to_fri?file_type=news&version=1&cflag=0",
"mqq://im/chat?chat_type=select",
"mqqapi://",
"mqq://"
]
for s in candidates {
guard let url = URL(string: s),
UIApplication.shared.canOpenURL(url) else { continue }
UIApplication.shared.open(url)
return
}
}
// 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 {
// URL req_type=0
parts = ["version=1", "cflag=0", "req_type=0"]
parts.append("req_type=0")
}
if !content.title.isEmpty {
parts.append("title=\(Self.encode(content.title))")
@@ -69,21 +95,11 @@ public final class QQShare: SharePlatform {
}
let urlString = "mqqapi://\(host)?\(parts.joined(separator: "&"))"
Self.log.debug("QQShare open: \(urlString, privacy: .public)")
guard let qqURL = URL(string: urlString),
UIApplication.shared.canOpenURL(qqURL)
else {
return .failed(reason: "invalid QQ URL")
}
// URL Scheme msext success
let opened = await UIApplication.shared.open(qqURL)
return opened ? .success : .failed(reason: "openURL failed")
UIApplication.shared.canOpenURL(qqURL) else { return }
UIApplication.shared.open(qqURL)
}
/// msext QQShareManager.m:1262-1268 RFC 3986 unreserved
/// alphanumeric + "-._~" percent encode
nonisolated static func encode(_ string: String) -> String {
var allowed = CharacterSet.alphanumerics
allowed.insert(charactersIn: "-._~")