From 704956a8b02a921c7fb2f9d648228f8bff06dd94 Mon Sep 17 00:00:00 2001 From: zjiecode Date: Sat, 21 Mar 2026 23:13:47 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0showToast=E5=92=8CgetBase?= =?UTF-8?q?Apiurl=E7=9A=84JS=E6=A1=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../page/web/bridge/WxpWebBridgeManager.kt | 4 +++ .../handlers/GetBaseApiUrlBridgeHandler.kt | 17 ++++++++++++ .../bridge/handlers/ShowToastBridgeHandler.kt | 26 +++++++++++++++++++ .../Page/WebView/WxpWebViewController.swift | 9 +++++++ .../WxpGetBaseApiUrlBridgeHandler.swift | 8 ++++++ .../handlers/WxpShowToastBridgeHandler.swift | 14 ++++++++++ .../wxpusher.xcodeproj/project.pbxproj | 8 ++++++ .../providerlist/WxpProviderListPresenter.kt | 2 +- 8 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/GetBaseApiUrlBridgeHandler.kt create mode 100644 androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/ShowToastBridgeHandler.kt create mode 100644 iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetBaseApiUrlBridgeHandler.swift create mode 100644 iosApp/wxpusher/Page/WebView/bridge/handlers/WxpShowToastBridgeHandler.swift diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt index c744bc65..367033a3 100644 --- a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt @@ -1,9 +1,11 @@ package com.smjcco.wxpusher.page.web.bridge import com.smjcco.wxpusher.base.common.WxpLogUtils +import com.smjcco.wxpusher.page.web.bridge.handlers.GetBaseApiUrlBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.GetLoginInfoBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.OpenUrlBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.PayRequestBridgeHandler +import com.smjcco.wxpusher.page.web.bridge.handlers.ShowToastBridgeHandler class WxpWebBridgeManager( private val context: BridgeContext, @@ -37,6 +39,8 @@ class WxpWebBridgeManager( registerHandler("payRequest", requiresWhitelist = true, handler = PayRequestBridgeHandler) registerHandler("openUrl", requiresWhitelist = false, handler = OpenUrlBridgeHandler) registerHandler("getLoginInfo", requiresWhitelist = true, handler = GetLoginInfoBridgeHandler) + registerHandler("getBaseApiUrl", requiresWhitelist = true, handler = GetBaseApiUrlBridgeHandler) + registerHandler("showToast", requiresWhitelist = true, handler = ShowToastBridgeHandler) } fun onMessage(messageJson: String) { diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/GetBaseApiUrlBridgeHandler.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/GetBaseApiUrlBridgeHandler.kt new file mode 100644 index 00000000..ea05a2d1 --- /dev/null +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/GetBaseApiUrlBridgeHandler.kt @@ -0,0 +1,17 @@ +package com.smjcco.wxpusher.page.web.bridge.handlers + +import com.smjcco.wxpusher.WxpConfig +import com.smjcco.wxpusher.page.web.bridge.BridgeActionHandler +import com.smjcco.wxpusher.page.web.bridge.BridgeContext +import com.smjcco.wxpusher.page.web.bridge.BridgeRequest +import com.smjcco.wxpusher.page.web.bridge.WxpBridgeEmitter + +object GetBaseApiUrlBridgeHandler : BridgeActionHandler { + override fun handle(request: BridgeRequest, context: BridgeContext, emitter: WxpBridgeEmitter) { + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = true, + data = mapOf("apiUrl" to WxpConfig.baseUrl) + ) + } +} diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/ShowToastBridgeHandler.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/ShowToastBridgeHandler.kt new file mode 100644 index 00000000..5c579a7e --- /dev/null +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/ShowToastBridgeHandler.kt @@ -0,0 +1,26 @@ +package com.smjcco.wxpusher.page.web.bridge.handlers + +import com.smjcco.wxpusher.base.common.WxpToastUtils +import com.smjcco.wxpusher.page.web.bridge.BridgeActionHandler +import com.smjcco.wxpusher.page.web.bridge.BridgeContext +import com.smjcco.wxpusher.page.web.bridge.BridgeRequest +import com.smjcco.wxpusher.page.web.bridge.WxpBridgeEmitter + +object ShowToastBridgeHandler : BridgeActionHandler { + override fun handle(request: BridgeRequest, context: BridgeContext, emitter: WxpBridgeEmitter) { + val msg = request.data["msg"] as? String + if (msg.isNullOrBlank()) { + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = false, + error = "msg is empty" + ) + return + } + WxpToastUtils.showToast(msg) + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = true + ) + } +} diff --git a/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift b/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift index 2b936ba1..dfbe096b 100644 --- a/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift +++ b/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift @@ -274,6 +274,9 @@ class WxpWebViewController: UIViewController { self?.buildDictionary(from: object) ?? [:] } ) + let getBaseApiUrlHandler = WxpGetBaseApiUrlBridgeHandler() + let showToastHandler = WxpShowToastBridgeHandler() + manager.registerHandler(action: "payRequest", requiresWhitelist: true) { request, completion in payRequestHandler.handle(request, completion: completion) } @@ -283,6 +286,12 @@ class WxpWebViewController: UIViewController { manager.registerHandler(action: "getLoginInfo", requiresWhitelist: true) { _, completion in getLoginInfoHandler.handle(completion: completion) } + manager.registerHandler(action: "getBaseApiUrl", requiresWhitelist: true) { _, completion in + getBaseApiUrlHandler.handle(completion: completion) + } + manager.registerHandler(action: "showToast", requiresWhitelist: true) { request, completion in + showToastHandler.handle(request, completion: completion) + } bridgeManager = manager let proxy = WxpWeakScriptMessageHandler(target: self) bridgeMessageHandlerProxy = proxy diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetBaseApiUrlBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetBaseApiUrlBridgeHandler.swift new file mode 100644 index 00000000..d6ac3b99 --- /dev/null +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetBaseApiUrlBridgeHandler.swift @@ -0,0 +1,8 @@ +import Foundation +import shared + +final class WxpGetBaseApiUrlBridgeHandler { + func handle(completion: @escaping WxpBridgeCompletion) { + completion(.ok(["apiUrl": WxpConfig.shared.baseUrl])) + } +} diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpShowToastBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpShowToastBridgeHandler.swift new file mode 100644 index 00000000..4b80fd72 --- /dev/null +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpShowToastBridgeHandler.swift @@ -0,0 +1,14 @@ +import Foundation +import shared + +final class WxpShowToastBridgeHandler { + + func handle(_ request: WxpBridgeRequest, completion: @escaping WxpBridgeCompletion) { + guard let msg = request.data["msg"] as? String, !msg.isEmpty else { + completion(.fail("msg is empty")) + return + } + WxpToastUtils.shared.showToast(msg: msg) + completion(.ok()) + } +} diff --git a/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj b/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj index 83acc030..95d2e821 100644 --- a/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj +++ b/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj @@ -28,6 +28,8 @@ 1A63A13F2F669047005FF070 /* WxpBridgeContracts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1342F669047005FF070 /* WxpBridgeContracts.swift */; }; 1A63A1402F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1302F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift */; }; 1A63A1412F669047005FF070 /* WxpBridgeEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1352F669047005FF070 /* WxpBridgeEmitter.swift */; }; + 1A63A1422F669047005FF070 /* WxpGetBaseApiUrlBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1432F669047005FF070 /* WxpGetBaseApiUrlBridgeHandler.swift */; }; + 1A63A1442F669047005FF070 /* WxpShowToastBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */; }; 1A962E672E08610200C966A3 /* KtSwiftJumpPageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E662E08610200C966A3 /* KtSwiftJumpPageUtils.m */; }; 1A962E6A2E08625700C966A3 /* WxpJumpPageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E692E08625700C966A3 /* WxpJumpPageUtils.swift */; }; 1A962E6F2E09B3D200C966A3 /* WxpBaseUIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E6E2E09B3D200C966A3 /* WxpBaseUIViewController.swift */; }; @@ -79,6 +81,8 @@ 1A63A1302F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpGetLoginInfoBridgeHandler.swift; sourceTree = ""; }; 1A63A1312F669047005FF070 /* WxpOpenUrlBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpOpenUrlBridgeHandler.swift; sourceTree = ""; }; 1A63A1322F669047005FF070 /* WxpPayRequestBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpPayRequestBridgeHandler.swift; sourceTree = ""; }; + 1A63A1432F669047005FF070 /* WxpGetBaseApiUrlBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpGetBaseApiUrlBridgeHandler.swift; sourceTree = ""; }; + 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpShowToastBridgeHandler.swift; sourceTree = ""; }; 1A63A1342F669047005FF070 /* WxpBridgeContracts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeContracts.swift; sourceTree = ""; }; 1A63A1352F669047005FF070 /* WxpBridgeEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeEmitter.swift; sourceTree = ""; }; 1A63A1362F669047005FF070 /* WxpBridgeMessageParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeMessageParser.swift; sourceTree = ""; }; @@ -241,8 +245,10 @@ isa = PBXGroup; children = ( 1A63A1302F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift */, + 1A63A1432F669047005FF070 /* WxpGetBaseApiUrlBridgeHandler.swift */, 1A63A1312F669047005FF070 /* WxpOpenUrlBridgeHandler.swift */, 1A63A1322F669047005FF070 /* WxpPayRequestBridgeHandler.swift */, + 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */, ); path = handlers; sourceTree = ""; @@ -630,6 +636,8 @@ 1A63A13F2F669047005FF070 /* WxpBridgeContracts.swift in Sources */, 1A63A1402F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift in Sources */, 1A63A1412F669047005FF070 /* WxpBridgeEmitter.swift in Sources */, + 1A63A1422F669047005FF070 /* WxpGetBaseApiUrlBridgeHandler.swift in Sources */, + 1A63A1442F669047005FF070 /* WxpShowToastBridgeHandler.swift in Sources */, 1ABD14262EDBF9890019AA3A /* WxpLoadingService.swift in Sources */, 1A039F712F08131000C1CA76 /* TestPanelViewController.swift in Sources */, 1ABD142A2EDDE3720019AA3A /* AccountDetailViewController.swift in Sources */, diff --git a/shared/src/commonMain/kotlin/com/smjcco/wxpusher/page/providerlist/WxpProviderListPresenter.kt b/shared/src/commonMain/kotlin/com/smjcco/wxpusher/page/providerlist/WxpProviderListPresenter.kt index b592f3b5..ea8230fa 100644 --- a/shared/src/commonMain/kotlin/com/smjcco/wxpusher/page/providerlist/WxpProviderListPresenter.kt +++ b/shared/src/commonMain/kotlin/com/smjcco/wxpusher/page/providerlist/WxpProviderListPresenter.kt @@ -23,7 +23,7 @@ class WxpProviderListPresenter(view: IWxpProviderListView) : WxpToastUtils.showToast("获取openId失败,请重试") return@runAtMainSuspend } - view?.onLoadPage("${WxpConfig.baseUrl}/wxuser/?openId=${openId}#/product-list") + view?.onLoadPage("${WxpConfig.appFeUrl}/app#/market") } } } \ No newline at end of file From ac194750a57946002d3638cc9772256bc28e7e14 Mon Sep 17 00:00:00 2001 From: zjiecode Date: Sun, 22 Mar 2026 17:15:15 +0800 Subject: [PATCH 2/8] =?UTF-8?q?Android=20&&=20iOS=20=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=8E=AF=E5=A2=83url=E7=9A=84=E6=A1=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../smjcco/wxpusher/page/web/WxpWebViewFragment.kt | 1 + .../wxpusher/page/web/bridge/WxpWebBridgeManager.kt | 5 +++-- ...dgeHandler.kt => WxpGetEnvBaseUrlBridgeHandler.kt} | 7 +++++-- .../wxpusher/Page/WebView/WxpWebViewController.swift | 6 +++--- .../handlers/WxpGetBaseApiUrlBridgeHandler.swift | 8 -------- .../handlers/WxpGetEnvBaseUrlBridgeHandler.swift | 11 +++++++++++ iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj | 8 ++++---- 7 files changed, 27 insertions(+), 19 deletions(-) rename androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/{GetBaseApiUrlBridgeHandler.kt => WxpGetEnvBaseUrlBridgeHandler.kt} (73%) delete mode 100644 iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetBaseApiUrlBridgeHandler.swift create mode 100644 iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.swift diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt index 73b94286..2f9ffaa4 100644 --- a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt @@ -60,6 +60,7 @@ open class WxpWebViewFragment : WxpBaseFragment() { "wxpusher.zjiecode.com", "wxpusher.test.zjiecode.com", "10.0.0.11", + "10.0.2.2", "127.0.0.1" ) diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt index 367033a3..4dd06962 100644 --- a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt @@ -1,11 +1,11 @@ package com.smjcco.wxpusher.page.web.bridge import com.smjcco.wxpusher.base.common.WxpLogUtils -import com.smjcco.wxpusher.page.web.bridge.handlers.GetBaseApiUrlBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.GetLoginInfoBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.OpenUrlBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.PayRequestBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.ShowToastBridgeHandler +import com.smjcco.wxpusher.page.web.bridge.handlers.WxpGetEnvBaseUrlBridgeHandler class WxpWebBridgeManager( private val context: BridgeContext, @@ -17,6 +17,7 @@ class WxpWebBridgeManager( "wxpusher.zjiecode.com", "wxpusher.test.zjiecode.com", "10.0.0.11", + "10.0.2.2", "127.0.0.1" ) } @@ -39,7 +40,7 @@ class WxpWebBridgeManager( registerHandler("payRequest", requiresWhitelist = true, handler = PayRequestBridgeHandler) registerHandler("openUrl", requiresWhitelist = false, handler = OpenUrlBridgeHandler) registerHandler("getLoginInfo", requiresWhitelist = true, handler = GetLoginInfoBridgeHandler) - registerHandler("getBaseApiUrl", requiresWhitelist = true, handler = GetBaseApiUrlBridgeHandler) + registerHandler("getEnvBaseUrl", requiresWhitelist = true, handler = WxpGetEnvBaseUrlBridgeHandler) registerHandler("showToast", requiresWhitelist = true, handler = ShowToastBridgeHandler) } diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/GetBaseApiUrlBridgeHandler.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.kt similarity index 73% rename from androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/GetBaseApiUrlBridgeHandler.kt rename to androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.kt index ea05a2d1..81cde71c 100644 --- a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/GetBaseApiUrlBridgeHandler.kt +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.kt @@ -6,12 +6,15 @@ import com.smjcco.wxpusher.page.web.bridge.BridgeContext import com.smjcco.wxpusher.page.web.bridge.BridgeRequest import com.smjcco.wxpusher.page.web.bridge.WxpBridgeEmitter -object GetBaseApiUrlBridgeHandler : BridgeActionHandler { +object WxpGetEnvBaseUrlBridgeHandler : BridgeActionHandler { override fun handle(request: BridgeRequest, context: BridgeContext, emitter: WxpBridgeEmitter) { emitter.sendBridgeCallback( callbackId = request.callbackId, success = true, - data = mapOf("apiUrl" to WxpConfig.baseUrl) + data = mapOf( + "apiBaseUrl" to WxpConfig.baseUrl, + "appFeBaseUrl" to WxpConfig.appFeUrl + ) ) } } diff --git a/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift b/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift index dfbe096b..757957c2 100644 --- a/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift +++ b/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift @@ -274,7 +274,7 @@ class WxpWebViewController: UIViewController { self?.buildDictionary(from: object) ?? [:] } ) - let getBaseApiUrlHandler = WxpGetBaseApiUrlBridgeHandler() + let getEnvBaseUrlHandler = WxpGetEnvBaseUrlBridgeHandler() let showToastHandler = WxpShowToastBridgeHandler() manager.registerHandler(action: "payRequest", requiresWhitelist: true) { request, completion in @@ -286,8 +286,8 @@ class WxpWebViewController: UIViewController { manager.registerHandler(action: "getLoginInfo", requiresWhitelist: true) { _, completion in getLoginInfoHandler.handle(completion: completion) } - manager.registerHandler(action: "getBaseApiUrl", requiresWhitelist: true) { _, completion in - getBaseApiUrlHandler.handle(completion: completion) + manager.registerHandler(action: "getEnvBaseUrl", requiresWhitelist: true) { _, completion in + getEnvBaseUrlHandler.handle(completion: completion) } manager.registerHandler(action: "showToast", requiresWhitelist: true) { request, completion in showToastHandler.handle(request, completion: completion) diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetBaseApiUrlBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetBaseApiUrlBridgeHandler.swift deleted file mode 100644 index d6ac3b99..00000000 --- a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetBaseApiUrlBridgeHandler.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation -import shared - -final class WxpGetBaseApiUrlBridgeHandler { - func handle(completion: @escaping WxpBridgeCompletion) { - completion(.ok(["apiUrl": WxpConfig.shared.baseUrl])) - } -} diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.swift new file mode 100644 index 00000000..f0d55f8b --- /dev/null +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.swift @@ -0,0 +1,11 @@ +import Foundation +import shared + +final class WxpGetEnvBaseUrlBridgeHandler { + func handle(completion: @escaping WxpBridgeCompletion) { + completion(.ok([ + "apiBaseUrl": WxpConfig.shared.baseUrl, + "appFeBaseUrl": WxpConfig.shared.appFeUrl + ])) + } +} diff --git a/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj b/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj index 95d2e821..f5fdcd97 100644 --- a/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj +++ b/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj @@ -28,7 +28,7 @@ 1A63A13F2F669047005FF070 /* WxpBridgeContracts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1342F669047005FF070 /* WxpBridgeContracts.swift */; }; 1A63A1402F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1302F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift */; }; 1A63A1412F669047005FF070 /* WxpBridgeEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1352F669047005FF070 /* WxpBridgeEmitter.swift */; }; - 1A63A1422F669047005FF070 /* WxpGetBaseApiUrlBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1432F669047005FF070 /* WxpGetBaseApiUrlBridgeHandler.swift */; }; + 1A63A1422F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1432F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift */; }; 1A63A1442F669047005FF070 /* WxpShowToastBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */; }; 1A962E672E08610200C966A3 /* KtSwiftJumpPageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E662E08610200C966A3 /* KtSwiftJumpPageUtils.m */; }; 1A962E6A2E08625700C966A3 /* WxpJumpPageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E692E08625700C966A3 /* WxpJumpPageUtils.swift */; }; @@ -81,7 +81,7 @@ 1A63A1302F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpGetLoginInfoBridgeHandler.swift; sourceTree = ""; }; 1A63A1312F669047005FF070 /* WxpOpenUrlBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpOpenUrlBridgeHandler.swift; sourceTree = ""; }; 1A63A1322F669047005FF070 /* WxpPayRequestBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpPayRequestBridgeHandler.swift; sourceTree = ""; }; - 1A63A1432F669047005FF070 /* WxpGetBaseApiUrlBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpGetBaseApiUrlBridgeHandler.swift; sourceTree = ""; }; + 1A63A1432F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpGetEnvBaseUrlBridgeHandler.swift; sourceTree = ""; }; 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpShowToastBridgeHandler.swift; sourceTree = ""; }; 1A63A1342F669047005FF070 /* WxpBridgeContracts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeContracts.swift; sourceTree = ""; }; 1A63A1352F669047005FF070 /* WxpBridgeEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeEmitter.swift; sourceTree = ""; }; @@ -245,7 +245,7 @@ isa = PBXGroup; children = ( 1A63A1302F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift */, - 1A63A1432F669047005FF070 /* WxpGetBaseApiUrlBridgeHandler.swift */, + 1A63A1432F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift */, 1A63A1312F669047005FF070 /* WxpOpenUrlBridgeHandler.swift */, 1A63A1322F669047005FF070 /* WxpPayRequestBridgeHandler.swift */, 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */, @@ -636,7 +636,7 @@ 1A63A13F2F669047005FF070 /* WxpBridgeContracts.swift in Sources */, 1A63A1402F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift in Sources */, 1A63A1412F669047005FF070 /* WxpBridgeEmitter.swift in Sources */, - 1A63A1422F669047005FF070 /* WxpGetBaseApiUrlBridgeHandler.swift in Sources */, + 1A63A1422F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift in Sources */, 1A63A1442F669047005FF070 /* WxpShowToastBridgeHandler.swift in Sources */, 1ABD14262EDBF9890019AA3A /* WxpLoadingService.swift in Sources */, 1A039F712F08131000C1CA76 /* TestPanelViewController.swift in Sources */, From acdf897b3428e074fccd04525c78753d07301b8b Mon Sep 17 00:00:00 2001 From: zjiecode Date: Sun, 22 Mar 2026 18:52:52 +0800 Subject: [PATCH 3/8] =?UTF-8?q?Android=20&&=20iOS=20=E4=BC=98=E5=8C=96JS?= =?UTF-8?q?=E6=A1=A5=E7=BB=93=E6=9E=84=EF=BC=8C=E7=BB=9F=E4=B8=80host?= =?UTF-8?q?=E7=99=BD=E5=90=8D=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wxpusher/page/web/WxpWebViewFragment.kt | 12 +- .../page/web/bridge/WxpWebBridgeManager.kt | 14 +- .../Page/WebView/WxpWebViewController.swift | 175 ++---------------- .../WebView/bridge/WxpBridgeContext.swift | 32 ++++ .../WebView/bridge/WxpBridgeContracts.swift | 102 +++++++++- .../WebView/bridge/WxpWebBridgeManager.swift | 48 +++-- .../WxpGetEnvBaseUrlBridgeHandler.swift | 15 +- .../WxpGetLoginInfoBridgeHandler.swift | 27 +-- .../handlers/WxpOpenUrlBridgeHandler.swift | 8 +- .../handlers/WxpPayRequestBridgeHandler.swift | 51 +++-- .../handlers/WxpShowToastBridgeHandler.swift | 9 +- .../wxpusher.xcodeproj/project.pbxproj | 4 + .../smjcco/wxpusher/web/WxpWebHostPolicy.kt | 27 +++ 13 files changed, 251 insertions(+), 273 deletions(-) create mode 100644 iosApp/wxpusher/Page/WebView/bridge/WxpBridgeContext.swift create mode 100644 shared/src/commonMain/kotlin/com/smjcco/wxpusher/web/WxpWebHostPolicy.kt diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt index 2f9ffaa4..0cc8c64f 100644 --- a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt @@ -45,6 +45,7 @@ import com.smjcco.wxpusher.page.web.bridge.BridgeContext import com.smjcco.wxpusher.page.web.bridge.WxpWebBridgeManager import com.smjcco.wxpusher.utils.DeviceUtils import com.smjcco.wxpusher.web.AppFeVersionManager +import com.smjcco.wxpusher.web.WxpWebHostPolicy import com.smjcco.wxpusher.wxapi.WxpWeixinOpenManager import com.tencent.mm.opensdk.modelmsg.SendMessageToWX @@ -55,15 +56,6 @@ open class WxpWebViewFragment : WxpBaseFragment() { private const val DEVICE_PLATFORM_KEY = "platform" private const val DEVICE_VERSION_NAME_KEY = "versionName" - // 白名单域名列表 - private val WHITELIST_HOSTS = setOf( - "wxpusher.zjiecode.com", - "wxpusher.test.zjiecode.com", - "10.0.0.11", - "10.0.2.2", - "127.0.0.1" - ) - fun newInstance(url: String): WxpWebViewFragment { val fragment = WxpWebViewFragment() val args = Bundle() @@ -419,7 +411,7 @@ open class WxpWebViewFragment : WxpBaseFragment() { } private fun isHostInWhitelist(host: String?): Boolean { - return host != null && WHITELIST_HOSTS.contains(host) + return WxpWebHostPolicy.isHostInWhitelist(host) } private fun createRequestWithTokenIfNeeded(url: String): Map { diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt index 4dd06962..941c962e 100644 --- a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt @@ -6,22 +6,12 @@ import com.smjcco.wxpusher.page.web.bridge.handlers.OpenUrlBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.PayRequestBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.ShowToastBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.WxpGetEnvBaseUrlBridgeHandler +import com.smjcco.wxpusher.web.WxpWebHostPolicy class WxpWebBridgeManager( private val context: BridgeContext, - private val whitelistHosts: Set = DEFAULT_WHITELIST_HOSTS, private val parser: WxpBridgeMessageParser = WxpBridgeMessageParser() ) { - companion object { - val DEFAULT_WHITELIST_HOSTS = setOf( - "wxpusher.zjiecode.com", - "wxpusher.test.zjiecode.com", - "10.0.0.11", - "10.0.2.2", - "127.0.0.1" - ) - } - private val emitter = WxpBridgeEmitter(context) private val handlers = mutableMapOf() @@ -82,6 +72,6 @@ class WxpWebBridgeManager( } private fun isHostInWhitelist(host: String?): Boolean { - return host != null && whitelistHosts.contains(host) + return WxpWebHostPolicy.isHostInWhitelist(host) } } diff --git a/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift b/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift index 757957c2..9ffa238d 100644 --- a/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift +++ b/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift @@ -4,14 +4,6 @@ import shared @preconcurrency import WebKit class WxpWebViewController: UIViewController { - // 白名单域名列表 - // 在白名单的域名会携带token,可以调用桥,不会显示警告提醒 - private let whitelistHosts: Set = [ - "wxpusher.zjiecode.com", - "wxpusher.test.zjiecode.com", - "10.0.0.11", - "127.0.0.1", - ] private let DeviceTokenKey = "deviceToken" private let DevicePlatformKey = "platform" private let DeviceVersionNameKey = "versionName" @@ -24,6 +16,7 @@ class WxpWebViewController: UIViewController { private var progressTimer: Timer? private var webDescription: String = "" private var showThirdPartyBanner = true + private var bridgeContext: WxpBridgeContext? private var bridgeManager: WxpWebBridgeManager? private var bridgeMessageHandlerProxy: WxpWeakScriptMessageHandler? private var titleObservation: NSKeyValueObservation? @@ -240,59 +233,16 @@ class WxpWebViewController: UIViewController { } private func setupBridge() { - guard webView != nil else { + guard let webView else { return } let emitter = WxpBridgeEmitter { [weak self] js in self?.webView?.evaluateJavaScript(js, completionHandler: nil) } - let manager = WxpWebBridgeManager( - whitelistHosts: whitelistHosts, - parser: WxpBridgeMessageParser(), - emitter: emitter, - currentHostProvider: { [weak self] in - self?.webView?.url?.host - } - ) - let payRequestHandler = WxpPayRequestBridgeHandler( - paymentRequester: { data, completion in - WxpWeixinOpenManager.shared.requestPayment(with: data, completion: completion) - }, - resultConverter: { [weak self] result in - self?.convertPaymentResultToData(result) ?? [:] - }, - eventSender: { [weak manager] action, data in - manager?.sendNativeEvent(action: action, data: data) - } - ) - let openUrlHandler = WxpOpenUrlBridgeHandler() - let getLoginInfoHandler = WxpGetLoginInfoBridgeHandler( - loginInfoProvider: { - WxpAppDataService.shared.getLoginInfo() - }, - dictionaryBuilder: { [weak self] object in - self?.buildDictionary(from: object) ?? [:] - } - ) - let getEnvBaseUrlHandler = WxpGetEnvBaseUrlBridgeHandler() - let showToastHandler = WxpShowToastBridgeHandler() - - manager.registerHandler(action: "payRequest", requiresWhitelist: true) { request, completion in - payRequestHandler.handle(request, completion: completion) - } - manager.registerHandler(action: "openUrl", requiresWhitelist: false) { request, completion in - openUrlHandler.handle(request, completion: completion) - } - manager.registerHandler(action: "getLoginInfo", requiresWhitelist: true) { _, completion in - getLoginInfoHandler.handle(completion: completion) - } - manager.registerHandler(action: "getEnvBaseUrl", requiresWhitelist: true) { _, completion in - getEnvBaseUrlHandler.handle(completion: completion) - } - manager.registerHandler(action: "showToast", requiresWhitelist: true) { request, completion in - showToastHandler.handle(request, completion: completion) - } - bridgeManager = manager + let context = WxpBridgeContext(webView: webView, viewController: self) + context.updateCurrentUrl(webView.url) + bridgeContext = context + bridgeManager = WxpWebBridgeManager(context: context, emitter: emitter) let proxy = WxpWeakScriptMessageHandler(target: self) bridgeMessageHandlerProxy = proxy configuration.userContentController.add(proxy, name: "wxpusher") @@ -507,6 +457,7 @@ class WxpWebViewController: UIViewController { private func loadWebContent() { if(url != nil){ let request = createRequestWithTokenIfNeeded(for: url!) + bridgeContext?.updateCurrentUrl(request.url) webView!.load(request) } } @@ -537,6 +488,7 @@ class WxpWebViewController: UIViewController { return } let request = self.createRequestWithTokenIfNeeded(for: finalUrl) + self.bridgeContext?.updateCurrentUrl(request.url) self.webView?.load(request) } } @@ -573,8 +525,7 @@ class WxpWebViewController: UIViewController { /// 检查主机是否在白名单内 private func isHostInWhitelist(_ host: String?) -> Bool { - guard let host = host else { return false } - return whitelistHosts.contains(host) + return WxpWebHostPolicy.shared.isHostInWhitelist(host: host) } private func shouldCheckAppFeVersion(_ url: URL) -> Bool { @@ -793,12 +744,14 @@ extension WxpWebViewController: WKNavigationDelegate { let newRequest = createRequestWithTokenIfNeeded(for: url) // 如果当前请求没有 token header,重新加载带 token 的请求 if newRequest != wxpLoadRequest && navigationAction.request.value(forHTTPHeaderField: DeviceTokenKey) == nil { + bridgeContext?.updateCurrentUrl(newRequest.url) webView.load(newRequest) wxpLoadRequest = newRequest decisionHandler(.cancel) return } } + bridgeContext?.updateCurrentUrl(url) decisionHandler(.allow) return } @@ -810,6 +763,7 @@ extension WxpWebViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { // 记录开始加载时间 loadingStartTime = Date() + bridgeContext?.updateCurrentUrl(webView.url) webDescription = "" progressView.progress = 0.0 @@ -830,6 +784,7 @@ extension WxpWebViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { // 设置网页标题 + bridgeContext?.updateCurrentUrl(webView.url) if let webTitle = webView.title, !webTitle.isEmpty { setPageTitle(title: webTitle) } else { @@ -901,110 +856,6 @@ extension WxpWebViewController: WKScriptMessageHandler { } bridgeManager?.onMessage(message.body) } - - private func convertPaymentResultToData(_ result: Result) -> [String: Any] { - switch result { - case .success(let success): - return ["success": success, "message": success ? "支付成功" : "支付失败"] - case .failure(let error): - return ["success": false, "message": error.localizedDescription] - } - } - - private func buildDictionary(from object: Any) -> [String: Any] { - if(isKmpModel(of: type(of: object))){ - let jsonStr = WxpSerializationUtils.shared.toJson(value: object, serializer: WxpLoginInfo.companion.serializer()) - guard let jsonData = jsonStr?.data(using: .utf8) else { - print("无法将字符串转换为Data") - return [:] - } - - do { - let json = try JSONSerialization.jsonObject(with: jsonData, options: []) - return json as? [String: Any] ?? [:] - } catch { - print("JSON解析错误: \(error)") - return [:] - } - } - guard let value = normalizeBridgeValue(object) else { - return [:] - } - return value as? [String: Any] ?? [:] - } - - private func isKmpModel(of type: Any.Type) -> Bool { - // 1) 模块名判断:shared.xxx - let fullTypeName = String(reflecting: type) // 比 String(describing:) 更稳定 - if fullTypeName.hasPrefix("Shared") { - return true; - } - - // 2) 是否继承 KotlinBase(KMP导出对象常见父类) - if let cls = type as? AnyClass, - let kotlinBaseClass = NSClassFromString("KotlinBase"), - cls.isSubclass(of: kotlinBaseClass) { - return true - } - - return false; - } - - private func normalizeBridgeValue(_ value: Any) -> Any? { - let mirror = Mirror(reflecting: value) - - // Optional 递归拆包 - if mirror.displayStyle == .optional { - guard let (_, someValue) = mirror.children.first else { - return nil - } - return normalizeBridgeValue(someValue) - } - - if let displayStyle = mirror.displayStyle { - switch displayStyle { - case .collection, .set: - return mirror.children.compactMap { normalizeBridgeValue($0.value) } - case .dictionary: - var dict: [String: Any] = [:] - for child in mirror.children { - let pairMirror = Mirror(reflecting: child.value) - let pairValues = Array(pairMirror.children.map(\.value)) - if pairValues.count == 2, - let key = normalizeBridgeValue(pairValues[0]), - let val = normalizeBridgeValue(pairValues[1]) { - dict[String(describing: key)] = val - } - } - return dict - case .struct, .class: - var dict: [String: Any] = [:] - for child in mirror.children { - guard let key = child.label, - let val = normalizeBridgeValue(child.value) else { - continue - } - dict[key] = val - } - return dict - case .enum: - return String(describing: value) - case .tuple: - return mirror.children.compactMap { normalizeBridgeValue($0.value) } - default: - break - } - } - - // 基础类型保持原样,复杂未知类型降级为字符串,避免 JSON 序列化失败 - if value is NSString || value is NSNumber || value is NSNull || value is Date { - return value - } - if value is String || value is Int || value is Double || value is Float || value is Bool { - return value - } - return String(describing: value) - } } extension WxpWebViewController: WKUIDelegate { diff --git a/iosApp/wxpusher/Page/WebView/bridge/WxpBridgeContext.swift b/iosApp/wxpusher/Page/WebView/bridge/WxpBridgeContext.swift new file mode 100644 index 00000000..07c9324b --- /dev/null +++ b/iosApp/wxpusher/Page/WebView/bridge/WxpBridgeContext.swift @@ -0,0 +1,32 @@ +import Foundation +import UIKit + +@preconcurrency import WebKit + +final class WxpBridgeContext { + weak var webView: WKWebView? + weak var viewController: UIViewController? + private var currentUrlSnapshot: URL? + + init(webView: WKWebView?, viewController: UIViewController?) { + self.webView = webView + self.viewController = viewController + self.currentUrlSnapshot = webView?.url + } + + var currentUrl: URL? { + currentUrlSnapshot + } + + var currentHost: String? { + currentUrl?.host + } + + func updateCurrentUrl(_ url: URL?) { + currentUrlSnapshot = url + } + + func evaluateJavaScript(_ script: String) { + webView?.evaluateJavaScript(script, completionHandler: nil) + } +} diff --git a/iosApp/wxpusher/Page/WebView/bridge/WxpBridgeContracts.swift b/iosApp/wxpusher/Page/WebView/bridge/WxpBridgeContracts.swift index dd0397e6..84ebf2f5 100644 --- a/iosApp/wxpusher/Page/WebView/bridge/WxpBridgeContracts.swift +++ b/iosApp/wxpusher/Page/WebView/bridge/WxpBridgeContracts.swift @@ -1,4 +1,5 @@ import Foundation +import shared struct WxpBridgeRequest { let action: String @@ -21,4 +22,103 @@ struct WxpBridgeResponse { } typealias WxpBridgeCompletion = (WxpBridgeResponse) -> Void -typealias WxpBridgeActionHandler = (_ request: WxpBridgeRequest, _ completion: @escaping WxpBridgeCompletion) -> Void + +protocol WxpBridgeActionHandler { + func handle(request: WxpBridgeRequest, context: WxpBridgeContext, emitter: WxpBridgeEmitter) +} + +extension WxpBridgeActionHandler { + func buildDictionary(from object: Any) -> [String: Any] { + if isKmpModel(of: type(of: object)) { + let jsonStr = WxpSerializationUtils.shared.toJson(value: object, serializer: WxpLoginInfo.companion.serializer()) + guard let jsonData = jsonStr?.data(using: .utf8) else { + return [:] + } + + do { + let json = try JSONSerialization.jsonObject(with: jsonData, options: []) + return json as? [String: Any] ?? [:] + } catch { + return [:] + } + } + guard let value = normalizeBridgeValue(object) else { + return [:] + } + return value as? [String: Any] ?? [:] + } + + private func isKmpModel(of type: Any.Type) -> Bool { + let fullTypeName = String(reflecting: type) + if fullTypeName.hasPrefix("Shared") { + return true + } + + if let cls = type as? AnyClass, + let kotlinBaseClass = NSClassFromString("KotlinBase"), + cls.isSubclass(of: kotlinBaseClass) { + return true + } + + return false + } + + private func normalizeBridgeValue(_ value: Any) -> Any? { + let mirror = Mirror(reflecting: value) + + if mirror.displayStyle == .optional { + guard let (_, someValue) = mirror.children.first else { + return nil + } + return normalizeBridgeValue(someValue) + } + + if let displayStyle = mirror.displayStyle { + switch displayStyle { + case .collection, .set: + return mirror.children.compactMap { normalizeBridgeValue($0.value) } + case .dictionary: + var dict: [String: Any] = [:] + for child in mirror.children { + let pairMirror = Mirror(reflecting: child.value) + let pairValues = Array(pairMirror.children.map(\.value)) + if pairValues.count == 2, + let key = normalizeBridgeValue(pairValues[0]), + let val = normalizeBridgeValue(pairValues[1]) { + dict[String(describing: key)] = val + } + } + return dict + case .struct, .class: + var dict: [String: Any] = [:] + for child in mirror.children { + guard let key = child.label, + let val = normalizeBridgeValue(child.value) else { + continue + } + dict[key] = val + } + return dict + case .enum: + return String(describing: value) + case .tuple: + return mirror.children.compactMap { normalizeBridgeValue($0.value) } + default: + break + } + } + + if value is NSString || value is NSNumber || value is NSNull || value is Date { + return value + } + if value is String || value is Int || value is Double || value is Float || value is Bool { + return value + } + return String(describing: value) + } +} + +struct WxpRegisteredBridgeHandler { + let requiresWhitelist: Bool + let handler: WxpBridgeActionHandler +} diff --git a/iosApp/wxpusher/Page/WebView/bridge/WxpWebBridgeManager.swift b/iosApp/wxpusher/Page/WebView/bridge/WxpWebBridgeManager.swift index 7723443d..ad260f7a 100644 --- a/iosApp/wxpusher/Page/WebView/bridge/WxpWebBridgeManager.swift +++ b/iosApp/wxpusher/Page/WebView/bridge/WxpWebBridgeManager.swift @@ -1,31 +1,21 @@ import Foundation +import shared final class WxpWebBridgeManager { - private struct BridgeHandler { - let requiresWhitelist: Bool - let handler: WxpBridgeActionHandler - } - - private let whitelistHosts: Set + private let context: WxpBridgeContext private let parser: WxpBridgeMessageParser private let emitter: WxpBridgeEmitter - private let currentHostProvider: () -> String? - private var handlers: [String: BridgeHandler] = [:] + private var handlers: [String: WxpRegisteredBridgeHandler] = [:] init( - whitelistHosts: Set, - parser: WxpBridgeMessageParser, - emitter: WxpBridgeEmitter, - currentHostProvider: @escaping () -> String? + context: WxpBridgeContext, + parser: WxpBridgeMessageParser = WxpBridgeMessageParser(), + emitter: WxpBridgeEmitter ) { - self.whitelistHosts = whitelistHosts + self.context = context self.parser = parser self.emitter = emitter - self.currentHostProvider = currentHostProvider - } - - func registerHandler(action: String, requiresWhitelist: Bool, handler: @escaping WxpBridgeActionHandler) { - handlers[action] = BridgeHandler(requiresWhitelist: requiresWhitelist, handler: handler) + registerDefaultHandlers() } func onMessage(_ body: Any) { @@ -35,8 +25,16 @@ final class WxpWebBridgeManager { dispatch(request) } - func sendNativeEvent(action: String, data: [String: Any]) { - emitter.sendNativeEvent(action: action, data: data) + private func registerDefaultHandlers() { + registerHandler(action: "payRequest", requiresWhitelist: true, handler: WxpPayRequestBridgeHandler()) + registerHandler(action: "openUrl", requiresWhitelist: false, handler: WxpOpenUrlBridgeHandler()) + registerHandler(action: "getLoginInfo", requiresWhitelist: true, handler: WxpGetLoginInfoBridgeHandler()) + registerHandler(action: "getEnvBaseUrl", requiresWhitelist: true, handler: WxpGetEnvBaseUrlBridgeHandler()) + registerHandler(action: "showToast", requiresWhitelist: true, handler: WxpShowToastBridgeHandler()) + } + + private func registerHandler(action: String, requiresWhitelist: Bool, handler: WxpBridgeActionHandler) { + handlers[action] = WxpRegisteredBridgeHandler(requiresWhitelist: requiresWhitelist, handler: handler) } private func dispatch(_ request: WxpBridgeRequest) { @@ -47,21 +45,17 @@ final class WxpWebBridgeManager { ) return } - if bridgeHandler.requiresWhitelist && !isHostInWhitelist(currentHostProvider()) { + if bridgeHandler.requiresWhitelist && !isHostInWhitelist(context.currentHost) { emitter.sendBridgeCallback( callbackId: request.callbackId, response: .fail("host is not allowed") ) return } - bridgeHandler.handler(request) { [weak self] response in - self?.emitter.sendBridgeCallback(callbackId: request.callbackId, response: response) - } + bridgeHandler.handler.handle(request: request, context: context, emitter: emitter) } private func isHostInWhitelist(_ host: String?) -> Bool { - guard let host else { return false } - return whitelistHosts.contains(host) + return WxpWebHostPolicy.shared.isHostInWhitelist(host: host) } } - diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.swift index f0d55f8b..4e9c4c62 100644 --- a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.swift +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetEnvBaseUrlBridgeHandler.swift @@ -1,11 +1,14 @@ import Foundation import shared -final class WxpGetEnvBaseUrlBridgeHandler { - func handle(completion: @escaping WxpBridgeCompletion) { - completion(.ok([ - "apiBaseUrl": WxpConfig.shared.baseUrl, - "appFeBaseUrl": WxpConfig.shared.appFeUrl - ])) +final class WxpGetEnvBaseUrlBridgeHandler: WxpBridgeActionHandler { + func handle(request: WxpBridgeRequest, context: WxpBridgeContext, emitter: WxpBridgeEmitter) { + emitter.sendBridgeCallback( + callbackId: request.callbackId, + response: .ok([ + "apiBaseUrl": WxpConfig.shared.baseUrl, + "appFeBaseUrl": WxpConfig.shared.appFeUrl + ]) + ) } } diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetLoginInfoBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetLoginInfoBridgeHandler.swift index 4763b6b3..2a62b418 100644 --- a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetLoginInfoBridgeHandler.swift +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpGetLoginInfoBridgeHandler.swift @@ -1,26 +1,15 @@ import Foundation import shared -final class WxpGetLoginInfoBridgeHandler { - typealias LoginInfoProvider = () -> Any? - typealias DictionaryBuilder = (_ object: Any) -> [String: Any] - - private let loginInfoProvider: LoginInfoProvider - private let dictionaryBuilder: DictionaryBuilder - - init( - loginInfoProvider: @escaping LoginInfoProvider, - dictionaryBuilder: @escaping DictionaryBuilder - ) { - self.loginInfoProvider = loginInfoProvider - self.dictionaryBuilder = dictionaryBuilder - } - - func handle(completion: @escaping WxpBridgeCompletion) { - guard let loginInfo = loginInfoProvider() else { - completion(.ok([:])) +final class WxpGetLoginInfoBridgeHandler: WxpBridgeActionHandler { + func handle(request: WxpBridgeRequest, context: WxpBridgeContext, emitter: WxpBridgeEmitter) { + guard let loginInfo = WxpAppDataService.shared.getLoginInfo() else { + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .ok([:])) return } - completion(.ok(dictionaryBuilder(loginInfo))) + emitter.sendBridgeCallback( + callbackId: request.callbackId, + response: .ok(buildDictionary(from: loginInfo)) + ) } } diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpOpenUrlBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpOpenUrlBridgeHandler.swift index dcdde95e..6390ff9c 100644 --- a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpOpenUrlBridgeHandler.swift +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpOpenUrlBridgeHandler.swift @@ -1,12 +1,12 @@ import Foundation -final class WxpOpenUrlBridgeHandler { - func handle(_ request: WxpBridgeRequest, completion: @escaping WxpBridgeCompletion) { +final class WxpOpenUrlBridgeHandler: WxpBridgeActionHandler { + func handle(request: WxpBridgeRequest, context: WxpBridgeContext, emitter: WxpBridgeEmitter) { guard let url = request.data["url"] as? String, !url.isEmpty else { - completion(.fail("url is empty")) + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .fail("url is empty")) return } WxpJumpPageUtils.jumpToWebUrl(url: url) - completion(.ok(["opened": true])) + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .ok(["opened": true])) } } diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpPayRequestBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpPayRequestBridgeHandler.swift index 28d4e40a..781628b1 100644 --- a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpPayRequestBridgeHandler.swift +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpPayRequestBridgeHandler.swift @@ -1,40 +1,37 @@ import Foundation final class WxpPayRequestBridgeHandler { - typealias PaymentRequester = (_ data: [String: Any], _ completion: @escaping (Result) -> Void) -> Void - typealias ResultConverter = (_ result: Result) -> [String: Any] - typealias EventSender = (_ action: String, _ data: [String: Any]) -> Void - - private let paymentRequester: PaymentRequester - private let resultConverter: ResultConverter - private let eventSender: EventSender - - init( - paymentRequester: @escaping PaymentRequester, - resultConverter: @escaping ResultConverter, - eventSender: @escaping EventSender - ) { - self.paymentRequester = paymentRequester - self.resultConverter = resultConverter - self.eventSender = eventSender - } +} - func handle(_ request: WxpBridgeRequest, completion: @escaping WxpBridgeCompletion) { - paymentRequester(request.data) { [weak self] result in - guard let self else { - completion(.fail("controller released")) - return - } +extension WxpPayRequestBridgeHandler: WxpBridgeActionHandler { + func handle(request: WxpBridgeRequest, context: WxpBridgeContext, emitter: WxpBridgeEmitter) { + WxpWeixinOpenManager.shared.requestPayment(with: request.data) { result in DispatchQueue.main.async { - let responseData = self.resultConverter(result) - self.eventSender("payResponse", responseData) + let responseData = Self.convertPaymentResultToData(result) + emitter.sendNativeEvent(action: "payResponse", data: responseData) switch result { case .success: - completion(.ok(responseData)) + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .ok(responseData)) case .failure(let error): - completion(.fail(error.localizedDescription)) + emitter.sendBridgeCallback( + callbackId: request.callbackId, + response: WxpBridgeResponse( + success: false, + data: responseData, + error: error.localizedDescription + ) + ) } } } } + + private static func convertPaymentResultToData(_ result: Result) -> [String: Any] { + switch result { + case .success(let success): + return ["success": success, "message": success ? "支付成功" : "支付失败"] + case .failure(let error): + return ["success": false, "message": error.localizedDescription] + } + } } diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpShowToastBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpShowToastBridgeHandler.swift index 4b80fd72..be4c8251 100644 --- a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpShowToastBridgeHandler.swift +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpShowToastBridgeHandler.swift @@ -1,14 +1,13 @@ import Foundation import shared -final class WxpShowToastBridgeHandler { - - func handle(_ request: WxpBridgeRequest, completion: @escaping WxpBridgeCompletion) { +final class WxpShowToastBridgeHandler: WxpBridgeActionHandler { + func handle(request: WxpBridgeRequest, context: WxpBridgeContext, emitter: WxpBridgeEmitter) { guard let msg = request.data["msg"] as? String, !msg.isEmpty else { - completion(.fail("msg is empty")) + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .fail("msg is empty")) return } WxpToastUtils.shared.showToast(msg: msg) - completion(.ok()) + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .ok()) } } diff --git a/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj b/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj index f5fdcd97..21bd5857 100644 --- a/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj +++ b/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 1A63A1412F669047005FF070 /* WxpBridgeEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1352F669047005FF070 /* WxpBridgeEmitter.swift */; }; 1A63A1422F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1432F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift */; }; 1A63A1442F669047005FF070 /* WxpShowToastBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */; }; + 1A63A1472F669047005FF070 /* WxpBridgeContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1462F669047005FF070 /* WxpBridgeContext.swift */; }; 1A962E672E08610200C966A3 /* KtSwiftJumpPageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E662E08610200C966A3 /* KtSwiftJumpPageUtils.m */; }; 1A962E6A2E08625700C966A3 /* WxpJumpPageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E692E08625700C966A3 /* WxpJumpPageUtils.swift */; }; 1A962E6F2E09B3D200C966A3 /* WxpBaseUIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E6E2E09B3D200C966A3 /* WxpBaseUIViewController.swift */; }; @@ -83,6 +84,7 @@ 1A63A1322F669047005FF070 /* WxpPayRequestBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpPayRequestBridgeHandler.swift; sourceTree = ""; }; 1A63A1432F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpGetEnvBaseUrlBridgeHandler.swift; sourceTree = ""; }; 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpShowToastBridgeHandler.swift; sourceTree = ""; }; + 1A63A1462F669047005FF070 /* WxpBridgeContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeContext.swift; sourceTree = ""; }; 1A63A1342F669047005FF070 /* WxpBridgeContracts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeContracts.swift; sourceTree = ""; }; 1A63A1352F669047005FF070 /* WxpBridgeEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeEmitter.swift; sourceTree = ""; }; 1A63A1362F669047005FF070 /* WxpBridgeMessageParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeMessageParser.swift; sourceTree = ""; }; @@ -258,6 +260,7 @@ children = ( 1A63A1332F669047005FF070 /* handlers */, 1A63A1342F669047005FF070 /* WxpBridgeContracts.swift */, + 1A63A1462F669047005FF070 /* WxpBridgeContext.swift */, 1A63A1352F669047005FF070 /* WxpBridgeEmitter.swift */, 1A63A1362F669047005FF070 /* WxpBridgeMessageParser.swift */, 1A63A1372F669047005FF070 /* WxpWeakScriptMessageHandler.swift */, @@ -634,6 +637,7 @@ 1A63A13D2F669047005FF070 /* WxpPayRequestBridgeHandler.swift in Sources */, 1A63A13E2F669047005FF070 /* WxpWeakScriptMessageHandler.swift in Sources */, 1A63A13F2F669047005FF070 /* WxpBridgeContracts.swift in Sources */, + 1A63A1472F669047005FF070 /* WxpBridgeContext.swift in Sources */, 1A63A1402F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift in Sources */, 1A63A1412F669047005FF070 /* WxpBridgeEmitter.swift in Sources */, 1A63A1422F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift in Sources */, diff --git a/shared/src/commonMain/kotlin/com/smjcco/wxpusher/web/WxpWebHostPolicy.kt b/shared/src/commonMain/kotlin/com/smjcco/wxpusher/web/WxpWebHostPolicy.kt new file mode 100644 index 00000000..92cd3b23 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/smjcco/wxpusher/web/WxpWebHostPolicy.kt @@ -0,0 +1,27 @@ +package com.smjcco.wxpusher.web + +import io.ktor.http.URLBuilder + +object WxpWebHostPolicy { + val DEFAULT_WHITELIST_HOSTS = setOf( + "wxpusher.zjiecode.com", + "wxpusher.test.zjiecode.com", + "10.0.0.11", + "10.0.2.2", + "127.0.0.1" + ) + + fun isHostInWhitelist(host: String?): Boolean { + return host != null && DEFAULT_WHITELIST_HOSTS.contains(host) + } + + fun isUrlInWhitelist(url: String?): Boolean { + if (url.isNullOrBlank()) { + return false + } + val host = runCatching { + URLBuilder(url).build().host + }.getOrNull() + return isHostInWhitelist(host) + } +} From dcde8570184f08edffe410d46a623608112a61fd Mon Sep 17 00:00:00 2001 From: zjiecode Date: Sun, 22 Mar 2026 23:36:18 +0800 Subject: [PATCH 4/8] =?UTF-8?q?Android=20&&=20iOS=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=BD=91=E9=A1=B5=E8=8F=9C=E5=8D=95=E6=8E=A7=E5=88=B6=E7=9A=84?= =?UTF-8?q?JS=E6=A1=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wxpusher/page/web/WxpWebViewFragment.kt | 103 ++++++++++-- .../page/web/bridge/WxpWebBridgeManager.kt | 4 + .../handlers/SetWebBottomBarBridgeHandler.kt | 49 ++++++ .../handlers/SetWebOptionMenuBridgeHandler.kt | 88 ++++++++++ iosApp/Podfile | 1 - iosApp/Podfile.lock | 2 +- .../Page/WebView/WxpWebViewController.swift | 158 +++++++++++++----- .../WebView/bridge/WxpWebBridgeManager.swift | 2 + .../WxpSetWebBottomBarBridgeHandler.swift | 39 +++++ .../WxpSetWebOptionMenuBridgeHandler.swift | 78 +++++++++ .../wxpusher.xcodeproj/project.pbxproj | 8 + 11 files changed, 473 insertions(+), 59 deletions(-) create mode 100644 androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/SetWebBottomBarBridgeHandler.kt create mode 100644 androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/SetWebOptionMenuBridgeHandler.kt create mode 100644 iosApp/wxpusher/Page/WebView/bridge/handlers/WxpSetWebBottomBarBridgeHandler.swift create mode 100644 iosApp/wxpusher/Page/WebView/bridge/handlers/WxpSetWebOptionMenuBridgeHandler.swift diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt index 0cc8c64f..b3fdbb07 100644 --- a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/WxpWebViewFragment.kt @@ -55,6 +55,16 @@ open class WxpWebViewFragment : WxpBaseFragment() { private const val DEVICE_TOKEN_KEY = "deviceToken" private const val DEVICE_PLATFORM_KEY = "platform" private const val DEVICE_VERSION_NAME_KEY = "versionName" + const val OPTION_MENU_COPY_LINK = "copy_link" + const val OPTION_MENU_WEIXIN_SHARE = "weixin_share" + const val OPTION_MENU_SHARE = "share" + const val OPTION_MENU_OPEN_BROWSER = "open_browser" + val SUPPORTED_OPTION_MENU_KEYS = setOf( + OPTION_MENU_COPY_LINK, + OPTION_MENU_WEIXIN_SHARE, + OPTION_MENU_SHARE, + OPTION_MENU_OPEN_BROWSER + ) fun newInstance(url: String): WxpWebViewFragment { val fragment = WxpWebViewFragment() @@ -82,6 +92,9 @@ open class WxpWebViewFragment : WxpBaseFragment() { private var showThirdPartyBanner = true private var lastLoadRequest: String? = null private var webDescription: String? = null + private var optionMenuVisibleOverride: Boolean? = null + private var optionMenuItemsOverride: Set? = null + private var bottomBarVisibleOverride: Boolean? = null private lateinit var bridgeContext: BridgeContext private lateinit var webBridgeManager: WxpWebBridgeManager @@ -454,22 +467,78 @@ open class WxpWebViewFragment : WxpBaseFragment() { } private fun updateMenuVisibility(url: String?) { - if (url == null) return + applyWebChromeVisibility(url) + } + + private fun applyWebChromeVisibility(url: String?) { + val resolvedUrl = url ?: (webView.url ?: targetUrl) + activity?.invalidateOptionsMenu() + webOptBannerView.visibility = if (resolveBottomBarVisible(resolvedUrl)) View.VISIBLE else View.GONE + } + private fun resolveOptionMenuVisible(url: String?): Boolean { + optionMenuVisibleOverride?.let { + return it + } + if (url.isNullOrBlank()) { + return true + } val uri = url.toUri() - //浏览器左上角的按钮 - if (isHostInWhitelist(uri.host) && uri.path?.contains("wxuser") == true) { - // 订阅管理页面,隐藏菜单 - activity?.invalidateOptionsMenu() + return !(isHostInWhitelist(uri.host) && uri.path?.contains("wxuser") == true) + } + + private fun resolveBottomBarVisible(url: String?): Boolean { + bottomBarVisibleOverride?.let { + return it + } + if (url.isNullOrBlank()) { + return true } + val uri = url.toUri() + return !(isHostInWhitelist(uri.host) && uri.path?.contains("app") == true) + } - //下面的导航按钮 - if (isHostInWhitelist(uri.host) && uri.path?.contains("app") == true) { - webOptBannerView.visibility = View.GONE + private fun resolveEnabledOptionMenuItems(): Set { + val overrideItems = optionMenuItemsOverride + return if (overrideItems == null) { + SUPPORTED_OPTION_MENU_KEYS } else { - webOptBannerView.visibility = View.VISIBLE + overrideItems.intersect(SUPPORTED_OPTION_MENU_KEYS) } + } + private fun optionMenuKeyToItemId(optionKey: String): Int? { + return when (optionKey) { + OPTION_MENU_COPY_LINK -> R.id.action_copy_link + OPTION_MENU_WEIXIN_SHARE -> R.id.action_weixin_share + OPTION_MENU_SHARE -> R.id.action_share + OPTION_MENU_OPEN_BROWSER -> R.id.action_open_browser + else -> null + } + } + + fun setOptionMenuVisibleOverride(visible: Boolean?) { + activity?.runOnUiThread { + optionMenuVisibleOverride = visible + val currentUrl = if (::webView.isInitialized) webView.url ?: targetUrl else targetUrl + applyWebChromeVisibility(currentUrl) + } + } + + fun setOptionMenuItemsOverride(options: Set?) { + activity?.runOnUiThread { + optionMenuItemsOverride = options?.intersect(SUPPORTED_OPTION_MENU_KEYS) + val currentUrl = if (::webView.isInitialized) webView.url ?: targetUrl else targetUrl + applyWebChromeVisibility(currentUrl) + } + } + + fun setBottomBarVisibleOverride(visible: Boolean?) { + activity?.runOnUiThread { + bottomBarVisibleOverride = visible + val currentUrl = if (::webView.isInitialized) webView.url ?: targetUrl else targetUrl + applyWebChromeVisibility(currentUrl) + } } private fun updateWebOptionBtnStatus() { @@ -516,14 +585,22 @@ open class WxpWebViewFragment : WxpBaseFragment() { */ fun onActivityCreateOptionsMenu(menu: Menu?, menuInflater: MenuInflater): Boolean { val url = webView.url ?: targetUrl - val uri = url.toUri() - - // 如果是订阅管理页面,不显示菜单 - if (isHostInWhitelist(uri.host) && uri.path?.contains("wxuser") == true) { + if (!resolveOptionMenuVisible(url)) { + return false + } + val enabledOptionKeys = resolveEnabledOptionMenuItems() + if (enabledOptionKeys.isEmpty()) { return false } menuInflater.inflate(R.menu.webview_menu, menu) + val enabledItemIds = enabledOptionKeys.mapNotNull { optionMenuKeyToItemId(it) }.toSet() + val allItemIds = SUPPORTED_OPTION_MENU_KEYS.mapNotNull { optionMenuKeyToItemId(it) } + allItemIds.forEach { itemId -> + if (!enabledItemIds.contains(itemId)) { + menu?.removeItem(itemId) + } + } return true } diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt index 941c962e..124aa794 100644 --- a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/WxpWebBridgeManager.kt @@ -4,6 +4,8 @@ import com.smjcco.wxpusher.base.common.WxpLogUtils import com.smjcco.wxpusher.page.web.bridge.handlers.GetLoginInfoBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.OpenUrlBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.PayRequestBridgeHandler +import com.smjcco.wxpusher.page.web.bridge.handlers.SetWebBottomBarBridgeHandler +import com.smjcco.wxpusher.page.web.bridge.handlers.SetWebOptionMenuBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.ShowToastBridgeHandler import com.smjcco.wxpusher.page.web.bridge.handlers.WxpGetEnvBaseUrlBridgeHandler import com.smjcco.wxpusher.web.WxpWebHostPolicy @@ -32,6 +34,8 @@ class WxpWebBridgeManager( registerHandler("getLoginInfo", requiresWhitelist = true, handler = GetLoginInfoBridgeHandler) registerHandler("getEnvBaseUrl", requiresWhitelist = true, handler = WxpGetEnvBaseUrlBridgeHandler) registerHandler("showToast", requiresWhitelist = true, handler = ShowToastBridgeHandler) + registerHandler("setWebOptionMenu", requiresWhitelist = true, handler = SetWebOptionMenuBridgeHandler) + registerHandler("setWebBottomBar", requiresWhitelist = true, handler = SetWebBottomBarBridgeHandler) } fun onMessage(messageJson: String) { diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/SetWebBottomBarBridgeHandler.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/SetWebBottomBarBridgeHandler.kt new file mode 100644 index 00000000..664073c2 --- /dev/null +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/SetWebBottomBarBridgeHandler.kt @@ -0,0 +1,49 @@ +package com.smjcco.wxpusher.page.web.bridge.handlers + +import com.smjcco.wxpusher.page.web.WxpWebViewFragment +import com.smjcco.wxpusher.page.web.bridge.BridgeActionHandler +import com.smjcco.wxpusher.page.web.bridge.BridgeContext +import com.smjcco.wxpusher.page.web.bridge.BridgeRequest +import com.smjcco.wxpusher.page.web.bridge.WxpBridgeEmitter + +object SetWebBottomBarBridgeHandler : BridgeActionHandler { + override fun handle(request: BridgeRequest, context: BridgeContext, emitter: WxpBridgeEmitter) { + val webFragment = context.fragment as? WxpWebViewFragment + if (webFragment == null) { + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = false, + error = "web fragment not found" + ) + return + } + if (!request.data.containsKey("visible")) { + webFragment.setBottomBarVisibleOverride(null) + emitter.sendBridgeCallback(callbackId = request.callbackId, success = true) + return + } + val visible = parseBooleanValue(request.data["visible"]) + if (visible == null) { + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = false, + error = "visible must be boolean" + ) + return + } + webFragment.setBottomBarVisibleOverride(visible) + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = true + ) + } + + private fun parseBooleanValue(value: Any?): Boolean? { + return when (value) { + is Boolean -> value + is Number -> value.toInt() != 0 + is String -> value.toBooleanStrictOrNull() + else -> null + } + } +} diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/SetWebOptionMenuBridgeHandler.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/SetWebOptionMenuBridgeHandler.kt new file mode 100644 index 00000000..9e451867 --- /dev/null +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/page/web/bridge/handlers/SetWebOptionMenuBridgeHandler.kt @@ -0,0 +1,88 @@ +package com.smjcco.wxpusher.page.web.bridge.handlers + +import com.smjcco.wxpusher.page.web.WxpWebViewFragment +import com.smjcco.wxpusher.page.web.bridge.BridgeActionHandler +import com.smjcco.wxpusher.page.web.bridge.BridgeContext +import com.smjcco.wxpusher.page.web.bridge.BridgeRequest +import com.smjcco.wxpusher.page.web.bridge.WxpBridgeEmitter + +object SetWebOptionMenuBridgeHandler : BridgeActionHandler { + override fun handle(request: BridgeRequest, context: BridgeContext, emitter: WxpBridgeEmitter) { + val webFragment = context.fragment as? WxpWebViewFragment + if (webFragment == null) { + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = false, + error = "web fragment not found" + ) + return + } + val hasVisibleField = request.data.containsKey("visible") + val hasOptionsField = request.data.containsKey("options") + if (!hasVisibleField && !hasOptionsField) { + webFragment.setOptionMenuVisibleOverride(null) + webFragment.setOptionMenuItemsOverride(null) + emitter.sendBridgeCallback(callbackId = request.callbackId, success = true) + return + } + if (hasVisibleField) { + val visible = parseBooleanValue(request.data["visible"]) + if (visible == null) { + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = false, + error = "visible must be boolean" + ) + return + } + webFragment.setOptionMenuVisibleOverride(visible) + } + if (hasOptionsField) { + val parsedOptions = parseOptionKeys(request.data["options"]) + if (parsedOptions == null) { + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = false, + error = "options must be string array" + ) + return + } + val unsupportedOptions = parsedOptions.filterNot { WxpWebViewFragment.SUPPORTED_OPTION_MENU_KEYS.contains(it) } + if (unsupportedOptions.isNotEmpty()) { + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = false, + error = "unsupported options: ${unsupportedOptions.joinToString(",")}" + ) + return + } + webFragment.setOptionMenuItemsOverride(parsedOptions.toSet()) + } + emitter.sendBridgeCallback( + callbackId = request.callbackId, + success = true + ) + } + + private fun parseOptionKeys(value: Any?): List? { + if (value == null) { + return null + } + val listValue = value as? List<*> ?: return null + val parsed = mutableListOf() + for (item in listValue) { + val text = item as? String ?: return null + parsed.add(text) + } + return parsed + } + + private fun parseBooleanValue(value: Any?): Boolean? { + return when (value) { + is Boolean -> value + is Number -> value.toInt() != 0 + is String -> value.toBooleanStrictOrNull() + else -> null + } + } +} diff --git a/iosApp/Podfile b/iosApp/Podfile index eb6d9971..0e18a4d1 100644 --- a/iosApp/Podfile +++ b/iosApp/Podfile @@ -38,4 +38,3 @@ post_install do |installer| end end end - diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock index f3be96e2..449e564d 100644 --- a/iosApp/Podfile.lock +++ b/iosApp/Podfile.lock @@ -27,6 +27,6 @@ SPEC CHECKSUMS: Toaster: c3473963c78e8cabbf6ea6f11ad0fdaae6f54987 WechatOpenSDK-XCFramework: b072030c9eeee91dfff1856a7846f70f7b9a88ed -PODFILE CHECKSUM: ea36281b2064275ab519803ff43cd4ce608415f9 +PODFILE CHECKSUM: 09b1a5dff7118c39e747087e5f7ccac49c4f1ba5 COCOAPODS: 1.15.2 diff --git a/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift b/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift index 9ffa238d..7a44ff13 100644 --- a/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift +++ b/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift @@ -16,6 +16,10 @@ class WxpWebViewController: UIViewController { private var progressTimer: Timer? private var webDescription: String = "" private var showThirdPartyBanner = true + private let allWebOptionItems: [String] = ["copy_link", "weixin_share", "share", "open_browser"] + private var optionMenuVisibleOverride: Bool? + private var optionMenuItemsOverride: Set? + private var bottomBarVisibleOverride: Bool? private var bridgeContext: WxpBridgeContext? private var bridgeManager: WxpWebBridgeManager? private var bridgeMessageHandlerProxy: WxpWeakScriptMessageHandler? @@ -212,7 +216,7 @@ class WxpWebViewController: UIViewController { view.backgroundColor = .systemBackground setupWebview() setupUI() - showOption() + applyWebMenuVisibility(for: url) loadWebContentWithVersionCheck() } @@ -306,38 +310,8 @@ class WxpWebViewController: UIViewController { } - private func showOption(){ - let mainMenu = UIMenu(title: "", children: [ - UIAction( - title: "复制链接", - image: UIImage(systemName: "doc.on.doc"), - handler:{ [weak self]_ in - self?.copyLinkToClipboard() - } - ), - UIAction( - title: "微信分享", - image: UIImage(systemName: "message"), - handler:{ [weak self]_ in - self?.shareToWeixin() - } - ), - UIAction( - title: "分享", - image: UIImage(systemName: "square.and.arrow.up"), - handler:{ [weak self]_ in - self?.shareURL() - } - ), - UIAction( - title: "在浏览器中打开", - image: UIImage(systemName: "safari"), - handler:{ [weak self]_ in - self?.openInBrowser() - } - ) - ]) - + private func showOption(options: [String]){ + let mainMenu = UIMenu(title: "", children: buildOptionActions(options: options)) let menuButton = UIBarButtonItem( title: "操作选项", image: UIImage(systemName: "ellipsis.circle"), @@ -351,6 +325,112 @@ class WxpWebViewController: UIViewController { private func hideOption(){ navigationItem.rightBarButtonItem = nil } + + private func buildOptionActions(options: [String]) -> [UIAction] { + var actions: [UIAction] = [] + for option in options { + switch option { + case "copy_link": + actions.append(UIAction( + title: "复制链接", + image: UIImage(systemName: "doc.on.doc"), + handler:{ [weak self]_ in + self?.copyLinkToClipboard() + } + )) + case "weixin_share": + actions.append(UIAction( + title: "微信分享", + image: UIImage(systemName: "message"), + handler:{ [weak self]_ in + self?.shareToWeixin() + } + )) + case "share": + actions.append(UIAction( + title: "分享", + image: UIImage(systemName: "square.and.arrow.up"), + handler:{ [weak self]_ in + self?.shareURL() + } + )) + case "open_browser": + actions.append(UIAction( + title: "在浏览器中打开", + image: UIImage(systemName: "safari"), + handler:{ [weak self]_ in + self?.openInBrowser() + } + )) + default: + continue + } + } + return actions + } + + private func resolveOptionMenuVisible(for url: URL?) -> Bool { + if let optionMenuVisibleOverride { + return optionMenuVisibleOverride + } + guard let url else { + return true + } + return !(isHostInWhitelist(url.host) && url.path.contains("wxuser")) + } + + private func resolveOptionMenuItems() -> [String] { + if let overrideItems = optionMenuItemsOverride { + return allWebOptionItems.filter { overrideItems.contains($0) } + } + return allWebOptionItems + } + + private func resolveBottomBarVisible(for url: URL?) -> Bool { + if let bottomBarVisibleOverride { + return bottomBarVisibleOverride + } + guard let url else { + return true + } + return !(isHostInWhitelist(url.host) && url.path.contains("app")) + } + + private func applyWebMenuVisibility(for url: URL?) { + let effectiveUrl = url ?? webView?.url ?? self.url + let shouldShowMenu = resolveOptionMenuVisible(for: effectiveUrl) + let menuItems = resolveOptionMenuItems() + if shouldShowMenu && !menuItems.isEmpty { + showOption(options: menuItems) + } else { + hideOption() + } + webOptionView.isHidden = !resolveBottomBarVisible(for: effectiveUrl) + } + + func setOptionMenuVisibleOverride(_ visible: Bool?) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.optionMenuVisibleOverride = visible + self.applyWebMenuVisibility(for: self.webView?.url ?? self.url) + } + } + + func setOptionMenuItemsOverride(_ options: Set?) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.optionMenuItemsOverride = options + self.applyWebMenuVisibility(for: self.webView?.url ?? self.url) + } + } + + func setBottomBarVisibleOverride(_ visible: Bool?) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.bottomBarVisibleOverride = visible + self.applyWebMenuVisibility(for: self.webView?.url ?? self.url) + } + } func updateWebOptionBtnStatus(){ webBackButton.isEnabled = webView!.canGoBack @@ -834,17 +914,7 @@ extension WxpWebViewController: WKNavigationDelegate { } private func updateWebMenuVisibility(for url: URL?) { - guard let url else { - showOption() - webOptionView.isHidden = false - return - } - if isHostInWhitelist(url.host) && url.path.contains("wxuser") { - hideOption() - } else { - showOption() - } - webOptionView.isHidden = isHostInWhitelist(url.host) && url.path.contains("app") + applyWebMenuVisibility(for: url) } } diff --git a/iosApp/wxpusher/Page/WebView/bridge/WxpWebBridgeManager.swift b/iosApp/wxpusher/Page/WebView/bridge/WxpWebBridgeManager.swift index ad260f7a..c01671d5 100644 --- a/iosApp/wxpusher/Page/WebView/bridge/WxpWebBridgeManager.swift +++ b/iosApp/wxpusher/Page/WebView/bridge/WxpWebBridgeManager.swift @@ -31,6 +31,8 @@ final class WxpWebBridgeManager { registerHandler(action: "getLoginInfo", requiresWhitelist: true, handler: WxpGetLoginInfoBridgeHandler()) registerHandler(action: "getEnvBaseUrl", requiresWhitelist: true, handler: WxpGetEnvBaseUrlBridgeHandler()) registerHandler(action: "showToast", requiresWhitelist: true, handler: WxpShowToastBridgeHandler()) + registerHandler(action: "setWebOptionMenu", requiresWhitelist: true, handler: WxpSetWebOptionMenuBridgeHandler()) + registerHandler(action: "setWebBottomBar", requiresWhitelist: true, handler: WxpSetWebBottomBarBridgeHandler()) } private func registerHandler(action: String, requiresWhitelist: Bool, handler: WxpBridgeActionHandler) { diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpSetWebBottomBarBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpSetWebBottomBarBridgeHandler.swift new file mode 100644 index 00000000..5d123a7e --- /dev/null +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpSetWebBottomBarBridgeHandler.swift @@ -0,0 +1,39 @@ +import Foundation + +final class WxpSetWebBottomBarBridgeHandler: WxpBridgeActionHandler { + func handle(request: WxpBridgeRequest, context: WxpBridgeContext, emitter: WxpBridgeEmitter) { + guard let webController = context.viewController as? WxpWebViewController else { + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .fail("web view controller not found")) + return + } + guard request.data.keys.contains("visible") else { + webController.setBottomBarVisibleOverride(nil) + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .ok()) + return + } + guard let visible = parseBool(value: request.data["visible"]) else { + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .fail("visible must be boolean")) + return + } + webController.setBottomBarVisibleOverride(visible) + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .ok()) + } + + private func parseBool(value: Any?) -> Bool? { + if let boolValue = value as? Bool { + return boolValue + } + if let strValue = value as? String { + if strValue.lowercased() == "true" { + return true + } + if strValue.lowercased() == "false" { + return false + } + } + if let numberValue = value as? NSNumber { + return numberValue.intValue != 0 + } + return nil + } +} diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpSetWebOptionMenuBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpSetWebOptionMenuBridgeHandler.swift new file mode 100644 index 00000000..93db09cf --- /dev/null +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpSetWebOptionMenuBridgeHandler.swift @@ -0,0 +1,78 @@ +import Foundation + +final class WxpSetWebOptionMenuBridgeHandler: WxpBridgeActionHandler { + private let supportedOptionItems: Set = ["copy_link", "weixin_share", "share", "open_browser"] + + func handle(request: WxpBridgeRequest, context: WxpBridgeContext, emitter: WxpBridgeEmitter) { + guard let webController = context.viewController as? WxpWebViewController else { + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .fail("web view controller not found")) + return + } + let hasVisible = request.data.keys.contains("visible") + let hasOptions = request.data.keys.contains("options") + if !hasVisible && !hasOptions { + webController.setOptionMenuVisibleOverride(nil) + webController.setOptionMenuItemsOverride(nil) + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .ok()) + return + } + if hasVisible { + guard let visible = parseBool(value: request.data["visible"]) else { + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .fail("visible must be boolean")) + return + } + webController.setOptionMenuVisibleOverride(visible) + } + if hasOptions { + guard let options = parseOptions(value: request.data["options"]) else { + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .fail("options must be string array")) + return + } + let unsupportedOptions = options.filter { !supportedOptionItems.contains($0) } + if !unsupportedOptions.isEmpty { + emitter.sendBridgeCallback( + callbackId: request.callbackId, + response: .fail("unsupported options: \(unsupportedOptions.joined(separator: ","))") + ) + return + } + webController.setOptionMenuItemsOverride(Set(options)) + } + emitter.sendBridgeCallback(callbackId: request.callbackId, response: .ok()) + } + + private func parseOptions(value: Any?) -> [String]? { + guard let value else { + return nil + } + guard let rawList = value as? [Any] else { + return nil + } + var parsed: [String] = [] + for item in rawList { + guard let text = item as? String else { + return nil + } + parsed.append(text) + } + return parsed + } + + private func parseBool(value: Any?) -> Bool? { + if let boolValue = value as? Bool { + return boolValue + } + if let strValue = value as? String { + if strValue.lowercased() == "true" { + return true + } + if strValue.lowercased() == "false" { + return false + } + } + if let numberValue = value as? NSNumber { + return numberValue.intValue != 0 + } + return nil + } +} diff --git a/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj b/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj index 21bd5857..1186c8ce 100644 --- a/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj +++ b/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj @@ -31,6 +31,8 @@ 1A63A1422F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1432F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift */; }; 1A63A1442F669047005FF070 /* WxpShowToastBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */; }; 1A63A1472F669047005FF070 /* WxpBridgeContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1462F669047005FF070 /* WxpBridgeContext.swift */; }; + 7B8A1D4E3F2C100100A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8A1D4E3F2C100200A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift */; }; + 7B8A1D4E3F2C100300A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8A1D4E3F2C100400A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift */; }; 1A962E672E08610200C966A3 /* KtSwiftJumpPageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E662E08610200C966A3 /* KtSwiftJumpPageUtils.m */; }; 1A962E6A2E08625700C966A3 /* WxpJumpPageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E692E08625700C966A3 /* WxpJumpPageUtils.swift */; }; 1A962E6F2E09B3D200C966A3 /* WxpBaseUIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E6E2E09B3D200C966A3 /* WxpBaseUIViewController.swift */; }; @@ -90,6 +92,8 @@ 1A63A1362F669047005FF070 /* WxpBridgeMessageParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeMessageParser.swift; sourceTree = ""; }; 1A63A1372F669047005FF070 /* WxpWeakScriptMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpWeakScriptMessageHandler.swift; sourceTree = ""; }; 1A63A1382F669047005FF070 /* WxpWebBridgeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpWebBridgeManager.swift; sourceTree = ""; }; + 7B8A1D4E3F2C100200A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpSetWebOptionMenuBridgeHandler.swift; sourceTree = ""; }; + 7B8A1D4E3F2C100400A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpSetWebBottomBarBridgeHandler.swift; sourceTree = ""; }; 1A962E662E08610200C966A3 /* KtSwiftJumpPageUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KtSwiftJumpPageUtils.m; sourceTree = ""; }; 1A962E682E08611300C966A3 /* KtSwiftJumpPageUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KtSwiftJumpPageUtils.h; sourceTree = ""; }; 1A962E692E08625700C966A3 /* WxpJumpPageUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpJumpPageUtils.swift; sourceTree = ""; }; @@ -251,6 +255,8 @@ 1A63A1312F669047005FF070 /* WxpOpenUrlBridgeHandler.swift */, 1A63A1322F669047005FF070 /* WxpPayRequestBridgeHandler.swift */, 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */, + 7B8A1D4E3F2C100200A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift */, + 7B8A1D4E3F2C100400A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift */, ); path = handlers; sourceTree = ""; @@ -642,6 +648,8 @@ 1A63A1412F669047005FF070 /* WxpBridgeEmitter.swift in Sources */, 1A63A1422F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift in Sources */, 1A63A1442F669047005FF070 /* WxpShowToastBridgeHandler.swift in Sources */, + 7B8A1D4E3F2C100100A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift in Sources */, + 7B8A1D4E3F2C100300A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift in Sources */, 1ABD14262EDBF9890019AA3A /* WxpLoadingService.swift in Sources */, 1A039F712F08131000C1CA76 /* TestPanelViewController.swift in Sources */, 1ABD142A2EDDE3720019AA3A /* AccountDetailViewController.swift in Sources */, From 4745c1200246aa47d89862fe7f7623863c0a00a3 Mon Sep 17 00:00:00 2001 From: zjiecode Date: Mon, 23 Mar 2026 00:03:12 +0800 Subject: [PATCH 5/8] =?UTF-8?q?iOS=20=E4=BF=AE=E5=A4=8Dwebview=E5=BA=95?= =?UTF-8?q?=E9=83=A8=E6=9C=89=E7=99=BD=E6=9D=A1=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Page/WebView/WxpWebViewController.swift | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift b/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift index 7a44ff13..e023ac3d 100644 --- a/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift +++ b/iosApp/wxpusher/Page/WebView/WxpWebViewController.swift @@ -185,6 +185,9 @@ class WxpWebViewController: UIViewController { } private var bannerHeightConstraint: NSLayoutConstraint! + private var webOptionViewHeightConstraint: NSLayoutConstraint! + private var webOptionViewBottomToSafeArea: NSLayoutConstraint! + private var webOptionViewBottomToViewBottom: NSLayoutConstraint! init() { @@ -283,6 +286,9 @@ class WxpWebViewController: UIViewController { // 先创建约束引用 bannerHeightConstraint = thirdPartyBannerView.heightAnchor.constraint(equalToConstant: 0) + webOptionViewHeightConstraint = webOptionView.heightAnchor.constraint(equalToConstant: 44) + webOptionViewBottomToSafeArea = webOptionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + webOptionViewBottomToViewBottom = webOptionView.bottomAnchor.constraint(equalTo: view.bottomAnchor) NSLayoutConstraint.activate([ @@ -301,10 +307,10 @@ class WxpWebViewController: UIViewController { progressView.trailingAnchor.constraint(equalTo: view.trailingAnchor), progressView.heightAnchor.constraint(equalToConstant: 1.0), - webOptionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + webOptionViewBottomToSafeArea, webOptionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), webOptionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - webOptionView.heightAnchor.constraint(equalToConstant: 44) + webOptionViewHeightConstraint, ]) } @@ -405,7 +411,13 @@ class WxpWebViewController: UIViewController { } else { hideOption() } - webOptionView.isHidden = !resolveBottomBarVisible(for: effectiveUrl) + let bottomBarVisible = resolveBottomBarVisible(for: effectiveUrl) + webOptionView.isHidden = !bottomBarVisible + webOptionViewHeightConstraint.constant = bottomBarVisible ? 44 : 0 + let hasVisibleTabBar = tabBarController != nil && !hidesBottomBarWhenPushed + let shouldExtendToViewBottom = !bottomBarVisible && !hasVisibleTabBar + webOptionViewBottomToSafeArea.isActive = !shouldExtendToViewBottom + webOptionViewBottomToViewBottom.isActive = shouldExtendToViewBottom } func setOptionMenuVisibleOverride(_ visible: Bool?) { From bd55e4433d6b6d3443b3812047cfbaf3199dffed Mon Sep 17 00:00:00 2001 From: zjiecode Date: Wed, 25 Mar 2026 18:04:41 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E9=80=82=E9=85=8D?= =?UTF-8?q?=E7=8E=AF=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/smjcco/wxpusher/common/WxpConstants.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/common/WxpConstants.kt b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/common/WxpConstants.kt index 52580e74..5ffae4d3 100644 --- a/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/common/WxpConstants.kt +++ b/androidApp/src/androidMain/kotlin/com/smjcco/wxpusher/common/WxpConstants.kt @@ -1,11 +1,11 @@ package com.smjcco.wxpusher.common +import com.smjcco.wxpusher.WxpConfig + object WxpConstants { //协议目录 - const val PrivacyUrl: String = - "https://wxpusher.zjiecode.com/admin/agreement/index-argeement.html" + var PrivacyUrl: String = WxpConfig.appFeUrl + "/admin/agreement/index-argeement.html" //隐私协议入口 - const val PrivacyPolicyUrl = - "https://wxpusher.zjiecode.com/admin/agreement/privacy-agreement.html" + var PrivacyPolicyUrl = WxpConfig.appFeUrl + "/admin/agreement/privacy-agreement.html" } \ No newline at end of file From 02cbe6ca1d1024bce91c0286bbd3cf228bd44235 Mon Sep 17 00:00:00 2001 From: zjiecode Date: Wed, 25 Mar 2026 23:10:35 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E4=B8=8D=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E7=9A=84ext?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WebView/bridge/handlers/WxpPayRequestBridgeHandler.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpPayRequestBridgeHandler.swift b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpPayRequestBridgeHandler.swift index 781628b1..548210da 100644 --- a/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpPayRequestBridgeHandler.swift +++ b/iosApp/wxpusher/Page/WebView/bridge/handlers/WxpPayRequestBridgeHandler.swift @@ -1,9 +1,6 @@ import Foundation -final class WxpPayRequestBridgeHandler { -} - -extension WxpPayRequestBridgeHandler: WxpBridgeActionHandler { +final class WxpPayRequestBridgeHandler: WxpBridgeActionHandler { func handle(request: WxpBridgeRequest, context: WxpBridgeContext, emitter: WxpBridgeEmitter) { WxpWeixinOpenManager.shared.requestPayment(with: request.data) { result in DispatchQueue.main.async { From 494e6756cbd78026b7ff752cbed6d9c2f60941bc Mon Sep 17 00:00:00 2001 From: zjiecode Date: Wed, 25 Mar 2026 23:11:25 +0800 Subject: [PATCH 8/8] Android && iOS 1.7.0 --- androidApp/build.gradle.kts | 4 ++-- .../wxpusher.xcodeproj/project.pbxproj | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 1157f985..c0afe334 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -65,8 +65,8 @@ android { applicationId = "com.smjcco.wxpusher" minSdk = libs.versions.android.minSdk.get().toInt() targetSdk = libs.versions.android.targetSdk.get().toInt() - versionCode = 10610 - versionName = "1.6.10" + versionCode = 10700 + versionName = "1.7.0" //指定产物名称 setProperty("archivesBaseName", "wxpusher-app-v$versionName") diff --git a/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj b/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj index 1186c8ce..1bf0ea26 100644 --- a/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj +++ b/iosApp/wxpusher/wxpusher.xcodeproj/project.pbxproj @@ -31,8 +31,6 @@ 1A63A1422F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1432F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift */; }; 1A63A1442F669047005FF070 /* WxpShowToastBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */; }; 1A63A1472F669047005FF070 /* WxpBridgeContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63A1462F669047005FF070 /* WxpBridgeContext.swift */; }; - 7B8A1D4E3F2C100100A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8A1D4E3F2C100200A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift */; }; - 7B8A1D4E3F2C100300A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8A1D4E3F2C100400A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift */; }; 1A962E672E08610200C966A3 /* KtSwiftJumpPageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E662E08610200C966A3 /* KtSwiftJumpPageUtils.m */; }; 1A962E6A2E08625700C966A3 /* WxpJumpPageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E692E08625700C966A3 /* WxpJumpPageUtils.swift */; }; 1A962E6F2E09B3D200C966A3 /* WxpBaseUIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A962E6E2E09B3D200C966A3 /* WxpBaseUIViewController.swift */; }; @@ -60,6 +58,8 @@ 1ACFF6112E63190500608219 /* KtSwiftDialogUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ACFF6102E63190500608219 /* KtSwiftDialogUtils.m */; }; 1ACFF6172E65E74600608219 /* WxpProviderListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACFF6162E65E74600608219 /* WxpProviderListViewController.swift */; }; 1ADEF6B02ACDC6D6002FEB57 /* WxPusher_MacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADEF6AF2ACDC6D6002FEB57 /* WxPusher_MacApp.swift */; }; + 7B8A1D4E3F2C100100A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8A1D4E3F2C100200A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift */; }; + 7B8A1D4E3F2C100300A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8A1D4E3F2C100400A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift */; }; 986EF3A412E72E2E14454E8B /* libPods-WxPusher-Mac.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C69846B4E017B608DEE27FE0 /* libPods-WxPusher-Mac.a */; }; E8F4FFB984C6ECD1DE28A901 /* libPods-WxPusher-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 63C6789F00DFBD4BD46990F9 /* libPods-WxPusher-iOS.a */; }; /* End PBXBuildFile section */ @@ -84,16 +84,14 @@ 1A63A1302F669047005FF070 /* WxpGetLoginInfoBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpGetLoginInfoBridgeHandler.swift; sourceTree = ""; }; 1A63A1312F669047005FF070 /* WxpOpenUrlBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpOpenUrlBridgeHandler.swift; sourceTree = ""; }; 1A63A1322F669047005FF070 /* WxpPayRequestBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpPayRequestBridgeHandler.swift; sourceTree = ""; }; - 1A63A1432F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpGetEnvBaseUrlBridgeHandler.swift; sourceTree = ""; }; - 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpShowToastBridgeHandler.swift; sourceTree = ""; }; - 1A63A1462F669047005FF070 /* WxpBridgeContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeContext.swift; sourceTree = ""; }; 1A63A1342F669047005FF070 /* WxpBridgeContracts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeContracts.swift; sourceTree = ""; }; 1A63A1352F669047005FF070 /* WxpBridgeEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeEmitter.swift; sourceTree = ""; }; 1A63A1362F669047005FF070 /* WxpBridgeMessageParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeMessageParser.swift; sourceTree = ""; }; 1A63A1372F669047005FF070 /* WxpWeakScriptMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpWeakScriptMessageHandler.swift; sourceTree = ""; }; 1A63A1382F669047005FF070 /* WxpWebBridgeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpWebBridgeManager.swift; sourceTree = ""; }; - 7B8A1D4E3F2C100200A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpSetWebOptionMenuBridgeHandler.swift; sourceTree = ""; }; - 7B8A1D4E3F2C100400A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpSetWebBottomBarBridgeHandler.swift; sourceTree = ""; }; + 1A63A1432F669047005FF070 /* WxpGetEnvBaseUrlBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpGetEnvBaseUrlBridgeHandler.swift; sourceTree = ""; }; + 1A63A1452F669047005FF070 /* WxpShowToastBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpShowToastBridgeHandler.swift; sourceTree = ""; }; + 1A63A1462F669047005FF070 /* WxpBridgeContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpBridgeContext.swift; sourceTree = ""; }; 1A962E662E08610200C966A3 /* KtSwiftJumpPageUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KtSwiftJumpPageUtils.m; sourceTree = ""; }; 1A962E682E08611300C966A3 /* KtSwiftJumpPageUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KtSwiftJumpPageUtils.h; sourceTree = ""; }; 1A962E692E08625700C966A3 /* WxpJumpPageUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpJumpPageUtils.swift; sourceTree = ""; }; @@ -131,6 +129,8 @@ 1ADEF6B82ACDC6D9002FEB57 /* WxPusher_Mac.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WxPusher_Mac.entitlements; sourceTree = ""; }; 1ADEF6C72ACDCA13002FEB57 /* WxPusher-Mac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "WxPusher-Mac.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 63C6789F00DFBD4BD46990F9 /* libPods-WxPusher-iOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WxPusher-iOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7B8A1D4E3F2C100200A1B2C3 /* WxpSetWebOptionMenuBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpSetWebOptionMenuBridgeHandler.swift; sourceTree = ""; }; + 7B8A1D4E3F2C100400A1B2C3 /* WxpSetWebBottomBarBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WxpSetWebBottomBarBridgeHandler.swift; sourceTree = ""; }; 8791CA31329B1B39D73E40C2 /* Pods-wxpusher.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-wxpusher.release.xcconfig"; path = "Target Support Files/Pods-wxpusher/Pods-wxpusher.release.xcconfig"; sourceTree = ""; }; 90E0FD6062BC805D458B9E8B /* Pods-WxPusher-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WxPusher-iOS.debug.xcconfig"; path = "Target Support Files/Pods-WxPusher-iOS/Pods-WxPusher-iOS.debug.xcconfig"; sourceTree = ""; }; B25772F3799991F9E1679618 /* Pods-WxPusher-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WxPusher-iOS.release.xcconfig"; path = "Target Support Files/Pods-WxPusher-iOS/Pods-WxPusher-iOS.release.xcconfig"; sourceTree = ""; }; @@ -802,7 +802,7 @@ CODE_SIGN_ENTITLEMENTS = "WxPusher-iOS/wxpusher.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 10610; + CURRENT_PROJECT_VERSION = 10700; DEVELOPMENT_TEAM = 8UU533BM7N; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; @@ -823,7 +823,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.6.10; + MARKETING_VERSION = 1.7.0; PRODUCT_BUNDLE_IDENTIFIER = com.smjcco.wxpusher; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -853,7 +853,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 10610; + CURRENT_PROJECT_VERSION = 10700; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8UU533BM7N; ENABLE_HARDENED_RUNTIME = YES; @@ -874,7 +874,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.6.10; + MARKETING_VERSION = 1.7.0; PRODUCT_BUNDLE_IDENTIFIER = com.smjcco.wxpusher; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "";