feat: update app workflows and permissions
This commit is contained in:
@ -33,7 +33,7 @@ enum WeChatShareScene: Int, CaseIterable, Sendable {
|
||||
}
|
||||
|
||||
/// 微信分享请求结果。
|
||||
enum WeChatShareResult: Equatable, Sendable {
|
||||
enum WeChatShareResult: Equatable, Error, Sendable {
|
||||
case success
|
||||
case cancelled
|
||||
case notInstalled
|
||||
@ -55,6 +55,25 @@ enum WeChatShareResult: Equatable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
/// 微信小程序分享版本。
|
||||
enum WeChatMiniProgramType: Int, Equatable, Sendable {
|
||||
/// 正式版。
|
||||
case release = 0
|
||||
/// 开发版。
|
||||
case test = 1
|
||||
/// 体验版。
|
||||
case preview = 2
|
||||
|
||||
/// 当前编译配置下实际传给微信 SDK 的小程序版本。
|
||||
var resolvedForCurrentBuild: WeChatMiniProgramType {
|
||||
#if DEBUG
|
||||
return .test
|
||||
#else
|
||||
return self
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// 网页分享载荷,供邀请等业务模块调用微信分享。
|
||||
struct WeChatWebPageSharePayload: Equatable, Sendable {
|
||||
let title: String
|
||||
@ -71,3 +90,36 @@ struct WeChatTextSharePayload: Equatable, Sendable {
|
||||
struct WeChatImageSharePayload: Equatable, Sendable {
|
||||
let imageData: Data
|
||||
}
|
||||
|
||||
/// 微信小程序分享载荷。
|
||||
struct WeChatMiniProgramSharePayload: Equatable, Sendable {
|
||||
let title: String
|
||||
let description: String
|
||||
let webpageURL: String
|
||||
let userName: String
|
||||
let path: String?
|
||||
let miniProgramType: WeChatMiniProgramType
|
||||
let withShareTicket: Bool
|
||||
let disableForward: Bool
|
||||
|
||||
/// 初始化小程序分享载荷。
|
||||
init(
|
||||
title: String,
|
||||
description: String,
|
||||
webpageURL: String,
|
||||
userName: String,
|
||||
path: String? = nil,
|
||||
miniProgramType: WeChatMiniProgramType = .release,
|
||||
withShareTicket: Bool = false,
|
||||
disableForward: Bool = false
|
||||
) {
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.webpageURL = webpageURL
|
||||
self.userName = userName
|
||||
self.path = path
|
||||
self.miniProgramType = miniProgramType
|
||||
self.withShareTicket = withShareTicket
|
||||
self.disableForward = disableForward
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import UIKit
|
||||
import WechatOpenSDK
|
||||
|
||||
/// 微信分享服务,封装文本、网页和图片分享能力,不包含微信支付。
|
||||
/// 微信分享服务,封装文本、网页、图片和小程序分享能力,不包含微信支付。
|
||||
enum WeChatShareService {
|
||||
/// 当前设备是否已安装微信且支持 Open SDK。
|
||||
static var isWeChatAvailable: Bool {
|
||||
@ -102,6 +102,73 @@ enum WeChatShareService {
|
||||
WeChatManager.shared.sendShareRequest(request, completion: completion)
|
||||
}
|
||||
|
||||
/// 分享小程序到微信好友会话。
|
||||
@MainActor
|
||||
static func shareMiniProgram(
|
||||
_ payload: WeChatMiniProgramSharePayload,
|
||||
previewImage: UIImage? = nil,
|
||||
completion: ((WeChatShareResult) -> Void)? = nil
|
||||
) {
|
||||
switch makeMiniProgramRequest(payload, scene: .session, previewImage: previewImage ?? defaultThumbImage()) {
|
||||
case .success(let request):
|
||||
WeChatManager.shared.sendShareRequest(request, completion: completion)
|
||||
case .failure(let result):
|
||||
completion?(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建小程序分享请求,供业务分享入口和单元测试复用。
|
||||
static func makeMiniProgramRequest(
|
||||
_ payload: WeChatMiniProgramSharePayload,
|
||||
scene: WeChatShareScene,
|
||||
previewImage: UIImage? = nil
|
||||
) -> Result<SendMessageToWXReq, WeChatShareResult> {
|
||||
guard scene == .session else {
|
||||
return .failure(.invalidPayload("小程序仅支持分享到微信好友"))
|
||||
}
|
||||
|
||||
let title = payload.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let webpageURL = payload.webpageURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let userName = payload.userName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let path = payload.path?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
guard !title.isEmpty else {
|
||||
return .failure(.invalidPayload("分享标题不能为空"))
|
||||
}
|
||||
guard !webpageURL.isEmpty else {
|
||||
return .failure(.invalidPayload("小程序兼容网页链接不能为空"))
|
||||
}
|
||||
guard webpageURL.lengthOfBytes(using: .utf8) <= 1024 else {
|
||||
return .failure(.invalidPayload("小程序兼容网页链接不能超过1024字节"))
|
||||
}
|
||||
guard !userName.isEmpty else {
|
||||
return .failure(.invalidPayload("小程序原始 ID 不能为空"))
|
||||
}
|
||||
|
||||
let miniProgramObject = WXMiniProgramObject()
|
||||
miniProgramObject.webpageUrl = webpageURL
|
||||
miniProgramObject.userName = userName
|
||||
miniProgramObject.path = path?.isEmpty == false ? path : nil
|
||||
miniProgramObject.miniProgramType = WXMiniProgramType(rawValue: UInt(payload.miniProgramType.resolvedForCurrentBuild.rawValue))
|
||||
?? WXMiniProgramType(rawValue: UInt(WeChatMiniProgramType.release.rawValue))!
|
||||
miniProgramObject.withShareTicket = payload.withShareTicket
|
||||
miniProgramObject.disableForward = payload.disableForward
|
||||
if let previewImage {
|
||||
miniProgramObject.hdImageData = WeChatThumbImageProcessor.makeMiniProgramHDImageData(from: previewImage)
|
||||
}
|
||||
|
||||
let message = WXMediaMessage()
|
||||
message.title = title
|
||||
message.description = payload.description
|
||||
message.mediaObject = miniProgramObject
|
||||
|
||||
let request = SendMessageToWXReq()
|
||||
request.bText = false
|
||||
request.message = message
|
||||
request.scene = scene.wxSceneValue
|
||||
return .success(request)
|
||||
}
|
||||
|
||||
/// 读取 App 图标,用作微信分享缩略图兜底。
|
||||
static func defaultThumbImage() -> UIImage? {
|
||||
guard let icons = Bundle.main.infoDictionary?["CFBundleIcons"] as? [String: Any],
|
||||
|
||||
@ -9,6 +9,8 @@ import UIKit
|
||||
enum WeChatThumbImageProcessor {
|
||||
/// 微信缩略图最大字节数。
|
||||
static let maxThumbBytes = 32 * 1024
|
||||
/// 微信小程序预览图最大字节数。
|
||||
static let maxMiniProgramHDImageBytes = 128 * 1024
|
||||
|
||||
/// 将图片压缩为符合微信要求的缩略图数据。
|
||||
static func makeThumbData(from image: UIImage, maxBytes: Int = maxThumbBytes) -> Data? {
|
||||
@ -27,6 +29,26 @@ enum WeChatThumbImageProcessor {
|
||||
return result
|
||||
}
|
||||
|
||||
/// 将图片压缩为符合微信小程序分享要求的高清预览图数据。
|
||||
static func makeMiniProgramHDImageData(
|
||||
from image: UIImage,
|
||||
maxBytes: Int = maxMiniProgramHDImageBytes
|
||||
) -> Data? {
|
||||
guard maxBytes > 0 else { return nil }
|
||||
|
||||
let resized = resize(image, maxPixel: 500)
|
||||
var quality: CGFloat = 0.86
|
||||
var data = resized.jpegData(compressionQuality: quality)
|
||||
|
||||
while let current = data, current.count > maxBytes, quality > 0.12 {
|
||||
quality -= 0.08
|
||||
data = resized.jpegData(compressionQuality: quality)
|
||||
}
|
||||
|
||||
guard let result = data, result.count <= maxBytes else { return nil }
|
||||
return result
|
||||
}
|
||||
|
||||
/// 等比缩放图片,使最长边不超过指定像素。
|
||||
static func resize(_ image: UIImage, maxPixel: CGFloat) -> UIImage {
|
||||
let sourceSize = image.size
|
||||
|
||||
Reference in New Issue
Block a user