pythonnative 0.36.0__py3-none-any.whl → 0.37.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonnative/__init__.py +6 -4
- pythonnative/_ios_log.py +1 -1
- pythonnative/alerts.py +34 -88
- pythonnative/bootstrap.py +90 -0
- pythonnative/bridge/__init__.py +306 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +164 -0
- pythonnative/bridge/fake.py +236 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/components/layout.py +65 -3
- pythonnative/gestures.py +13 -10
- pythonnative/hooks.py +41 -26
- pythonnative/hosts/__init__.py +13 -36
- pythonnative/hosts/native.py +348 -0
- pythonnative/images.py +6 -5
- pythonnative/native_modules/__init__.py +30 -29
- pythonnative/native_modules/app_state.py +10 -6
- pythonnative/native_modules/battery.py +20 -84
- pythonnative/native_modules/biometrics.py +11 -124
- pythonnative/native_modules/camera.py +15 -291
- pythonnative/native_modules/clipboard.py +14 -109
- pythonnative/native_modules/desktop.py +322 -0
- pythonnative/native_modules/file_system.py +15 -21
- pythonnative/native_modules/haptics.py +19 -129
- pythonnative/native_modules/linking.py +24 -137
- pythonnative/native_modules/location.py +28 -180
- pythonnative/native_modules/net_info.py +25 -135
- pythonnative/native_modules/notifications.py +36 -257
- pythonnative/native_modules/permissions.py +20 -184
- pythonnative/native_modules/registry.py +513 -0
- pythonnative/native_modules/secure_store.py +18 -155
- pythonnative/native_modules/share.py +8 -113
- pythonnative/native_views/__init__.py +67 -86
- pythonnative/native_views/bridge_backend.py +264 -0
- pythonnative/platform.py +19 -15
- pythonnative/platform_metrics.py +3 -3
- pythonnative/project/android.py +0 -3
- pythonnative/project/builder.py +38 -16
- pythonnative/project/config.py +12 -2
- pythonnative/project/plugins.py +366 -0
- pythonnative/py.typed +0 -0
- pythonnative/runtime.py +35 -203
- pythonnative/sdk/__init__.py +48 -33
- pythonnative/sdk/_components.py +57 -76
- pythonnative/storage.py +18 -243
- pythonnative/templates/android_template/app/build.gradle +6 -9
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
- pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
- pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
- pythonnative/templates/android_template/settings.gradle +1 -0
- pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
- pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
- pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
- pythonnative/utils.py +4 -82
- pythonnative/virtual_rows.py +3 -3
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
- pythonnative-0.37.0.dist-info/RECORD +263 -0
- pythonnative/hosts/android.py +0 -269
- pythonnative/hosts/ios.py +0 -404
- pythonnative/native_views/android.py +0 -3702
- pythonnative/native_views/ios.py +0 -5253
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
- pythonnative-0.36.0.dist-info/RECORD +0 -155
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import WebKit
|
|
3
|
+
|
|
4
|
+
/// `WebView`: `WKWebView` with navigation and script-message delegates.
|
|
5
|
+
///
|
|
6
|
+
/// Page JavaScript reaches `on_message` through
|
|
7
|
+
/// `window.webkit.messageHandlers.pythonnative.postMessage(value)`.
|
|
8
|
+
public final class PNWebViewManager: PNComponentManager {
|
|
9
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
10
|
+
let configuration = WKWebViewConfiguration()
|
|
11
|
+
let delegate = PNWebViewDelegate()
|
|
12
|
+
configuration.userContentController.add(delegate, name: "pythonnative")
|
|
13
|
+
let webView = WKWebView(frame: .zero, configuration: configuration)
|
|
14
|
+
webView.navigationDelegate = delegate
|
|
15
|
+
delegate.webView = webView
|
|
16
|
+
pendingDelegates[ObjectIdentifier(webView)] = delegate
|
|
17
|
+
return webView
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private var pendingDelegates: [ObjectIdentifier: PNWebViewDelegate] = [:]
|
|
21
|
+
|
|
22
|
+
public override func createView(tag: Int64, props: [String: Any]) -> UIView {
|
|
23
|
+
let view = super.createView(tag: tag, props: props)
|
|
24
|
+
if let delegate = pendingDelegates.removeValue(forKey: ObjectIdentifier(view)) {
|
|
25
|
+
PNViewState.existing(for: view)?.retained.append(delegate)
|
|
26
|
+
delegate.injectJavaScript = PNProps.string(PNProps.value(props, "inject_javascript"))
|
|
27
|
+
}
|
|
28
|
+
return view
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public override func teardown(view: UIView) {
|
|
32
|
+
guard let webView = view as? WKWebView else { return }
|
|
33
|
+
webView.configuration.userContentController.removeScriptMessageHandler(forName: "pythonnative")
|
|
34
|
+
webView.navigationDelegate = nil
|
|
35
|
+
webView.stopLoading()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public override func apply(view: UIView, props: [String: Any], initial: Bool) {
|
|
39
|
+
guard let webView = view as? WKWebView else { return }
|
|
40
|
+
if PNProps.has(props, "inject_javascript"),
|
|
41
|
+
let delegate = PNViewState.existing(for: webView)?.retained.compactMap({ $0 as? PNWebViewDelegate }).first
|
|
42
|
+
{
|
|
43
|
+
delegate.injectJavaScript = PNProps.string(PNProps.value(props, "inject_javascript"))
|
|
44
|
+
}
|
|
45
|
+
if let html = PNProps.string(PNProps.value(props, "html")), !html.isEmpty {
|
|
46
|
+
let base = PNProps.string(PNProps.value(mergedProps(webView), "base_url")).flatMap { URL(string: $0) }
|
|
47
|
+
webView.loadHTMLString(html, baseURL: base)
|
|
48
|
+
} else if let url = PNProps.string(PNProps.value(props, "url")), !url.isEmpty, let target = URL(string: url) {
|
|
49
|
+
webView.load(URLRequest(url: target))
|
|
50
|
+
}
|
|
51
|
+
if PNProps.has(props, "scroll_enabled") {
|
|
52
|
+
webView.scrollView.isScrollEnabled = PNProps.bool(PNProps.value(props, "scroll_enabled")) ?? true
|
|
53
|
+
}
|
|
54
|
+
if PNProps.has(props, "allows_back_forward_gestures") {
|
|
55
|
+
webView.allowsBackForwardNavigationGestures = PNProps.bool(PNProps.value(props, "allows_back_forward_gestures")) ?? false
|
|
56
|
+
}
|
|
57
|
+
PNViewStyler.applyCommon(webView, props)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public override func command(view: UIView, name: String, args: [String: Any]) -> Any? {
|
|
61
|
+
guard let webView = view as? WKWebView else { return nil }
|
|
62
|
+
switch name {
|
|
63
|
+
case "eval_js", "inject_javascript":
|
|
64
|
+
webView.evaluateJavaScript(PNProps.string(args["source"]) ?? PNProps.string(args["script"]) ?? "") { _, _ in }
|
|
65
|
+
case "reload": webView.reload()
|
|
66
|
+
case "go_back": webView.goBack()
|
|
67
|
+
case "go_forward": webView.goForward()
|
|
68
|
+
case "stop_loading": webView.stopLoading()
|
|
69
|
+
case "load_url":
|
|
70
|
+
if let url = PNProps.string(args["url"]).flatMap({ URL(string: $0) }) { webView.load(URLRequest(url: url)) }
|
|
71
|
+
case "get_url":
|
|
72
|
+
return webView.url?.absoluteString ?? ""
|
|
73
|
+
case "can_go_back":
|
|
74
|
+
return webView.canGoBack
|
|
75
|
+
case "can_go_forward":
|
|
76
|
+
return webView.canGoForward
|
|
77
|
+
default:
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
return nil
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// `WKNavigationDelegate` + `WKScriptMessageHandler` bridge.
|
|
85
|
+
final class PNWebViewDelegate: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
|
|
86
|
+
weak var webView: WKWebView?
|
|
87
|
+
var injectJavaScript: String?
|
|
88
|
+
|
|
89
|
+
private func currentURL() -> String {
|
|
90
|
+
webView?.url?.absoluteString ?? ""
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
94
|
+
if let js = injectJavaScript, !js.isEmpty {
|
|
95
|
+
webView.evaluateJavaScript(js) { _, _ in }
|
|
96
|
+
}
|
|
97
|
+
PNEvents.emit(webView, "on_load", [currentURL()])
|
|
98
|
+
PNEvents.emitIfWired(webView, "on_navigation_state_change", [["url": currentURL(), "loading": false, "can_go_back": webView.canGoBack, "can_go_forward": webView.canGoForward, "title": webView.title ?? ""]])
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
|
|
102
|
+
PNEvents.emitIfWired(webView, "on_load_start", [currentURL()])
|
|
103
|
+
PNEvents.emitIfWired(webView, "on_navigation_state_change", [["url": currentURL(), "loading": true, "can_go_back": webView.canGoBack, "can_go_forward": webView.canGoForward, "title": webView.title ?? ""]])
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
|
107
|
+
PNEvents.emit(webView, "on_error", [error.localizedDescription])
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
|
111
|
+
PNEvents.emit(webView, "on_error", [error.localizedDescription])
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
|
115
|
+
guard let webView = webView else { return }
|
|
116
|
+
let body: String
|
|
117
|
+
if let text = message.body as? String {
|
|
118
|
+
body = text
|
|
119
|
+
} else {
|
|
120
|
+
body = PNJSON.encode(message.body)
|
|
121
|
+
}
|
|
122
|
+
PNEvents.emit(webView, "on_message", [body])
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Installs `UIGestureRecognizer`s from the `gestures` prop and emits
|
|
4
|
+
/// `gesture:<index>` events with the payload dict Python's
|
|
5
|
+
/// `GestureEvent` expects.
|
|
6
|
+
///
|
|
7
|
+
/// Spec fields:
|
|
8
|
+
/// - `kind`: `tap`, `long_press`, `pan`, `swipe`, `fling`, `pinch`, `rotation`
|
|
9
|
+
/// - per-kind config (`n_taps`, `max_distance`, `min_duration_ms`,
|
|
10
|
+
/// `min_distance`, `direction`, `n_pointers`, `min_velocity`)
|
|
11
|
+
/// - `simultaneous`: indices allowed to recognize together
|
|
12
|
+
/// - `wait_for`: indices that must fail before this one may begin
|
|
13
|
+
public final class PNGestureCoordinator: NSObject, UIGestureRecognizerDelegate {
|
|
14
|
+
public static let shared = PNGestureCoordinator()
|
|
15
|
+
|
|
16
|
+
/// Bookkeeping for one installed recognizer.
|
|
17
|
+
final class Entry {
|
|
18
|
+
let index: Int
|
|
19
|
+
let kind: String
|
|
20
|
+
var simultaneous: Set<Int>
|
|
21
|
+
var minVelocity: CGFloat
|
|
22
|
+
var direction: String
|
|
23
|
+
var lastLocation: CGPoint = .zero
|
|
24
|
+
|
|
25
|
+
init(index: Int, kind: String, simultaneous: Set<Int>, minVelocity: CGFloat, direction: String) {
|
|
26
|
+
self.index = index
|
|
27
|
+
self.kind = kind
|
|
28
|
+
self.simultaneous = simultaneous
|
|
29
|
+
self.minVelocity = minVelocity
|
|
30
|
+
self.direction = direction
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private var entries: [ObjectIdentifier: Entry] = [:]
|
|
35
|
+
|
|
36
|
+
private override init() {
|
|
37
|
+
super.init()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// MARK: - Wiring
|
|
41
|
+
|
|
42
|
+
/// Replace the recognizers on `view` with the ones described by `specs`.
|
|
43
|
+
public func wire(view: UIView, specs: Any?) {
|
|
44
|
+
unwire(view: view)
|
|
45
|
+
guard let state = PNViewState.existing(for: view) else { return }
|
|
46
|
+
let list = ((specs as? [Any]) ?? []).map { ($0 as? [String: Any]) ?? [:] }
|
|
47
|
+
guard !list.isEmpty else { return }
|
|
48
|
+
// A spec may own several recognizers (an any-direction swipe is
|
|
49
|
+
// one `UISwipeGestureRecognizer` per direction), so relationships
|
|
50
|
+
// are wired spec-to-spec across every recognizer on each side.
|
|
51
|
+
var built: [Int: [UIGestureRecognizer]] = [:]
|
|
52
|
+
for (index, spec) in list.enumerated() {
|
|
53
|
+
let recognizers = makeRecognizers(spec)
|
|
54
|
+
if recognizers.isEmpty {
|
|
55
|
+
PNLog.once(PNLog.gestures, key: "kind:\(PNProps.string(spec["kind"]) ?? "")", "unsupported gesture kind '\(PNProps.string(spec["kind"]) ?? "")'")
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
for (recognizer, direction) in recognizers {
|
|
59
|
+
let entry = Entry(
|
|
60
|
+
index: index,
|
|
61
|
+
kind: PNProps.string(spec["kind"]) ?? "",
|
|
62
|
+
simultaneous: Set(((spec["simultaneous"] as? [Any]) ?? []).compactMap { PNProps.int($0) }),
|
|
63
|
+
minVelocity: CGFloat(PNProps.double(spec["min_velocity"]) ?? 300),
|
|
64
|
+
direction: direction ?? PNProps.string(spec["direction"]) ?? "any"
|
|
65
|
+
)
|
|
66
|
+
entries[ObjectIdentifier(recognizer)] = entry
|
|
67
|
+
recognizer.delegate = self
|
|
68
|
+
recognizer.cancelsTouchesInView = false
|
|
69
|
+
recognizer.addTarget(self, action: #selector(handle(_:)))
|
|
70
|
+
view.addGestureRecognizer(recognizer)
|
|
71
|
+
state.gestureRecognizers.append(recognizer)
|
|
72
|
+
built[index, default: []].append(recognizer)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (index, spec) in list.enumerated() {
|
|
76
|
+
guard let recognizers = built[index] else { continue }
|
|
77
|
+
for wait in ((spec["wait_for"] as? [Any]) ?? []).compactMap({ PNProps.int($0) }) {
|
|
78
|
+
guard wait != index, let others = built[wait] else { continue }
|
|
79
|
+
for recognizer in recognizers {
|
|
80
|
+
for other in others {
|
|
81
|
+
recognizer.require(toFail: other)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
view.isUserInteractionEnabled = true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Remove every recognizer previously installed on `view`.
|
|
90
|
+
public func unwire(view: UIView) {
|
|
91
|
+
guard let state = PNViewState.existing(for: view) else { return }
|
|
92
|
+
for recognizer in state.gestureRecognizers {
|
|
93
|
+
recognizer.removeTarget(self, action: nil)
|
|
94
|
+
view.removeGestureRecognizer(recognizer)
|
|
95
|
+
entries.removeValue(forKey: ObjectIdentifier(recognizer))
|
|
96
|
+
}
|
|
97
|
+
state.gestureRecognizers = []
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// MARK: - Recognizer construction
|
|
101
|
+
|
|
102
|
+
/// Build the recognizer(s) for one spec as `(recognizer, direction)`
|
|
103
|
+
/// pairs. `direction` is set only for swipe / fling recognizers: a
|
|
104
|
+
/// `UISwipeGestureRecognizer` reports its configured mask rather than
|
|
105
|
+
/// the direction it resolved, so `direction = "any"` installs one
|
|
106
|
+
/// recognizer per direction and tags each with its own name.
|
|
107
|
+
func makeRecognizers(_ spec: [String: Any]) -> [(UIGestureRecognizer, String?)] {
|
|
108
|
+
switch PNProps.string(spec["kind"]) ?? "" {
|
|
109
|
+
case "tap":
|
|
110
|
+
let tap = UITapGestureRecognizer()
|
|
111
|
+
tap.numberOfTapsRequired = max(1, PNProps.int(spec["n_taps"]) ?? 1)
|
|
112
|
+
return [(tap, nil)]
|
|
113
|
+
case "long_press":
|
|
114
|
+
let press = UILongPressGestureRecognizer()
|
|
115
|
+
press.minimumPressDuration = max(0.01, (PNProps.double(spec["min_duration_ms"]) ?? 500) / 1000)
|
|
116
|
+
press.allowableMovement = CGFloat(PNProps.double(spec["max_distance"]) ?? 12)
|
|
117
|
+
return [(press, nil)]
|
|
118
|
+
case "pan":
|
|
119
|
+
return [(PNPanGestureRecognizer(minDistance: CGFloat(PNProps.double(spec["min_distance"]) ?? 10)), nil)]
|
|
120
|
+
case "swipe", "fling":
|
|
121
|
+
let requested = PNProps.string(spec["direction"]) ?? "any"
|
|
122
|
+
let directions = PNGestureCoordinator.swipeDirectionNames.contains(requested)
|
|
123
|
+
? [requested]
|
|
124
|
+
: PNGestureCoordinator.swipeDirectionNames
|
|
125
|
+
let touches = max(1, PNProps.int(spec["n_pointers"]) ?? 1)
|
|
126
|
+
return directions.map { name in
|
|
127
|
+
let swipe = UISwipeGestureRecognizer()
|
|
128
|
+
swipe.direction = PNGestureCoordinator.swipeDirection(name)
|
|
129
|
+
swipe.numberOfTouchesRequired = touches
|
|
130
|
+
return (swipe, name)
|
|
131
|
+
}
|
|
132
|
+
case "pinch":
|
|
133
|
+
return [(UIPinchGestureRecognizer(), nil)]
|
|
134
|
+
case "rotation":
|
|
135
|
+
return [(UIRotationGestureRecognizer(), nil)]
|
|
136
|
+
default:
|
|
137
|
+
return []
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// The single-direction names a swipe spec may request.
|
|
142
|
+
public static let swipeDirectionNames = ["left", "right", "up", "down"]
|
|
143
|
+
|
|
144
|
+
/// Map a Python direction name to `UISwipeGestureRecognizer.Direction`.
|
|
145
|
+
public static func swipeDirection(_ name: String) -> UISwipeGestureRecognizer.Direction {
|
|
146
|
+
switch name {
|
|
147
|
+
case "left": return .left
|
|
148
|
+
case "right": return .right
|
|
149
|
+
case "up": return .up
|
|
150
|
+
case "down": return .down
|
|
151
|
+
default: return [.left, .right, .up, .down]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// MARK: - Relationship lookups (exposed for tests)
|
|
156
|
+
|
|
157
|
+
/// The spec index recorded for `recognizer`, if it was installed by the coordinator.
|
|
158
|
+
public func index(of recognizer: UIGestureRecognizer) -> Int? {
|
|
159
|
+
entries[ObjectIdentifier(recognizer)]?.index
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Whether the two recognizers are allowed to recognize together.
|
|
163
|
+
public func allowsSimultaneous(_ a: UIGestureRecognizer, _ b: UIGestureRecognizer) -> Bool {
|
|
164
|
+
guard let ea = entries[ObjectIdentifier(a)], let eb = entries[ObjectIdentifier(b)] else {
|
|
165
|
+
// One side isn't ours (Pressable, scroll view): don't block it.
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
if a.view !== b.view { return true }
|
|
169
|
+
return ea.simultaneous.contains(eb.index) || eb.simultaneous.contains(ea.index)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// MARK: - UIGestureRecognizerDelegate
|
|
173
|
+
|
|
174
|
+
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer) -> Bool {
|
|
175
|
+
allowsSimultaneous(gestureRecognizer, other)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// MARK: - Emission
|
|
179
|
+
|
|
180
|
+
@objc private func handle(_ recognizer: UIGestureRecognizer) {
|
|
181
|
+
guard let view = recognizer.view, let entry = entries[ObjectIdentifier(recognizer)] else { return }
|
|
182
|
+
let location = recognizer.location(in: view)
|
|
183
|
+
var payload: [String: Any] = [
|
|
184
|
+
"kind": entry.kind,
|
|
185
|
+
"x": Double(location.x),
|
|
186
|
+
"y": Double(location.y),
|
|
187
|
+
"translation_x": 0.0,
|
|
188
|
+
"translation_y": 0.0,
|
|
189
|
+
"velocity_x": 0.0,
|
|
190
|
+
"velocity_y": 0.0,
|
|
191
|
+
"scale": 1.0,
|
|
192
|
+
"rotation": 0.0,
|
|
193
|
+
"pointer_count": recognizer.numberOfTouches,
|
|
194
|
+
"direction": NSNull(),
|
|
195
|
+
]
|
|
196
|
+
var state = PNGestureCoordinator.stateName(recognizer.state)
|
|
197
|
+
switch recognizer {
|
|
198
|
+
case let pan as UIPanGestureRecognizer:
|
|
199
|
+
let translation = pan.translation(in: view)
|
|
200
|
+
let velocity = pan.velocity(in: view)
|
|
201
|
+
payload["translation_x"] = Double(translation.x)
|
|
202
|
+
payload["translation_y"] = Double(translation.y)
|
|
203
|
+
payload["velocity_x"] = Double(velocity.x)
|
|
204
|
+
payload["velocity_y"] = Double(velocity.y)
|
|
205
|
+
case let pinch as UIPinchGestureRecognizer:
|
|
206
|
+
payload["scale"] = Double(pinch.scale)
|
|
207
|
+
payload["velocity_x"] = Double(pinch.velocity)
|
|
208
|
+
case let rotation as UIRotationGestureRecognizer:
|
|
209
|
+
payload["rotation"] = Double(rotation.rotation)
|
|
210
|
+
payload["velocity_x"] = Double(rotation.velocity)
|
|
211
|
+
case let swipe as UISwipeGestureRecognizer:
|
|
212
|
+
// UIKit reports swipes as a single `.ended`; Python expects `ended`.
|
|
213
|
+
guard swipe.state == .ended || swipe.state == .recognized else { return }
|
|
214
|
+
state = "ended"
|
|
215
|
+
payload["direction"] = entry.direction
|
|
216
|
+
payload["pointer_count"] = max(1, swipe.numberOfTouchesRequired)
|
|
217
|
+
case is UITapGestureRecognizer:
|
|
218
|
+
guard recognizer.state == .ended || recognizer.state == .recognized else { return }
|
|
219
|
+
state = "ended"
|
|
220
|
+
default:
|
|
221
|
+
break
|
|
222
|
+
}
|
|
223
|
+
guard state != "possible" else { return }
|
|
224
|
+
payload["state"] = state
|
|
225
|
+
PNEvents.emit(view, "gesture:\(entry.index)", [payload])
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static func stateName(_ state: UIGestureRecognizer.State) -> String {
|
|
229
|
+
switch state {
|
|
230
|
+
case .began: return "began"
|
|
231
|
+
case .changed: return "changed"
|
|
232
|
+
case .ended: return "ended"
|
|
233
|
+
case .cancelled: return "cancelled"
|
|
234
|
+
case .failed: return "failed"
|
|
235
|
+
default: return "possible"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// The direction name recorded for a swipe / fling recognizer (exposed for tests).
|
|
240
|
+
public func direction(of recognizer: UIGestureRecognizer) -> String? {
|
|
241
|
+
guard let entry = entries[ObjectIdentifier(recognizer)], entry.kind == "swipe" || entry.kind == "fling" else {
|
|
242
|
+
return nil
|
|
243
|
+
}
|
|
244
|
+
return entry.direction
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/// A pan recognizer that records the Python `min_distance` threshold.
|
|
249
|
+
///
|
|
250
|
+
/// UIKit applies its own ~10 pt hysteresis before a pan begins, which
|
|
251
|
+
/// matches the Python default; the value is kept for diagnostics and
|
|
252
|
+
/// for future tuning rather than overriding UIKit's touch pipeline.
|
|
253
|
+
final class PNPanGestureRecognizer: UIPanGestureRecognizer {
|
|
254
|
+
let minDistance: CGFloat
|
|
255
|
+
|
|
256
|
+
init(minDistance: CGFloat) {
|
|
257
|
+
self.minDistance = max(0, minDistance)
|
|
258
|
+
super.init(target: nil, action: nil)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Presents `UIAlertController`s from the top view controller.
|
|
4
|
+
public enum PNAlertPresenter {
|
|
5
|
+
/// Show an alert or action sheet. `buttons` are
|
|
6
|
+
/// `{"label", "style": "default"|"cancel"|"destructive"}` dicts;
|
|
7
|
+
/// `completion` receives the tapped index, or `-1` on dismissal.
|
|
8
|
+
public static func present(
|
|
9
|
+
title: String?, message: String?, buttons: [[String: Any]], style: String,
|
|
10
|
+
completion: @escaping (Int) -> Void
|
|
11
|
+
) {
|
|
12
|
+
guard let top = PNWindow.topViewController() else {
|
|
13
|
+
completion(-1)
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
let preferred: UIAlertController.Style = style == "action_sheet" ? .actionSheet : .alert
|
|
17
|
+
let alert = UIAlertController(title: title, message: message, preferredStyle: preferred)
|
|
18
|
+
var settled = false
|
|
19
|
+
let finish: (Int) -> Void = { index in
|
|
20
|
+
if settled { return }
|
|
21
|
+
settled = true
|
|
22
|
+
completion(index)
|
|
23
|
+
}
|
|
24
|
+
let list = buttons.isEmpty ? [["label": "OK"]] : buttons
|
|
25
|
+
for (index, button) in list.enumerated() {
|
|
26
|
+
let label = PNProps.string(button["label"]) ?? PNProps.string(button["title"]) ?? "OK"
|
|
27
|
+
let actionStyle: UIAlertAction.Style
|
|
28
|
+
switch PNProps.string(button["style"]) {
|
|
29
|
+
case "cancel": actionStyle = .cancel
|
|
30
|
+
case "destructive": actionStyle = .destructive
|
|
31
|
+
default: actionStyle = .default
|
|
32
|
+
}
|
|
33
|
+
alert.addAction(UIAlertAction(title: label, style: actionStyle) { _ in finish(index) })
|
|
34
|
+
}
|
|
35
|
+
if preferred == .actionSheet, let popover = alert.popoverPresentationController {
|
|
36
|
+
popover.sourceView = top.view
|
|
37
|
+
popover.sourceRect = CGRect(x: top.view.bounds.midX, y: top.view.bounds.maxY - 1, width: 1, height: 1)
|
|
38
|
+
popover.permittedArrowDirections = []
|
|
39
|
+
}
|
|
40
|
+
top.present(alert, animated: true)
|
|
41
|
+
// A sheet dismissed by tapping outside (iPad popover) never runs
|
|
42
|
+
// an action; report -1 once it disappears.
|
|
43
|
+
PNAlertDismissWatcher.watch(alert) { finish(-1) }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Observes an alert's disappearance to report an outside-tap dismissal.
|
|
48
|
+
final class PNAlertDismissWatcher: NSObject, UIPopoverPresentationControllerDelegate {
|
|
49
|
+
private let onDismiss: () -> Void
|
|
50
|
+
private static var watchers: [ObjectIdentifier: PNAlertDismissWatcher] = [:]
|
|
51
|
+
|
|
52
|
+
private init(onDismiss: @escaping () -> Void) {
|
|
53
|
+
self.onDismiss = onDismiss
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static func watch(_ alert: UIAlertController, onDismiss: @escaping () -> Void) {
|
|
57
|
+
let watcher = PNAlertDismissWatcher(onDismiss: onDismiss)
|
|
58
|
+
watchers[ObjectIdentifier(alert)] = watcher
|
|
59
|
+
alert.popoverPresentationController?.delegate = watcher
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
|
63
|
+
onDismiss()
|
|
64
|
+
PNAlertDismissWatcher.watchers.removeValue(forKey: ObjectIdentifier(presentationController.presentedViewController))
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// `Alert`: `present` (async, resolves the tapped index) and `show` (fire-and-forget).
|
|
69
|
+
public final class AlertModule: PNNativeModule {
|
|
70
|
+
public static let name = "Alert"
|
|
71
|
+
|
|
72
|
+
public init() {}
|
|
73
|
+
|
|
74
|
+
public func call(_ method: String, args: [String: Any], promise: PNPromise) {
|
|
75
|
+
switch method {
|
|
76
|
+
case "present", "show":
|
|
77
|
+
let buttons = ((args["buttons"] as? [Any]) ?? []).compactMap { $0 as? [String: Any] }
|
|
78
|
+
let fireAndForget = method == "show"
|
|
79
|
+
if fireAndForget { promise.resolve(nil) }
|
|
80
|
+
PNAlertPresenter.present(
|
|
81
|
+
title: PNProps.string(args["title"]),
|
|
82
|
+
message: PNProps.string(args["message"]),
|
|
83
|
+
buttons: buttons,
|
|
84
|
+
style: PNProps.string(args["style"]) ?? "alert"
|
|
85
|
+
) { index in
|
|
86
|
+
if !fireAndForget { promise.resolve(index) }
|
|
87
|
+
}
|
|
88
|
+
default:
|
|
89
|
+
promise.reject("Alert has no method '\(method)'", code: "unknown_method")
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// `Host`: screen root attachment, stack navigation, viewport queries,
|
|
4
|
+
/// and the main-queue pump.
|
|
5
|
+
public final class HostModule: PNNativeModule {
|
|
6
|
+
public static let name = "Host"
|
|
7
|
+
|
|
8
|
+
public init() {}
|
|
9
|
+
|
|
10
|
+
public func call(_ method: String, args: [String: Any], promise: PNPromise) {
|
|
11
|
+
switch method {
|
|
12
|
+
case "post":
|
|
13
|
+
DispatchQueue.main.async {
|
|
14
|
+
PNBridge.shared.callPython(kind: "pump", tag: 0, name: "", payload: "")
|
|
15
|
+
}
|
|
16
|
+
promise.resolve(nil)
|
|
17
|
+
case "is_main_thread":
|
|
18
|
+
promise.resolve(Thread.isMainThread)
|
|
19
|
+
case "attach_root":
|
|
20
|
+
guard let controller = screen(args, promise) else { return }
|
|
21
|
+
guard let tag = PNProps.int(args["tag"]), let view = PNViewRegistry.shared.view(for: Int64(tag)) else {
|
|
22
|
+
promise.reject("attach_root: unknown view tag", code: "unknown_tag")
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
controller.attachRoot(view)
|
|
26
|
+
promise.resolve(controller.viewport())
|
|
27
|
+
case "detach_root":
|
|
28
|
+
guard let controller = screen(args, promise) else { return }
|
|
29
|
+
if let tag = PNProps.int(args["tag"]), let view = PNViewRegistry.shared.view(for: Int64(tag)) {
|
|
30
|
+
controller.detachRoot(view)
|
|
31
|
+
}
|
|
32
|
+
promise.resolve(nil)
|
|
33
|
+
case "viewport":
|
|
34
|
+
guard let controller = screen(args, promise) else { return }
|
|
35
|
+
promise.resolve(controller.viewport())
|
|
36
|
+
case "set_options":
|
|
37
|
+
guard let controller = screen(args, promise) else { return }
|
|
38
|
+
HostModule.applyOptions(PNProps.dict(args["options"]) ?? [:], to: controller)
|
|
39
|
+
promise.resolve(nil)
|
|
40
|
+
case "push":
|
|
41
|
+
guard let controller = screen(args, promise) else { return }
|
|
42
|
+
let next = HostModule.makeScreen(args, from: controller)
|
|
43
|
+
promise.resolve(HostModule.runNavOp(controller) { nav in nav.pushViewController(next, animated: true) })
|
|
44
|
+
case "pop":
|
|
45
|
+
guard let controller = screen(args, promise) else { return }
|
|
46
|
+
let count = max(1, PNProps.int(args["count"]) ?? 1)
|
|
47
|
+
promise.resolve(HostModule.runNavOp(controller) { nav in
|
|
48
|
+
if count <= 1 {
|
|
49
|
+
nav.popViewController(animated: true)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
let controllers = nav.viewControllers
|
|
53
|
+
let target = max(0, controllers.count - 1 - count)
|
|
54
|
+
nav.popToViewController(controllers[target], animated: true)
|
|
55
|
+
})
|
|
56
|
+
case "pop_to_root":
|
|
57
|
+
guard let controller = screen(args, promise) else { return }
|
|
58
|
+
promise.resolve(HostModule.runNavOp(controller) { nav in nav.popToRootViewController(animated: true) })
|
|
59
|
+
case "replace":
|
|
60
|
+
guard let controller = screen(args, promise) else { return }
|
|
61
|
+
let next = HostModule.makeScreen(args, from: controller)
|
|
62
|
+
promise.resolve(HostModule.runNavOp(controller) { nav in
|
|
63
|
+
var controllers = nav.viewControllers
|
|
64
|
+
if let index = controllers.firstIndex(where: { $0 === controller }) {
|
|
65
|
+
controllers[index] = next
|
|
66
|
+
} else if controllers.isEmpty {
|
|
67
|
+
controllers = [next]
|
|
68
|
+
} else {
|
|
69
|
+
controllers[controllers.count - 1] = next
|
|
70
|
+
}
|
|
71
|
+
nav.setViewControllers(controllers, animated: true)
|
|
72
|
+
})
|
|
73
|
+
case "reset":
|
|
74
|
+
guard let controller = screen(args, promise) else { return }
|
|
75
|
+
let specs = ((args["screens"] as? [Any]) ?? []).compactMap { $0 as? [String: Any] }
|
|
76
|
+
let screens = specs.map { HostModule.makeScreen($0, from: controller) }
|
|
77
|
+
promise.resolve(HostModule.runNavOp(controller) { nav in
|
|
78
|
+
guard let root = nav.viewControllers.first else {
|
|
79
|
+
nav.setViewControllers(screens, animated: true)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
nav.setViewControllers([root] + screens, animated: true)
|
|
83
|
+
})
|
|
84
|
+
default:
|
|
85
|
+
promise.reject("Host has no method '\(method)'", code: "unknown_method")
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// MARK: - Helpers
|
|
90
|
+
|
|
91
|
+
private func screen(_ args: [String: Any], _ promise: PNPromise) -> PNViewController? {
|
|
92
|
+
guard let id = PNProps.int(args["screen"]) else {
|
|
93
|
+
promise.reject("missing 'screen' id", code: "bad_args")
|
|
94
|
+
return nil
|
|
95
|
+
}
|
|
96
|
+
guard let controller = PNScreenRegistry.shared.controller(for: Int64(id)) else {
|
|
97
|
+
promise.reject("no screen with id \(id)", code: "unknown_screen")
|
|
98
|
+
return nil
|
|
99
|
+
}
|
|
100
|
+
return controller
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Build a sibling screen controller of the same class as `source`.
|
|
104
|
+
static func makeScreen(_ args: [String: Any], from source: PNViewController) -> PNViewController {
|
|
105
|
+
let next = type(of: source).init(nibName: nil, bundle: nil)
|
|
106
|
+
next.requestedScreenPath = PNProps.string(args["path"])
|
|
107
|
+
if let screenArgs = args["args"], !(screenArgs is NSNull) {
|
|
108
|
+
next.requestedScreenArgsJSON = (screenArgs as? String) ?? PNJSON.encode(screenArgs)
|
|
109
|
+
}
|
|
110
|
+
next.devRoot = source.devRoot
|
|
111
|
+
if let title = PNProps.string(args["title"]) {
|
|
112
|
+
next.title = title
|
|
113
|
+
}
|
|
114
|
+
if let options = PNProps.dict(args["options"]) {
|
|
115
|
+
applyOptions(options, to: next)
|
|
116
|
+
}
|
|
117
|
+
return next
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static func applyOptions(_ options: [String: Any], to controller: UIViewController) {
|
|
121
|
+
if let title = PNProps.string(options["title"]) {
|
|
122
|
+
controller.title = title
|
|
123
|
+
}
|
|
124
|
+
if let hidden = PNProps.bool(options["header_shown"]) {
|
|
125
|
+
controller.navigationController?.setNavigationBarHidden(!hidden, animated: false)
|
|
126
|
+
}
|
|
127
|
+
if let hidesBack = PNProps.bool(options["hide_back_button"]) {
|
|
128
|
+
controller.navigationItem.hidesBackButton = hidesBack
|
|
129
|
+
}
|
|
130
|
+
if let color = PNColor.parse(options["header_tint_color"]) {
|
|
131
|
+
controller.navigationController?.navigationBar.tintColor = color
|
|
132
|
+
}
|
|
133
|
+
if let color = PNColor.parse(options["header_background_color"]) {
|
|
134
|
+
let appearance = UINavigationBarAppearance()
|
|
135
|
+
appearance.configureWithOpaqueBackground()
|
|
136
|
+
appearance.backgroundColor = color
|
|
137
|
+
controller.navigationItem.standardAppearance = appearance
|
|
138
|
+
controller.navigationItem.scrollEdgeAppearance = appearance
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Run `op` on the controller's navigation stack unless a transition
|
|
143
|
+
/// is in flight (UIKit doesn't queue navigation calls). Returns
|
|
144
|
+
/// whether the op ran.
|
|
145
|
+
@discardableResult
|
|
146
|
+
static func runNavOp(_ controller: UIViewController, _ op: (UINavigationController) -> Void) -> Bool {
|
|
147
|
+
guard let nav = controller.navigationController else {
|
|
148
|
+
PNLog.screens.error("navigation requested but the screen is not inside a UINavigationController")
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
if nav.transitionCoordinator != nil {
|
|
152
|
+
PNLog.rateLimited(PNLog.screens, key: "nav-inflight", "navigation op dropped: transition in flight")
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
op(nav)
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
}
|