pythonnative 0.35.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/cli/pn.py +24 -6
- 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/mutations.py +21 -0
- 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.35.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.35.0.dist-info/RECORD +0 -155
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import Network
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// `AppState`: foreground / background transitions.
|
|
5
|
+
///
|
|
6
|
+
/// The scene delegate calls `AppStateModule.dispatch(_:)`; the module
|
|
7
|
+
/// emits `change` with the state string (`"active"`, `"inactive"`,
|
|
8
|
+
/// `"background"`) as the payload, which is what the Python facade reads.
|
|
9
|
+
public final class AppStateModule: PNNativeModule {
|
|
10
|
+
public static let name = "AppState"
|
|
11
|
+
public private(set) static var current = "active"
|
|
12
|
+
|
|
13
|
+
public init() {}
|
|
14
|
+
|
|
15
|
+
public func call(_ method: String, args: [String: Any], promise: PNPromise) {
|
|
16
|
+
switch method {
|
|
17
|
+
case "current_state":
|
|
18
|
+
promise.resolve(AppStateModule.current)
|
|
19
|
+
default:
|
|
20
|
+
promise.reject("AppState has no method '\(method)'", code: "unknown_method")
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Record and publish a lifecycle transition.
|
|
25
|
+
public static func dispatch(_ state: String) {
|
|
26
|
+
guard ["active", "inactive", "background"].contains(state), state != current else { return }
|
|
27
|
+
current = state
|
|
28
|
+
PNBridge.shared.whenCallbackRegistered {
|
|
29
|
+
PNBridge.shared.callPython(kind: "module", tag: 0, name: name, payload: PNJSON.encode(["event": "change", "payload": state]))
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// `Battery`: level / state getters plus `change` events while monitoring.
|
|
35
|
+
public final class BatteryModule: PNNativeModule {
|
|
36
|
+
public static let name = "Battery"
|
|
37
|
+
private static var observers: [NSObjectProtocol] = []
|
|
38
|
+
|
|
39
|
+
public init() {
|
|
40
|
+
BatteryModule.startMonitoring()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public func call(_ method: String, args: [String: Any], promise: PNPromise) {
|
|
44
|
+
switch method {
|
|
45
|
+
case "get_level":
|
|
46
|
+
promise.resolve(Double(UIDevice.current.batteryLevel))
|
|
47
|
+
case "get_state":
|
|
48
|
+
promise.resolve(BatteryModule.stateName())
|
|
49
|
+
default:
|
|
50
|
+
promise.reject("Battery has no method '\(method)'", code: "unknown_method")
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Enable battery monitoring and forward UIDevice notifications.
|
|
55
|
+
public static func startMonitoring() {
|
|
56
|
+
guard observers.isEmpty else { return }
|
|
57
|
+
UIDevice.current.isBatteryMonitoringEnabled = true
|
|
58
|
+
let center = NotificationCenter.default
|
|
59
|
+
for notification in [UIDevice.batteryLevelDidChangeNotification, UIDevice.batteryStateDidChangeNotification] {
|
|
60
|
+
observers.append(center.addObserver(forName: notification, object: nil, queue: .main) { _ in
|
|
61
|
+
BatteryModule.dispatch()
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Emit the current `{"level", "state"}` snapshot.
|
|
67
|
+
public static func dispatch() {
|
|
68
|
+
PNModuleEvents.emit(module: name, event: "change", payload: [
|
|
69
|
+
"level": Double(UIDevice.current.batteryLevel),
|
|
70
|
+
"state": stateName(),
|
|
71
|
+
])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static func stateName() -> String {
|
|
75
|
+
switch UIDevice.current.batteryState {
|
|
76
|
+
case .unplugged: return "unplugged"
|
|
77
|
+
case .charging: return "charging"
|
|
78
|
+
case .full: return "full"
|
|
79
|
+
default: return "unknown"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// `NetInfo`: connectivity snapshots from `NWPathMonitor`.
|
|
85
|
+
public final class NetInfoModule: PNNativeModule {
|
|
86
|
+
public static let name = "NetInfo"
|
|
87
|
+
|
|
88
|
+
private let monitor = NWPathMonitor()
|
|
89
|
+
private var last: [String: Any]?
|
|
90
|
+
|
|
91
|
+
public init() {
|
|
92
|
+
monitor.pathUpdateHandler = { [weak self] path in
|
|
93
|
+
let snapshot = NetInfoModule.snapshot(path)
|
|
94
|
+
DispatchQueue.main.async {
|
|
95
|
+
guard let self = self else { return }
|
|
96
|
+
if let last = self.last, NSDictionary(dictionary: last).isEqual(to: snapshot) { return }
|
|
97
|
+
self.last = snapshot
|
|
98
|
+
PNModuleEvents.emit(module: NetInfoModule.name, event: "change", payload: snapshot)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
monitor.start(queue: DispatchQueue(label: "com.pythonnative.netinfo"))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
deinit {
|
|
105
|
+
monitor.cancel()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public func call(_ method: String, args: [String: Any], promise: PNPromise) {
|
|
109
|
+
switch method {
|
|
110
|
+
case "fetch":
|
|
111
|
+
let snapshot = NetInfoModule.snapshot(monitor.currentPath)
|
|
112
|
+
last = snapshot
|
|
113
|
+
promise.resolve(snapshot)
|
|
114
|
+
default:
|
|
115
|
+
promise.reject("NetInfo has no method '\(method)'", code: "unknown_method")
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static func snapshot(_ path: NWPath) -> [String: Any] {
|
|
120
|
+
let connected = path.status == .satisfied
|
|
121
|
+
let type: String
|
|
122
|
+
if !connected {
|
|
123
|
+
type = "none"
|
|
124
|
+
} else if path.usesInterfaceType(.wifi) {
|
|
125
|
+
type = "wifi"
|
|
126
|
+
} else if path.usesInterfaceType(.cellular) {
|
|
127
|
+
type = "cellular"
|
|
128
|
+
} else if path.usesInterfaceType(.wiredEthernet) {
|
|
129
|
+
type = "ethernet"
|
|
130
|
+
} else {
|
|
131
|
+
type = "unknown"
|
|
132
|
+
}
|
|
133
|
+
return ["is_connected": connected, "type": type, "is_internet_reachable": connected]
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// `Linking`: open URLs and deliver inbound deep links.
|
|
138
|
+
///
|
|
139
|
+
/// `deliver(url:)` buffers until Python's callback is registered so a
|
|
140
|
+
/// cold-start deep link reaches `Linking.get_initial_url()`.
|
|
141
|
+
public final class LinkingModule: PNNativeModule {
|
|
142
|
+
public static let name = "Linking"
|
|
143
|
+
|
|
144
|
+
public init() {}
|
|
145
|
+
|
|
146
|
+
public func call(_ method: String, args: [String: Any], promise: PNPromise) {
|
|
147
|
+
switch method {
|
|
148
|
+
case "open_url":
|
|
149
|
+
guard let url = PNProps.string(args["url"]).flatMap({ URL(string: $0) }) else { return promise.resolve(false) }
|
|
150
|
+
let app = UIApplication.shared
|
|
151
|
+
guard app.canOpenURL(url) || url.scheme?.hasPrefix("http") == true else { return promise.resolve(false) }
|
|
152
|
+
app.open(url, options: [:]) { _ in }
|
|
153
|
+
promise.resolve(true)
|
|
154
|
+
case "can_open_url":
|
|
155
|
+
guard let url = PNProps.string(args["url"]).flatMap({ URL(string: $0) }) else { return promise.resolve(false) }
|
|
156
|
+
promise.resolve(UIApplication.shared.canOpenURL(url))
|
|
157
|
+
case "open_settings":
|
|
158
|
+
guard let url = URL(string: UIApplication.openSettingsURLString) else { return promise.resolve(false) }
|
|
159
|
+
UIApplication.shared.open(url, options: [:]) { _ in }
|
|
160
|
+
promise.resolve(true)
|
|
161
|
+
default:
|
|
162
|
+
promise.reject("Linking has no method '\(method)'", code: "unknown_method")
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Forward an inbound URL as a `url` event (payload: the URL string).
|
|
167
|
+
public static func deliver(url: String) {
|
|
168
|
+
PNBridge.shared.whenCallbackRegistered {
|
|
169
|
+
PNMain.run {
|
|
170
|
+
PNBridge.shared.callPython(kind: "module", tag: 0, name: name, payload: PNJSON.encode(["event": "url", "payload": url]))
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import CoreLocation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// `Camera`: `take_photo` / `pick_from_gallery` through `UIImagePickerController`.
|
|
5
|
+
public final class CameraModule: PNNativeModule {
|
|
6
|
+
public static let name = "Camera"
|
|
7
|
+
|
|
8
|
+
private var activePicker: PNImagePickerSession?
|
|
9
|
+
|
|
10
|
+
public init() {}
|
|
11
|
+
|
|
12
|
+
public func call(_ method: String, args: [String: Any], promise: PNPromise) {
|
|
13
|
+
let source: UIImagePickerController.SourceType
|
|
14
|
+
switch method {
|
|
15
|
+
case "take_photo": source = .camera
|
|
16
|
+
case "pick_from_gallery": source = .photoLibrary
|
|
17
|
+
default:
|
|
18
|
+
promise.reject("Camera has no method '\(method)'", code: "unknown_method")
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
guard UIImagePickerController.isSourceTypeAvailable(source), let top = PNWindow.topViewController() else {
|
|
22
|
+
promise.resolve(nil)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
if activePicker != nil {
|
|
26
|
+
promise.reject("a picker is already open", code: "busy")
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
let quality = PNProps.double(args["quality"]) ?? 0.9
|
|
30
|
+
let session = PNImagePickerSession(quality: CGFloat(max(0, min(1, quality)))) { [weak self] path in
|
|
31
|
+
self?.activePicker = nil
|
|
32
|
+
promise.resolve(path)
|
|
33
|
+
}
|
|
34
|
+
activePicker = session
|
|
35
|
+
let picker = UIImagePickerController()
|
|
36
|
+
picker.sourceType = source
|
|
37
|
+
picker.allowsEditing = PNProps.bool(args["allow_editing"]) ?? false
|
|
38
|
+
picker.delegate = session
|
|
39
|
+
top.present(picker, animated: true)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Picker delegate that writes the chosen image to Caches and reports its path.
|
|
44
|
+
final class PNImagePickerSession: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
|
45
|
+
private let quality: CGFloat
|
|
46
|
+
private let done: (String?) -> Void
|
|
47
|
+
|
|
48
|
+
init(quality: CGFloat, done: @escaping (String?) -> Void) {
|
|
49
|
+
self.quality = quality
|
|
50
|
+
self.done = done
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
|
|
54
|
+
picker.dismiss(animated: true)
|
|
55
|
+
let image = (info[.editedImage] as? UIImage) ?? (info[.originalImage] as? UIImage)
|
|
56
|
+
guard let image = image, let data = image.jpegData(compressionQuality: quality) else {
|
|
57
|
+
done(nil)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
let caches = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first ?? URL(fileURLWithPath: NSTemporaryDirectory())
|
|
61
|
+
let dir = caches.appendingPathComponent("pn_camera", isDirectory: true)
|
|
62
|
+
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
63
|
+
let file = dir.appendingPathComponent("\(UUID().uuidString).jpg")
|
|
64
|
+
do {
|
|
65
|
+
try data.write(to: file, options: [.atomic])
|
|
66
|
+
done(file.path)
|
|
67
|
+
} catch {
|
|
68
|
+
PNLog.modules.error("could not save picked image: \(error.localizedDescription)")
|
|
69
|
+
done(nil)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
|
74
|
+
picker.dismiss(animated: true)
|
|
75
|
+
done(nil)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// `Location`: one-shot `get_current` fix via `CLLocationManager`.
|
|
80
|
+
public final class LocationModule: PNNativeModule {
|
|
81
|
+
public static let name = "Location"
|
|
82
|
+
|
|
83
|
+
private var sessions: [PNLocationSession] = []
|
|
84
|
+
|
|
85
|
+
public init() {}
|
|
86
|
+
|
|
87
|
+
public func call(_ method: String, args: [String: Any], promise: PNPromise) {
|
|
88
|
+
guard method == "get_current" else {
|
|
89
|
+
promise.reject("Location has no method '\(method)'", code: "unknown_method")
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
let timeout = PNProps.double(args["timeout"]) ?? 15
|
|
93
|
+
var session: PNLocationSession?
|
|
94
|
+
session = PNLocationSession(accuracy: PNProps.string(args["accuracy"]), timeout: timeout) { [weak self] fix in
|
|
95
|
+
if let finished = session {
|
|
96
|
+
self?.sessions.removeAll { $0 === finished }
|
|
97
|
+
}
|
|
98
|
+
promise.resolve(fix)
|
|
99
|
+
}
|
|
100
|
+
guard let started = session else { return }
|
|
101
|
+
sessions.append(started)
|
|
102
|
+
started.start()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// One location request: authorization, a single fix, and a timeout.
|
|
107
|
+
final class PNLocationSession: NSObject, CLLocationManagerDelegate {
|
|
108
|
+
private let manager = CLLocationManager()
|
|
109
|
+
private let done: ([String: Any]?) -> Void
|
|
110
|
+
private let timeout: TimeInterval
|
|
111
|
+
private var finished = false
|
|
112
|
+
private var timer: Timer?
|
|
113
|
+
|
|
114
|
+
init(accuracy: String?, timeout: TimeInterval, done: @escaping ([String: Any]?) -> Void) {
|
|
115
|
+
self.done = done
|
|
116
|
+
self.timeout = max(1, timeout)
|
|
117
|
+
super.init()
|
|
118
|
+
manager.delegate = self
|
|
119
|
+
switch accuracy {
|
|
120
|
+
case "high", "best": manager.desiredAccuracy = kCLLocationAccuracyBest
|
|
121
|
+
case "low", "coarse": manager.desiredAccuracy = kCLLocationAccuracyKilometer
|
|
122
|
+
default: manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func start() {
|
|
127
|
+
timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in
|
|
128
|
+
self?.finish(nil)
|
|
129
|
+
}
|
|
130
|
+
switch manager.pnAuthorizationStatus {
|
|
131
|
+
case .notDetermined:
|
|
132
|
+
manager.requestWhenInUseAuthorization()
|
|
133
|
+
case .denied, .restricted:
|
|
134
|
+
finish(nil)
|
|
135
|
+
default:
|
|
136
|
+
manager.requestLocation()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
141
|
+
switch manager.pnAuthorizationStatus {
|
|
142
|
+
case .authorizedAlways, .authorizedWhenInUse:
|
|
143
|
+
manager.requestLocation()
|
|
144
|
+
case .denied, .restricted:
|
|
145
|
+
finish(nil)
|
|
146
|
+
default:
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
152
|
+
guard let location = locations.last else { return }
|
|
153
|
+
var fix: [String: Any] = [
|
|
154
|
+
"latitude": location.coordinate.latitude,
|
|
155
|
+
"longitude": location.coordinate.longitude,
|
|
156
|
+
"accuracy": location.horizontalAccuracy,
|
|
157
|
+
"altitude": location.altitude,
|
|
158
|
+
"timestamp": location.timestamp.timeIntervalSince1970,
|
|
159
|
+
]
|
|
160
|
+
if location.speed >= 0 { fix["speed"] = location.speed }
|
|
161
|
+
if location.course >= 0 { fix["heading"] = location.course }
|
|
162
|
+
finish(fix)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
166
|
+
PNLog.modules.error("location failed: \(error.localizedDescription)")
|
|
167
|
+
finish(nil)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private func finish(_ fix: [String: Any]?) {
|
|
171
|
+
if finished { return }
|
|
172
|
+
finished = true
|
|
173
|
+
timer?.invalidate()
|
|
174
|
+
manager.stopUpdatingLocation()
|
|
175
|
+
done(fix)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// A named native module callable from Python through `pn_bridge_call`.
|
|
4
|
+
public protocol PNNativeModule: AnyObject {
|
|
5
|
+
/// The module name Python addresses (`native_module("Clipboard")`).
|
|
6
|
+
static var name: String { get }
|
|
7
|
+
init()
|
|
8
|
+
/// Handle one method call, settling `promise` now or later.
|
|
9
|
+
func call(_ method: String, args: [String: Any], promise: PNPromise)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/// The settlement handle for one module call.
|
|
13
|
+
///
|
|
14
|
+
/// Resolving or rejecting before `call` returns answers Python inline;
|
|
15
|
+
/// settling afterwards posts `callback("module", 0, module,
|
|
16
|
+
/// {"call_id": n, "ok": ..., ...})` on the main thread.
|
|
17
|
+
public final class PNPromise {
|
|
18
|
+
public let callId: Int64
|
|
19
|
+
public let module: String
|
|
20
|
+
public let method: String
|
|
21
|
+
|
|
22
|
+
private(set) var result: [String: Any]?
|
|
23
|
+
private var returned = false
|
|
24
|
+
private let lock = NSLock()
|
|
25
|
+
|
|
26
|
+
init(callId: Int64, module: String, method: String) {
|
|
27
|
+
self.callId = callId
|
|
28
|
+
self.module = module
|
|
29
|
+
self.method = method
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Whether the promise has been resolved or rejected.
|
|
33
|
+
public var isSettled: Bool {
|
|
34
|
+
lock.lock()
|
|
35
|
+
defer { lock.unlock() }
|
|
36
|
+
return result != nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Settle successfully with `value` (JSON-encodable or `nil`).
|
|
40
|
+
public func resolve(_ value: Any?) {
|
|
41
|
+
settle(["ok": true, "value": value ?? NSNull()])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Settle with an error message and optional machine-readable code.
|
|
45
|
+
public func reject(_ message: String, code: String? = nil) {
|
|
46
|
+
var envelope: [String: Any] = ["ok": false, "error": message]
|
|
47
|
+
if let code = code { envelope["code"] = code }
|
|
48
|
+
settle(envelope)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Settle from a Swift `Error`.
|
|
52
|
+
public func reject(_ error: Error) {
|
|
53
|
+
reject(error.localizedDescription, code: (error as NSError).domain)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private func settle(_ envelope: [String: Any]) {
|
|
57
|
+
lock.lock()
|
|
58
|
+
if result != nil {
|
|
59
|
+
lock.unlock()
|
|
60
|
+
PNLog.rateLimited(PNLog.modules, key: "double-settle", "\(module).\(method) settled twice; ignoring")
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
result = envelope
|
|
64
|
+
let deliverAsync = returned
|
|
65
|
+
lock.unlock()
|
|
66
|
+
if deliverAsync {
|
|
67
|
+
var payload = envelope
|
|
68
|
+
payload["call_id"] = callId
|
|
69
|
+
let module = self.module
|
|
70
|
+
PNMain.run {
|
|
71
|
+
PNBridge.shared.callPython(kind: "module", tag: 0, name: module, payload: PNJSON.encode(payload))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Called by the dispatcher once `call` returns: the inline result,
|
|
77
|
+
/// or `{"pending": true}` when the module will settle later.
|
|
78
|
+
func takeInlineResult() -> [String: Any] {
|
|
79
|
+
lock.lock()
|
|
80
|
+
defer { lock.unlock() }
|
|
81
|
+
returned = true
|
|
82
|
+
if let result = result { return result }
|
|
83
|
+
return ["pending": true]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Unsolicited module events: `callback("module", 0, module, {"event", "payload"})`.
|
|
88
|
+
public enum PNModuleEvents {
|
|
89
|
+
/// Emit `event` from `module` with `payload` on the main thread.
|
|
90
|
+
public static func emit(module: String, event: String, payload: [String: Any]) {
|
|
91
|
+
PNMain.run {
|
|
92
|
+
PNBridge.shared.callPython(kind: "module", tag: 0, name: module, payload: PNJSON.encode(["event": event, "payload": payload]))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Main-thread hop that runs inline when already on main.
|
|
98
|
+
public enum PNMain {
|
|
99
|
+
public static func run(_ block: @escaping () -> Void) {
|
|
100
|
+
if Thread.isMainThread {
|
|
101
|
+
block()
|
|
102
|
+
} else {
|
|
103
|
+
DispatchQueue.main.async(execute: block)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Routes `pn_bridge_call` to modules and shapes the reply envelope.
|
|
109
|
+
public final class PNModuleDispatcher {
|
|
110
|
+
public static let shared = PNModuleDispatcher()
|
|
111
|
+
|
|
112
|
+
private init() {}
|
|
113
|
+
|
|
114
|
+
/// Dispatch one call. `envelope` is `{"call_id": n, "args": {...}}`.
|
|
115
|
+
public func call(module: String, method: String, envelope: [String: Any]) -> [String: Any] {
|
|
116
|
+
let callId = Int64(PNProps.int(envelope["call_id"]) ?? 0)
|
|
117
|
+
let args = PNProps.dict(envelope["args"]) ?? [:]
|
|
118
|
+
guard let instance = PNRegistry.shared.module(named: module) else {
|
|
119
|
+
return ["ok": false, "error": "unknown native module '\(module)'", "code": "unknown_module"]
|
|
120
|
+
}
|
|
121
|
+
let promise = PNPromise(callId: callId, module: module, method: method)
|
|
122
|
+
instance.call(method, args: args, promise: promise)
|
|
123
|
+
return promise.takeInlineResult()
|
|
124
|
+
}
|
|
125
|
+
}
|