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.
Files changed (176) hide show
  1. pythonnative/__init__.py +6 -4
  2. pythonnative/_ios_log.py +1 -1
  3. pythonnative/alerts.py +34 -88
  4. pythonnative/bootstrap.py +90 -0
  5. pythonnative/bridge/__init__.py +306 -0
  6. pythonnative/bridge/android.py +78 -0
  7. pythonnative/bridge/codec.py +164 -0
  8. pythonnative/bridge/fake.py +236 -0
  9. pythonnative/bridge/ios.py +138 -0
  10. pythonnative/cli/pn.py +24 -6
  11. pythonnative/components/layout.py +65 -3
  12. pythonnative/gestures.py +13 -10
  13. pythonnative/hooks.py +41 -26
  14. pythonnative/hosts/__init__.py +13 -36
  15. pythonnative/hosts/native.py +348 -0
  16. pythonnative/images.py +6 -5
  17. pythonnative/mutations.py +21 -0
  18. pythonnative/native_modules/__init__.py +30 -29
  19. pythonnative/native_modules/app_state.py +10 -6
  20. pythonnative/native_modules/battery.py +20 -84
  21. pythonnative/native_modules/biometrics.py +11 -124
  22. pythonnative/native_modules/camera.py +15 -291
  23. pythonnative/native_modules/clipboard.py +14 -109
  24. pythonnative/native_modules/desktop.py +322 -0
  25. pythonnative/native_modules/file_system.py +15 -21
  26. pythonnative/native_modules/haptics.py +19 -129
  27. pythonnative/native_modules/linking.py +24 -137
  28. pythonnative/native_modules/location.py +28 -180
  29. pythonnative/native_modules/net_info.py +25 -135
  30. pythonnative/native_modules/notifications.py +36 -257
  31. pythonnative/native_modules/permissions.py +20 -184
  32. pythonnative/native_modules/registry.py +513 -0
  33. pythonnative/native_modules/secure_store.py +18 -155
  34. pythonnative/native_modules/share.py +8 -113
  35. pythonnative/native_views/__init__.py +67 -86
  36. pythonnative/native_views/bridge_backend.py +264 -0
  37. pythonnative/platform.py +19 -15
  38. pythonnative/platform_metrics.py +3 -3
  39. pythonnative/project/android.py +0 -3
  40. pythonnative/project/builder.py +38 -16
  41. pythonnative/project/config.py +12 -2
  42. pythonnative/project/plugins.py +366 -0
  43. pythonnative/py.typed +0 -0
  44. pythonnative/runtime.py +35 -203
  45. pythonnative/sdk/__init__.py +48 -33
  46. pythonnative/sdk/_components.py +57 -76
  47. pythonnative/storage.py +18 -243
  48. pythonnative/templates/android_template/app/build.gradle +6 -9
  49. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  50. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  51. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  52. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  53. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  54. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  55. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  56. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  57. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  58. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  59. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  60. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  61. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  62. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  100. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  101. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  102. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  103. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  104. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  105. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  106. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  107. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  108. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  109. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  110. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  111. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  112. pythonnative/templates/android_template/settings.gradle +1 -0
  113. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  114. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  115. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  116. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  117. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  118. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  119. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  120. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  121. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  122. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  123. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  155. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  156. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  157. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  158. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  159. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
  160. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  161. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  162. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
  163. pythonnative/utils.py +4 -82
  164. pythonnative/virtual_rows.py +3 -3
  165. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
  166. pythonnative-0.37.0.dist-info/RECORD +263 -0
  167. pythonnative/hosts/android.py +0 -269
  168. pythonnative/hosts/ios.py +0 -404
  169. pythonnative/native_views/android.py +0 -3702
  170. pythonnative/native_views/ios.py +0 -5253
  171. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  172. pythonnative-0.35.0.dist-info/RECORD +0 -155
  173. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
  174. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
  175. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
  176. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,301 @@
1
+ import UIKit
2
+
3
+ /// Drives the `animate(tag, request)` protocol for animatable props
4
+ /// (`opacity`, `background_color`, `color`, `translate_x`, `translate_y`,
5
+ /// `scale`, `scale_x`, `scale_y`, `rotate`).
6
+ ///
7
+ /// - `set` applies one Python-driven frame immediately.
8
+ /// - `start` runs `timing` / `spring` with `UIViewPropertyAnimator` and
9
+ /// `decay` with a display-link integrator; completion posts
10
+ /// `callback("animation", 0, "", {"id": n, "finished": bool})`.
11
+ /// - `cancel` stops the animation and returns the presentation value.
12
+ public final class PNAnimator {
13
+ public static let shared = PNAnimator()
14
+
15
+ /// Prop names accepted by `set` and `start`.
16
+ public static let animatableProps: Set<String> = [
17
+ "opacity", "background_color", "color", "translate_x", "translate_y", "scale", "scale_x", "scale_y", "rotate",
18
+ ]
19
+
20
+ final class Running {
21
+ weak var view: UIView?
22
+ let prop: String
23
+ var animator: UIViewPropertyAnimator?
24
+ var decay: PNDecayDriver?
25
+ init(view: UIView, prop: String) {
26
+ self.view = view
27
+ self.prop = prop
28
+ }
29
+ }
30
+
31
+ private var running: [Int64: Running] = [:]
32
+
33
+ private init() {}
34
+
35
+ // MARK: - Request routing
36
+
37
+ /// Handle one decoded `animate` request for the view with `tag`.
38
+ public func handle(tag: Int64, request: [String: Any]) -> Any? {
39
+ let op = PNProps.string(request["op"]) ?? ""
40
+ switch op {
41
+ case "set":
42
+ guard let record = PNViewRegistry.shared.resolve(tag), let prop = PNProps.string(request["prop"]) else { return nil }
43
+ record.manager.setAnimatedProperty(view: record.view, prop: prop, value: request["value"])
44
+ return nil
45
+ case "start":
46
+ guard let record = PNViewRegistry.shared.resolve(tag), let prop = PNProps.string(request["prop"]),
47
+ let id = PNProps.int(request["id"])
48
+ else { return ["ok": false] }
49
+ let spec = PNProps.dict(request["spec"]) ?? [:]
50
+ let ok = record.manager.startAnimation(view: record.view, id: Int64(id), prop: prop, spec: spec)
51
+ return ["ok": ok]
52
+ case "cancel":
53
+ guard let id = PNProps.int(request["id"]) else { return nil }
54
+ let value: Any?
55
+ if let record = PNViewRegistry.shared.resolve(tag) {
56
+ value = record.manager.cancelAnimation(view: record.view, id: Int64(id))
57
+ } else {
58
+ value = cancel(id: Int64(id))
59
+ }
60
+ guard let value = value else { return nil }
61
+ return ["value": value]
62
+ default:
63
+ PNLog.rateLimited(PNLog.animation, key: "op:\(op)", "unknown animate op '\(op)'")
64
+ return nil
65
+ }
66
+ }
67
+
68
+ // MARK: - Frame application
69
+
70
+ /// Apply one value of `prop` to `view` immediately.
71
+ public func applyValue(view: UIView, prop: String, value: Any?) {
72
+ switch prop {
73
+ case "opacity":
74
+ if let v = PNProps.double(value) { view.alpha = CGFloat(v) }
75
+ case "background_color":
76
+ if let color = PNColor.parse(value) { view.backgroundColor = color }
77
+ case "color":
78
+ guard let color = PNColor.parse(value) else { return }
79
+ if let label = view as? UILabel {
80
+ label.textColor = color
81
+ } else if let field = view as? UITextField {
82
+ field.textColor = color
83
+ } else if let textView = view as? UITextView {
84
+ textView.textColor = color
85
+ } else if let button = view as? UIButton {
86
+ button.setTitleColor(color, for: .normal)
87
+ }
88
+ case "translate_x", "translate_y", "scale", "scale_x", "scale_y", "rotate":
89
+ guard let v = PNProps.double(value), v.isFinite else { return }
90
+ PNTransform.apply(view, spec: [[prop: v]])
91
+ default:
92
+ PNLog.once(PNLog.animation, key: "prop:\(prop)", "ignoring unknown animated prop '\(prop)'")
93
+ }
94
+ }
95
+
96
+ /// Read the value of `prop` currently on screen (presentation layer when animating).
97
+ public func presentationValue(view: UIView, prop: String) -> Any? {
98
+ let layer = view.layer.presentation() ?? view.layer
99
+ switch prop {
100
+ case "opacity":
101
+ return Double(layer.opacity)
102
+ case "background_color":
103
+ return layer.backgroundColor.map { PNColor.hexString(UIColor(cgColor: $0)) }
104
+ case "translate_x", "translate_y", "scale", "scale_x", "scale_y", "rotate":
105
+ let t = layer.affineTransform()
106
+ let parts = PNTransform.decompose(t)
107
+ switch prop {
108
+ case "translate_x": return Double(parts.translateX)
109
+ case "translate_y": return Double(parts.translateY)
110
+ case "scale", "scale_x": return Double(parts.scaleX)
111
+ case "scale_y": return Double(parts.scaleY)
112
+ default: return Double(parts.rotateDegrees)
113
+ }
114
+ default:
115
+ return nil
116
+ }
117
+ }
118
+
119
+ // MARK: - Start / cancel
120
+
121
+ /// Start a native animation. Returns `false` when the spec must be
122
+ /// ticked by Python instead.
123
+ public func start(view: UIView, id: Int64, prop: String, spec: [String: Any]) -> Bool {
124
+ guard PNAnimator.animatableProps.contains(prop), Thread.isMainThread else { return false }
125
+ let kind = PNProps.string(spec["kind"]) ?? ""
126
+ let entry = Running(view: view, prop: prop)
127
+ switch kind {
128
+ case "timing", "spring":
129
+ guard spec["to"] != nil else { return false }
130
+ if let from = spec["from"] { applyValue(view: view, prop: prop, value: from) }
131
+ let timing = PNAnimator.timingParameters(kind: kind, spec: spec)
132
+ let animator = UIViewPropertyAnimator(duration: timing.duration, timingParameters: timing.parameters)
133
+ animator.isUserInteractionEnabled = true
134
+ animator.addAnimations { [weak self, weak view] in
135
+ guard let self = self, let view = view else { return }
136
+ self.applyValue(view: view, prop: prop, value: spec["to"])
137
+ }
138
+ animator.addCompletion { [weak self] position in
139
+ guard let self = self, self.running[id] != nil else { return }
140
+ self.running.removeValue(forKey: id)
141
+ PNAnimator.reportCompletion(id: id, finished: position == .end)
142
+ }
143
+ entry.animator = animator
144
+ running[id] = entry
145
+ let delay = max(0, (PNProps.double(spec["delay_ms"]) ?? 0) / 1000)
146
+ animator.startAnimation(afterDelay: delay)
147
+ return true
148
+ case "decay":
149
+ guard let from = PNProps.double(spec["from"]), let velocity = PNProps.double(spec["velocity"]) else { return false }
150
+ let deceleration = max(1e-6, PNProps.double(spec["deceleration"]) ?? 0.997)
151
+ let driver = PNDecayDriver(from: from, velocity: velocity, deceleration: deceleration) { [weak self, weak view] value in
152
+ guard let self = self, let view = view else { return }
153
+ self.applyValue(view: view, prop: prop, value: value)
154
+ } completion: { [weak self] in
155
+ guard let self = self, self.running[id] != nil else { return }
156
+ self.running.removeValue(forKey: id)
157
+ PNAnimator.reportCompletion(id: id, finished: true)
158
+ }
159
+ entry.decay = driver
160
+ running[id] = entry
161
+ driver.start()
162
+ return true
163
+ default:
164
+ return false
165
+ }
166
+ }
167
+
168
+ /// Stop animation `id`, leaving the view at its presentation value.
169
+ /// Returns that value (or `nil` when the animation isn't running).
170
+ public func cancel(id: Int64) -> Any? {
171
+ guard let entry = running.removeValue(forKey: id) else { return nil }
172
+ var value: Any?
173
+ if let view = entry.view {
174
+ value = presentationValue(view: view, prop: entry.prop)
175
+ }
176
+ if let animator = entry.animator {
177
+ animator.stopAnimation(true)
178
+ }
179
+ if let decay = entry.decay {
180
+ value = decay.currentValue
181
+ decay.stop()
182
+ }
183
+ if let view = entry.view, let value = value {
184
+ view.layer.removeAllAnimations()
185
+ applyValue(view: view, prop: entry.prop, value: value)
186
+ }
187
+ return value
188
+ }
189
+
190
+ /// Drop bookkeeping for animations targeting `view` (on destroy).
191
+ public func forget(view: UIView) {
192
+ for (id, entry) in running where entry.view === view || entry.view == nil {
193
+ entry.animator?.stopAnimation(true)
194
+ entry.decay?.stop()
195
+ running.removeValue(forKey: id)
196
+ }
197
+ }
198
+
199
+ // MARK: - Helpers
200
+
201
+ static func reportCompletion(id: Int64, finished: Bool) {
202
+ PNBridge.shared.callPython(kind: "animation", tag: 0, name: "", payload: PNJSON.encode(["id": id, "finished": finished]))
203
+ }
204
+
205
+ /// Resolve `timing` / `spring` specs to UIKit timing parameters.
206
+ static func timingParameters(kind: String, spec: [String: Any]) -> (duration: TimeInterval, parameters: UITimingCurveProvider) {
207
+ if kind == "spring" {
208
+ let stiffness = max(1e-3, PNProps.double(spec["stiffness"]) ?? 100)
209
+ let damping = max(1e-3, PNProps.double(spec["damping"]) ?? 10)
210
+ let mass = max(1e-3, PNProps.double(spec["mass"]) ?? 1)
211
+ let omega0 = sqrt(stiffness / mass)
212
+ let zeta = damping / (2 * sqrt(stiffness * mass))
213
+ let duration = zeta < 1 ? min(10, max(0.15, 4 / max(0.05, zeta * omega0))) : min(10, max(0.15, 4 / omega0))
214
+ let distance = abs((PNProps.double(spec["to"]) ?? 0) - (PNProps.double(spec["from"]) ?? 0))
215
+ let v0 = PNProps.double(spec["initial_velocity"]) ?? 0
216
+ let normalized = distance > 1e-9 ? v0 / distance : 0
217
+ let parameters = UISpringTimingParameters(
218
+ mass: CGFloat(mass), stiffness: CGFloat(stiffness), damping: CGFloat(damping),
219
+ initialVelocity: CGVector(dx: normalized, dy: normalized)
220
+ )
221
+ return (duration, parameters)
222
+ }
223
+ let duration = max(0, (PNProps.double(spec["duration_ms"]) ?? 300) / 1000)
224
+ return (duration, curve(for: spec["easing"]))
225
+ }
226
+
227
+ /// Map an easing name (or `[x1, y1, x2, y2]` control points) to a timing curve.
228
+ static func curve(for easing: Any?) -> UITimingCurveProvider {
229
+ if let points = easing as? [Any], points.count == 4 {
230
+ let values = points.compactMap { PNProps.double($0) }
231
+ if values.count == 4 {
232
+ return UICubicTimingParameters(
233
+ controlPoint1: CGPoint(x: values[0], y: values[1]), controlPoint2: CGPoint(x: values[2], y: values[3])
234
+ )
235
+ }
236
+ }
237
+ switch PNProps.string(easing) ?? "ease_in_out" {
238
+ case "linear": return UICubicTimingParameters(animationCurve: .linear)
239
+ case "ease_in", "ease_in_quad": return UICubicTimingParameters(animationCurve: .easeIn)
240
+ case "ease_out", "ease_out_quad": return UICubicTimingParameters(animationCurve: .easeOut)
241
+ case "ease", "ease_in_out": return UICubicTimingParameters(animationCurve: .easeInOut)
242
+ case "bounce":
243
+ // No CoreAnimation equivalent; approximate with an overshoot curve.
244
+ return UICubicTimingParameters(controlPoint1: CGPoint(x: 0.34, y: 1.56), controlPoint2: CGPoint(x: 0.64, y: 1))
245
+ default: return UICubicTimingParameters(animationCurve: .easeInOut)
246
+ }
247
+ }
248
+ }
249
+
250
+ /// Integrates the `decay` model on a `CADisplayLink`, mirroring the
251
+ /// Python ticker: `v(t) = v0 * e^(-k * 1000 * t)` with `t` in seconds and
252
+ /// `v0` in units per second, so the closed-form position is
253
+ /// `from + v0 / (k * 1000) * (1 - e^(-k * 1000 * t))`.
254
+ final class PNDecayDriver {
255
+ private(set) var currentValue: Double
256
+ private let from: Double
257
+ private let velocity0: Double
258
+ private let k: Double
259
+ private let frame: (Double) -> Void
260
+ private let completion: () -> Void
261
+ private var link: CADisplayLink?
262
+ private var startTime: CFTimeInterval = 0
263
+ private let restVelocity = 0.001
264
+
265
+ init(from: Double, velocity: Double, deceleration: Double, frame: @escaping (Double) -> Void, completion: @escaping () -> Void) {
266
+ self.from = from
267
+ currentValue = from
268
+ velocity0 = velocity
269
+ k = deceleration
270
+ self.frame = frame
271
+ self.completion = completion
272
+ }
273
+
274
+ func start() {
275
+ startTime = CACurrentMediaTime()
276
+ if abs(velocity0) < restVelocity {
277
+ completion()
278
+ return
279
+ }
280
+ let link = CADisplayLink(target: self, selector: #selector(tick(_:)))
281
+ link.add(to: .main, forMode: .common)
282
+ self.link = link
283
+ }
284
+
285
+ func stop() {
286
+ link?.invalidate()
287
+ link = nil
288
+ }
289
+
290
+ @objc private func tick(_ link: CADisplayLink) {
291
+ let elapsed = max(0, link.targetTimestamp - startTime)
292
+ let decayFactor = exp(-k * 1000 * elapsed)
293
+ let velocity = velocity0 * decayFactor
294
+ currentValue = from + velocity0 / (k * 1000) * (1 - decayFactor)
295
+ frame(currentValue)
296
+ if abs(velocity) < restVelocity {
297
+ stop()
298
+ completion()
299
+ }
300
+ }
301
+ }
@@ -0,0 +1,191 @@
1
+ import Foundation
2
+ import UIKit
3
+
4
+ /// The version of the wire protocol compiled into this library. Python
5
+ /// refuses to start when `pythonnative.bridge.PROTOCOL_VERSION` differs.
6
+ public let PNProtocolVersion: Int32 = 1
7
+
8
+ /// The C signature Python registers through `pn_bridge_set_callback`.
9
+ ///
10
+ /// `(kind, tag, name, payload_json) -> optional JSON string owned by
11
+ /// Python and valid until the next callback returns`.
12
+ public typealias PNCallbackFn = @convention(c) (
13
+ UnsafePointer<CChar>?, Int64, UnsafePointer<CChar>?, UnsafePointer<CChar>?
14
+ ) -> UnsafePointer<CChar>?
15
+
16
+ /// Swift-side entry point for the reverse direction (native -> Python) and
17
+ /// the owner of the registered C callback.
18
+ public final class PNBridge {
19
+ public static let shared = PNBridge()
20
+
21
+ private var callback: PNCallbackFn?
22
+ private var registrationWaiters: [() -> Void] = []
23
+
24
+ private init() {
25
+ // Reference every exported entry point so the linker never
26
+ // dead-strips them out of the host executable; Python resolves
27
+ // them through `dlsym` at runtime.
28
+ _ = PNBridge.exportedSymbols
29
+ }
30
+
31
+ /// Whether Python has registered its callback yet.
32
+ public var hasCallback: Bool { callback != nil }
33
+
34
+ /// Install (or clear) the Python callback. Runs any pending
35
+ /// `whenCallbackRegistered` blocks when a callback becomes available.
36
+ public func setCallback(_ fn: PNCallbackFn?) {
37
+ callback = fn
38
+ guard fn != nil else { return }
39
+ let waiters = registrationWaiters
40
+ registrationWaiters = []
41
+ for waiter in waiters { waiter() }
42
+ }
43
+
44
+ /// Run `block` immediately if the callback is registered, otherwise
45
+ /// once it is. Used to buffer deep links and other early events.
46
+ public func whenCallbackRegistered(_ block: @escaping () -> Void) {
47
+ if callback != nil {
48
+ block()
49
+ } else {
50
+ registrationWaiters.append(block)
51
+ }
52
+ }
53
+
54
+ /// Invoke the Python callback. Returns the JSON string Python returned
55
+ /// (copied immediately), or `nil` when Python returned NULL or no
56
+ /// callback is registered.
57
+ @discardableResult
58
+ public func callPython(kind: String, tag: Int64, name: String, payload: String) -> String? {
59
+ PNBridge.ensureMainThread("callPython(\(kind))")
60
+ guard let callback = callback else {
61
+ PNLog.rateLimited(PNLog.bridge, key: "no-callback", "callPython(\(kind), \(name)) dropped: no Python callback registered")
62
+ return nil
63
+ }
64
+ let result: UnsafePointer<CChar>? = kind.withCString { kindPtr in
65
+ name.withCString { namePtr in
66
+ payload.withCString { payloadPtr in
67
+ callback(kindPtr, tag, namePtr, payloadPtr)
68
+ }
69
+ }
70
+ }
71
+ guard let result = result else { return nil }
72
+ return String(cString: result)
73
+ }
74
+
75
+ /// Emit a view event: `callback("event", tag, name, args_array_json)`.
76
+ @discardableResult
77
+ public func emitEvent(tag: Int64, name: String, args: [Any?]) -> String? {
78
+ callPython(kind: "event", tag: tag, name: name, payload: PNJSON.encode(args))
79
+ }
80
+
81
+ /// Log when an entry point is reached off the main thread. Python
82
+ /// always calls from the main thread, so this only guards misuse.
83
+ static func ensureMainThread(_ context: String) {
84
+ if !Thread.isMainThread {
85
+ PNLog.rateLimited(PNLog.bridge, key: "off-main:\(context)", "\(context) called off the main thread; running inline")
86
+ }
87
+ }
88
+
89
+ /// Copy a Swift string into a `strdup`'d buffer Python frees via `pn_bridge_free`.
90
+ static func duplicate(_ string: String?) -> UnsafeMutablePointer<CChar>? {
91
+ guard let string = string else { return nil }
92
+ return strdup(string)
93
+ }
94
+
95
+ private static let exportedSymbols: [Any] = [
96
+ pn_bridge_apply as Any,
97
+ pn_bridge_measure as Any,
98
+ pn_bridge_command as Any,
99
+ pn_bridge_animate as Any,
100
+ pn_bridge_call as Any,
101
+ pn_bridge_free as Any,
102
+ pn_bridge_set_callback as Any,
103
+ pn_bridge_protocol_version as Any,
104
+ ]
105
+ }
106
+
107
+ // MARK: - C exports
108
+
109
+ /// Apply one serialized transaction (a JSON array of ops).
110
+ @_cdecl("pn_bridge_apply")
111
+ public func pn_bridge_apply(_ transactionJSON: UnsafePointer<CChar>?) {
112
+ PNBridge.ensureMainThread("pn_bridge_apply")
113
+ guard let transactionJSON = transactionJSON else { return }
114
+ PNTransaction.apply(String(cString: transactionJSON))
115
+ }
116
+
117
+ /// Measure the view with `tag` under the given constraints (`1e6` means unconstrained).
118
+ @_cdecl("pn_bridge_measure")
119
+ public func pn_bridge_measure(
120
+ _ tag: Int64, _ maxW: Double, _ maxH: Double,
121
+ _ outW: UnsafeMutablePointer<Double>?, _ outH: UnsafeMutablePointer<Double>?
122
+ ) {
123
+ PNBridge.ensureMainThread("pn_bridge_measure")
124
+ var size = CGSize.zero
125
+ if let record = PNViewRegistry.shared.resolve(tag) {
126
+ size = record.manager.measure(view: record.view, maxW: CGFloat(maxW), maxH: CGFloat(maxH))
127
+ }
128
+ outW?.pointee = size.width.isFinite ? Double(size.width) : 0
129
+ outH?.pointee = size.height.isFinite ? Double(size.height) : 0
130
+ }
131
+
132
+ /// Run an imperative command on one view; returns an optional JSON result.
133
+ @_cdecl("pn_bridge_command")
134
+ public func pn_bridge_command(
135
+ _ tag: Int64, _ name: UnsafePointer<CChar>?, _ argsJSON: UnsafePointer<CChar>?
136
+ ) -> UnsafeMutablePointer<CChar>? {
137
+ PNBridge.ensureMainThread("pn_bridge_command")
138
+ guard let name = name, let record = PNViewRegistry.shared.resolve(tag) else { return nil }
139
+ let args = PNJSON.decodeObject(argsJSON.map { String(cString: $0) })
140
+ let result = record.manager.command(view: record.view, name: String(cString: name), args: args)
141
+ guard let result = result else { return nil }
142
+ return PNBridge.duplicate(PNJSON.encode(result))
143
+ }
144
+
145
+ /// Drive a view's animatable properties (`set` / `start` / `cancel`).
146
+ @_cdecl("pn_bridge_animate")
147
+ public func pn_bridge_animate(
148
+ _ tag: Int64, _ requestJSON: UnsafePointer<CChar>?
149
+ ) -> UnsafeMutablePointer<CChar>? {
150
+ PNBridge.ensureMainThread("pn_bridge_animate")
151
+ let request = PNJSON.decodeObject(requestJSON.map { String(cString: $0) })
152
+ let result = PNAnimator.shared.handle(tag: tag, request: request)
153
+ guard let result = result else { return nil }
154
+ return PNBridge.duplicate(PNJSON.encode(result))
155
+ }
156
+
157
+ /// Call a native module method. The result is `{"ok":..}`, `{"pending":true}`, or an error envelope.
158
+ @_cdecl("pn_bridge_call")
159
+ public func pn_bridge_call(
160
+ _ module: UnsafePointer<CChar>?, _ method: UnsafePointer<CChar>?, _ argsJSON: UnsafePointer<CChar>?
161
+ ) -> UnsafeMutablePointer<CChar>? {
162
+ PNBridge.ensureMainThread("pn_bridge_call")
163
+ guard let module = module, let method = method else {
164
+ return PNBridge.duplicate(PNJSON.encode(["ok": false, "error": "missing module or method"]))
165
+ }
166
+ let envelope = PNJSON.decodeObject(argsJSON.map { String(cString: $0) })
167
+ let result = PNModuleDispatcher.shared.call(
168
+ module: String(cString: module), method: String(cString: method), envelope: envelope
169
+ )
170
+ return PNBridge.duplicate(PNJSON.encode(result))
171
+ }
172
+
173
+ /// Free a string previously returned by a `pn_bridge_*` function.
174
+ @_cdecl("pn_bridge_free")
175
+ public func pn_bridge_free(_ ptr: UnsafeMutablePointer<CChar>?) {
176
+ free(ptr)
177
+ }
178
+
179
+ /// Register the Python callback used for every native -> Python message.
180
+ @_cdecl("pn_bridge_set_callback")
181
+ public func pn_bridge_set_callback(_ fn: PNCallbackFn?) {
182
+ PNBridge.ensureMainThread("pn_bridge_set_callback")
183
+ PNRegistry.shared.ensureBuiltins()
184
+ PNBridge.shared.setCallback(fn)
185
+ }
186
+
187
+ /// The protocol version this library speaks.
188
+ @_cdecl("pn_bridge_protocol_version")
189
+ public func pn_bridge_protocol_version() -> Int32 {
190
+ PNProtocolVersion
191
+ }
@@ -0,0 +1,140 @@
1
+ import UIKit
2
+
3
+ /// A native plugin bundled into the app by `pn build`. The generated
4
+ /// `PNGeneratedPlugins.registerAll(into:)` calls each entry's `register`.
5
+ public protocol PNPlugin {
6
+ /// Register component managers and native modules.
7
+ static func register(into registry: PNRegistry)
8
+ }
9
+
10
+ /// Component-manager and native-module registry.
11
+ ///
12
+ /// Built-ins register lazily on first use; plugins register through
13
+ /// `PNPlugin.register(into:)`. Component factories are invoked once per
14
+ /// element type and the resulting manager is shared by every view of
15
+ /// that type (managers are stateless; per-view state lives in
16
+ /// `PNViewState`).
17
+ public final class PNRegistry {
18
+ public static let shared = PNRegistry()
19
+
20
+ private var componentFactories: [String: () -> PNComponentManager] = [:]
21
+ private var componentManagers: [String: PNComponentManager] = [:]
22
+ private var moduleTypes: [String: PNNativeModule.Type] = [:]
23
+ private var moduleInstances: [String: PNNativeModule] = [:]
24
+ private var builtinsRegistered = false
25
+ private let placeholder = PNPlaceholderManager()
26
+
27
+ private init() {}
28
+
29
+ // MARK: - Registration
30
+
31
+ /// Register (or replace) the manager factory for element type `name`.
32
+ public func registerComponent(_ name: String, factory: @escaping () -> PNComponentManager) {
33
+ componentFactories[name] = factory
34
+ componentManagers.removeValue(forKey: name)
35
+ }
36
+
37
+ /// Register (or replace) a native module by its `name`.
38
+ public func registerModule(_ type: PNNativeModule.Type) {
39
+ moduleTypes[type.name] = type
40
+ moduleInstances.removeValue(forKey: type.name)
41
+ }
42
+
43
+ /// Element type names currently registered.
44
+ public var componentNames: [String] {
45
+ ensureBuiltins()
46
+ return Array(componentFactories.keys).sorted()
47
+ }
48
+
49
+ /// Module names currently registered.
50
+ public var moduleNames: [String] {
51
+ ensureBuiltins()
52
+ return Array(moduleTypes.keys).sorted()
53
+ }
54
+
55
+ // MARK: - Lookup
56
+
57
+ /// The shared manager for `type`, or a placeholder manager for unknown types.
58
+ public func manager(for type: String) -> PNComponentManager {
59
+ ensureBuiltins()
60
+ if let existing = componentManagers[type] { return existing }
61
+ if let factory = componentFactories[type] {
62
+ let manager = factory()
63
+ componentManagers[type] = manager
64
+ return manager
65
+ }
66
+ PNLog.once(PNLog.components, key: "unknown-type:\(type)", "unknown element type '\(type)'; using a placeholder UIView")
67
+ return placeholder
68
+ }
69
+
70
+ /// The module instance registered under `name`, created on first use.
71
+ public func module(named name: String) -> PNNativeModule? {
72
+ ensureBuiltins()
73
+ if let existing = moduleInstances[name] { return existing }
74
+ guard let type = moduleTypes[name] else { return nil }
75
+ let instance = type.init()
76
+ moduleInstances[name] = instance
77
+ return instance
78
+ }
79
+
80
+ // MARK: - Built-ins
81
+
82
+ /// Register every built-in manager and module, then the generated plugins.
83
+ public func ensureBuiltins() {
84
+ if builtinsRegistered { return }
85
+ builtinsRegistered = true
86
+ PNBuiltins.register(into: self)
87
+ PNGeneratedPlugins.registerAll(into: self)
88
+ }
89
+ }
90
+
91
+ /// Registration of the components and modules that ship with PythonNativeKit.
92
+ enum PNBuiltins {
93
+ static func register(into registry: PNRegistry) {
94
+ let flex: () -> PNComponentManager = { PNViewManager() }
95
+ registry.registerComponent("View", factory: flex)
96
+ registry.registerComponent("Column", factory: flex)
97
+ registry.registerComponent("Row", factory: flex)
98
+ registry.registerComponent("Text") { PNTextManager() }
99
+ registry.registerComponent("Button") { PNButtonManager() }
100
+ registry.registerComponent("TextInput") { PNTextInputManager() }
101
+ registry.registerComponent("Image") { PNImageManager() }
102
+ registry.registerComponent("Switch") { PNSwitchManager() }
103
+ registry.registerComponent("ProgressBar") { PNProgressBarManager() }
104
+ registry.registerComponent("ActivityIndicator") { PNActivityIndicatorManager() }
105
+ registry.registerComponent("WebView") { PNWebViewManager() }
106
+ registry.registerComponent("Spacer") { PNSpacerManager() }
107
+ registry.registerComponent("ScrollView") { PNScrollViewManager() }
108
+ registry.registerComponent("SafeAreaView") { PNSafeAreaViewManager() }
109
+ registry.registerComponent("Modal") { PNModalManager() }
110
+ registry.registerComponent("Portal") { PNPortalManager() }
111
+ registry.registerComponent("Slider") { PNSliderManager() }
112
+ registry.registerComponent("TabBar") { PNTabBarManager() }
113
+ registry.registerComponent("Pressable") { PNPressableManager() }
114
+ registry.registerComponent("StatusBar") { PNStatusBarManager() }
115
+ registry.registerComponent("KeyboardAvoidingView") { PNKeyboardAvoidingViewManager() }
116
+ registry.registerComponent("Picker") { PNPickerManager() }
117
+ registry.registerComponent("Checkbox") { PNCheckboxManager() }
118
+ registry.registerComponent("SegmentedControl") { PNSegmentedControlManager() }
119
+ registry.registerComponent("DatePicker") { PNDatePickerManager() }
120
+ registry.registerComponent("VirtualList") { PNVirtualListManager() }
121
+
122
+ registry.registerModule(HostModule.self)
123
+ registry.registerModule(DeviceModule.self)
124
+ registry.registerModule(AlertModule.self)
125
+ registry.registerModule(StorageModule.self)
126
+ registry.registerModule(SecureStoreModule.self)
127
+ registry.registerModule(ClipboardModule.self)
128
+ registry.registerModule(ShareModule.self)
129
+ registry.registerModule(LinkingModule.self)
130
+ registry.registerModule(HapticsModule.self)
131
+ registry.registerModule(BatteryModule.self)
132
+ registry.registerModule(NetInfoModule.self)
133
+ registry.registerModule(AppStateModule.self)
134
+ registry.registerModule(PermissionsModule.self)
135
+ registry.registerModule(NotificationsModule.self)
136
+ registry.registerModule(CameraModule.self)
137
+ registry.registerModule(LocationModule.self)
138
+ registry.registerModule(BiometricsModule.self)
139
+ }
140
+ }