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
+103 -62
View File
@@ -2,19 +2,15 @@
// DouyinShare.swift
// ylgamehall
//
// SharePlatform Phase 4.D
// SharePlatform 1:1 daoqi msext DouyinShareManager
// `shareWithContent:completion:` type
// - type == "2" UIPasteboard ""
// - else title + "\n\n" + desc UIPasteboard
//
// msext DouyinShareManager.m:201-269 shareImageToDouyin
// 1. type=2 ImageProvider.captureScreenshot
// ImageProvider.downloadRemotetype=1
// 退msext H5
// 2. PhotoLibrarySaver.save addOnly
// 3. UIPasteboard
// 4. snssdk1128://camera / publish / share/image / scheme
// canOpenURL
// 5. successURL Scheme
// ** SDK**CLAUDE.md ADR-006 /
// + SDK
//
// ** SDK**
// fire-and-forgetsharesuccess FriendsShareHandler
//
import UIKit
@@ -27,68 +23,113 @@ public final class DouyinShare: SharePlatform {
private static let log = Logger(subsystem: "ylgamehall", category: "DouyinShare")
private static let douyinScheme = "snssdk1128://"
public let name = "Douyin"
public var isInstalled: Bool {
guard let url = URL(string: "snssdk1128://") else { return false }
guard let url = URL(string: Self.douyinScheme) else { return false }
return UIApplication.shared.canOpenURL(url)
}
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 }
// 1.
let image: UIImage
if content.type == "2" {
shareScreenshot()
} else {
shareText(content)
}
}
// MARK: -
/// msext DouyinShareManager.m:139-162 isScreenshotShare
/// pasteboard.image ""
private func shareScreenshot() {
do {
switch content.type {
case "2":
image = try ImageProvider.captureScreenshot()
case "1":
// 退msext
Self.log.debug("DouyinShare: type=1 链接,退化为截屏分享")
image = try ImageProvider.captureScreenshot()
default:
// type == URL
image = try await ImageProvider.downloadRemote(content.webpageUrl)
let shot = try ImageProvider.captureScreenshot()
UIPasteboard.general.image = shot
Self.log.debug("DouyinShare 截图已复制到剪贴板")
} catch {
Self.log.error("DouyinShare 截图失败 \(error.localizedDescription, privacy: .public)")
return
}
let message = """
✅ 图片已复制到剪贴板
🎯 分享到好友或群聊步骤:
1️⃣ 打开抖音,点击右下角「消息」
2️⃣ 选择好友或群聊进入聊天
3️⃣ 在输入框长按粘贴图片
4️⃣ 点击发送即可分享
"""
showGuidanceAlert(title: "抖音分享准备完成", message: message)
}
// MARK: -
/// msext DouyinShareManager.m:163-196 else
/// title + "\n\n" + desc pasteboard.string
/// msext ""
private func shareText(_ content: ShareContent) {
var shareText = ""
if !content.title.isEmpty {
shareText = content.title
}
if !content.desc.isEmpty {
shareText = shareText.isEmpty ? content.desc : "\(shareText)\n\n\(content.desc)"
}
if shareText.isEmpty {
shareText = "来自进贤聚友棋牌的精彩内容分享"
}
UIPasteboard.general.string = shareText
Self.log.debug("DouyinShare 文字已复制到剪贴板")
let message = """
✅ 内容已复制到剪贴板
🎯 分享到好友或群聊步骤:
1️⃣ 打开抖音,点击右下角「消息」
2️⃣ 选择好友或群聊进入聊天
3️⃣ 在输入框长按粘贴内容
4️⃣ 点击发送即可分享
"""
showGuidanceAlert(title: "抖音分享准备完成", message: message)
}
// MARK: -
/// " / " Alert msext
/// `showGuidanceAlertWithTitle:message:`
private func showGuidanceAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "立即打开抖音", style: .default) { _ in
if let url = URL(string: Self.douyinScheme),
UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
} catch {
Self.log.error("DouyinShare: 获取图片失败 \(error.localizedDescription, privacy: .public)")
return .failed(reason: "获取图片失败: \(error.localizedDescription)")
})
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
}
// 2.
do {
try await PhotoLibrarySaver.save(image)
} catch PhotoLibrarySaverError.permissionDenied {
Self.log.warning("DouyinShare: 相册写入权限被拒")
return .failed(reason: "相册写入权限被拒")
} catch {
Self.log.error("DouyinShare: 保存相册失败 \(error.localizedDescription, privacy: .public)")
return .failed(reason: "保存相册失败: \(error.localizedDescription)")
}
// 3. msext
UIPasteboard.general.image = image
// 4. msext URL Scheme
let schemes = [
"snssdk1128://camera", //
"snssdk1128://publish", //
"snssdk1128://share/image", //
"snssdk1128://" // scheme
]
for schemeString in schemes {
guard let url = URL(string: schemeString),
UIApplication.shared.canOpenURL(url)
else { continue }
Self.log.debug("DouyinShare open: \(schemeString, privacy: .public)")
let opened = await UIApplication.shared.open(url)
if opened { return .success }
}
return .failed(reason: "所有抖音 URL Scheme 都调起失败")
return top
}
}