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,12 +1,11 @@
1
1
  """Cross-platform camera and gallery access.
2
2
 
3
3
  Both entry points are coroutines: ``await Camera.take_photo()`` returns
4
- the saved image path (a ``str``) or ``None`` if the user cancels.
5
- Internally each call instantiates a fresh native delegate / activity
6
- request and bridges its completion onto the PythonNative asyncio
7
- runtime, so callers don't have to know whether the picker is backed by
8
- ``UIImagePickerController`` (iOS) or
9
- ``Intent(MediaStore.ACTION_IMAGE_CAPTURE)`` (Android).
4
+ the saved image path (a ``str``) or ``None`` if the user cancels. The
5
+ native ``Camera`` module presents ``UIImagePickerController`` (iOS) or
6
+ launches ``MediaStore.ACTION_IMAGE_CAPTURE`` / ``ACTION_PICK``
7
+ (Android) and resolves the call when the picker finishes; Python only
8
+ awaits the promise.
10
9
 
11
10
  Example:
12
11
  ```python
@@ -22,316 +21,41 @@ Example:
22
21
 
23
22
  from __future__ import annotations
24
23
 
25
- import asyncio
26
- from typing import Any, Callable, Dict, Optional
24
+ from typing import Any, Optional
27
25
 
28
- from .. import diagnostics
29
- from ..runtime import resolve_future
30
- from ..utils import IS_ANDROID, IS_IOS
31
-
32
- # Retain pool keyed by ``id(delegate)`` so iOS UIImagePickerController
33
- # delegates aren't garbage-collected before the picker calls back.
34
- _pending_delegates: Dict[int, Any] = {}
26
+ from .registry import native_module
35
27
 
36
28
 
37
29
  class Camera:
38
- """Camera and image-picker interface.
39
-
40
- All methods are static coroutines. They dispatch to the iOS or
41
- Android implementation at call time based on the runtime
42
- platform.
43
- """
30
+ """Camera and image-picker interface (static coroutines)."""
44
31
 
45
32
  @staticmethod
46
33
  async def take_photo(**options: Any) -> Optional[str]:
47
34
  """Launch the device camera to capture a photo.
48
35
 
49
36
  Args:
50
- **options: Reserved for platform-specific tuning. Currently
51
- unused; future kwargs (e.g., ``quality``,
52
- ``flash_mode``) will land here.
37
+ **options: Forwarded to the native module (``quality``,
38
+ ``allow_editing``, ...). Unknown keys are ignored.
53
39
 
54
40
  Returns:
55
41
  The saved image path, or ``None`` if the user cancelled or
56
42
  no camera is available.
57
43
  """
58
- return await _launch("camera", **options)
44
+ return await _launch("take_photo", options)
59
45
 
60
46
  @staticmethod
61
47
  async def pick_from_gallery(**options: Any) -> Optional[str]:
62
48
  """Open the system gallery picker.
63
49
 
64
- Args:
65
- **options: Reserved for platform-specific tuning.
66
-
67
50
  Returns:
68
51
  The selected image path, or ``None`` if the user cancelled.
69
52
  """
70
- return await _launch("gallery", **options)
71
-
72
-
73
- async def _launch(source: str, **options: Any) -> Optional[str]:
74
- del options
75
- loop = asyncio.get_running_loop()
76
- future: asyncio.Future[Optional[str]] = loop.create_future()
77
-
78
- def _on_result(path: Optional[str]) -> None:
79
- resolve_future(future, path)
80
-
81
- if IS_ANDROID:
82
- _android_launch_picker(_on_result, source=source)
83
- elif IS_IOS:
84
- _ios_launch_picker(_on_result, source=source)
85
- else:
86
- resolve_future(future, None)
87
-
88
- return await future
89
-
90
-
91
- # ======================================================================
92
- # iOS implementation: UIImagePickerControllerDelegate
93
- # ======================================================================
94
-
95
-
96
- def _ios_launch_picker(on_result: Callable[[Optional[str]], None], source: str) -> None:
97
- try:
98
- from rubicon.objc import SEL, ObjCClass, objc_method
99
-
100
- UIImagePickerController = ObjCClass("UIImagePickerController")
101
- NSObject = ObjCClass("NSObject")
102
-
103
- class _PNImagePickerDelegate(NSObject): # type: ignore[misc,valid-type]
104
- _callback: Optional[Callable[[Optional[str]], None]] = None
105
- _picker: Any = None
106
-
107
- @objc_method
108
- def imagePickerController_didFinishPickingMediaWithInfo_(self, picker: Any, info: Any) -> None:
109
- path = _ios_extract_path(info)
110
- try:
111
- picker.dismissViewControllerAnimated_completion_(True, None)
112
- except Exception:
113
- diagnostics.swallowed(
114
- "camera._ios_launch_picker._PNImagePickerDelegate.imagePickerController_didFinishPickingMediaWithInfo_"
115
- )
116
- cb = self._callback
117
- self._callback = None
118
- _pending_delegates.pop(id(self), None)
119
- if cb is not None:
120
- try:
121
- cb(path)
122
- except Exception:
123
- diagnostics.swallowed(
124
- "camera._ios_launch_picker._PNImagePickerDelegate.imagePickerController_didFinishPickingMediaWithInfo_"
125
- )
126
-
127
- @objc_method
128
- def imagePickerControllerDidCancel_(self, picker: Any) -> None:
129
- try:
130
- picker.dismissViewControllerAnimated_completion_(True, None)
131
- except Exception:
132
- diagnostics.swallowed(
133
- "camera._ios_launch_picker._PNImagePickerDelegate.imagePickerControllerDidCancel_"
134
- )
135
- cb = self._callback
136
- self._callback = None
137
- _pending_delegates.pop(id(self), None)
138
- if cb is not None:
139
- try:
140
- cb(None)
141
- except Exception:
142
- diagnostics.swallowed(
143
- "camera._ios_launch_picker._PNImagePickerDelegate.imagePickerControllerDidCancel_"
144
- )
145
-
146
- delegate = _PNImagePickerDelegate.new()
147
- delegate._callback = on_result
148
- _pending_delegates[id(delegate)] = delegate
149
-
150
- picker = UIImagePickerController.alloc().init()
151
- picker.setSourceType_(1 if source == "camera" else 0)
152
- picker.setDelegate_(delegate)
153
-
154
- UIApplication = ObjCClass("UIApplication")
155
- top = UIApplication.sharedApplication.keyWindow.rootViewController
156
- while top is not None and top.presentedViewController is not None:
157
- top = top.presentedViewController
158
- if top is not None:
159
- top.presentViewController_animated_completion_(picker, True, None)
160
- else:
161
- _pending_delegates.pop(id(delegate), None)
162
- on_result(None)
163
-
164
- # Reference SEL/objc_method so the lint pass keeps the import;
165
- # they're needed for the delegate class above.
166
- _ = (SEL, objc_method)
167
- except Exception:
168
- on_result(None)
169
-
170
-
171
- def _ios_extract_path(info: Any) -> Optional[str]:
172
- """Best-effort extraction of an image path from picker ``info``."""
173
- try:
174
- url = info.objectForKey_("UIImagePickerControllerImageURL")
175
- if url is not None:
176
- try:
177
- return str(url.absoluteString)
178
- except Exception:
179
- try:
180
- return str(url.path)
181
- except Exception:
182
- diagnostics.swallowed("camera._ios_extract_path")
183
- image = info.objectForKey_("UIImagePickerControllerOriginalImage")
184
- if image is not None:
185
- return _ios_write_image_to_tmp(image)
186
- except Exception:
187
- return None
188
- return None
189
-
190
-
191
- def _ios_write_image_to_tmp(image: Any) -> Optional[str]:
192
- """Encode a UIImage to JPEG and write it to NSCachesDirectory."""
193
- try:
194
- from rubicon.objc import ObjCClass
195
-
196
- try:
197
- data = image.jpegDataWithCompressionQuality_(0.85)
198
- except Exception:
199
- return None
200
- if data is None:
201
- return None
202
- NSString = ObjCClass("NSString")
203
- NSFileManager = ObjCClass("NSFileManager")
204
- manager = NSFileManager.defaultManager
205
- urls = manager.URLsForDirectory_inDomains_(13, 1) # NSCachesDirectory, NSUserDomainMask
206
- if urls is None or urls.count == 0:
207
- return None
208
- cache_dir = urls.firstObject
209
- import time as _time
210
-
211
- filename = NSString.stringWithFormat_("pn-camera-%d.jpg", int(_time.time() * 1000))
212
- target = cache_dir.URLByAppendingPathComponent_(filename)
213
- if not data.writeToURL_atomically_(target, True):
214
- return None
215
- try:
216
- return str(target.path)
217
- except Exception:
218
- return str(target.absoluteString)
219
- except Exception:
220
- return None
221
-
222
-
223
- # ======================================================================
224
- # Android implementation: ActivityResultLauncher / startActivityForResult
225
- # ======================================================================
226
-
227
-
228
- _android_pending_results: Dict[int, Callable[[Optional[str]], None]] = {}
229
- _android_next_request_code: int = 50001
230
-
231
-
232
- def _android_next_code() -> int:
233
- global _android_next_request_code
234
- code = _android_next_request_code
235
- _android_next_request_code += 1
236
- return code
237
-
238
-
239
- def _android_launch_picker(on_result: Callable[[Optional[str]], None], source: str) -> None:
240
- try:
241
- from java import jclass
242
-
243
- from ..utils import get_android_context
53
+ return await _launch("pick_from_gallery", options)
244
54
 
245
- Intent = jclass("android.content.Intent")
246
- MediaStore = jclass("android.provider.MediaStore")
247
- ctx = get_android_context()
248
55
 
249
- if source == "camera":
250
- intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
251
- else:
252
- intent = Intent(Intent.ACTION_PICK)
253
- intent.setType("image/*")
254
-
255
- # ``startActivityForResult`` requires the host to be an
256
- # Activity; in unit tests we may only have an Application
257
- # context, in which case we fall back to ``startActivity`` and
258
- # report ``None`` (the caller has no Activity to dispatch the
259
- # result back through).
260
- Activity = jclass("android.app.Activity")
261
- if not Activity.isInstance(ctx):
262
- ctx.startActivity(intent)
263
- on_result(None)
264
- return
265
-
266
- request_code = _android_next_code()
267
- _android_pending_results[request_code] = on_result
268
- try:
269
- ctx.startActivityForResult(intent, request_code)
270
- except Exception:
271
- _android_pending_results.pop(request_code, None)
272
- on_result(None)
273
- except Exception:
274
- on_result(None)
275
-
276
-
277
- def deliver_android_activity_result(request_code: int, result_code: int, data: Any) -> bool:
278
- """Forward an Activity result to the registered camera coroutine.
279
-
280
- The host Activity should call this from ``onActivityResult`` so
281
- the pending
282
- [`take_photo`][pythonnative.native_modules.camera.Camera.take_photo]
283
- /
284
- [`pick_from_gallery`][pythonnative.native_modules.camera.Camera.pick_from_gallery]
285
- awaitable receives a path. Returns ``True`` if a Python callback
286
- was invoked (so the host can short-circuit further handlers).
287
- """
288
- cb = _android_pending_results.pop(request_code, None)
289
- if cb is None:
290
- return False
291
- path: Optional[str] = None
292
- try:
293
- if result_code == -1 and data is not None: # RESULT_OK
294
- uri = data.getData()
295
- if uri is not None:
296
- path = str(uri)
297
- else:
298
- try:
299
- extras = data.getExtras()
300
- if extras is not None:
301
- thumb = extras.get("data")
302
- if thumb is not None:
303
- path = _android_write_bitmap_to_cache(thumb)
304
- except Exception:
305
- diagnostics.swallowed("camera.deliver_android_activity_result")
306
- except Exception:
307
- path = None
56
+ async def _launch(method: str, options: Any) -> Optional[str]:
308
57
  try:
309
- cb(path)
310
- except Exception:
311
- diagnostics.swallowed("camera.deliver_android_activity_result")
312
- return True
313
-
314
-
315
- def _android_write_bitmap_to_cache(bitmap: Any) -> Optional[str]:
316
- """Persist a Bitmap to the app cache directory and return its path."""
317
- try:
318
- from java import jclass
319
-
320
- from ..utils import get_android_context
321
-
322
- ctx = get_android_context()
323
- cache_dir = ctx.getCacheDir()
324
- File = jclass("java.io.File")
325
- FileOutputStream = jclass("java.io.FileOutputStream")
326
- Bitmap = jclass("android.graphics.Bitmap")
327
- import time as _time
328
-
329
- target = File(cache_dir, f"pn-camera-{int(_time.time() * 1000)}.jpg")
330
- out = FileOutputStream(target)
331
- try:
332
- bitmap.compress(Bitmap.CompressFormat.JPEG, 85, out)
333
- finally:
334
- out.close()
335
- return str(target.getAbsolutePath())
58
+ result = await native_module("Camera").call_async(method, **options)
336
59
  except Exception:
337
60
  return None
61
+ return str(result) if result else None
@@ -1,13 +1,11 @@
1
1
  """Cross-platform clipboard access.
2
2
 
3
3
  [`Clipboard`][pythonnative.Clipboard] reads and writes the system
4
- pasteboard. All methods are synchronous: ``UIPasteboard`` (iOS) and
5
- ``ClipboardManager`` (Android) both answer immediately, so there's no
6
- need for a coroutine.
4
+ pasteboard through the native ``Clipboard`` module (``UIPasteboard``
5
+ on iOS, ``ClipboardManager`` on Android). All methods are synchronous.
7
6
 
8
- On a desktop machine (neither Android nor iOS) the module falls back to
9
- a process-local string buffer. That keeps it usable in the desktop
10
- mock target and unit tests instead of raising.
7
+ Off device the module is a process-local string buffer, which keeps it
8
+ usable in ``pn preview`` and unit tests.
11
9
 
12
10
  Example:
13
11
  ```python
@@ -20,13 +18,8 @@ Example:
20
18
 
21
19
  from __future__ import annotations
22
20
 
23
- from typing import Optional
24
-
25
21
  from .. import diagnostics
26
- from ..utils import IS_ANDROID, IS_IOS
27
-
28
- # Desktop fallback buffer so the API is usable off-device.
29
- _desktop_buffer: str = ""
22
+ from .registry import native_module
30
23
 
31
24
 
32
25
  class Clipboard:
@@ -35,109 +28,21 @@ class Clipboard:
35
28
  @staticmethod
36
29
  def set_string(text: str) -> None:
37
30
  """Copy ``text`` onto the system clipboard."""
38
- global _desktop_buffer
39
- value = "" if text is None else str(text)
40
- if IS_ANDROID:
41
- _android_set(value)
42
- elif IS_IOS:
43
- _ios_set(value)
44
- else:
45
- _desktop_buffer = value
31
+ try:
32
+ native_module("Clipboard").call("set_string", text="" if text is None else str(text))
33
+ except Exception:
34
+ diagnostics.swallowed("clipboard.set_string")
46
35
 
47
36
  @staticmethod
48
37
  def get_string() -> str:
49
38
  """Return the current clipboard string (``""`` when empty)."""
50
- if IS_ANDROID:
51
- return _android_get()
52
- if IS_IOS:
53
- return _ios_get()
54
- return _desktop_buffer
39
+ try:
40
+ value = native_module("Clipboard").call("get_string")
41
+ except Exception:
42
+ return ""
43
+ return "" if value is None else str(value)
55
44
 
56
45
  @staticmethod
57
46
  def has_string() -> bool:
58
47
  """Return ``True`` when the clipboard holds non-empty text."""
59
48
  return bool(Clipboard.get_string())
60
-
61
-
62
- # ======================================================================
63
- # iOS: UIPasteboard
64
- # ======================================================================
65
-
66
-
67
- def _ios_pasteboard() -> Optional[object]:
68
- try:
69
- from rubicon.objc import ObjCClass
70
-
71
- return ObjCClass("UIPasteboard").generalPasteboard
72
- except Exception:
73
- return None
74
-
75
-
76
- def _ios_set(text: str) -> None:
77
- pb = _ios_pasteboard()
78
- if pb is None:
79
- return
80
- try:
81
- pb.string = text
82
- except Exception:
83
- diagnostics.swallowed("clipboard._ios_set")
84
-
85
-
86
- def _ios_get() -> str:
87
- pb = _ios_pasteboard()
88
- if pb is None:
89
- return ""
90
- try:
91
- value = pb.string
92
- return str(value) if value is not None else ""
93
- except Exception:
94
- return ""
95
-
96
-
97
- # ======================================================================
98
- # Android: ClipboardManager
99
- # ======================================================================
100
-
101
-
102
- def _android_manager() -> Optional[object]:
103
- try:
104
- from java import jclass
105
-
106
- from ..utils import get_android_context
107
-
108
- ctx = get_android_context()
109
- Context = jclass("android.content.Context")
110
- return ctx.getSystemService(Context.CLIPBOARD_SERVICE)
111
- except Exception:
112
- return None
113
-
114
-
115
- def _android_set(text: str) -> None:
116
- manager = _android_manager()
117
- if manager is None:
118
- return
119
- try:
120
- from java import jclass
121
-
122
- ClipData = jclass("android.content.ClipData")
123
- clip = ClipData.newPlainText("pythonnative", text)
124
- manager.setPrimaryClip(clip)
125
- except Exception:
126
- diagnostics.swallowed("clipboard._android_set")
127
-
128
-
129
- def _android_get() -> str:
130
- manager = _android_manager()
131
- if manager is None:
132
- return ""
133
- try:
134
- if not manager.hasPrimaryClip():
135
- return ""
136
- clip = manager.getPrimaryClip()
137
- if clip is None or clip.getItemCount() == 0:
138
- return ""
139
- item = clip.getItemAt(0)
140
- text = item.getText()
141
- return str(text) if text is not None else ""
142
- except Exception:
143
- return ""