From 5df6d700652acefeab609a4150d2f0bedf0f1708 Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Thu, 7 May 2026 19:39:48 +0800 Subject: [PATCH] chore: sync local changes --- Package.swift | 7 + README.md | 39 ++++- Sources/XuqmSDK/Core/SDKConfig.swift | 1 - Sources/XuqmSDK/Core/XuqmSDK.swift | 8 + Sources/XuqmSDK/IM/ImClient.swift | 32 ++-- Sources/XuqmSDK/IM/ImSDK.swift | 119 +++++++------ Sources/XuqmSDK/IM/ImTypes.swift | 12 +- Sources/XuqmSDK/Push/PushSDK.swift | 6 +- Sources/XuqmSDK/Update/UpdateSDK.swift | 2 +- Sources/XuqmWebViewSDK/XWebViewBridge.swift | 32 ++++ Sources/XuqmWebViewSDK/XWebViewPage.swift | 37 ++++ Sources/XuqmWebViewSDK/XWebViewTypes.swift | 33 ++++ Sources/XuqmWebViewSDK/XWebViewView.swift | 111 ++++++++++++ Tests/XuqmSDKTests/SmokeTests.swift | 4 +- XuqmDemo/Package.swift | 1 + XuqmDemo/Sources/Models/DemoModels.swift | 1 + .../Sources/ViewModels/AuthViewModel.swift | 4 +- XuqmDemo/Sources/Views/WebViewDemoView.swift | 159 ++++++++++++++++++ XuqmDemo/Sources/XuqmDemoApp.swift | 18 +- XuqmWebViewSDK.podspec | 13 ++ 20 files changed, 548 insertions(+), 91 deletions(-) create mode 100644 Sources/XuqmWebViewSDK/XWebViewBridge.swift create mode 100644 Sources/XuqmWebViewSDK/XWebViewPage.swift create mode 100644 Sources/XuqmWebViewSDK/XWebViewTypes.swift create mode 100644 Sources/XuqmWebViewSDK/XWebViewView.swift create mode 100644 XuqmDemo/Sources/Views/WebViewDemoView.swift create mode 100644 XuqmWebViewSDK.podspec diff --git a/Package.swift b/Package.swift index 13ddfee..e525654 100644 --- a/Package.swift +++ b/Package.swift @@ -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"], diff --git a/README.md b/README.md index 06aab63..50e0ac2 100644 --- a/README.md +++ b/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` 同时使用,也可单独依赖。 + --- ## 发版 diff --git a/Sources/XuqmSDK/Core/SDKConfig.swift b/Sources/XuqmSDK/Core/SDKConfig.swift index 0881d21..dfeced6 100644 --- a/Sources/XuqmSDK/Core/SDKConfig.swift +++ b/Sources/XuqmSDK/Core/SDKConfig.swift @@ -31,7 +31,6 @@ public extension SDKConfig { } extension SDKConfig { - var appId: String { appKey } } enum SDKEndpoints { diff --git a/Sources/XuqmSDK/Core/XuqmSDK.swift b/Sources/XuqmSDK/Core/XuqmSDK.swift index 98bde62..f1c3e97 100644 --- a/Sources/XuqmSDK/Core/XuqmSDK.swift +++ b/Sources/XuqmSDK/Core/XuqmSDK.swift @@ -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 diff --git a/Sources/XuqmSDK/IM/ImClient.swift b/Sources/XuqmSDK/IM/ImClient.swift index 1ed0555..09d6778 100644 --- a/Sources/XuqmSDK/IM/ImClient.swift +++ b/Sources/XuqmSDK/IM/ImClient.swift @@ -13,16 +13,16 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S private var groupSubscriptions = Set() 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, diff --git a/Sources/XuqmSDK/IM/ImSDK.swift b/Sources/XuqmSDK/IM/ImSDK.swift index ed16cf6..503ea22 100644 --- a/Sources/XuqmSDK/IM/ImSDK.swift +++ b/Sources/XuqmSDK/IM/ImSDK.swift @@ -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 { 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)), ] diff --git a/Sources/XuqmSDK/IM/ImTypes.swift b/Sources/XuqmSDK/IM/ImTypes.swift index 066863d..b0ac3da 100644 --- a/Sources/XuqmSDK/IM/ImTypes.swift +++ b/Sources/XuqmSDK/IM/ImTypes.swift @@ -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? diff --git a/Sources/XuqmSDK/Push/PushSDK.swift b/Sources/XuqmSDK/Push/PushSDK.swift index f0dcea6..488d34c 100644 --- a/Sources/XuqmSDK/Push/PushSDK.swift +++ b/Sources/XuqmSDK/Push/PushSDK.swift @@ -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"), ] ) diff --git a/Sources/XuqmSDK/Update/UpdateSDK.swift b/Sources/XuqmSDK/Update/UpdateSDK.swift index 8b1e822..b61de65 100644 --- a/Sources/XuqmSDK/Update/UpdateSDK.swift +++ b/Sources/XuqmSDK/Update/UpdateSDK.swift @@ -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)"), ] diff --git a/Sources/XuqmWebViewSDK/XWebViewBridge.swift b/Sources/XuqmWebViewSDK/XWebViewBridge.swift new file mode 100644 index 0000000..4bba0e0 --- /dev/null +++ b/Sources/XuqmWebViewSDK/XWebViewBridge.swift @@ -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) +} diff --git a/Sources/XuqmWebViewSDK/XWebViewPage.swift b/Sources/XuqmWebViewSDK/XWebViewPage.swift new file mode 100644 index 0000000..eeed68d --- /dev/null +++ b/Sources/XuqmWebViewSDK/XWebViewPage.swift @@ -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 + } + } + } +} diff --git a/Sources/XuqmWebViewSDK/XWebViewTypes.swift b/Sources/XuqmWebViewSDK/XWebViewTypes.swift new file mode 100644 index 0000000..06caca1 --- /dev/null +++ b/Sources/XuqmWebViewSDK/XWebViewTypes.swift @@ -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) +} diff --git a/Sources/XuqmWebViewSDK/XWebViewView.swift b/Sources/XuqmWebViewSDK/XWebViewView.swift new file mode 100644 index 0000000..d293597 --- /dev/null +++ b/Sources/XuqmWebViewSDK/XWebViewView.swift @@ -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 diff --git a/Tests/XuqmSDKTests/SmokeTests.swift b/Tests/XuqmSDKTests/SmokeTests.swift index dd0cb1e..133a960 100644 --- a/Tests/XuqmSDKTests/SmokeTests.swift +++ b/Tests/XuqmSDKTests/SmokeTests.swift @@ -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", diff --git a/XuqmDemo/Package.swift b/XuqmDemo/Package.swift index 4cf63ed..740b423 100644 --- a/XuqmDemo/Package.swift +++ b/XuqmDemo/Package.swift @@ -15,6 +15,7 @@ let package = Package( name: "XuqmDemo", dependencies: [ .product(name: "XuqmSDK", package: "XuqmGroup-iOSSDK"), + .product(name: "XuqmWebViewSDK", package: "XuqmGroup-iOSSDK"), ], path: "Sources" ), diff --git a/XuqmDemo/Sources/Models/DemoModels.swift b/XuqmDemo/Sources/Models/DemoModels.swift index 906be25..6f83ccd 100644 --- a/XuqmDemo/Sources/Models/DemoModels.swift +++ b/XuqmDemo/Sources/Models/DemoModels.swift @@ -3,6 +3,7 @@ import XuqmSDK enum AppRoute: Hashable { case chat(targetId: String, targetName: String) + case webView } struct DemoUser: Identifiable { diff --git a/XuqmDemo/Sources/ViewModels/AuthViewModel.swift b/XuqmDemo/Sources/ViewModels/AuthViewModel.swift index 60e031a..7c077f3 100644 --- a/XuqmDemo/Sources/ViewModels/AuthViewModel.swift +++ b/XuqmDemo/Sources/ViewModels/AuthViewModel.swift @@ -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 diff --git a/XuqmDemo/Sources/Views/WebViewDemoView.swift b/XuqmDemo/Sources/Views/WebViewDemoView.swift new file mode 100644 index 0000000..9bc62e4 --- /dev/null +++ b/XuqmDemo/Sources/Views/WebViewDemoView.swift @@ -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("网页") + } +} diff --git a/XuqmDemo/Sources/XuqmDemoApp.swift b/XuqmDemo/Sources/XuqmDemoApp.swift index 7e52e57..e62f61b 100644 --- a/XuqmDemo/Sources/XuqmDemoApp.swift +++ b/XuqmDemo/Sources/XuqmDemoApp.swift @@ -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: "独立页面" + ) + ) } } } diff --git a/XuqmWebViewSDK.podspec b/XuqmWebViewSDK.podspec new file mode 100644 index 0000000..5a9a72f --- /dev/null +++ b/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