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.
- 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/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/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.36.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.36.0.dist-info/RECORD +0 -155
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
"""Open URLs, deep links, and the system settings page.
|
|
2
2
|
|
|
3
3
|
[`Linking`][pythonnative.Linking] wraps ``UIApplication.openURL`` /
|
|
4
|
-
``Intent(ACTION_VIEW)``
|
|
5
|
-
``mailto:``, ``tel:``, a custom
|
|
4
|
+
``Intent(ACTION_VIEW)`` (in the native ``Linking`` module) so a Python
|
|
5
|
+
app can hand a URL (``https:``, ``mailto:``, ``tel:``, a custom
|
|
6
|
+
scheme, ...) to the OS.
|
|
6
7
|
|
|
7
8
|
Outbound methods are synchronous and return a ``bool`` describing
|
|
8
9
|
whether the platform accepted the request. On desktop they return
|
|
9
10
|
``False``.
|
|
10
11
|
|
|
11
12
|
Inbound deep links flow the other way: declare your schemes in
|
|
12
|
-
``pythonnative.toml`` (``app.url_schemes``) and the native
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
with
|
|
13
|
+
``pythonnative.toml`` (``app.url_schemes``) and the native module
|
|
14
|
+
pushes a ``url`` event for every URL that opens the app, which lands in
|
|
15
|
+
[`dispatch_url`][pythonnative.native_modules.linking.dispatch_url]. The
|
|
16
|
+
URL that cold-started the app is kept and returned by
|
|
17
|
+
``get_initial_url``; later URLs reach subscribers added with
|
|
18
|
+
``add_listener``.
|
|
17
19
|
|
|
18
20
|
Example:
|
|
19
21
|
```python
|
|
@@ -30,43 +32,36 @@ from __future__ import annotations
|
|
|
30
32
|
|
|
31
33
|
from typing import Any, Callable, List, Optional
|
|
32
34
|
|
|
33
|
-
from
|
|
35
|
+
from .registry import native_module, on_event
|
|
34
36
|
|
|
35
|
-
# Populated by the native host when the app is launched from a deep
|
|
36
|
-
# link; ``get_initial_url`` returns it.
|
|
37
37
|
_initial_url: Optional[str] = None
|
|
38
38
|
_url_listeners: List[Callable[[str], None]] = []
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
def _call_bool(method: str, **args: Any) -> bool:
|
|
42
|
+
try:
|
|
43
|
+
return bool(native_module("Linking").call(method, **args))
|
|
44
|
+
except Exception:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
41
48
|
class Linking:
|
|
42
49
|
"""System URL / deep-link interface (synchronous)."""
|
|
43
50
|
|
|
44
51
|
@staticmethod
|
|
45
52
|
def open_url(url: str) -> bool:
|
|
46
53
|
"""Hand ``url`` to the OS. Returns ``True`` if it was accepted."""
|
|
47
|
-
|
|
48
|
-
return _ios_open(url)
|
|
49
|
-
if IS_ANDROID:
|
|
50
|
-
return _android_open(url)
|
|
51
|
-
return False
|
|
54
|
+
return _call_bool("open_url", url=url)
|
|
52
55
|
|
|
53
56
|
@staticmethod
|
|
54
57
|
def can_open_url(url: str) -> bool:
|
|
55
58
|
"""Return ``True`` when some installed app can handle ``url``."""
|
|
56
|
-
|
|
57
|
-
return _ios_can_open(url)
|
|
58
|
-
if IS_ANDROID:
|
|
59
|
-
return _android_can_open(url)
|
|
60
|
-
return False
|
|
59
|
+
return _call_bool("can_open_url", url=url)
|
|
61
60
|
|
|
62
61
|
@staticmethod
|
|
63
62
|
def open_settings() -> bool:
|
|
64
63
|
"""Open this app's entry in the system Settings app."""
|
|
65
|
-
|
|
66
|
-
return _ios_open_settings()
|
|
67
|
-
if IS_ANDROID:
|
|
68
|
-
return _android_open_settings()
|
|
69
|
-
return False
|
|
64
|
+
return _call_bool("open_settings")
|
|
70
65
|
|
|
71
66
|
@staticmethod
|
|
72
67
|
def get_initial_url() -> Optional[str]:
|
|
@@ -97,18 +92,17 @@ class Linking:
|
|
|
97
92
|
|
|
98
93
|
|
|
99
94
|
def set_initial_url(url: Optional[str]) -> None:
|
|
100
|
-
"""Record the launch URL (
|
|
95
|
+
"""Record the launch URL (or clear it with ``None``)."""
|
|
101
96
|
global _initial_url
|
|
102
97
|
_initial_url = url
|
|
103
98
|
|
|
104
99
|
|
|
105
100
|
def dispatch_url(url: str) -> None:
|
|
106
|
-
"""Deliver an inbound deep link
|
|
101
|
+
"""Deliver an inbound deep link.
|
|
107
102
|
|
|
108
103
|
The first URL ever dispatched is also recorded as the initial URL
|
|
109
104
|
(a cold start from a deep link reaches Python only after the
|
|
110
|
-
interpreter boots, so
|
|
111
|
-
earlier than this).
|
|
105
|
+
interpreter boots, so native can't report it earlier than this).
|
|
112
106
|
|
|
113
107
|
Args:
|
|
114
108
|
url: The full URL string that opened the app.
|
|
@@ -125,111 +119,4 @@ def dispatch_url(url: str) -> None:
|
|
|
125
119
|
diagnostics.warn(f"Linking listener raised while handling {url!r}: {exc!r}")
|
|
126
120
|
|
|
127
121
|
|
|
128
|
-
|
|
129
|
-
# iOS
|
|
130
|
-
# ======================================================================
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def _ios_url(url: str) -> Optional[Any]:
|
|
134
|
-
try:
|
|
135
|
-
from rubicon.objc import ObjCClass
|
|
136
|
-
|
|
137
|
-
return ObjCClass("NSURL").URLWithString_(url)
|
|
138
|
-
except Exception:
|
|
139
|
-
return None
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def _ios_open(url: str) -> bool:
|
|
143
|
-
nsurl = _ios_url(url)
|
|
144
|
-
if nsurl is None:
|
|
145
|
-
return False
|
|
146
|
-
try:
|
|
147
|
-
from rubicon.objc import ObjCClass
|
|
148
|
-
|
|
149
|
-
app = ObjCClass("UIApplication").sharedApplication
|
|
150
|
-
if app.canOpenURL_(nsurl):
|
|
151
|
-
app.openURL_options_completionHandler_(nsurl, None, None)
|
|
152
|
-
return True
|
|
153
|
-
return False
|
|
154
|
-
except Exception:
|
|
155
|
-
return False
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def _ios_can_open(url: str) -> bool:
|
|
159
|
-
nsurl = _ios_url(url)
|
|
160
|
-
if nsurl is None:
|
|
161
|
-
return False
|
|
162
|
-
try:
|
|
163
|
-
from rubicon.objc import ObjCClass
|
|
164
|
-
|
|
165
|
-
return bool(ObjCClass("UIApplication").sharedApplication.canOpenURL_(nsurl))
|
|
166
|
-
except Exception:
|
|
167
|
-
return False
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def _ios_open_settings() -> bool:
|
|
171
|
-
try:
|
|
172
|
-
from rubicon.objc import ObjCClass
|
|
173
|
-
|
|
174
|
-
settings = ObjCClass("UIApplicationOpenSettingsURLString")
|
|
175
|
-
return _ios_open(str(settings))
|
|
176
|
-
except Exception:
|
|
177
|
-
try:
|
|
178
|
-
return _ios_open("app-settings:")
|
|
179
|
-
except Exception:
|
|
180
|
-
return False
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
# ======================================================================
|
|
184
|
-
# Android
|
|
185
|
-
# ======================================================================
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def _android_open(url: str) -> bool:
|
|
189
|
-
try:
|
|
190
|
-
from java import jclass
|
|
191
|
-
|
|
192
|
-
from ..utils import get_android_context
|
|
193
|
-
|
|
194
|
-
Intent = jclass("android.content.Intent")
|
|
195
|
-
Uri = jclass("android.net.Uri")
|
|
196
|
-
intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
|
197
|
-
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
198
|
-
get_android_context().startActivity(intent)
|
|
199
|
-
return True
|
|
200
|
-
except Exception:
|
|
201
|
-
return False
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def _android_can_open(url: str) -> bool:
|
|
205
|
-
try:
|
|
206
|
-
from java import jclass
|
|
207
|
-
|
|
208
|
-
from ..utils import get_android_context
|
|
209
|
-
|
|
210
|
-
Intent = jclass("android.content.Intent")
|
|
211
|
-
Uri = jclass("android.net.Uri")
|
|
212
|
-
intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
|
213
|
-
pm = get_android_context().getPackageManager()
|
|
214
|
-
return intent.resolveActivity(pm) is not None
|
|
215
|
-
except Exception:
|
|
216
|
-
return False
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def _android_open_settings() -> bool:
|
|
220
|
-
try:
|
|
221
|
-
from java import jclass
|
|
222
|
-
|
|
223
|
-
from ..utils import get_android_context
|
|
224
|
-
|
|
225
|
-
Intent = jclass("android.content.Intent")
|
|
226
|
-
Settings = jclass("android.provider.Settings")
|
|
227
|
-
Uri = jclass("android.net.Uri")
|
|
228
|
-
ctx = get_android_context()
|
|
229
|
-
intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
|
230
|
-
intent.setData(Uri.fromParts("package", ctx.getPackageName(), None))
|
|
231
|
-
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
232
|
-
ctx.startActivity(intent)
|
|
233
|
-
return True
|
|
234
|
-
except Exception:
|
|
235
|
-
return False
|
|
122
|
+
on_event("Linking", "url", lambda payload: dispatch_url(str(payload)))
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
[`Location.get_current`][pythonnative.native_modules.location.Location.get_current]
|
|
4
4
|
is a coroutine that resolves to a ``(latitude, longitude)`` tuple, or
|
|
5
5
|
``None`` if no recent fix is available or the user denies permission.
|
|
6
|
+
The native ``Location`` module owns the ``CLLocationManager`` /
|
|
7
|
+
``LocationManager`` session and resolves the call with
|
|
8
|
+
``{"latitude", "longitude", "accuracy", "altitude", "timestamp"}``.
|
|
6
9
|
|
|
7
10
|
Permission prompts are triggered the first time a location-using API
|
|
8
11
|
is called; ensure the appropriate manifest entries
|
|
@@ -24,16 +27,9 @@ Example:
|
|
|
24
27
|
|
|
25
28
|
from __future__ import annotations
|
|
26
29
|
|
|
27
|
-
import
|
|
28
|
-
from typing import Any, Callable, Dict, Optional, Tuple
|
|
30
|
+
from typing import Any, Dict, Optional, Tuple
|
|
29
31
|
|
|
30
|
-
from
|
|
31
|
-
from ..runtime import resolve_future
|
|
32
|
-
from ..utils import IS_ANDROID, IS_IOS
|
|
33
|
-
|
|
34
|
-
# Retain pool keyed by ``id(delegate)`` so iOS / Android listeners
|
|
35
|
-
# aren't garbage-collected before the platform calls back.
|
|
36
|
-
_pending_delegates: Dict[int, Any] = {}
|
|
32
|
+
from .registry import native_module
|
|
37
33
|
|
|
38
34
|
Coords = Tuple[float, float]
|
|
39
35
|
|
|
@@ -46,184 +42,36 @@ class Location:
|
|
|
46
42
|
"""Request the device's current location.
|
|
47
43
|
|
|
48
44
|
Args:
|
|
49
|
-
**options:
|
|
50
|
-
``
|
|
45
|
+
**options: Forwarded to the native module (``accuracy``,
|
|
46
|
+
``timeout``). Unknown keys are ignored.
|
|
51
47
|
|
|
52
48
|
Returns:
|
|
53
49
|
``(latitude, longitude)`` if a fix was obtained, otherwise
|
|
54
50
|
``None``.
|
|
55
51
|
"""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _on_result(coords: Optional[Coords]) -> None:
|
|
61
|
-
resolve_future(future, coords)
|
|
62
|
-
|
|
63
|
-
if IS_ANDROID:
|
|
64
|
-
_android_get(_on_result)
|
|
65
|
-
elif IS_IOS:
|
|
66
|
-
_ios_get(_on_result)
|
|
67
|
-
else:
|
|
68
|
-
resolve_future(future, None)
|
|
69
|
-
|
|
70
|
-
return await future
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# ======================================================================
|
|
74
|
-
# iOS implementation: CLLocationManagerDelegate
|
|
75
|
-
# ======================================================================
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def _ios_get(on_result: Callable[[Optional[Coords]], None]) -> None:
|
|
79
|
-
try:
|
|
80
|
-
from rubicon.objc import ObjCClass, objc_method
|
|
81
|
-
|
|
82
|
-
CLLocationManager = ObjCClass("CLLocationManager")
|
|
83
|
-
NSObject = ObjCClass("NSObject")
|
|
52
|
+
fix = await Location.get_current_fix(**options)
|
|
53
|
+
if fix is None:
|
|
54
|
+
return None
|
|
55
|
+
return (fix["latitude"], fix["longitude"])
|
|
84
56
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
_delivered: bool = False
|
|
89
|
-
|
|
90
|
-
def _finish(self, coords: Optional[Coords]) -> None:
|
|
91
|
-
if self._delivered:
|
|
92
|
-
return
|
|
93
|
-
self._delivered = True
|
|
94
|
-
try:
|
|
95
|
-
if self._manager is not None:
|
|
96
|
-
self._manager.stopUpdatingLocation()
|
|
97
|
-
except Exception:
|
|
98
|
-
diagnostics.swallowed("location._ios_get._PNLocationDelegate._finish")
|
|
99
|
-
cb = self._callback
|
|
100
|
-
self._callback = None
|
|
101
|
-
_pending_delegates.pop(id(self), None)
|
|
102
|
-
if cb is not None:
|
|
103
|
-
try:
|
|
104
|
-
cb(coords)
|
|
105
|
-
except Exception:
|
|
106
|
-
diagnostics.swallowed("location._ios_get._PNLocationDelegate._finish")
|
|
107
|
-
|
|
108
|
-
@objc_method
|
|
109
|
-
def locationManager_didUpdateLocations_(self, manager: Any, locations: Any) -> None:
|
|
110
|
-
del manager
|
|
111
|
-
try:
|
|
112
|
-
last = locations.lastObject
|
|
113
|
-
if last is None:
|
|
114
|
-
return
|
|
115
|
-
coord = last.coordinate
|
|
116
|
-
self._finish((float(coord.latitude), float(coord.longitude)))
|
|
117
|
-
except Exception:
|
|
118
|
-
self._finish(None)
|
|
119
|
-
|
|
120
|
-
@objc_method
|
|
121
|
-
def locationManager_didFailWithError_(self, manager: Any, error: Any) -> None:
|
|
122
|
-
del manager, error
|
|
123
|
-
self._finish(None)
|
|
124
|
-
|
|
125
|
-
@objc_method
|
|
126
|
-
def locationManagerDidChangeAuthorization_(self, manager: Any) -> None:
|
|
127
|
-
try:
|
|
128
|
-
status = int(manager.authorizationStatus)
|
|
129
|
-
except Exception:
|
|
130
|
-
status = 0
|
|
131
|
-
if status in (3, 4): # AuthorizedAlways, AuthorizedWhenInUse
|
|
132
|
-
try:
|
|
133
|
-
manager.startUpdatingLocation()
|
|
134
|
-
except Exception:
|
|
135
|
-
diagnostics.swallowed(
|
|
136
|
-
"location._ios_get._PNLocationDelegate.locationManagerDidChangeAuthorization_"
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
delegate = _PNLocationDelegate.new()
|
|
140
|
-
delegate._callback = on_result
|
|
141
|
-
_pending_delegates[id(delegate)] = delegate
|
|
142
|
-
|
|
143
|
-
manager = CLLocationManager.alloc().init()
|
|
144
|
-
manager.setDelegate_(delegate)
|
|
145
|
-
delegate._manager = manager
|
|
146
|
-
try:
|
|
147
|
-
manager.requestWhenInUseAuthorization()
|
|
148
|
-
except Exception:
|
|
149
|
-
diagnostics.swallowed("location._ios_get")
|
|
150
|
-
try:
|
|
151
|
-
manager.startUpdatingLocation()
|
|
152
|
-
except Exception:
|
|
153
|
-
diagnostics.swallowed("location._ios_get")
|
|
154
|
-
except Exception:
|
|
155
|
-
on_result(None)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# ======================================================================
|
|
159
|
-
# Android implementation: LocationListener
|
|
160
|
-
# ======================================================================
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def _android_get(on_result: Callable[[Optional[Coords]], None]) -> None:
|
|
164
|
-
try:
|
|
165
|
-
from java import dynamic_proxy, jclass
|
|
166
|
-
|
|
167
|
-
from ..utils import get_android_context
|
|
168
|
-
|
|
169
|
-
ctx = get_android_context()
|
|
170
|
-
Context = jclass("android.content.Context")
|
|
171
|
-
lm = ctx.getSystemService(Context.LOCATION_SERVICE)
|
|
57
|
+
@staticmethod
|
|
58
|
+
async def get_current_fix(**options: Any) -> Optional[Dict[str, float]]:
|
|
59
|
+
"""Like ``get_current`` but returns the full fix dict.
|
|
172
60
|
|
|
173
|
-
|
|
174
|
-
|
|
61
|
+
Keys: ``latitude``, ``longitude``, and when the platform reports
|
|
62
|
+
them ``accuracy`` (meters), ``altitude`` (meters), ``speed``
|
|
63
|
+
(m/s), ``heading`` (degrees), ``timestamp`` (Unix seconds).
|
|
64
|
+
"""
|
|
175
65
|
try:
|
|
176
|
-
|
|
177
|
-
loc = lm.getLastKnownLocation(provider)
|
|
178
|
-
if loc is not None:
|
|
179
|
-
on_result((float(loc.getLatitude()), float(loc.getLongitude())))
|
|
180
|
-
return
|
|
66
|
+
result = await native_module("Location").call_async("get_current", **options)
|
|
181
67
|
except Exception:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
delivered = [False]
|
|
186
|
-
|
|
187
|
-
def _finish(coords: Optional[Coords], listener: Any) -> None:
|
|
188
|
-
if delivered[0]:
|
|
189
|
-
return
|
|
190
|
-
delivered[0] = True
|
|
191
|
-
try:
|
|
192
|
-
lm.removeUpdates(listener)
|
|
193
|
-
except Exception:
|
|
194
|
-
diagnostics.swallowed("location._android_get._finish")
|
|
195
|
-
_pending_delegates.pop(id(listener), None)
|
|
196
|
-
try:
|
|
197
|
-
on_result(coords)
|
|
198
|
-
except Exception:
|
|
199
|
-
diagnostics.swallowed("location._android_get._finish")
|
|
200
|
-
|
|
201
|
-
class _PNLocationListener(dynamic_proxy(LocationListener)): # type: ignore[misc]
|
|
202
|
-
def onLocationChanged(self, loc: Any) -> None:
|
|
203
|
-
try:
|
|
204
|
-
coords = (float(loc.getLatitude()), float(loc.getLongitude()))
|
|
205
|
-
except Exception:
|
|
206
|
-
coords = None
|
|
207
|
-
_finish(coords, self)
|
|
208
|
-
|
|
209
|
-
def onStatusChanged(self, provider: Any, status: int, extras: Any) -> None:
|
|
210
|
-
del provider, status, extras
|
|
211
|
-
|
|
212
|
-
def onProviderEnabled(self, provider: Any) -> None:
|
|
213
|
-
del provider
|
|
214
|
-
|
|
215
|
-
def onProviderDisabled(self, provider: Any) -> None:
|
|
216
|
-
del provider
|
|
217
|
-
_finish(None, self)
|
|
218
|
-
|
|
219
|
-
listener = _PNLocationListener()
|
|
220
|
-
_pending_delegates[id(listener)] = listener
|
|
68
|
+
return None
|
|
69
|
+
if not isinstance(result, dict):
|
|
70
|
+
return None
|
|
221
71
|
try:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
except Exception:
|
|
229
|
-
on_result(None)
|
|
72
|
+
fix: Dict[str, float] = {k: float(v) for k, v in result.items() if v is not None}
|
|
73
|
+
fix["latitude"]
|
|
74
|
+
fix["longitude"]
|
|
75
|
+
except (KeyError, TypeError, ValueError):
|
|
76
|
+
return None
|
|
77
|
+
return fix
|
|
@@ -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)
|