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.
Files changed (174) 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/components/layout.py +65 -3
  11. pythonnative/gestures.py +13 -10
  12. pythonnative/hooks.py +41 -26
  13. pythonnative/hosts/__init__.py +13 -36
  14. pythonnative/hosts/native.py +348 -0
  15. pythonnative/images.py +6 -5
  16. pythonnative/native_modules/__init__.py +30 -29
  17. pythonnative/native_modules/app_state.py +10 -6
  18. pythonnative/native_modules/battery.py +20 -84
  19. pythonnative/native_modules/biometrics.py +11 -124
  20. pythonnative/native_modules/camera.py +15 -291
  21. pythonnative/native_modules/clipboard.py +14 -109
  22. pythonnative/native_modules/desktop.py +322 -0
  23. pythonnative/native_modules/file_system.py +15 -21
  24. pythonnative/native_modules/haptics.py +19 -129
  25. pythonnative/native_modules/linking.py +24 -137
  26. pythonnative/native_modules/location.py +28 -180
  27. pythonnative/native_modules/net_info.py +25 -135
  28. pythonnative/native_modules/notifications.py +36 -257
  29. pythonnative/native_modules/permissions.py +20 -184
  30. pythonnative/native_modules/registry.py +513 -0
  31. pythonnative/native_modules/secure_store.py +18 -155
  32. pythonnative/native_modules/share.py +8 -113
  33. pythonnative/native_views/__init__.py +67 -86
  34. pythonnative/native_views/bridge_backend.py +264 -0
  35. pythonnative/platform.py +19 -15
  36. pythonnative/platform_metrics.py +3 -3
  37. pythonnative/project/android.py +0 -3
  38. pythonnative/project/builder.py +38 -16
  39. pythonnative/project/config.py +12 -2
  40. pythonnative/project/plugins.py +366 -0
  41. pythonnative/py.typed +0 -0
  42. pythonnative/runtime.py +35 -203
  43. pythonnative/sdk/__init__.py +48 -33
  44. pythonnative/sdk/_components.py +57 -76
  45. pythonnative/storage.py +18 -243
  46. pythonnative/templates/android_template/app/build.gradle +6 -9
  47. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  48. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  49. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  50. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  51. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  52. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  53. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  54. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  55. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  56. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  57. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  58. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  59. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  60. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  61. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  62. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  100. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  101. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  102. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  103. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  104. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  105. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  106. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  107. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  108. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  109. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  110. pythonnative/templates/android_template/settings.gradle +1 -0
  111. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  112. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  113. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  114. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  115. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  116. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  117. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  118. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  119. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  120. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  121. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  122. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  123. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  155. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  156. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  157. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
  158. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  159. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  160. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
  161. pythonnative/utils.py +4 -82
  162. pythonnative/virtual_rows.py +3 -3
  163. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
  164. pythonnative-0.37.0.dist-info/RECORD +263 -0
  165. pythonnative/hosts/android.py +0 -269
  166. pythonnative/hosts/ios.py +0 -404
  167. pythonnative/native_views/android.py +0 -3702
  168. pythonnative/native_views/ios.py +0 -5253
  169. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  170. pythonnative-0.36.0.dist-info/RECORD +0 -155
  171. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
  172. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
  173. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
  174. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,135 @@
1
+ package com.pythonnative.runtime.modules
2
+
3
+ import com.pythonnative.runtime.PNBridge
4
+ import com.pythonnative.runtime.bridge.JsonUtil
5
+ import com.pythonnative.runtime.bridge.MainThread
6
+ import org.json.JSONObject
7
+
8
+ /**
9
+ * A named native module exposing methods to Python through
10
+ * `PNBridge.call(module, method, argsJson)`.
11
+ *
12
+ * Implementations settle the [Promise] synchronously (the result is
13
+ * returned inline) or later from any thread (the result is delivered
14
+ * as a `module` callback on the main thread). Unknown methods should
15
+ * call [Promise.rejectUnknownMethod].
16
+ */
17
+ interface NativeModule {
18
+ /** The module name Python addresses, for example `"Clipboard"`. */
19
+ val name: String
20
+
21
+ /** Handle one call; must settle `promise` exactly once (now or later). */
22
+ fun call(method: String, args: JSONObject, promise: Promise)
23
+ }
24
+
25
+ /**
26
+ * Result envelopes for module calls and events, as JSON strings.
27
+ */
28
+ object ModuleEnvelope {
29
+ /** Decode `{"call_id": n, "args": {...}}` into `(callId, args)`. */
30
+ fun decodeCall(argsJson: String): Pair<Long, JSONObject> {
31
+ val obj = if (argsJson.isBlank()) JSONObject() else JSONObject(argsJson)
32
+ val callId = obj.optLong("call_id", 0L)
33
+ val args = obj.optJSONObject("args") ?: JSONObject()
34
+ return callId to args
35
+ }
36
+
37
+ /** `{"ok": true, "value": ...}` */
38
+ fun ok(value: Any?): String = JSONObject().put("ok", true).put("value", JsonUtil.wrap(value)).toString()
39
+
40
+ /** `{"ok": false, "error": message, "code": code}` */
41
+ fun error(message: String, code: String? = null): String {
42
+ val obj = JSONObject().put("ok", false).put("error", message)
43
+ if (code != null) obj.put("code", code)
44
+ return obj.toString()
45
+ }
46
+
47
+ /** `{"pending": true}` */
48
+ fun pending(): String = JSONObject().put("pending", true).toString()
49
+
50
+ /** `{"call_id": n, "ok": true, "value": ...}` for asynchronous settlement. */
51
+ fun settled(callId: Long, value: Any?): String =
52
+ JSONObject().put("call_id", callId).put("ok", true).put("value", JsonUtil.wrap(value)).toString()
53
+
54
+ /** `{"call_id": n, "ok": false, "error": message, "code": code}` for asynchronous rejection. */
55
+ fun settledError(callId: Long, message: String, code: String?): String {
56
+ val obj = JSONObject().put("call_id", callId).put("ok", false).put("error", message)
57
+ if (code != null) obj.put("code", code)
58
+ return obj.toString()
59
+ }
60
+
61
+ /** `{"event": name, "payload": {...}}` */
62
+ fun event(event: String, payload: Any?): String =
63
+ JSONObject().put("event", event).put("payload", JsonUtil.wrap(payload)).toString()
64
+ }
65
+
66
+ /**
67
+ * The settlement handle for one module call.
68
+ *
69
+ * If [resolve] or [reject] runs before the module's `call` returns,
70
+ * the result is returned inline to Python. Otherwise Python receives
71
+ * `{"pending": true}` and the eventual settlement is delivered on the
72
+ * main thread as a `module` callback carrying the `call_id`.
73
+ */
74
+ class Promise(val callId: Long, val module: String, private val dispatch: (Runnable) -> Unit = defaultDispatcher) {
75
+ private var returned = false
76
+ private var settledInline: String? = null
77
+
78
+ /** Whether the promise has been resolved or rejected. */
79
+ var isSettled = false
80
+ private set
81
+
82
+ /** Resolve with `value` (JSON-encodable: primitives, maps, lists, `JSONObject`). */
83
+ fun resolve(value: Any?) {
84
+ if (isSettled) return
85
+ isSettled = true
86
+ if (!returned) {
87
+ settledInline = ModuleEnvelope.ok(value)
88
+ } else {
89
+ deliver(ModuleEnvelope.settled(callId, value))
90
+ }
91
+ }
92
+
93
+ /** Reject with `message` and an optional machine-readable `code`. */
94
+ fun reject(message: String, code: String? = null) {
95
+ if (isSettled) return
96
+ isSettled = true
97
+ if (!returned) {
98
+ settledInline = ModuleEnvelope.error(message, code)
99
+ } else {
100
+ deliver(ModuleEnvelope.settledError(callId, message, code))
101
+ }
102
+ }
103
+
104
+ /** Reject because the module has no method named `method`. */
105
+ fun rejectUnknownMethod(method: String) = reject("$module has no method '$method'", "unknown_method")
106
+
107
+ /**
108
+ * Called by the bridge after `call` returns: yields the inline
109
+ * envelope when already settled, otherwise `{"pending": true}` and
110
+ * arms asynchronous delivery.
111
+ */
112
+ fun markReturned(): String {
113
+ returned = true
114
+ return settledInline ?: ModuleEnvelope.pending()
115
+ }
116
+
117
+ private fun deliver(payload: String) {
118
+ val moduleName = module
119
+ dispatch(Runnable { PNBridge.callPython("module", 0, moduleName, payload) })
120
+ }
121
+
122
+ companion object {
123
+ /** How asynchronous settlements reach the main thread (replaceable in tests). */
124
+ var defaultDispatcher: (Runnable) -> Unit = { MainThread.post(it) }
125
+ }
126
+ }
127
+
128
+ /** Unsolicited module events (`{"event": name, "payload": {...}}`). */
129
+ object ModuleEvents {
130
+ /** Emit `event` for `module` on the main thread (posting if needed). */
131
+ fun emit(module: String, event: String, payload: Any?) {
132
+ val json = ModuleEnvelope.event(event, payload)
133
+ MainThread.runOnMain { PNBridge.callPython("module", 0, module, json) }
134
+ }
135
+ }
@@ -0,0 +1,100 @@
1
+ package com.pythonnative.runtime.modules
2
+
3
+ import android.app.Notification
4
+ import android.app.NotificationChannel
5
+ import android.app.NotificationManager
6
+ import android.content.Context
7
+ import android.os.Build
8
+ import com.pythonnative.runtime.PNBridge
9
+ import com.pythonnative.runtime.bridge.JsonUtil
10
+ import com.pythonnative.runtime.bridge.MainThread
11
+ import com.pythonnative.runtime.bridge.PNLog
12
+ import com.pythonnative.runtime.bridge.str
13
+ import org.json.JSONObject
14
+ import kotlin.math.abs
15
+
16
+ /**
17
+ * `Notifications`: `request_permission()` (async; `POST_NOTIFICATIONS`
18
+ * on API 33+), `schedule({title, body, delay_seconds, identifier})`
19
+ * (a Handler-delayed local notification, in-process only),
20
+ * `cancel(identifier)`, and `get_device_token()` (`null`; no built-in
21
+ * push on Android).
22
+ */
23
+ class NotificationsModule : NativeModule {
24
+ override val name = "Notifications"
25
+ private val delayed = HashMap<String, Runnable>()
26
+
27
+ override fun call(method: String, args: JSONObject, promise: Promise) {
28
+ when (method) {
29
+ "request_permission" -> requestPermission(promise)
30
+ "schedule" -> promise.resolve(schedule(args))
31
+ "cancel" -> {
32
+ cancel(args.str("identifier") ?: "")
33
+ promise.resolve(null)
34
+ }
35
+ "get_device_token" -> promise.resolve(null)
36
+ else -> promise.rejectUnknownMethod(method)
37
+ }
38
+ }
39
+
40
+ private fun requestPermission(promise: Promise) {
41
+ if (Build.VERSION.SDK_INT < 33) return promise.resolve(true)
42
+ BuiltinModules.permissions.request("notifications") { status ->
43
+ promise.resolve(status == PermissionsModule.GRANTED)
44
+ }
45
+ }
46
+
47
+ private fun schedule(args: JSONObject): Boolean {
48
+ val title = args.str("title") ?: ""
49
+ val body = args.str("body") ?: ""
50
+ val identifier = args.str("identifier") ?: "pn_${System.currentTimeMillis()}"
51
+ val delaySeconds = JsonUtil.toDouble(args.opt("delay_seconds"), 0.0)
52
+ cancel(identifier)
53
+ if (delaySeconds <= 0.0) return post(title, body, identifier)
54
+ val runnable = Runnable {
55
+ delayed.remove(identifier)
56
+ post(title, body, identifier)
57
+ }
58
+ delayed[identifier] = runnable
59
+ MainThread.postDelayed(runnable, (delaySeconds * 1000).toLong())
60
+ return true
61
+ }
62
+
63
+ private fun cancel(identifier: String) {
64
+ delayed.remove(identifier)?.let { MainThread.remove(it) }
65
+ try {
66
+ manager()?.cancel(notificationId(identifier))
67
+ } catch (e: Exception) {
68
+ PNLog.swallowed("Notifications.cancel", e)
69
+ }
70
+ }
71
+
72
+ private fun manager(): NotificationManager? =
73
+ PNBridge.context().getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
74
+
75
+ private fun post(title: String, body: String, identifier: String): Boolean {
76
+ return try {
77
+ val ctx = PNBridge.context()
78
+ val nm = manager() ?: return false
79
+ val builder = if (Build.VERSION.SDK_INT >= 26) {
80
+ nm.createNotificationChannel(NotificationChannel(CHANNEL_ID, "PythonNative", NotificationManager.IMPORTANCE_DEFAULT))
81
+ Notification.Builder(ctx, CHANNEL_ID)
82
+ } else {
83
+ @Suppress("DEPRECATION")
84
+ Notification.Builder(ctx)
85
+ }
86
+ builder.setContentTitle(title).setContentText(body).setSmallIcon(android.R.drawable.ic_dialog_info)
87
+ nm.notify(notificationId(identifier), builder.build())
88
+ true
89
+ } catch (e: Exception) {
90
+ PNLog.swallowed("Notifications.post", e)
91
+ false
92
+ }
93
+ }
94
+
95
+ private fun notificationId(identifier: String): Int = abs(identifier.hashCode()) % Int.MAX_VALUE
96
+
97
+ private companion object {
98
+ const val CHANNEL_ID = "pn_default"
99
+ }
100
+ }
@@ -0,0 +1,88 @@
1
+ package com.pythonnative.runtime.modules
2
+
3
+ import android.content.pm.PackageManager
4
+ import android.os.Build
5
+ import androidx.core.app.ActivityCompat
6
+ import androidx.core.content.ContextCompat
7
+ import com.pythonnative.runtime.PNBridge
8
+ import com.pythonnative.runtime.bridge.str
9
+ import org.json.JSONObject
10
+
11
+ /**
12
+ * `Permissions.check(permission)` (sync) and `request(permission)`
13
+ * (async, routed through `onRequestPermissionsResult`). Statuses are
14
+ * `granted`, `denied`, `blocked`, and `undetermined`.
15
+ */
16
+ class PermissionsModule : NativeModule {
17
+ override val name = "Permissions"
18
+ private val pending = HashMap<Int, Pair<String, (String) -> Unit>>()
19
+
20
+ override fun call(method: String, args: JSONObject, promise: Promise) {
21
+ val permission = args.str("permission") ?: ""
22
+ when (method) {
23
+ "check" -> promise.resolve(check(permission))
24
+ "request" -> request(permission) { promise.resolve(it) }
25
+ else -> promise.rejectUnknownMethod(method)
26
+ }
27
+ }
28
+
29
+ /** Current status of `permission` without prompting. */
30
+ fun check(permission: String): String {
31
+ val manifest = MANIFEST[permission] ?: return UNDETERMINED
32
+ if (permission == "notifications" && Build.VERSION.SDK_INT < 33) return GRANTED
33
+ if (permission == "photos" && Build.VERSION.SDK_INT < 33) {
34
+ return statusOf("android.permission.READ_EXTERNAL_STORAGE")
35
+ }
36
+ return statusOf(manifest)
37
+ }
38
+
39
+ private fun statusOf(manifest: String): String {
40
+ val ctx = PNBridge.activity() ?: return UNDETERMINED
41
+ val granted = ContextCompat.checkSelfPermission(ctx, manifest) == PackageManager.PERMISSION_GRANTED
42
+ if (granted) return GRANTED
43
+ // "denied" with no rationale after a previous denial means "don't ask again".
44
+ val asked = ctx.getSharedPreferences("pn_permissions", 0).getBoolean(manifest, false)
45
+ return if (asked && !ActivityCompat.shouldShowRequestPermissionRationale(ctx, manifest)) BLOCKED else DENIED
46
+ }
47
+
48
+ /** Prompt for `permission` if needed and report the resulting status to `onDone`. */
49
+ fun request(permission: String, onDone: (String) -> Unit) {
50
+ val manifest = MANIFEST[permission] ?: return onDone(UNDETERMINED)
51
+ if (check(permission) == GRANTED) return onDone(GRANTED)
52
+ val activity = PNBridge.activity() ?: return onDone(check(permission))
53
+ val target = if (permission == "photos" && Build.VERSION.SDK_INT < 33) "android.permission.READ_EXTERNAL_STORAGE" else manifest
54
+ val code = RequestCodes.next()
55
+ pending[code] = target to onDone
56
+ try {
57
+ ActivityCompat.requestPermissions(activity, arrayOf(target), code)
58
+ } catch (e: Exception) {
59
+ pending.remove(code)
60
+ onDone(check(permission))
61
+ }
62
+ }
63
+
64
+ /** Route `Activity.onRequestPermissionsResult`; `true` when a pending request matched. */
65
+ fun onRequestPermissionsResult(requestCode: Int, grantResults: IntArray): Boolean {
66
+ val (manifest, onDone) = pending.remove(requestCode) ?: return false
67
+ PNBridge.activity()?.getSharedPreferences("pn_permissions", 0)?.edit()?.putBoolean(manifest, true)?.apply()
68
+ val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
69
+ onDone(if (granted) GRANTED else statusOf(manifest))
70
+ return true
71
+ }
72
+
73
+ companion object {
74
+ const val GRANTED = "granted"
75
+ const val DENIED = "denied"
76
+ const val BLOCKED = "blocked"
77
+ const val UNDETERMINED = "undetermined"
78
+
79
+ val MANIFEST = mapOf(
80
+ "camera" to "android.permission.CAMERA",
81
+ "microphone" to "android.permission.RECORD_AUDIO",
82
+ "location" to "android.permission.ACCESS_FINE_LOCATION",
83
+ "contacts" to "android.permission.READ_CONTACTS",
84
+ "notifications" to "android.permission.POST_NOTIFICATIONS",
85
+ "photos" to "android.permission.READ_MEDIA_IMAGES",
86
+ )
87
+ }
88
+ }
@@ -0,0 +1,89 @@
1
+ package com.pythonnative.runtime.modules
2
+
3
+ import android.content.Context
4
+ import android.content.SharedPreferences
5
+ import com.pythonnative.runtime.PNBridge
6
+ import com.pythonnative.runtime.bridge.PNLog
7
+ import com.pythonnative.runtime.bridge.str
8
+ import org.json.JSONObject
9
+
10
+ /** `Storage`: `get/set/delete/all_keys/clear` over the `pn_async_storage` SharedPreferences file. */
11
+ class StorageModule : NativeModule {
12
+ override val name = "Storage"
13
+
14
+ private fun prefs(): SharedPreferences =
15
+ PNBridge.context().getSharedPreferences("pn_async_storage", Context.MODE_PRIVATE)
16
+
17
+ override fun call(method: String, args: JSONObject, promise: Promise) {
18
+ val key = args.str("key")
19
+ when (method) {
20
+ "get", "get_item" -> promise.resolve(prefs().getString(key ?: "", null))
21
+ "set", "set_item" -> {
22
+ prefs().edit().putString(key ?: "", args.str("value") ?: "").apply()
23
+ promise.resolve(null)
24
+ }
25
+ "delete", "remove", "remove_item" -> {
26
+ prefs().edit().remove(key ?: "").apply()
27
+ promise.resolve(null)
28
+ }
29
+ "all_keys", "get_all_keys" -> promise.resolve(prefs().all.keys.sorted())
30
+ "clear" -> {
31
+ prefs().edit().clear().apply()
32
+ promise.resolve(null)
33
+ }
34
+ else -> promise.rejectUnknownMethod(method)
35
+ }
36
+ }
37
+ }
38
+
39
+ /**
40
+ * `SecureStore`: `set_item/get_item/delete_item` over `EncryptedSharedPreferences`.
41
+ *
42
+ * security-crypto 1.1 deprecates the whole API in favour of hand-rolled Keystore code, but the
43
+ * file name and schemes must stay identical to what earlier PythonNative releases wrote so that
44
+ * existing secrets remain readable, hence the suppression.
45
+ */
46
+ @Suppress("DEPRECATION")
47
+ class SecureStoreModule : NativeModule {
48
+ override val name = "SecureStore"
49
+ private var prefs: SharedPreferences? = null
50
+
51
+ private fun prefs(): SharedPreferences? {
52
+ prefs?.let { return it }
53
+ return try {
54
+ val ctx = PNBridge.context()
55
+ val master = androidx.security.crypto.MasterKey.Builder(ctx)
56
+ .setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme.AES256_GCM)
57
+ .build()
58
+ androidx.security.crypto.EncryptedSharedPreferences.create(
59
+ ctx,
60
+ "com.pythonnative.securestore",
61
+ master,
62
+ androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
63
+ androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
64
+ ).also { prefs = it }
65
+ } catch (e: Exception) {
66
+ PNLog.rateLimited("securestore", "EncryptedSharedPreferences unavailable", e)
67
+ null
68
+ }
69
+ }
70
+
71
+ override fun call(method: String, args: JSONObject, promise: Promise) {
72
+ val key = args.str("key") ?: ""
73
+ val store = prefs()
74
+ when (method) {
75
+ "set_item" -> {
76
+ if (store == null) return promise.resolve(false)
77
+ store.edit().putString(key, args.str("value") ?: "").apply()
78
+ promise.resolve(true)
79
+ }
80
+ "get_item" -> promise.resolve(store?.getString(key, null))
81
+ "delete_item" -> {
82
+ if (store == null) return promise.resolve(false)
83
+ store.edit().remove(key).apply()
84
+ promise.resolve(true)
85
+ }
86
+ else -> promise.rejectUnknownMethod(method)
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,178 @@
1
+ package com.pythonnative.runtime.modules
2
+
3
+ import android.app.Activity
4
+ import android.content.BroadcastReceiver
5
+ import android.content.Context
6
+ import android.content.Intent
7
+ import android.content.IntentFilter
8
+ import android.net.ConnectivityManager
9
+ import android.net.Network
10
+ import android.net.NetworkCapabilities
11
+ import android.os.BatteryManager
12
+ import com.pythonnative.runtime.PNBridge
13
+ import com.pythonnative.runtime.bridge.MainThread
14
+ import com.pythonnative.runtime.bridge.PNLog
15
+ import org.json.JSONObject
16
+
17
+ /**
18
+ * `Battery`: `get_level()` (0..1 or -1) and `get_state()`
19
+ * (`unknown|unplugged|charging|full`), plus `change` events from the
20
+ * sticky `ACTION_BATTERY_CHANGED` broadcast.
21
+ */
22
+ class BatteryModule : NativeModule {
23
+ override val name = "Battery"
24
+ private var receiver: BroadcastReceiver? = null
25
+
26
+ override fun call(method: String, args: JSONObject, promise: Promise) {
27
+ when (method) {
28
+ "get_level" -> promise.resolve(level())
29
+ "get_state" -> promise.resolve(state())
30
+ else -> promise.rejectUnknownMethod(method)
31
+ }
32
+ }
33
+
34
+ private fun manager(): BatteryManager? =
35
+ PNBridge.context().getSystemService(Context.BATTERY_SERVICE) as? BatteryManager
36
+
37
+ private fun level(): Double {
38
+ val pct = manager()?.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) ?: return -1.0
39
+ return if (pct in 0..100) pct / 100.0 else -1.0
40
+ }
41
+
42
+ private fun state(): String = stateName(manager()?.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS) ?: -1)
43
+
44
+ private fun stateName(status: Int): String = when (status) {
45
+ BatteryManager.BATTERY_STATUS_CHARGING -> "charging"
46
+ BatteryManager.BATTERY_STATUS_FULL -> "full"
47
+ BatteryManager.BATTERY_STATUS_DISCHARGING, BatteryManager.BATTERY_STATUS_NOT_CHARGING -> "unplugged"
48
+ else -> "unknown"
49
+ }
50
+
51
+ fun attach(activity: Activity) {
52
+ if (receiver != null) return
53
+ val r = object : BroadcastReceiver() {
54
+ override fun onReceive(context: Context?, intent: Intent?) {
55
+ if (intent == null) return
56
+ val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
57
+ val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
58
+ val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
59
+ val fraction = if (level >= 0 && scale > 0) level.toDouble() / scale.toDouble() else -1.0
60
+ ModuleEvents.emit(name, "change", JSONObject().put("level", fraction).put("state", stateName(status)))
61
+ }
62
+ }
63
+ try {
64
+ activity.registerReceiver(r, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
65
+ receiver = r
66
+ } catch (e: Exception) {
67
+ PNLog.swallowed("Battery.attach", e)
68
+ }
69
+ }
70
+
71
+ fun detach(activity: Activity) {
72
+ receiver?.let {
73
+ try {
74
+ activity.unregisterReceiver(it)
75
+ } catch (e: Exception) {
76
+ PNLog.swallowed("Battery.detach", e)
77
+ }
78
+ }
79
+ receiver = null
80
+ }
81
+ }
82
+
83
+ /**
84
+ * `NetInfo.fetch()` → `{is_connected, type, is_internet_reachable}`;
85
+ * a default `NetworkCallback` emits `change` events on the main thread.
86
+ */
87
+ class NetInfoModule : NativeModule {
88
+ override val name = "NetInfo"
89
+ private var callback: ConnectivityManager.NetworkCallback? = null
90
+ private var last: JSONObject? = null
91
+
92
+ override fun call(method: String, args: JSONObject, promise: Promise) {
93
+ when (method) {
94
+ "fetch" -> promise.resolve(snapshot())
95
+ else -> promise.rejectUnknownMethod(method)
96
+ }
97
+ }
98
+
99
+ private fun manager(): ConnectivityManager? =
100
+ PNBridge.context().getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
101
+
102
+ fun snapshot(): JSONObject {
103
+ val offline = JSONObject().put("is_connected", false).put("type", "none").put("is_internet_reachable", false)
104
+ return try {
105
+ val manager = manager() ?: return offline
106
+ val network = manager.activeNetwork ?: return offline
107
+ val caps = manager.getNetworkCapabilities(network) ?: return offline
108
+ val kind = when {
109
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "wifi"
110
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> "cellular"
111
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> "ethernet"
112
+ else -> "other"
113
+ }
114
+ JSONObject()
115
+ .put("is_connected", true)
116
+ .put("type", kind)
117
+ .put("is_internet_reachable", caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET))
118
+ } catch (e: Exception) {
119
+ offline
120
+ }
121
+ }
122
+
123
+ fun attach(activity: Activity) {
124
+ if (callback != null) return
125
+ try {
126
+ val manager = activity.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
127
+ val cb = object : ConnectivityManager.NetworkCallback() {
128
+ override fun onAvailable(network: Network) = changed()
129
+ override fun onLost(network: Network) = changed()
130
+ override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) = changed()
131
+ }
132
+ manager.registerDefaultNetworkCallback(cb)
133
+ callback = cb
134
+ } catch (e: Exception) {
135
+ PNLog.swallowed("NetInfo.attach", e)
136
+ }
137
+ }
138
+
139
+ private fun changed() {
140
+ // NetworkCallback fires on a binder thread; hop to main before reading state.
141
+ MainThread.post {
142
+ val current = snapshot()
143
+ if (last?.toString() == current.toString()) return@post
144
+ last = current
145
+ ModuleEvents.emit(name, "change", current)
146
+ }
147
+ }
148
+
149
+ fun detach(activity: Activity) {
150
+ callback?.let {
151
+ try {
152
+ (activity.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).unregisterNetworkCallback(it)
153
+ } catch (e: Exception) {
154
+ PNLog.swallowed("NetInfo.detach", e)
155
+ }
156
+ }
157
+ callback = null
158
+ }
159
+ }
160
+
161
+ /** `AppState.current_state()` plus `change` events (`active|inactive|background`). */
162
+ class AppStateModule : NativeModule {
163
+ override val name = "AppState"
164
+ private var state = "active"
165
+
166
+ override fun call(method: String, args: JSONObject, promise: Promise) {
167
+ when (method) {
168
+ "current_state" -> promise.resolve(state)
169
+ else -> promise.rejectUnknownMethod(method)
170
+ }
171
+ }
172
+
173
+ fun transition(next: String) {
174
+ if (state == next) return
175
+ state = next
176
+ ModuleEvents.emit(name, "change", JSONObject().put("state", next))
177
+ }
178
+ }
@@ -0,0 +1,16 @@
1
+ package com.pythonnative.runtime.plugins
2
+
3
+ import com.pythonnative.runtime.bridge.PNRegistry
4
+
5
+ /**
6
+ * Plugin registrations generated by `pn build`.
7
+ *
8
+ * The build tool overwrites this file with one `register` call per
9
+ * plugin entry declared in the project's `pythonnative.plugins` entry
10
+ * points. Nothing else may depend on its content beyond [registerAll].
11
+ */
12
+ object GeneratedPlugins {
13
+ /** Register every generated plugin with `registry`. */
14
+ @Suppress("UNUSED_PARAMETER")
15
+ fun registerAll(registry: PNRegistry) {}
16
+ }