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
@@ -1,8 +1,9 @@
1
- """Cross-platform local notifications and remote push registration.
1
+ """Local notifications and remote push registration.
2
2
 
3
- Provides coroutines for requesting permission and scheduling /
4
- cancelling local push notifications. Uses Android's
5
- ``NotificationManager`` or iOS's ``UNUserNotificationCenter``.
3
+ Coroutines for requesting permission and scheduling / cancelling local
4
+ notifications, backed by the native ``Notifications`` module
5
+ (``UNUserNotificationCenter`` on iOS, ``NotificationManager`` on
6
+ Android).
6
7
 
7
8
  On iOS you must ``await Notifications.request_permission()`` before
8
9
  scheduling. On Android 13+ the ``POST_NOTIFICATIONS`` runtime
@@ -36,17 +37,9 @@ Example:
36
37
 
37
38
  from __future__ import annotations
38
39
 
39
- import asyncio
40
- from typing import Any, Dict, List, Optional
40
+ from typing import Any, Optional
41
41
 
42
- from .. import diagnostics
43
- from ..runtime import reject_future, resolve_future
44
- from ..utils import IS_ANDROID, IS_IOS
45
-
46
- # Delayed Android deliveries in flight, keyed by identifier, so
47
- # ``cancel`` can abort a notification that hasn't posted yet. (iOS
48
- # delivers delays natively through UNTimeIntervalNotificationTrigger.)
49
- _android_delayed: Dict[str, "asyncio.Task[Any]"] = {}
42
+ from .registry import NativeModuleError, native_module
50
43
 
51
44
 
52
45
  class Notifications:
@@ -58,17 +51,17 @@ class Notifications:
58
51
 
59
52
  On Android 12 and below the manifest declaration is sufficient
60
53
  and this returns ``True`` without prompting. On Android 13+
61
- (API 33) the ``POST_NOTIFICATIONS`` runtime permission is
62
- requested through [`Permissions`][pythonnative.Permissions],
63
- which shows the system prompt if the user hasn't decided yet.
54
+ (API 33) the ``POST_NOTIFICATIONS`` runtime permission prompt
55
+ is shown if the user hasn't decided yet.
64
56
 
65
57
  Returns:
66
58
  ``True`` if granted (or no prompt is needed), ``False``
67
59
  otherwise.
68
60
  """
69
- if IS_ANDROID:
70
- return await _android_request_permission()
71
- return await _ios_request_permission()
61
+ try:
62
+ return bool(await native_module("Notifications").call_async("request_permission"))
63
+ except Exception:
64
+ return False
72
65
 
73
66
  @staticmethod
74
67
  async def schedule(
@@ -89,42 +82,34 @@ class Notifications:
89
82
  identifier: Stable ID used by
90
83
  [`cancel`][pythonnative.native_modules.notifications.Notifications.cancel]
91
84
  to target this notification.
92
- **options: Reserved for future tuning (e.g., ``sound``,
93
- ``badge``, ``category``).
85
+ **options: Forwarded to the native module (``sound``,
86
+ ``badge``, ...). Unknown keys are ignored.
94
87
 
95
88
  Returns:
96
89
  ``True`` on success, ``False`` if the underlying native
97
90
  call failed or (on Android 13+) the user denied the
98
91
  notification permission.
99
92
  """
100
- del options
101
- if IS_ANDROID:
102
- # Android 13+ silently drops notifications posted without
103
- # the POST_NOTIFICATIONS grant, so prompt before posting.
104
- if not await _android_request_permission():
105
- return False
106
- if delay_seconds > 0:
107
- _cancel_android_delayed(identifier)
108
- _android_delayed[identifier] = asyncio.ensure_future(
109
- _android_schedule_later(title, body, delay_seconds, identifier)
110
- )
111
- return True
112
- return await asyncio.to_thread(_android_schedule, title, body, identifier)
113
- return await asyncio.to_thread(_ios_schedule, title, body, delay_seconds, identifier)
93
+ try:
94
+ result = await native_module("Notifications").call_async(
95
+ "schedule",
96
+ title=title,
97
+ body=body,
98
+ delay_seconds=float(delay_seconds),
99
+ identifier=identifier,
100
+ **options,
101
+ )
102
+ except Exception:
103
+ return False
104
+ return bool(result)
114
105
 
115
106
  @staticmethod
116
107
  async def cancel(identifier: str = "default") -> None:
117
- """Cancel a pending notification by its identifier.
118
-
119
- Args:
120
- identifier: The same string passed to
121
- [`schedule`][pythonnative.native_modules.notifications.Notifications.schedule].
122
- """
123
- if IS_ANDROID:
124
- _cancel_android_delayed(identifier)
125
- await asyncio.to_thread(_android_cancel, identifier)
126
- return
127
- await asyncio.to_thread(_ios_cancel, identifier)
108
+ """Cancel a pending notification by its identifier."""
109
+ try:
110
+ await native_module("Notifications").call_async("cancel", identifier=identifier)
111
+ except Exception:
112
+ pass
128
113
 
129
114
  @staticmethod
130
115
  async def get_device_token() -> Optional[str]:
@@ -144,214 +129,8 @@ class Notifications:
144
129
  RuntimeError: If APNs registration fails, with the native
145
130
  error description.
146
131
  """
147
- if not IS_IOS:
148
- return None
149
- return await _ios_get_device_token()
150
-
151
-
152
- # ======================================================================
153
- # Android implementation
154
- # ======================================================================
155
-
156
-
157
- async def _android_request_permission() -> bool:
158
- """Request ``POST_NOTIFICATIONS`` on Android 13+; older versions skip.
159
-
160
- Delegates to the shared
161
- [`Permissions`][pythonnative.Permissions] machinery, which routes
162
- through the activity's ``requestPermissions`` and resolves once
163
- ``onRequestPermissionsResult`` arrives. Below API 33 there's no
164
- runtime permission, so this resolves ``True`` immediately.
165
- """
166
- try:
167
- from java import jclass
168
-
169
- if int(jclass("android.os.Build$VERSION").SDK_INT) < 33:
170
- return True
171
- except Exception:
172
- return True
173
- from .permissions import GRANTED, Permissions
174
-
175
- return await Permissions.request("notifications") == GRANTED
176
-
177
-
178
- def _cancel_android_delayed(identifier: str) -> None:
179
- pending = _android_delayed.pop(identifier, None)
180
- if pending is not None and not pending.done():
181
- pending.cancel()
182
-
183
-
184
- async def _android_schedule_later(title: str, body: str, delay_seconds: float, identifier: str) -> None:
185
- """Post an Android notification after ``delay_seconds`` on the framework loop.
186
-
187
- The delay lives in the app process (matching the semantics of
188
- ``UNTimeIntervalNotificationTrigger`` closely enough for in-app
189
- reminders); notifications whose delay outlives the process need
190
- ``AlarmManager``, which is out of scope for the built-in module.
191
- """
192
- try:
193
- await asyncio.sleep(delay_seconds)
194
- await asyncio.to_thread(_android_schedule, title, body, identifier)
195
- finally:
196
- _android_delayed.pop(identifier, None)
197
-
198
-
199
- def _android_schedule(title: str, body: str, identifier: str) -> bool:
200
- try:
201
- from java import jclass
202
-
203
- from ..utils import get_android_context
204
-
205
- ctx = get_android_context()
206
- nm = ctx.getSystemService(jclass("android.content.Context").NOTIFICATION_SERVICE)
207
- channel_id = "pn_default"
208
- NotificationChannel = jclass("android.app.NotificationChannel")
209
- channel = NotificationChannel(channel_id, "PythonNative", 3) # IMPORTANCE_DEFAULT
210
- nm.createNotificationChannel(channel)
211
-
212
- Builder = jclass("android.app.Notification$Builder")
213
- builder = Builder(ctx, channel_id)
214
- builder.setContentTitle(title)
215
- builder.setContentText(body)
216
- builder.setSmallIcon(jclass("android.R$drawable").ic_dialog_info)
217
- nm.notify(abs(hash(identifier)) % (2**31), builder.build())
218
- return True
219
- except Exception:
220
- return False
221
-
222
-
223
- def _android_cancel(identifier: str) -> None:
224
- try:
225
- from java import jclass
226
-
227
- from ..utils import get_android_context
228
-
229
- ctx = get_android_context()
230
- nm = ctx.getSystemService(jclass("android.content.Context").NOTIFICATION_SERVICE)
231
- nm.cancel(abs(hash(identifier)) % (2**31))
232
- except Exception:
233
- diagnostics.swallowed("notifications._android_cancel")
234
-
235
-
236
- # ======================================================================
237
- # iOS implementation
238
- # ======================================================================
239
-
240
-
241
- async def _ios_request_permission() -> bool:
242
- loop = asyncio.get_running_loop()
243
- future: asyncio.Future[bool] = loop.create_future()
244
- try:
245
- from rubicon.objc import Block, ObjCClass
246
-
247
- center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
248
-
249
- # Build a block that mirrors the UNNotificationCenter signature
250
- # ``void(^)(BOOL granted, NSError* error)``.
251
- def _completion(granted: bool, _error: Any) -> None:
252
- resolve_future(future, bool(granted))
253
-
254
132
  try:
255
- block = Block(_completion, None, bool, object)
256
- except Exception:
257
- # If Block ctor signature changed across rubicon versions,
258
- # fall back to optimistic "granted" without polling.
259
- resolve_future(future, True)
260
- return await future
261
-
262
- center.requestAuthorizationWithOptions_completionHandler_(0x07, block)
263
- except Exception:
264
- resolve_future(future, False)
265
- return await future
266
-
267
-
268
- def _ios_schedule(title: str, body: str, delay_seconds: float, identifier: str) -> bool:
269
- try:
270
- from rubicon.objc import ObjCClass
271
-
272
- content = ObjCClass("UNMutableNotificationContent").alloc().init()
273
- content.setTitle_(title)
274
- content.setBody_(body)
275
-
276
- interval = float(delay_seconds) if delay_seconds > 0 else 1.0
277
- trigger = ObjCClass("UNTimeIntervalNotificationTrigger").triggerWithTimeInterval_repeats_(interval, False)
278
- request = ObjCClass("UNNotificationRequest").requestWithIdentifier_content_trigger_(
279
- identifier, content, trigger
280
- )
281
- center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
282
- center.addNotificationRequest_withCompletionHandler_(request, None)
283
- return True
284
- except Exception:
285
- return False
286
-
287
-
288
- def _ios_cancel(identifier: str) -> None:
289
- try:
290
- from rubicon.objc import ObjCClass
291
-
292
- center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
293
- NSArray = ObjCClass("NSArray")
294
- arr = NSArray.arrayWithObject_(identifier)
295
- center.removePendingNotificationRequestsWithIdentifiers_(arr)
296
- except Exception:
297
- diagnostics.swallowed("notifications._ios_cancel")
298
-
299
-
300
- # ----------------------------------------------------------------------
301
- # Remote notifications (APNs)
302
- # ----------------------------------------------------------------------
303
- #
304
- # The registration result arrives through UIApplicationDelegate
305
- # callbacks in the native template, which forward here via
306
- # dispatch_device_token / dispatch_device_token_error. The token is
307
- # cached so later get_device_token() calls resolve immediately.
308
-
309
- _device_token: Optional[str] = None
310
- _device_token_error: Optional[str] = None
311
- _token_waiters: List["asyncio.Future[str]"] = []
312
-
313
-
314
- async def _ios_get_device_token() -> str:
315
- if _device_token is not None:
316
- return _device_token
317
- if _device_token_error is not None:
318
- raise RuntimeError(f"APNs registration failed: {_device_token_error}")
319
-
320
- loop = asyncio.get_running_loop()
321
- future: asyncio.Future[str] = loop.create_future()
322
- _token_waiters.append(future)
323
-
324
- def _register() -> None:
325
- from rubicon.objc import ObjCClass
326
-
327
- ObjCClass("UIApplication").sharedApplication.registerForRemoteNotifications()
328
-
329
- try:
330
- # registerForRemoteNotifications must run on the main thread.
331
- from ..runtime import call_on_main_thread
332
-
333
- call_on_main_thread(_register)
334
- except Exception as exc:
335
- _token_waiters.remove(future)
336
- raise RuntimeError(f"Could not start APNs registration: {exc}") from exc
337
- return await future
338
-
339
-
340
- def dispatch_device_token(token: str) -> None:
341
- """Deliver the APNs device token from the native host."""
342
- global _device_token, _device_token_error
343
- _device_token = token
344
- _device_token_error = None
345
- waiters, _token_waiters[:] = list(_token_waiters), []
346
- for future in waiters:
347
- resolve_future(future, token)
348
-
349
-
350
- def dispatch_device_token_error(message: str) -> None:
351
- """Deliver an APNs registration failure from the native host."""
352
- global _device_token_error
353
- _device_token_error = message
354
- waiters, _token_waiters[:] = list(_token_waiters), []
355
- error = RuntimeError(f"APNs registration failed: {message}")
356
- for future in waiters:
357
- reject_future(future, error)
133
+ token = await native_module("Notifications").call_async("get_device_token")
134
+ except NativeModuleError as exc:
135
+ raise RuntimeError(f"APNs registration failed: {exc.message}") from exc
136
+ return str(token) if token else None
@@ -1,7 +1,8 @@
1
1
  """Runtime permission checks and requests.
2
2
 
3
3
  [`Permissions`][pythonnative.Permissions] normalizes the very different
4
- iOS and Android permission models behind two calls:
4
+ iOS and Android permission models behind two calls, both served by the
5
+ native ``Permissions`` module:
5
6
 
6
7
  - ``check(permission)``: synchronous, returns a status string without
7
8
  prompting.
@@ -18,12 +19,7 @@ Unknown names resolve to ``"undetermined"``.
18
19
 
19
20
  from __future__ import annotations
20
21
 
21
- import asyncio
22
- from typing import Any, Callable, Dict
23
-
24
- from .. import diagnostics
25
- from ..runtime import resolve_future
26
- from ..utils import IS_ANDROID, IS_IOS
22
+ from .registry import native_module
27
23
 
28
24
  PermissionStatus = str
29
25
  PermissionName = str
@@ -33,15 +29,15 @@ DENIED = "denied"
33
29
  BLOCKED = "blocked"
34
30
  UNDETERMINED = "undetermined"
35
31
 
36
- # PythonNative permission name -> Android manifest permission string.
37
- _ANDROID_PERMS: Dict[str, str] = {
38
- "camera": "android.permission.CAMERA",
39
- "microphone": "android.permission.RECORD_AUDIO",
40
- "location": "android.permission.ACCESS_FINE_LOCATION",
41
- "contacts": "android.permission.READ_CONTACTS",
42
- "notifications": "android.permission.POST_NOTIFICATIONS",
43
- "photos": "android.permission.READ_MEDIA_IMAGES",
44
- }
32
+ _STATUSES = (GRANTED, DENIED, BLOCKED, UNDETERMINED)
33
+
34
+ PERMISSION_NAMES = ("camera", "microphone", "location", "photos", "notifications", "contacts")
35
+ """Permission names understood by every platform."""
36
+
37
+
38
+ def _coerce(value: object) -> PermissionStatus:
39
+ status = str(value) if value is not None else UNDETERMINED
40
+ return status if status in _STATUSES else UNDETERMINED
45
41
 
46
42
 
47
43
  class Permissions:
@@ -50,175 +46,15 @@ class Permissions:
50
46
  @staticmethod
51
47
  def check(permission: PermissionName) -> PermissionStatus:
52
48
  """Return the current status of ``permission`` without prompting."""
53
- if IS_ANDROID:
54
- return _android_check(permission)
55
- if IS_IOS:
56
- return _ios_check(permission)
57
- return UNDETERMINED
49
+ try:
50
+ return _coerce(native_module("Permissions").call("check", permission=permission))
51
+ except Exception:
52
+ return UNDETERMINED
58
53
 
59
54
  @staticmethod
60
55
  async def request(permission: PermissionName) -> PermissionStatus:
61
56
  """Prompt for ``permission`` (if needed) and return the result."""
62
- loop = asyncio.get_running_loop()
63
- future: asyncio.Future[PermissionStatus] = loop.create_future()
64
-
65
- def _done(status: PermissionStatus) -> None:
66
- resolve_future(future, status)
67
-
68
- if IS_ANDROID:
69
- _android_request(permission, _done)
70
- elif IS_IOS:
71
- _ios_request(permission, _done)
72
- else:
73
- _done(UNDETERMINED)
74
-
75
- return await future
76
-
77
-
78
- # ======================================================================
79
- # Android
80
- # ======================================================================
81
-
82
- _android_pending: Dict[int, Callable[[PermissionStatus], None]] = {}
83
- _android_next_code = 60001
84
-
85
-
86
- def _android_check(permission: str) -> PermissionStatus:
87
- manifest = _ANDROID_PERMS.get(permission)
88
- if manifest is None:
89
- return UNDETERMINED
90
- try:
91
- from java import jclass
92
-
93
- from ..utils import get_android_context
94
-
95
- PackageManager = jclass("android.content.pm.PackageManager")
96
- ctx = get_android_context()
97
- granted = ctx.checkSelfPermission(manifest) == PackageManager.PERMISSION_GRANTED
98
- return GRANTED if granted else DENIED
99
- except Exception:
100
- return UNDETERMINED
101
-
102
-
103
- def _android_request(permission: str, on_done: Callable[[PermissionStatus], None]) -> None:
104
- manifest = _ANDROID_PERMS.get(permission)
105
- if manifest is None:
106
- on_done(UNDETERMINED)
107
- return
108
- if _android_check(permission) == GRANTED:
109
- on_done(GRANTED)
110
- return
111
- try:
112
- global _android_next_code
113
- from java import jclass
114
-
115
- from ..utils import get_android_context
116
-
117
- ctx = get_android_context()
118
- Activity = jclass("android.app.Activity")
119
- if not Activity.isInstance(ctx):
120
- on_done(_android_check(permission))
121
- return
122
- code = _android_next_code
123
- _android_next_code += 1
124
- _android_pending[code] = on_done
125
- ctx.requestPermissions([manifest], code)
126
- except Exception:
127
- on_done(_android_check(permission))
128
-
129
-
130
- def deliver_android_permission_result(request_code: int, permissions: Any, grant_results: Any) -> bool:
131
- """Forward an ``onRequestPermissionsResult`` to the pending coroutine."""
132
- cb = _android_pending.pop(request_code, None)
133
- if cb is None:
134
- return False
135
- status = DENIED
136
- try:
137
- del permissions
138
- if grant_results is not None and len(grant_results) > 0 and grant_results[0] == 0:
139
- status = GRANTED
140
- except Exception:
141
- status = DENIED
142
- try:
143
- cb(status)
144
- except Exception:
145
- diagnostics.swallowed("permissions.deliver_android_permission_result")
146
- return True
147
-
148
-
149
- # ======================================================================
150
- # iOS
151
- # ======================================================================
152
-
153
-
154
- def _ios_media_status(media_type: str) -> PermissionStatus:
155
- from rubicon.objc import ObjCClass
156
-
157
- AVCaptureDevice = ObjCClass("AVCaptureDevice")
158
- status = AVCaptureDevice.authorizationStatusForMediaType_(media_type)
159
- # 0 notDetermined, 1 restricted, 2 denied, 3 authorized
160
- return {0: UNDETERMINED, 1: BLOCKED, 2: DENIED, 3: GRANTED}.get(int(status), UNDETERMINED)
161
-
162
-
163
- def _ios_check(permission: str) -> PermissionStatus:
164
- try:
165
- if permission == "camera":
166
- return _ios_media_status("vide")
167
- if permission == "microphone":
168
- return _ios_media_status("soun")
169
- if permission == "photos":
170
- from rubicon.objc import ObjCClass
171
-
172
- status = ObjCClass("PHPhotoLibrary").authorizationStatus()
173
- return {0: UNDETERMINED, 1: BLOCKED, 2: DENIED, 3: GRANTED, 4: GRANTED}.get(int(status), UNDETERMINED)
174
- if permission == "location":
175
- from rubicon.objc import ObjCClass
176
-
177
- status = ObjCClass("CLLocationManager").authorizationStatus()
178
- return {0: UNDETERMINED, 1: BLOCKED, 2: DENIED, 3: GRANTED, 4: GRANTED}.get(int(status), UNDETERMINED)
179
- except Exception:
180
- return UNDETERMINED
181
- return UNDETERMINED
182
-
183
-
184
- def _ios_request(permission: str, on_done: Callable[[PermissionStatus], None]) -> None:
185
- try:
186
- from rubicon.objc import Block, ObjCClass
187
-
188
- if permission in ("camera", "microphone"):
189
- media = "vide" if permission == "camera" else "soun"
190
- AVCaptureDevice = ObjCClass("AVCaptureDevice")
191
-
192
- def _granted(ok: bool) -> None:
193
- on_done(GRANTED if ok else DENIED)
194
-
195
- AVCaptureDevice.requestAccessForMediaType_completionHandler_(media, Block(_granted, None, bool))
196
- return
197
-
198
- if permission == "photos":
199
- PHPhotoLibrary = ObjCClass("PHPhotoLibrary")
200
-
201
- def _photos_done(status: int) -> None:
202
- # 0 notDetermined, 1 restricted, 2 denied, 3 authorized,
203
- # 4 limited (counts as granted).
204
- on_done({0: UNDETERMINED, 1: BLOCKED, 2: DENIED, 3: GRANTED, 4: GRANTED}.get(int(status), UNDETERMINED))
205
-
206
- PHPhotoLibrary.requestAuthorization_(Block(_photos_done, None, int))
207
- return
208
-
209
- if permission == "notifications":
210
- center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
211
-
212
- def _notif_done(granted: bool, _error: Any) -> None:
213
- on_done(GRANTED if granted else DENIED)
214
-
215
- # 0x07 = badge | sound | alert.
216
- center.requestAuthorizationWithOptions_completionHandler_(0x07, Block(_notif_done, None, bool, object))
217
- return
218
- except Exception:
219
- diagnostics.swallowed("permissions._ios_request")
220
- # Fall back to reporting the current status for permissions whose
221
- # request flow isn't wired up natively (location needs a
222
- # CLLocationManager delegate; use the Location module's own
223
- # request path for that).
224
- on_done(_ios_check(permission))
57
+ try:
58
+ return _coerce(await native_module("Permissions").call_async("request", permission=permission))
59
+ except Exception:
60
+ return UNDETERMINED