pythonnative 0.35.0__py3-none-any.whl → 0.37.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonnative/__init__.py +6 -4
- pythonnative/_ios_log.py +1 -1
- pythonnative/alerts.py +34 -88
- pythonnative/bootstrap.py +90 -0
- pythonnative/bridge/__init__.py +306 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +164 -0
- pythonnative/bridge/fake.py +236 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/cli/pn.py +24 -6
- pythonnative/components/layout.py +65 -3
- pythonnative/gestures.py +13 -10
- pythonnative/hooks.py +41 -26
- pythonnative/hosts/__init__.py +13 -36
- pythonnative/hosts/native.py +348 -0
- pythonnative/images.py +6 -5
- pythonnative/mutations.py +21 -0
- pythonnative/native_modules/__init__.py +30 -29
- pythonnative/native_modules/app_state.py +10 -6
- pythonnative/native_modules/battery.py +20 -84
- pythonnative/native_modules/biometrics.py +11 -124
- pythonnative/native_modules/camera.py +15 -291
- pythonnative/native_modules/clipboard.py +14 -109
- pythonnative/native_modules/desktop.py +322 -0
- pythonnative/native_modules/file_system.py +15 -21
- pythonnative/native_modules/haptics.py +19 -129
- pythonnative/native_modules/linking.py +24 -137
- pythonnative/native_modules/location.py +28 -180
- pythonnative/native_modules/net_info.py +25 -135
- pythonnative/native_modules/notifications.py +36 -257
- pythonnative/native_modules/permissions.py +20 -184
- pythonnative/native_modules/registry.py +513 -0
- pythonnative/native_modules/secure_store.py +18 -155
- pythonnative/native_modules/share.py +8 -113
- pythonnative/native_views/__init__.py +67 -86
- pythonnative/native_views/bridge_backend.py +264 -0
- pythonnative/platform.py +19 -15
- pythonnative/platform_metrics.py +3 -3
- pythonnative/project/android.py +0 -3
- pythonnative/project/builder.py +38 -16
- pythonnative/project/config.py +12 -2
- pythonnative/project/plugins.py +366 -0
- pythonnative/py.typed +0 -0
- pythonnative/runtime.py +35 -203
- pythonnative/sdk/__init__.py +48 -33
- pythonnative/sdk/_components.py +57 -76
- pythonnative/storage.py +18 -243
- pythonnative/templates/android_template/app/build.gradle +6 -9
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
- pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
- pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
- pythonnative/templates/android_template/settings.gradle +1 -0
- pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
- pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
- pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
- pythonnative/utils.py +4 -82
- pythonnative/virtual_rows.py +3 -3
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
- pythonnative-0.37.0.dist-info/RECORD +263 -0
- pythonnative/hosts/android.py +0 -269
- pythonnative/hosts/ios.py +0 -404
- pythonnative/native_views/android.py +0 -3702
- pythonnative/native_views/ios.py +0 -5253
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
- pythonnative-0.35.0.dist-info/RECORD +0 -155
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
[`NetInfo`][pythonnative.NetInfo] reports whether the device is online
|
|
4
4
|
and over what kind of connection. ``fetch`` returns a snapshot dict;
|
|
5
5
|
``add_listener`` (and the [`use_net_info`][pythonnative.use_net_info]
|
|
6
|
-
hook) deliver live updates
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
hosts may also push changes directly through
|
|
6
|
+
hook) deliver live updates pushed by the native ``NetInfo`` module
|
|
7
|
+
(``NWPathMonitor`` on iOS, ``ConnectivityManager.NetworkCallback`` on
|
|
8
|
+
Android) as ``change`` events. Off device, tests push snapshots through
|
|
10
9
|
[`dispatch_net_info`][pythonnative.native_modules.net_info.dispatch_net_info].
|
|
11
10
|
|
|
12
11
|
A snapshot looks like::
|
|
@@ -19,12 +18,11 @@ A snapshot looks like::
|
|
|
19
18
|
|
|
20
19
|
from __future__ import annotations
|
|
21
20
|
|
|
22
|
-
import
|
|
23
|
-
from typing import Callable, Dict, List, Optional
|
|
21
|
+
from typing import Any, Callable, Dict, List
|
|
24
22
|
|
|
25
23
|
from .. import diagnostics
|
|
26
24
|
from ..hooks import use_effect, use_state
|
|
27
|
-
from
|
|
25
|
+
from .registry import native_module, on_event
|
|
28
26
|
|
|
29
27
|
NetInfoState = Dict[str, object]
|
|
30
28
|
|
|
@@ -35,11 +33,6 @@ _last_state: NetInfoState = {
|
|
|
35
33
|
"is_internet_reachable": True,
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
# Background watcher driving live updates while listeners exist.
|
|
39
|
-
_WATCH_INTERVAL_S = 2.0
|
|
40
|
-
_watcher_lock = threading.Lock()
|
|
41
|
-
_watcher_stop: Optional[threading.Event] = None
|
|
42
|
-
|
|
43
36
|
|
|
44
37
|
class NetInfo:
|
|
45
38
|
"""Network connectivity interface."""
|
|
@@ -48,66 +41,34 @@ class NetInfo:
|
|
|
48
41
|
def fetch() -> NetInfoState:
|
|
49
42
|
"""Return a fresh snapshot of connectivity state."""
|
|
50
43
|
global _last_state
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
try:
|
|
45
|
+
snapshot = native_module("NetInfo").call("fetch")
|
|
46
|
+
except Exception:
|
|
47
|
+
snapshot = None
|
|
48
|
+
if isinstance(snapshot, dict):
|
|
49
|
+
_last_state = _normalize(snapshot)
|
|
55
50
|
return dict(_last_state)
|
|
56
51
|
|
|
57
52
|
@staticmethod
|
|
58
53
|
def add_listener(callback: Callable[[NetInfoState], None]) -> Callable[[], None]:
|
|
59
|
-
"""Subscribe to connectivity changes; returns an unsubscribe fn.
|
|
60
|
-
|
|
61
|
-
The first subscription starts a background watcher that polls
|
|
62
|
-
the platform connectivity state every couple of seconds and
|
|
63
|
-
dispatches a snapshot on change; the watcher stops when the
|
|
64
|
-
last listener unsubscribes.
|
|
65
|
-
"""
|
|
54
|
+
"""Subscribe to connectivity changes; returns an unsubscribe fn."""
|
|
66
55
|
_listeners.append(callback)
|
|
67
|
-
_ensure_watcher()
|
|
68
56
|
|
|
69
57
|
def _unsubscribe() -> None:
|
|
70
58
|
try:
|
|
71
59
|
_listeners.remove(callback)
|
|
72
60
|
except ValueError:
|
|
73
61
|
pass
|
|
74
|
-
if not _listeners:
|
|
75
|
-
_stop_watcher()
|
|
76
62
|
|
|
77
63
|
return _unsubscribe
|
|
78
64
|
|
|
79
65
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if _watcher_stop is not None:
|
|
87
|
-
return
|
|
88
|
-
stop = threading.Event()
|
|
89
|
-
_watcher_stop = stop
|
|
90
|
-
|
|
91
|
-
def _watch() -> None:
|
|
92
|
-
previous = dict(_last_state)
|
|
93
|
-
while not stop.wait(_WATCH_INTERVAL_S):
|
|
94
|
-
try:
|
|
95
|
-
current = _android_state() if IS_ANDROID else _ios_state()
|
|
96
|
-
except Exception:
|
|
97
|
-
continue
|
|
98
|
-
if current != previous:
|
|
99
|
-
previous = dict(current)
|
|
100
|
-
dispatch_net_info(current)
|
|
101
|
-
|
|
102
|
-
threading.Thread(target=_watch, daemon=True, name="pn-netinfo").start()
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _stop_watcher() -> None:
|
|
106
|
-
global _watcher_stop
|
|
107
|
-
with _watcher_lock:
|
|
108
|
-
if _watcher_stop is not None:
|
|
109
|
-
_watcher_stop.set()
|
|
110
|
-
_watcher_stop = None
|
|
66
|
+
def _normalize(snapshot: Dict[str, Any]) -> NetInfoState:
|
|
67
|
+
return {
|
|
68
|
+
"is_connected": bool(snapshot.get("is_connected", False)),
|
|
69
|
+
"type": str(snapshot.get("type", "unknown")),
|
|
70
|
+
"is_internet_reachable": bool(snapshot.get("is_internet_reachable", False)),
|
|
71
|
+
}
|
|
111
72
|
|
|
112
73
|
|
|
113
74
|
def dispatch_net_info(state: NetInfoState) -> None:
|
|
@@ -121,25 +82,18 @@ def dispatch_net_info(state: NetInfoState) -> None:
|
|
|
121
82
|
diagnostics.swallowed("net_info.dispatch_net_info")
|
|
122
83
|
|
|
123
84
|
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
``ConnectivityManager.NetworkCallback`` it registers via
|
|
129
|
-
``registerDefaultNetworkCallback`` whenever the default network
|
|
130
|
-
appears, drops, or changes capabilities. The callback lives in
|
|
131
|
-
Kotlin because Chaquopy's ``dynamic_proxy`` implements interfaces
|
|
132
|
-
only and ``NetworkCallback`` is a class. Re-reading the snapshot
|
|
133
|
-
here keeps the transport-type mapping in one place, and identical
|
|
134
|
-
consecutive snapshots are dropped so paired ``onAvailable`` /
|
|
135
|
-
``onCapabilitiesChanged`` callbacks don't double-notify.
|
|
136
|
-
"""
|
|
137
|
-
state = _android_state()
|
|
85
|
+
def _on_native_change(payload: Any) -> None:
|
|
86
|
+
if not isinstance(payload, dict):
|
|
87
|
+
return
|
|
88
|
+
state = _normalize(payload)
|
|
138
89
|
if state == _last_state:
|
|
139
90
|
return
|
|
140
91
|
dispatch_net_info(state)
|
|
141
92
|
|
|
142
93
|
|
|
94
|
+
on_event("NetInfo", "change", _on_native_change)
|
|
95
|
+
|
|
96
|
+
|
|
143
97
|
def use_net_info() -> NetInfoState:
|
|
144
98
|
"""Subscribe a component to [`NetInfo`][pythonnative.NetInfo].
|
|
145
99
|
|
|
@@ -159,67 +113,3 @@ def use_net_info() -> NetInfoState:
|
|
|
159
113
|
|
|
160
114
|
use_effect(_subscribe, [])
|
|
161
115
|
return state
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
# ======================================================================
|
|
165
|
-
# iOS: SCNetworkReachability
|
|
166
|
-
# ======================================================================
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def _ios_state() -> NetInfoState:
|
|
170
|
-
try:
|
|
171
|
-
from ctypes import CDLL, byref, c_uint32, c_void_p, util
|
|
172
|
-
|
|
173
|
-
sc = CDLL(util.find_library("SystemConfiguration"))
|
|
174
|
-
sc.SCNetworkReachabilityCreateWithName.restype = c_void_p
|
|
175
|
-
ref = sc.SCNetworkReachabilityCreateWithName(None, b"8.8.8.8")
|
|
176
|
-
if not ref:
|
|
177
|
-
return dict(_last_state)
|
|
178
|
-
flags = c_uint32(0)
|
|
179
|
-
ok = sc.SCNetworkReachabilityGetFlags(c_void_p(ref), byref(flags))
|
|
180
|
-
if not ok:
|
|
181
|
-
return {"is_connected": False, "type": "none", "is_internet_reachable": False}
|
|
182
|
-
reachable = bool(flags.value & 0x2) # kSCNetworkReachabilityFlagsReachable
|
|
183
|
-
is_wwan = bool(flags.value & (1 << 18)) # kSCNetworkReachabilityFlagsIsWWAN
|
|
184
|
-
return {
|
|
185
|
-
"is_connected": reachable,
|
|
186
|
-
"type": ("cellular" if is_wwan else "wifi") if reachable else "none",
|
|
187
|
-
"is_internet_reachable": reachable,
|
|
188
|
-
}
|
|
189
|
-
except Exception:
|
|
190
|
-
return dict(_last_state)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
# ======================================================================
|
|
194
|
-
# Android: ConnectivityManager
|
|
195
|
-
# ======================================================================
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def _android_state() -> NetInfoState:
|
|
199
|
-
try:
|
|
200
|
-
from java import jclass
|
|
201
|
-
|
|
202
|
-
from ..utils import get_android_context
|
|
203
|
-
|
|
204
|
-
ctx = get_android_context()
|
|
205
|
-
Context = jclass("android.content.Context")
|
|
206
|
-
manager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE)
|
|
207
|
-
network = manager.getActiveNetwork()
|
|
208
|
-
if network is None:
|
|
209
|
-
return {"is_connected": False, "type": "none", "is_internet_reachable": False}
|
|
210
|
-
caps = manager.getNetworkCapabilities(network)
|
|
211
|
-
if caps is None:
|
|
212
|
-
return {"is_connected": False, "type": "none", "is_internet_reachable": False}
|
|
213
|
-
NetworkCapabilities = jclass("android.net.NetworkCapabilities")
|
|
214
|
-
if caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI):
|
|
215
|
-
kind = "wifi"
|
|
216
|
-
elif caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR):
|
|
217
|
-
kind = "cellular"
|
|
218
|
-
elif caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET):
|
|
219
|
-
kind = "ethernet"
|
|
220
|
-
else:
|
|
221
|
-
kind = "unknown"
|
|
222
|
-
reachable = bool(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET))
|
|
223
|
-
return {"is_connected": True, "type": kind, "is_internet_reachable": reachable}
|
|
224
|
-
except Exception:
|
|
225
|
-
return dict(_last_state)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Local notifications and remote push registration.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
``
|
|
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
|
|
40
|
-
from typing import Any, Dict, List, Optional
|
|
40
|
+
from typing import Any, Optional
|
|
41
41
|
|
|
42
|
-
from
|
|
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
|
|
62
|
-
|
|
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
|
-
|
|
70
|
-
return await
|
|
71
|
-
|
|
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:
|
|
93
|
-
``badge``,
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
256
|
-
except
|
|
257
|
-
|
|
258
|
-
|
|
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
|