chore: sync local changes
这个提交包含在:
父节点
bc77850c3e
当前提交
5df6d70065
@ -6,6 +6,7 @@ let package = Package(
|
||||
platforms: [.iOS(.v16), .macOS(.v13)],
|
||||
products: [
|
||||
.library(name: "XuqmSDK", targets: ["XuqmSDK"]),
|
||||
.library(name: "XuqmWebViewSDK", targets: ["XuqmWebViewSDK"]),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
@ -13,6 +14,12 @@ let package = Package(
|
||||
path: "Sources/XuqmSDK",
|
||||
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
|
||||
),
|
||||
.target(
|
||||
name: "XuqmWebViewSDK",
|
||||
dependencies: ["XuqmSDK"],
|
||||
path: "Sources/XuqmWebViewSDK",
|
||||
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
|
||||
),
|
||||
.testTarget(
|
||||
name: "XuqmSDKTests",
|
||||
dependencies: ["XuqmSDK"],
|
||||
|
||||
39
README.md
39
README.md
@ -8,8 +8,9 @@
|
||||
XuqmGroup-iOSSDK/
|
||||
├── Package.swift # SPM 包定义
|
||||
├── XuqmSDK.podspec # CocoaPods 发版描述
|
||||
├── XuqmWebViewSDK.podspec # WebView 独立模块发版描述
|
||||
├── PUBLISH.md # 发版操作手册
|
||||
└── Sources/XuqmSDK/
|
||||
├── Sources/XuqmSDK/
|
||||
├── Core/
|
||||
│ ├── XuqmSDK.swift # 入口:init / setToken
|
||||
│ ├── SDKConfig.swift # 配置结构体
|
||||
@ -21,6 +22,11 @@ XuqmGroup-iOSSDK/
|
||||
│ └── PushSDK.swift # APNs Token 注册
|
||||
└── Update/
|
||||
└── UpdateSDK.swift # 版本检查 / App 更新
|
||||
└── Sources/XuqmWebViewSDK/
|
||||
├── XWebViewBridge.swift # 页面配置 / 控制器桥接
|
||||
├── XWebViewPage.swift # 独立页面
|
||||
├── XWebViewTypes.swift # 配置 / 控制器协议
|
||||
└── XWebViewView.swift # 嵌入式组件
|
||||
```
|
||||
|
||||
## 集成
|
||||
@ -39,7 +45,7 @@ dependencies: [
|
||||
.package(url: "https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git", from: "0.1.0")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "YourApp", dependencies: ["XuqmSDK"])
|
||||
.target(name: "YourApp", dependencies: ["XuqmSDK", "XuqmWebViewSDK"])
|
||||
]
|
||||
```
|
||||
|
||||
@ -51,6 +57,7 @@ source 'https://xuqinmin.com/xuqinmin12/xuqm-specs.git'
|
||||
source 'https://cdn.cocoapods.org/'
|
||||
|
||||
pod 'XuqmSDK', '~> 0.1.0'
|
||||
pod 'XuqmWebViewSDK', '~> 0.1.0'
|
||||
```
|
||||
|
||||
---
|
||||
@ -81,6 +88,32 @@ struct MyApp: App {
|
||||
await XuqmSDK.setToken(token)
|
||||
```
|
||||
|
||||
### 3. WebView 独立模块
|
||||
|
||||
`XuqmWebViewSDK` 不需要单独初始化,直接依赖即可使用。
|
||||
|
||||
```swift
|
||||
import XuqmWebViewSDK
|
||||
|
||||
// 嵌入式组件:直接放到任意 SwiftUI 页面中
|
||||
XWebViewView(
|
||||
config: XWebViewConfig(
|
||||
url: "https://example.com",
|
||||
title: "嵌入式网页"
|
||||
)
|
||||
)
|
||||
|
||||
// 独立页面:在导航栈中直接 push
|
||||
NavigationLink("打开页面模式") {
|
||||
XWebViewPage(
|
||||
config: XWebViewConfig(
|
||||
url: "https://example.com",
|
||||
title: "独立页面"
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core
|
||||
@ -227,6 +260,8 @@ if result.needsUpdate, let info = result.info {
|
||||
|
||||
`UpdateSDK` 这里只负责 iOS App 版本更新。RN 热更新如果需要,走独立的 RN SDK / RN 更新模块,不并入统一发版能力。
|
||||
|
||||
`XuqmWebViewSDK` 提供独立的 WebView 组件和页面,可与 `XuqmSDK` 同时使用,也可单独依赖。
|
||||
|
||||
---
|
||||
|
||||
## 发版
|
||||
|
||||
@ -31,7 +31,6 @@ public extension SDKConfig {
|
||||
}
|
||||
|
||||
extension SDKConfig {
|
||||
var appId: String { appKey }
|
||||
}
|
||||
|
||||
enum SDKEndpoints {
|
||||
|
||||
@ -11,13 +11,18 @@ public final class XuqmSDK: NSObject {
|
||||
|
||||
private var userSig: String?
|
||||
private var cachedDeviceToken: String?
|
||||
private var lastInitializedAppKey: String?
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public func initialize(config: SDKConfig) {
|
||||
if let lastInitializedAppKey, lastInitializedAppKey == config.appKey {
|
||||
return
|
||||
}
|
||||
self.config = config
|
||||
self.lastInitializedAppKey = config.appKey
|
||||
self.tokenStore = TokenStore()
|
||||
ApiClient.shared.configure(with: config)
|
||||
if config.autoRegisterPush {
|
||||
@ -35,6 +40,9 @@ public final class XuqmSDK: NSObject {
|
||||
}
|
||||
|
||||
public func login(userId: String, userSig: String) async {
|
||||
if currentUserId == userId && self.userSig == userSig {
|
||||
return
|
||||
}
|
||||
self.currentUserId = userId
|
||||
self.userSig = userSig
|
||||
|
||||
|
||||
@ -13,16 +13,16 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S
|
||||
private var groupSubscriptions = Set<String>()
|
||||
|
||||
private let tokenOverride: String?
|
||||
private let appIdOverride: String?
|
||||
private let appKeyOverride: String?
|
||||
|
||||
private var activeWsURL: URL?
|
||||
private var activeToken: String?
|
||||
private var activeAppId: String?
|
||||
private var activeAppKey: String?
|
||||
private var activeUserId: String?
|
||||
|
||||
public init(token: String? = nil, appId: String? = nil) {
|
||||
public init(token: String? = nil, appKey: String? = nil) {
|
||||
self.tokenOverride = token
|
||||
self.appIdOverride = appId
|
||||
self.appKeyOverride = appKey
|
||||
super.init()
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S
|
||||
|
||||
activeWsURL = SDKEndpoints.imWebSocketURL
|
||||
activeToken = tokenOverride
|
||||
activeAppId = appIdOverride
|
||||
activeAppKey = appKeyOverride
|
||||
|
||||
guard let activeWsURL, let activeToken else {
|
||||
delegate?.imClientDidError("IM config or token not found")
|
||||
@ -73,7 +73,7 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S
|
||||
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
let message = ImMessage(
|
||||
id: messageId,
|
||||
appId: activeAppId ?? "",
|
||||
appKey: activeAppKey ?? "",
|
||||
fromUserId: activeUserId ?? "",
|
||||
fromId: activeUserId,
|
||||
toId: toId,
|
||||
@ -87,8 +87,8 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S
|
||||
createdAt: now,
|
||||
editedAt: nil
|
||||
)
|
||||
guard let activeAppId else {
|
||||
delegate?.imClientDidError("IM appId not configured")
|
||||
guard let activeAppKey else {
|
||||
delegate?.imClientDidError("IM appKey not configured")
|
||||
return message.failedCopy()
|
||||
}
|
||||
let sent = sendFrame(
|
||||
@ -98,7 +98,7 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S
|
||||
"content-type": "application/json",
|
||||
],
|
||||
body: encodeJSONString([
|
||||
"appId": activeAppId,
|
||||
"appKey": activeAppKey,
|
||||
"messageId": messageId,
|
||||
"toId": toId,
|
||||
"chatType": chatType.rawValue,
|
||||
@ -111,8 +111,8 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S
|
||||
}
|
||||
|
||||
public func revoke(messageId: String) {
|
||||
guard let activeAppId else {
|
||||
delegate?.imClientDidError("IM appId not configured")
|
||||
guard let activeAppKey else {
|
||||
delegate?.imClientDidError("IM appKey not configured")
|
||||
return
|
||||
}
|
||||
sendFrame(
|
||||
@ -122,15 +122,15 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S
|
||||
"content-type": "application/json",
|
||||
],
|
||||
body: encodeJSONString([
|
||||
"appId": activeAppId,
|
||||
"appKey": activeAppKey,
|
||||
"messageId": messageId,
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
public func sync() {
|
||||
guard let activeAppId else {
|
||||
delegate?.imClientDidError("IM appId not configured")
|
||||
guard let activeAppKey else {
|
||||
delegate?.imClientDidError("IM appKey not configured")
|
||||
return
|
||||
}
|
||||
sendFrame(
|
||||
@ -139,7 +139,7 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S
|
||||
"destination": "/app/chat.sync",
|
||||
"content-type": "application/json",
|
||||
],
|
||||
body: encodeJSONString(["appId": activeAppId]),
|
||||
body: encodeJSONString(["appKey": activeAppKey]),
|
||||
)
|
||||
}
|
||||
|
||||
@ -321,7 +321,7 @@ private extension ImMessage {
|
||||
func failedCopy() -> ImMessage {
|
||||
ImMessage(
|
||||
id: id,
|
||||
appId: appId,
|
||||
appKey: appKey,
|
||||
fromUserId: fromUserId,
|
||||
fromId: fromId,
|
||||
toId: toId,
|
||||
|
||||
@ -12,6 +12,7 @@ public final class ImSDK {
|
||||
private weak var delegate: ImEventDelegate?
|
||||
private weak var conversationDelegate: ConversationDelegate?
|
||||
private var currentUserId: String?
|
||||
private var currentUserSig: String?
|
||||
private var _conversations: [ConversationData] = []
|
||||
|
||||
public private(set) var connectionState: ImConnectionState = .disconnected
|
||||
@ -31,11 +32,15 @@ public final class ImSDK {
|
||||
}
|
||||
|
||||
public func login(_ userId: String, _ userSig: String) async throws {
|
||||
if currentUserId == userId && currentUserSig == userSig {
|
||||
return
|
||||
}
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
currentUserId = userId
|
||||
currentUserSig = userSig
|
||||
XuqmSDK.shared.tokenStore?.save(userSig)
|
||||
client?.disconnect()
|
||||
client = ImClient(token: userSig, appId: config.appId)
|
||||
client = ImClient(token: userSig, appKey: config.appKey)
|
||||
client?.setCurrentUserId(userId)
|
||||
client?.delegate = self
|
||||
updateConnectionState(.connecting)
|
||||
@ -328,7 +333,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/messages/\(messageId)/revoke",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -337,7 +342,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/messages/\(messageId)",
|
||||
method: "PUT",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: EditMessageRequest(content: content)
|
||||
)
|
||||
}
|
||||
@ -359,7 +364,7 @@ public final class ImSDK {
|
||||
) -> ImMessage {
|
||||
ImMessage(
|
||||
id: UUID().uuidString,
|
||||
appId: XuqmSDK.shared.requireConfig().appId,
|
||||
appKey: XuqmSDK.shared.requireConfig().appKey,
|
||||
fromUserId: currentUserId ?? "",
|
||||
fromId: currentUserId,
|
||||
toId: toId,
|
||||
@ -471,14 +476,14 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
let groups: [ImGroup] = try await ApiClient.shared.request(
|
||||
path: "/api/im/groups",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
return groups
|
||||
}
|
||||
|
||||
public func listPublicGroups(keyword: String? = nil) async throws -> [ImGroup] {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
var items = [URLQueryItem(name: "appId", value: config.appId)]
|
||||
var items = [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
if let keyword { items.append(URLQueryItem(name: "keyword", value: keyword)) }
|
||||
let groups: [ImGroup] = try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/public",
|
||||
@ -492,7 +497,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/admin/users/search",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "keyword", value: keyword),
|
||||
URLQueryItem(name: "size", value: String(size)),
|
||||
]
|
||||
@ -504,7 +509,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/admin/groups/search",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "keyword", value: keyword),
|
||||
URLQueryItem(name: "size", value: String(size)),
|
||||
]
|
||||
@ -522,7 +527,7 @@ public final class ImSDK {
|
||||
) async throws -> PageResult<ImMessage> {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
var items: [URLQueryItem] = [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "page", value: String(page)),
|
||||
URLQueryItem(name: "size", value: String(size)),
|
||||
]
|
||||
@ -542,7 +547,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/groups",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: ImCreateGroupRequest(name: name, memberIds: memberIds, groupType: groupType)
|
||||
)
|
||||
}
|
||||
@ -555,7 +560,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/members",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -564,7 +569,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/members/search",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "keyword", value: keyword),
|
||||
URLQueryItem(name: "size", value: String(size))
|
||||
]
|
||||
@ -650,7 +655,7 @@ public final class ImSDK {
|
||||
|
||||
public func sendGroupJoinRequest(groupId: String, remark: String? = nil) async throws -> GroupJoinRequest {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
var items = [URLQueryItem(name: "appId", value: config.appId)]
|
||||
var items = [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
if let remark { items.append(URLQueryItem(name: "remark", value: remark)) }
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/join-requests",
|
||||
@ -663,7 +668,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/join-requests",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -672,7 +677,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/join-requests/\(requestId)/accept",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -681,7 +686,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/join-requests/\(requestId)/reject",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -689,7 +694,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/friends",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -699,7 +704,7 @@ public final class ImSDK {
|
||||
path: "/api/im/friends",
|
||||
method: "POST",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "friendId", value: friendId),
|
||||
]
|
||||
)
|
||||
@ -710,7 +715,7 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/friends",
|
||||
method: "DELETE",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -719,13 +724,13 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/friends/\(friendId)",
|
||||
method: "DELETE",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
public func setFriendGroup(friendId: String, groupName: String? = nil) async throws {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
var items = [URLQueryItem(name: "appId", value: config.appId)]
|
||||
var items = [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
if let groupName {
|
||||
items.append(URLQueryItem(name: "groupName", value: groupName))
|
||||
}
|
||||
@ -740,7 +745,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/friends/groups",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -748,7 +753,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/friends/groups/\(groupName)",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -757,7 +762,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/friend-requests",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "direction", value: direction),
|
||||
]
|
||||
)
|
||||
@ -766,7 +771,7 @@ public final class ImSDK {
|
||||
public func sendFriendRequest(toUserId: String, remark: String? = nil) async throws -> FriendRequest {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
var items = [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "toUserId", value: toUserId),
|
||||
]
|
||||
if let remark { items.append(URLQueryItem(name: "remark", value: remark)) }
|
||||
@ -782,7 +787,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/friend-requests/\(requestId)/accept",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -791,7 +796,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/friend-requests/\(requestId)/reject",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -799,7 +804,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/blacklist",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -809,7 +814,7 @@ public final class ImSDK {
|
||||
path: "/api/im/blacklist",
|
||||
method: "POST",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "blockedUserId", value: blockedUserId),
|
||||
]
|
||||
)
|
||||
@ -821,7 +826,7 @@ public final class ImSDK {
|
||||
path: "/api/im/blacklist",
|
||||
method: "DELETE",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "blockedUserId", value: blockedUserId),
|
||||
]
|
||||
)
|
||||
@ -832,7 +837,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/blacklist/check",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "targetUserId", value: targetUserId),
|
||||
]
|
||||
)
|
||||
@ -842,7 +847,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/accounts/\(userId)",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -853,7 +858,7 @@ public final class ImSDK {
|
||||
gender: String? = nil
|
||||
) async throws -> UserProfile {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
var items = [URLQueryItem(name: "appId", value: config.appId)]
|
||||
var items = [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
if let nickname { items.append(URLQueryItem(name: "nickname", value: nickname)) }
|
||||
if let avatar { items.append(URLQueryItem(name: "avatar", value: avatar)) }
|
||||
if let gender { items.append(URLQueryItem(name: "gender", value: gender)) }
|
||||
@ -868,7 +873,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
let result: [ConversationData] = try await ApiClient.shared.request(
|
||||
path: "/api/im/conversations",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
_conversations = result
|
||||
conversationDelegate?.conversationsDidChange(_conversations)
|
||||
@ -881,7 +886,7 @@ public final class ImSDK {
|
||||
path: "/api/im/conversations/\(targetId)/read",
|
||||
method: "PUT",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
||||
]
|
||||
)
|
||||
@ -893,7 +898,7 @@ public final class ImSDK {
|
||||
path: "/api/im/conversations/\(targetId)/pinned",
|
||||
method: "PUT",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
||||
URLQueryItem(name: "pinned", value: String(pinned)),
|
||||
]
|
||||
@ -906,7 +911,7 @@ public final class ImSDK {
|
||||
path: "/api/im/conversations/\(targetId)/muted",
|
||||
method: "PUT",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
||||
URLQueryItem(name: "muted", value: String(muted)),
|
||||
]
|
||||
@ -919,7 +924,7 @@ public final class ImSDK {
|
||||
path: "/api/im/conversations/\(targetId)/hidden",
|
||||
method: "PUT",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
||||
URLQueryItem(name: "hidden", value: String(hidden)),
|
||||
]
|
||||
@ -929,7 +934,7 @@ public final class ImSDK {
|
||||
public func setConversationGroup(targetId: String, chatType: ChatType, groupName: String? = nil) async throws {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
var items = [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
||||
]
|
||||
if let groupName {
|
||||
@ -946,7 +951,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/conversation-groups",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -954,7 +959,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/conversation-groups/\(groupName)",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -964,7 +969,7 @@ public final class ImSDK {
|
||||
path: "/api/im/conversations/\(targetId)/draft",
|
||||
method: "PUT",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
||||
URLQueryItem(name: "draft", value: draft),
|
||||
]
|
||||
@ -977,7 +982,7 @@ public final class ImSDK {
|
||||
path: "/api/im/conversations/\(targetId)",
|
||||
method: "DELETE",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
||||
]
|
||||
)
|
||||
@ -992,7 +997,7 @@ public final class ImSDK {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
let result: [String: Int] = try await ApiClient.shared.request(
|
||||
path: "/api/im/messages/offline/count",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
return result["count"] ?? 0
|
||||
}
|
||||
@ -1002,7 +1007,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/messages/offline",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)]
|
||||
)
|
||||
}
|
||||
|
||||
@ -1011,7 +1016,7 @@ public final class ImSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/admin/groups/\(groupId)/read-receipts",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: ImGroupReadReceiptRequest(messageIds: messageIds)
|
||||
)
|
||||
}
|
||||
@ -1021,7 +1026,7 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/friends/batch",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: BatchFriendIdsRequest(friendIds: friendIds)
|
||||
)
|
||||
}
|
||||
@ -1031,7 +1036,7 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/friends/batch/remove",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: BatchFriendIdsRequest(friendIds: friendIds)
|
||||
)
|
||||
}
|
||||
@ -1041,7 +1046,7 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/friend-requests/batch/accept",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: BatchRequestIdsRequest(requestIds: requestIds)
|
||||
)
|
||||
}
|
||||
@ -1051,7 +1056,7 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/friend-requests/batch/reject",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: BatchRequestIdsRequest(requestIds: requestIds)
|
||||
)
|
||||
}
|
||||
@ -1061,7 +1066,7 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/members/batch",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: BatchUserIdsRequest(userIds: userIds)
|
||||
)
|
||||
}
|
||||
@ -1071,7 +1076,7 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/members/batch/remove",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: BatchUserIdsRequest(userIds: userIds)
|
||||
)
|
||||
}
|
||||
@ -1081,7 +1086,7 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/join-requests/batch/accept",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: BatchRequestIdsRequest(requestIds: requestIds)
|
||||
)
|
||||
}
|
||||
@ -1091,7 +1096,7 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/join-requests/batch/reject",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: BatchRequestIdsRequest(requestIds: requestIds)
|
||||
)
|
||||
}
|
||||
@ -1101,7 +1106,7 @@ public final class ImSDK {
|
||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||
path: "/api/im/groups/\(groupId)/members/\(userId)/info",
|
||||
method: "PUT",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: ModifyMemberInfoRequest(nickname: nickname, role: role)
|
||||
)
|
||||
}
|
||||
@ -1110,6 +1115,8 @@ public final class ImSDK {
|
||||
client?.disconnect()
|
||||
client = nil
|
||||
updateConnectionState(.disconnected)
|
||||
currentUserId = nil
|
||||
currentUserSig = nil
|
||||
}
|
||||
|
||||
private func fetchHistoryInternal(
|
||||
@ -1125,7 +1132,7 @@ public final class ImSDK {
|
||||
) async throws -> [ImMessage] {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
var items = [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "page", value: String(page)),
|
||||
URLQueryItem(name: "size", value: String(size)),
|
||||
]
|
||||
|
||||
@ -40,7 +40,7 @@ public enum MsgStatus: String, Codable, Sendable {
|
||||
|
||||
public struct ImMessage: Codable, Sendable {
|
||||
public let id: String
|
||||
public let appId: String
|
||||
public let appKey: String
|
||||
public let fromUserId: String
|
||||
public let fromId: String?
|
||||
public let toId: String
|
||||
@ -84,7 +84,7 @@ public extension ImEventDelegate {
|
||||
|
||||
public struct ImGroup: Codable, Sendable {
|
||||
public let id: String
|
||||
public let appId: String
|
||||
public let appKey: String
|
||||
public let name: String
|
||||
public let groupType: String?
|
||||
public let creatorId: String
|
||||
@ -116,7 +116,7 @@ public struct ConversationGroupItem: Codable, Sendable {
|
||||
|
||||
public struct FriendRequest: Codable, Sendable {
|
||||
public let id: String
|
||||
public let appId: String
|
||||
public let appKey: String
|
||||
public let fromUserId: String
|
||||
public let toUserId: String
|
||||
public let remark: String?
|
||||
@ -127,7 +127,7 @@ public struct FriendRequest: Codable, Sendable {
|
||||
|
||||
public struct GroupJoinRequest: Codable, Sendable {
|
||||
public let id: String
|
||||
public let appId: String
|
||||
public let appKey: String
|
||||
public let groupId: String
|
||||
public let requesterId: String
|
||||
public let remark: String?
|
||||
@ -138,7 +138,7 @@ public struct GroupJoinRequest: Codable, Sendable {
|
||||
|
||||
public struct BlacklistEntry: Codable, Sendable {
|
||||
public let id: String
|
||||
public let appId: String
|
||||
public let appKey: String
|
||||
public let userId: String
|
||||
public let blockedUserId: String
|
||||
public let createdAt: Int64
|
||||
@ -161,7 +161,7 @@ public struct GroupReadReceiptSummary: Codable, Sendable {
|
||||
|
||||
public struct UserProfile: Codable, Sendable {
|
||||
public let id: String?
|
||||
public let appId: String?
|
||||
public let appKey: String?
|
||||
public let userId: String
|
||||
public let nickname: String?
|
||||
public let avatar: String?
|
||||
|
||||
@ -71,7 +71,7 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate {
|
||||
path: "/api/push/register",
|
||||
method: "POST",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "userId", value: userId),
|
||||
URLQueryItem(name: "vendor", value: vendor.rawValue),
|
||||
URLQueryItem(name: "token", value: token),
|
||||
@ -110,7 +110,7 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate {
|
||||
path: "/api/push/unregister",
|
||||
method: "DELETE",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "userId", value: userId),
|
||||
URLQueryItem(name: "vendor", value: vendor.rawValue),
|
||||
URLQueryItem(name: "deviceId", value: deviceId),
|
||||
@ -166,7 +166,7 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate {
|
||||
let response: SdkRuntimeConfigResponse = try await ApiClient.shared.request(
|
||||
path: "/api/sdk/config",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "platform", value: "IOS"),
|
||||
]
|
||||
)
|
||||
|
||||
@ -23,7 +23,7 @@ public final class UpdateSDK {
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/v1/updates/app/check",
|
||||
queryItems: [
|
||||
URLQueryItem(name: "appId", value: config.appId),
|
||||
URLQueryItem(name: "appKey", value: config.appKey),
|
||||
URLQueryItem(name: "platform", value: "IOS"),
|
||||
URLQueryItem(name: "currentVersionCode", value: "\(currentVersionCode)"),
|
||||
]
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
public final class XWebViewBridge {
|
||||
public static let shared = XWebViewBridge()
|
||||
|
||||
private var config = XWebViewConfig()
|
||||
private weak var controller: AnyObject?
|
||||
|
||||
private init() {}
|
||||
|
||||
public func open(_ config: XWebViewConfig) {
|
||||
self.config = config
|
||||
}
|
||||
|
||||
public func currentConfig() -> XWebViewConfig {
|
||||
config
|
||||
}
|
||||
|
||||
public func setController(_ controller: (any XWebViewController)?) {
|
||||
self.controller = controller
|
||||
}
|
||||
|
||||
public func currentController() -> (any XWebViewController)? {
|
||||
controller as? any XWebViewController
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public func openXWebView(_ config: XWebViewConfig) {
|
||||
XWebViewBridge.shared.open(config)
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct XWebViewPage: View {
|
||||
private let config: XWebViewConfig
|
||||
|
||||
public init(config: XWebViewConfig? = nil) {
|
||||
self.config = config ?? XWebViewBridge.shared.currentConfig()
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
NavigationStack {
|
||||
Group {
|
||||
#if canImport(UIKit)
|
||||
XWebViewView(config: config)
|
||||
.navigationTitle(config.title.isEmpty ? "WebView" : config.title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
if !config.hideToolbar {
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
Button {
|
||||
if let controller = XWebViewBridge.shared.currentController(), controller.canGoBack() {
|
||||
controller.goBack()
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "chevron.left")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
XWebViewView(config: config)
|
||||
.navigationTitle(config.title.isEmpty ? "WebView" : config.title)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
import Foundation
|
||||
|
||||
public struct XWebViewConfig: Sendable {
|
||||
public var url: String
|
||||
public var title: String
|
||||
public var hideToolbar: Bool
|
||||
public var hideStatusBar: Bool
|
||||
public var userAgent: String?
|
||||
|
||||
public init(
|
||||
url: String = "",
|
||||
title: String = "",
|
||||
hideToolbar: Bool = false,
|
||||
hideStatusBar: Bool = false,
|
||||
userAgent: String? = nil
|
||||
) {
|
||||
self.url = url
|
||||
self.title = title
|
||||
self.hideToolbar = hideToolbar
|
||||
self.hideStatusBar = hideStatusBar
|
||||
self.userAgent = userAgent
|
||||
}
|
||||
}
|
||||
|
||||
public protocol XWebViewController: AnyObject {
|
||||
func canGoBack() -> Bool
|
||||
func canGoForward() -> Bool
|
||||
func currentUrl() -> String?
|
||||
func goBack()
|
||||
func goForward()
|
||||
func reload()
|
||||
func load(url: String)
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
import SwiftUI
|
||||
|
||||
#if canImport(UIKit)
|
||||
import WebKit
|
||||
|
||||
@MainActor
|
||||
public struct XWebViewView: UIViewRepresentable {
|
||||
private let config: XWebViewConfig
|
||||
|
||||
public init(config: XWebViewConfig? = nil) {
|
||||
self.config = config ?? XWebViewBridge.shared.currentConfig()
|
||||
}
|
||||
|
||||
public func makeCoordinator() -> Coordinator {
|
||||
Coordinator()
|
||||
}
|
||||
|
||||
public func makeUIView(context: Context) -> WKWebView {
|
||||
let webView = WKWebView(frame: .zero)
|
||||
webView.navigationDelegate = context.coordinator
|
||||
webView.uiDelegate = context.coordinator
|
||||
if let userAgent = config.userAgent {
|
||||
webView.customUserAgent = userAgent
|
||||
}
|
||||
context.coordinator.attach(webView)
|
||||
XWebViewBridge.shared.setController(context.coordinator)
|
||||
if !config.url.isEmpty, let url = URL(string: config.url) {
|
||||
webView.load(URLRequest(url: url))
|
||||
}
|
||||
return webView
|
||||
}
|
||||
|
||||
public func updateUIView(_ webView: WKWebView, context: Context) {
|
||||
context.coordinator.attach(webView)
|
||||
if webView.url == nil, !config.url.isEmpty, let url = URL(string: config.url) {
|
||||
webView.load(URLRequest(url: url))
|
||||
}
|
||||
}
|
||||
|
||||
public static func dismantleUIView(_ uiView: WKWebView, coordinator: Coordinator) {
|
||||
coordinator.detach()
|
||||
if XWebViewBridge.shared.currentController() === coordinator {
|
||||
XWebViewBridge.shared.setController(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public final class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate, XWebViewController {
|
||||
private weak var webView: WKWebView?
|
||||
|
||||
func attach(_ webView: WKWebView) {
|
||||
self.webView = webView
|
||||
}
|
||||
|
||||
func detach() {
|
||||
webView = nil
|
||||
}
|
||||
|
||||
public func canGoBack() -> Bool {
|
||||
webView?.canGoBack == true
|
||||
}
|
||||
|
||||
public func canGoForward() -> Bool {
|
||||
webView?.canGoForward == true
|
||||
}
|
||||
|
||||
public func currentUrl() -> String? {
|
||||
webView?.url?.absoluteString
|
||||
}
|
||||
|
||||
public func goBack() {
|
||||
webView?.goBack()
|
||||
}
|
||||
|
||||
public func goForward() {
|
||||
webView?.goForward()
|
||||
}
|
||||
|
||||
public func reload() {
|
||||
webView?.reload()
|
||||
}
|
||||
|
||||
public func load(url: String) {
|
||||
guard let url = URL(string: url) else { return }
|
||||
webView?.load(URLRequest(url: url))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
public struct XWebViewView: View {
|
||||
private let config: XWebViewConfig
|
||||
|
||||
public init(config: XWebViewConfig? = nil) {
|
||||
self.config = config ?? XWebViewBridge.shared.currentConfig()
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
Text(config.title.isEmpty ? "WebView" : config.title)
|
||||
.font(.headline)
|
||||
Text("XWebView is available on iOS with UIKit-backed WebView.")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -8,7 +8,7 @@ final class SmokeTests: XCTestCase {
|
||||
XCTAssertEqual(config.appKey, "ak_test")
|
||||
XCTAssertTrue(config.debug)
|
||||
XCTAssertFalse(config.autoRegisterPush)
|
||||
XCTAssertEqual(config.appId, "ak_test")
|
||||
XCTAssertEqual(config.appKey, "ak_test")
|
||||
}
|
||||
|
||||
func testTokenStoreSaveGetClear() {
|
||||
@ -22,7 +22,7 @@ final class SmokeTests: XCTestCase {
|
||||
func testImMessageCoding() throws {
|
||||
let msg = ImMessage(
|
||||
id: "msg_1",
|
||||
appId: "ak_test",
|
||||
appKey: "ak_test",
|
||||
fromUserId: "user_a",
|
||||
fromId: "user_a",
|
||||
toId: "user_b",
|
||||
|
||||
@ -15,6 +15,7 @@ let package = Package(
|
||||
name: "XuqmDemo",
|
||||
dependencies: [
|
||||
.product(name: "XuqmSDK", package: "XuqmGroup-iOSSDK"),
|
||||
.product(name: "XuqmWebViewSDK", package: "XuqmGroup-iOSSDK"),
|
||||
],
|
||||
path: "Sources"
|
||||
),
|
||||
|
||||
@ -3,6 +3,7 @@ import XuqmSDK
|
||||
|
||||
enum AppRoute: Hashable {
|
||||
case chat(targetId: String, targetName: String)
|
||||
case webView
|
||||
}
|
||||
|
||||
struct DemoUser: Identifiable {
|
||||
|
||||
@ -27,8 +27,8 @@ final class AuthViewModel: ObservableObject {
|
||||
let res: DemoLoginResponse = try await ApiClient.shared.request(
|
||||
path: "/api/demo/auth/login",
|
||||
method: "POST",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
body: ["appId": config.appId, "userId": userId, "password": password]
|
||||
queryItems: [URLQueryItem(name: "appKey", value: config.appKey)],
|
||||
body: ["appKey": config.appKey, "userId": userId, "password": password]
|
||||
)
|
||||
await XuqmSDK.shared.login(userId: res.profile.userId, userSig: res.imToken)
|
||||
currentUserId = res.profile.userId
|
||||
|
||||
@ -0,0 +1,159 @@
|
||||
import SwiftUI
|
||||
import XuqmWebViewSDK
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
struct WebViewDemoView: View {
|
||||
@Binding var path: NavigationPath
|
||||
private enum WebViewMode: String, CaseIterable, Identifiable {
|
||||
case embedded = "嵌入式"
|
||||
case page = "页面模式"
|
||||
|
||||
var id: String { rawValue }
|
||||
}
|
||||
|
||||
@State private var url = "https://example.com"
|
||||
@State private var title = "示例网页"
|
||||
@State private var mode: WebViewMode = .embedded
|
||||
@State private var statusVersion = 0
|
||||
|
||||
private var config: XWebViewConfig {
|
||||
XWebViewConfig(
|
||||
url: url.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
title: title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
)
|
||||
}
|
||||
|
||||
private var currentUrl: String {
|
||||
let controllerUrl = XWebViewBridge.shared.currentController()?.currentUrl()
|
||||
return (controllerUrl?.isEmpty == false ? controllerUrl : nil) ?? config.url
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("XWebView")
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
|
||||
Text("可输入 URL,并在嵌入式组件和独立页面之间切换。")
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
TextField("URL", text: $url)
|
||||
|
||||
TextField("标题", text: $title)
|
||||
|
||||
Picker("模式", selection: $mode) {
|
||||
ForEach(WebViewMode.allCases) { item in
|
||||
Text(item.rawValue).tag(item)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("嵌入式组件")
|
||||
.font(.headline)
|
||||
|
||||
if mode == .embedded {
|
||||
XWebViewView(config: config)
|
||||
.frame(height: 320)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||
} else {
|
||||
Text("当前处于页面模式,点击下面按钮会 push 到独立页面。")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("页面控制")
|
||||
.font(.headline)
|
||||
|
||||
Text("当前 URL: \(currentUrl.isEmpty ? "未加载" : currentUrl)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Button {
|
||||
XWebViewBridge.shared.currentController()?.goBack()
|
||||
statusVersion += 1
|
||||
} label: {
|
||||
Text("后退")
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.gray.opacity(0.12))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
}
|
||||
.disabled(!(XWebViewBridge.shared.currentController()?.canGoBack() ?? false))
|
||||
|
||||
Button {
|
||||
XWebViewBridge.shared.currentController()?.goForward()
|
||||
statusVersion += 1
|
||||
} label: {
|
||||
Text("前进")
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.gray.opacity(0.12))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
}
|
||||
.disabled(!(XWebViewBridge.shared.currentController()?.canGoForward() ?? false))
|
||||
}
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Button {
|
||||
XWebViewBridge.shared.currentController()?.reload()
|
||||
statusVersion += 1
|
||||
} label: {
|
||||
Text("刷新")
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.gray.opacity(0.12))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
}
|
||||
|
||||
Button {
|
||||
#if canImport(UIKit)
|
||||
UIPasteboard.general.string = currentUrl
|
||||
#endif
|
||||
statusVersion += 1
|
||||
} label: {
|
||||
Text("复制 URL")
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.gray.opacity(0.12))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
XWebViewBridge.shared.currentController()?.load(url: url.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
statusVersion += 1
|
||||
} label: {
|
||||
Text("重新加载输入 URL")
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.accentColor)
|
||||
.foregroundStyle(.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
openXWebView(config)
|
||||
path.append(AppRoute.webView)
|
||||
} label: {
|
||||
Text("打开页面模式")
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.accentColor)
|
||||
.foregroundStyle(.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationTitle("网页")
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import SwiftUI
|
||||
import XuqmSDK
|
||||
import XuqmWebViewSDK
|
||||
|
||||
@main
|
||||
struct XuqmDemoApp: App {
|
||||
@ -30,7 +31,6 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
private func initializeSDK() {
|
||||
let config = SDKConfig(
|
||||
appKey: "ak_demo_chat",
|
||||
appSecret: "demo_secret",
|
||||
debug: true
|
||||
)
|
||||
XuqmSDK.shared.initialize(config: config)
|
||||
@ -82,17 +82,31 @@ struct MainTabView: View {
|
||||
}
|
||||
.tag(1)
|
||||
|
||||
WebViewDemoView(path: $path)
|
||||
.tabItem {
|
||||
Image(systemName: "globe")
|
||||
Text("网页")
|
||||
}
|
||||
.tag(2)
|
||||
|
||||
ProfileView(authViewModel: authViewModel)
|
||||
.tabItem {
|
||||
Image(systemName: "person.fill")
|
||||
Text("我的")
|
||||
}
|
||||
.tag(2)
|
||||
.tag(3)
|
||||
}
|
||||
.navigationDestination(for: AppRoute.self) { route in
|
||||
switch route {
|
||||
case .chat(let targetId, let targetName):
|
||||
ChatView(targetId: targetId, targetName: targetName, currentUserId: authViewModel.currentUserId)
|
||||
case .webView:
|
||||
XWebViewPage(
|
||||
config: XWebViewConfig(
|
||||
url: "https://example.com",
|
||||
title: "独立页面"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
XuqmWebViewSDK.podspec
普通文件
13
XuqmWebViewSDK.podspec
普通文件
@ -0,0 +1,13 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'XuqmWebViewSDK'
|
||||
s.version = '0.1.0'
|
||||
s.summary = 'XuqmGroup iOS SDK — WebView module'
|
||||
s.homepage = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK'
|
||||
s.license = { :type => 'MIT' }
|
||||
s.author = { 'XuqmGroup' => 'dev@xuqm.com' }
|
||||
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => s.version.to_s }
|
||||
s.ios.deployment_target = '16.0'
|
||||
s.swift_version = '5.9'
|
||||
s.source_files = 'Sources/XuqmWebViewSDK/**/*.swift'
|
||||
s.dependency 'XuqmSDK'
|
||||
end
|
||||
正在加载...
在新工单中引用
屏蔽一个用户