pythonnative 0.36.0__py3-none-any.whl → 0.38.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 +14 -4
- pythonnative/_ios_log.py +1 -1
- pythonnative/alerts.py +24 -86
- 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 +96 -10
- pythonnative/components/_base.py +31 -0
- pythonnative/components/controls.py +19 -16
- pythonnative/components/layout.py +70 -11
- pythonnative/components/lists.py +6 -6
- pythonnative/gestures.py +40 -16
- 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 +49 -29
- pythonnative/native_modules/app_state.py +10 -6
- pythonnative/native_modules/battery.py +22 -87
- pythonnative/native_modules/biometrics.py +15 -130
- pythonnative/native_modules/camera.py +24 -296
- pythonnative/native_modules/clipboard.py +14 -111
- pythonnative/native_modules/desktop.py +322 -0
- pythonnative/native_modules/file_system.py +90 -187
- pythonnative/native_modules/haptics.py +29 -135
- pythonnative/native_modules/linking.py +29 -141
- pythonnative/native_modules/location.py +32 -186
- pythonnative/native_modules/net_info.py +22 -135
- pythonnative/native_modules/notifications.py +37 -265
- pythonnative/native_modules/permissions.py +78 -194
- pythonnative/native_modules/registry.py +513 -0
- pythonnative/native_modules/secure_store.py +23 -164
- pythonnative/native_modules/share.py +9 -114
- pythonnative/native_views/__init__.py +67 -86
- pythonnative/native_views/bridge_backend.py +264 -0
- pythonnative/navigation/__init__.py +2 -1
- pythonnative/navigation/hooks.py +46 -7
- pythonnative/navigation/state.py +30 -9
- pythonnative/platform.py +19 -15
- pythonnative/platform_metrics.py +3 -3
- pythonnative/project/android.py +23 -3
- pythonnative/project/builder.py +99 -48
- pythonnative/project/config.py +96 -26
- pythonnative/project/deps.py +703 -0
- pythonnative/project/doctor.py +53 -4
- pythonnative/project/plugins.py +366 -0
- pythonnative/project/runtime_assets.py +13 -14
- 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/style.py +94 -44
- pythonnative/templates/android_template/app/build.gradle +11 -11
- 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/build.gradle +7 -4
- pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +1 -1
- 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 +99 -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 +266 -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 +41 -48
- 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 +82 -10
- pythonnative/utils.py +26 -109
- pythonnative/virtual_rows.py +13 -23
- {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/METADATA +10 -12
- pythonnative-0.38.0.dist-info/RECORD +264 -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.38.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/top_level.txt +0 -0
pythonnative/hooks.py
CHANGED
|
@@ -67,6 +67,7 @@ from typing import (
|
|
|
67
67
|
|
|
68
68
|
from . import diagnostics
|
|
69
69
|
from .element import Element, Node
|
|
70
|
+
from .platform_metrics import SafeAreaInsets, WindowDimensions
|
|
70
71
|
from .scheduler import TransitionQueue, in_transition, run_in_transition, schedule_trigger
|
|
71
72
|
from .suspense import CoroDriver, Resource
|
|
72
73
|
|
|
@@ -1235,27 +1236,39 @@ def use_mutation(
|
|
|
1235
1236
|
from .runtime import run_async
|
|
1236
1237
|
|
|
1237
1238
|
state, set_state = use_state(lambda: MutationState[T]())
|
|
1239
|
+
# The trigger is identity-stable across renders (like ``set_state``)
|
|
1240
|
+
# so it can sit in effect deps or be passed to memoized children;
|
|
1241
|
+
# it always calls the latest ``mutator``.
|
|
1242
|
+
latest = use_ref(mutator)
|
|
1243
|
+
latest.current = mutator
|
|
1244
|
+
|
|
1245
|
+
def _make_mutate() -> Callable[..., MutationCall[T]]:
|
|
1246
|
+
def mutate(*args: Any, **kwargs: Any) -> MutationCall[T]:
|
|
1247
|
+
set_state(lambda s: replace(s, loading=True, error=None))
|
|
1248
|
+
fn = latest.current
|
|
1238
1249
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1250
|
+
async def _runner() -> T:
|
|
1251
|
+
try:
|
|
1252
|
+
data = await fn(*args, **kwargs)
|
|
1253
|
+
set_state(lambda s: replace(s, data=data, loading=False, error=None))
|
|
1254
|
+
return data
|
|
1255
|
+
except asyncio.CancelledError:
|
|
1256
|
+
set_state(lambda s: replace(s, loading=False))
|
|
1257
|
+
raise
|
|
1258
|
+
except BaseException as exc:
|
|
1259
|
+
failure = exc
|
|
1260
|
+
set_state(lambda s: replace(s, loading=False, error=failure))
|
|
1261
|
+
raise
|
|
1241
1262
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
data = await mutator(*args, **kwargs)
|
|
1245
|
-
set_state(lambda s: replace(s, data=data, loading=False, error=None))
|
|
1246
|
-
return data
|
|
1247
|
-
except asyncio.CancelledError:
|
|
1248
|
-
set_state(lambda s: replace(s, loading=False))
|
|
1249
|
-
raise
|
|
1250
|
-
except BaseException as exc:
|
|
1251
|
-
failure = exc
|
|
1252
|
-
set_state(lambda s: replace(s, loading=False, error=failure))
|
|
1253
|
-
raise
|
|
1263
|
+
future = run_async(_runner())
|
|
1264
|
+
return MutationCall[T](future)
|
|
1254
1265
|
|
|
1255
|
-
|
|
1256
|
-
return MutationCall[T](future)
|
|
1266
|
+
return mutate
|
|
1257
1267
|
|
|
1258
|
-
|
|
1268
|
+
mutate_ref: Ref[Optional[Callable[..., MutationCall[T]]]] = use_ref(None)
|
|
1269
|
+
if mutate_ref.current is None:
|
|
1270
|
+
mutate_ref.current = _make_mutate()
|
|
1271
|
+
return state, mutate_ref.current
|
|
1259
1272
|
|
|
1260
1273
|
|
|
1261
1274
|
# ======================================================================
|
|
@@ -1294,7 +1307,7 @@ def use_subscription(subscribe: Callable[[Callable[[], None]], Callable[[], None
|
|
|
1294
1307
|
return get_snapshot()
|
|
1295
1308
|
|
|
1296
1309
|
|
|
1297
|
-
def use_window_dimensions() ->
|
|
1310
|
+
def use_window_dimensions() -> WindowDimensions:
|
|
1298
1311
|
"""Return the current viewport size and re-render when it changes.
|
|
1299
1312
|
|
|
1300
1313
|
Equivalent to React Native's ``useWindowDimensions``. The values
|
|
@@ -1302,26 +1315,29 @@ def use_window_dimensions() -> Dict[str, float]:
|
|
|
1302
1315
|
size (initial layout, rotation, multitasking split-view).
|
|
1303
1316
|
|
|
1304
1317
|
Returns:
|
|
1305
|
-
A
|
|
1318
|
+
A [`WindowDimensions`][pythonnative.platform_metrics.WindowDimensions]
|
|
1319
|
+
named tuple with ``width`` and ``height`` floats in layout
|
|
1306
1320
|
units (pt on iOS, dp on Android). Both are ``0.0`` until the
|
|
1307
|
-
screen host has run its first layout pass.
|
|
1321
|
+
screen host has run its first layout pass. Being a tuple, it
|
|
1322
|
+
unpacks (``width, height = pn.use_window_dimensions()``) and
|
|
1323
|
+
compares by value.
|
|
1308
1324
|
|
|
1309
1325
|
Raises:
|
|
1310
1326
|
RuntimeError: If called outside a ``@component`` function.
|
|
1311
1327
|
"""
|
|
1312
1328
|
from . import platform_metrics
|
|
1313
1329
|
|
|
1314
|
-
|
|
1315
|
-
return {"width": dims.width, "height": dims.height}
|
|
1330
|
+
return use_subscription(platform_metrics.subscribe, platform_metrics.get_window_dimensions)
|
|
1316
1331
|
|
|
1317
1332
|
|
|
1318
|
-
def use_safe_area_insets() ->
|
|
1333
|
+
def use_safe_area_insets() -> SafeAreaInsets:
|
|
1319
1334
|
"""Return the current safe-area insets and re-render on change.
|
|
1320
1335
|
|
|
1321
1336
|
Mirrors ``react-native-safe-area-context``'s ``useSafeAreaInsets``.
|
|
1322
1337
|
|
|
1323
1338
|
Returns:
|
|
1324
|
-
A
|
|
1339
|
+
A [`SafeAreaInsets`][pythonnative.platform_metrics.SafeAreaInsets]
|
|
1340
|
+
named tuple with ``top``, ``left``, ``bottom``, and ``right``
|
|
1325
1341
|
floats in layout units (pt on iOS, dp on Android).
|
|
1326
1342
|
|
|
1327
1343
|
Raises:
|
|
@@ -1329,8 +1345,7 @@ def use_safe_area_insets() -> Dict[str, float]:
|
|
|
1329
1345
|
"""
|
|
1330
1346
|
from . import platform_metrics
|
|
1331
1347
|
|
|
1332
|
-
|
|
1333
|
-
return {"top": insets.top, "bottom": insets.bottom, "left": insets.left, "right": insets.right}
|
|
1348
|
+
return use_subscription(platform_metrics.subscribe, platform_metrics.get_safe_area_insets)
|
|
1334
1349
|
|
|
1335
1350
|
|
|
1336
1351
|
def use_keyboard_height() -> float:
|
pythonnative/hosts/__init__.py
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
"""Screen hosts: the bridge between a native screen and the reconciler.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
runtime ([`AndroidScreenHost`][pythonnative.hosts.android.AndroidScreenHost],
|
|
12
|
-
[`IOSScreenHost`][pythonnative.hosts.ios.IOSScreenHost],
|
|
13
|
-
[`DesktopScreenHost`][pythonnative.hosts.desktop.DesktopScreenHost]);
|
|
14
|
-
the headless base [`ScreenHost`][pythonnative.hosts.ScreenHost] is used
|
|
15
|
-
in unit tests with a fake backend.
|
|
3
|
+
On device the native runtime creates screens through the bridge
|
|
4
|
+
(``callback("host", screen_id, "create", {...})``), which lands in
|
|
5
|
+
[`NativeScreenHost`][pythonnative.hosts.native.NativeScreenHost]. Off device,
|
|
6
|
+
``pn preview`` creates a
|
|
7
|
+
[`DesktopScreenHost`][pythonnative.hosts.desktop.DesktopScreenHost]
|
|
8
|
+
through [`create_screen`][pythonnative.hosts.create_screen], and unit
|
|
9
|
+
tests use the headless base [`ScreenHost`][pythonnative.hosts.ScreenHost]
|
|
10
|
+
with a fake backend.
|
|
16
11
|
"""
|
|
17
12
|
|
|
18
13
|
from __future__ import annotations
|
|
@@ -27,18 +22,14 @@ __all__ = ["ScreenHost", "create_screen", "host_class", "import_component"]
|
|
|
27
22
|
|
|
28
23
|
def host_class() -> Type[ScreenHost]:
|
|
29
24
|
"""The host class for the current runtime."""
|
|
30
|
-
if IS_ANDROID:
|
|
31
|
-
from .
|
|
25
|
+
if IS_ANDROID or IS_IOS:
|
|
26
|
+
from .native import NativeScreenHost
|
|
32
27
|
|
|
33
|
-
return
|
|
28
|
+
return NativeScreenHost
|
|
34
29
|
if IS_DESKTOP:
|
|
35
30
|
from .desktop import DesktopScreenHost
|
|
36
31
|
|
|
37
32
|
return DesktopScreenHost
|
|
38
|
-
if IS_IOS:
|
|
39
|
-
from .ios import IOSScreenHost
|
|
40
|
-
|
|
41
|
-
return IOSScreenHost
|
|
42
33
|
return ScreenHost
|
|
43
34
|
|
|
44
35
|
|
|
@@ -49,8 +40,8 @@ def create_screen(component_path: str, native_instance: Any = None, args_json: O
|
|
|
49
40
|
component_path: ``"app.main"`` (the module's ``App`` is used) or a
|
|
50
41
|
dotted path like ``"app.main.RootScreen"``. Imported lazily
|
|
51
42
|
so the dev server can reload it.
|
|
52
|
-
native_instance: The platform object owning the screen
|
|
53
|
-
|
|
43
|
+
native_instance: The platform object owning the screen (the
|
|
44
|
+
integer screen id on device, ``DesktopApp`` in the preview).
|
|
54
45
|
args_json: Optional JSON launch arguments (pushed screens
|
|
55
46
|
receive their navigation history here).
|
|
56
47
|
|
|
@@ -64,20 +55,6 @@ def create_screen(component_path: str, native_instance: Any = None, args_json: O
|
|
|
64
55
|
return host
|
|
65
56
|
|
|
66
57
|
|
|
67
|
-
def drain_ios_scheduled_renders() -> None:
|
|
68
|
-
"""Drain deferred iOS renders (called by the Swift template on the main thread)."""
|
|
69
|
-
from .ios import drain_ios_scheduled_renders as drain
|
|
70
|
-
|
|
71
|
-
drain()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def forward_lifecycle(native_addr: int, event: str) -> None:
|
|
75
|
-
"""Forward a Swift view-controller lifecycle event to its host."""
|
|
76
|
-
from .ios import forward_lifecycle as forward
|
|
77
|
-
|
|
78
|
-
forward(native_addr, event)
|
|
79
|
-
|
|
80
|
-
|
|
81
58
|
def drain_desktop_scheduled_renders() -> None:
|
|
82
59
|
"""Drain deferred desktop renders (called by the preview's Tk loop)."""
|
|
83
60
|
from .desktop import drain_desktop_scheduled_renders as drain
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""On-device screen host, shared by iOS and Android.
|
|
2
|
+
|
|
3
|
+
One [`NativeScreenHost`][pythonnative.hosts.native.NativeScreenHost]
|
|
4
|
+
exists per native screen (``UIViewController`` / ``Fragment``). The
|
|
5
|
+
native side never holds Python objects: screens are addressed by an
|
|
6
|
+
integer id it assigns, lifecycle arrives through
|
|
7
|
+
``callback("host", screen_id, event, payload)`` (routed here by
|
|
8
|
+
[`dispatch_host_event`][pythonnative.hosts.native.dispatch_host_event]),
|
|
9
|
+
and everything the host needs from the platform goes out through the
|
|
10
|
+
``Host`` native module (``attach_root``, ``push``, ``pop``, ...). See
|
|
11
|
+
``docs/concepts/bridge.md`` for the payloads.
|
|
12
|
+
|
|
13
|
+
Because native reports viewport size, safe-area insets, keyboard
|
|
14
|
+
height, and color scheme *with* each lifecycle event, this module
|
|
15
|
+
never queries the platform; it only publishes what it is told.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import threading
|
|
22
|
+
from typing import Any, Dict, Optional, Sequence, Tuple
|
|
23
|
+
|
|
24
|
+
from .. import appearance, diagnostics, platform_metrics
|
|
25
|
+
from ..bridge import codec
|
|
26
|
+
from ..native_modules.registry import native_module
|
|
27
|
+
from .base import ScreenHost, flush_hosts, log_pn
|
|
28
|
+
|
|
29
|
+
__all__ = ["NativeScreenHost", "dispatch_host_event", "host_for_screen", "live_hosts"]
|
|
30
|
+
|
|
31
|
+
_HOSTS: Dict[int, "NativeScreenHost"] = {}
|
|
32
|
+
_SCHEDULED: Dict[int, ScreenHost] = {}
|
|
33
|
+
_flush_pending = False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def host_for_screen(screen_id: int) -> Optional["NativeScreenHost"]:
|
|
37
|
+
"""Return the host registered for ``screen_id`` (``None`` if unknown)."""
|
|
38
|
+
return _HOSTS.get(int(screen_id))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def live_hosts() -> Sequence["NativeScreenHost"]:
|
|
42
|
+
"""Every host currently registered, in creation order."""
|
|
43
|
+
return list(_HOSTS.values())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _host_module() -> Any:
|
|
47
|
+
return native_module("Host")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ======================================================================
|
|
51
|
+
# Deferred renders
|
|
52
|
+
# ======================================================================
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _flush_scheduled() -> None:
|
|
56
|
+
global _flush_pending
|
|
57
|
+
_flush_pending = False
|
|
58
|
+
hosts = list(_SCHEDULED.values())
|
|
59
|
+
_SCHEDULED.clear()
|
|
60
|
+
if hosts:
|
|
61
|
+
log_pn(f"render_scheduler: flushing {len(hosts)} host(s)")
|
|
62
|
+
flush_hosts(hosts)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _request_flush() -> None:
|
|
66
|
+
"""Queue one main-thread flush of every scheduled host."""
|
|
67
|
+
global _flush_pending
|
|
68
|
+
if _flush_pending:
|
|
69
|
+
return
|
|
70
|
+
_flush_pending = True
|
|
71
|
+
from ..bridge import post_to_main
|
|
72
|
+
|
|
73
|
+
post_to_main(_flush_scheduled)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ======================================================================
|
|
77
|
+
# Payload helpers
|
|
78
|
+
# ======================================================================
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _publish_metrics(payload: Any) -> Tuple[float, float]:
|
|
82
|
+
"""Publish viewport, insets, keyboard, and color scheme from a host payload.
|
|
83
|
+
|
|
84
|
+
Returns the ``(width, height)`` it found (``0, 0`` when absent).
|
|
85
|
+
"""
|
|
86
|
+
if not isinstance(payload, dict):
|
|
87
|
+
return (0.0, 0.0)
|
|
88
|
+
width = float(payload.get("width") or 0.0)
|
|
89
|
+
height = float(payload.get("height") or 0.0)
|
|
90
|
+
insets = payload.get("insets")
|
|
91
|
+
if isinstance(insets, dict):
|
|
92
|
+
platform_metrics.set_safe_area_insets(
|
|
93
|
+
float(insets.get("top") or 0.0),
|
|
94
|
+
float(insets.get("left") or 0.0),
|
|
95
|
+
float(insets.get("bottom") or 0.0),
|
|
96
|
+
float(insets.get("right") or 0.0),
|
|
97
|
+
)
|
|
98
|
+
if "keyboard_height" in payload:
|
|
99
|
+
platform_metrics.set_keyboard_height(float(payload.get("keyboard_height") or 0.0))
|
|
100
|
+
scheme = payload.get("color_scheme")
|
|
101
|
+
if scheme in ("light", "dark"):
|
|
102
|
+
appearance.set_system_color_scheme(str(scheme))
|
|
103
|
+
if width > 0 and height > 0:
|
|
104
|
+
platform_metrics.set_window_dimensions(width, height)
|
|
105
|
+
return (width, height)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ======================================================================
|
|
109
|
+
# Host
|
|
110
|
+
# ======================================================================
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class NativeScreenHost(ScreenHost):
|
|
114
|
+
"""Screen host addressed by an integer ``screen_id`` on the bridge."""
|
|
115
|
+
|
|
116
|
+
def __init__(self, screen_id: int, component_path: str, component: Any) -> None:
|
|
117
|
+
super().__init__(int(screen_id), component_path, component)
|
|
118
|
+
self.screen_id = int(screen_id)
|
|
119
|
+
self._pending_viewport: Optional[Tuple[float, float]] = None
|
|
120
|
+
_HOSTS[self.screen_id] = self
|
|
121
|
+
|
|
122
|
+
# -- lifecycle ------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
def on_destroy(self) -> None:
|
|
125
|
+
"""Forget the screen id, then tear down the tree."""
|
|
126
|
+
_HOSTS.pop(self.screen_id, None)
|
|
127
|
+
_SCHEDULED.pop(id(self), None)
|
|
128
|
+
super().on_destroy()
|
|
129
|
+
|
|
130
|
+
def apply_metrics(self, payload: Any) -> None:
|
|
131
|
+
"""Publish the metrics carried by a host event and resize the viewport."""
|
|
132
|
+
width, height = _publish_metrics(payload)
|
|
133
|
+
if width > 0 and height > 0:
|
|
134
|
+
self._pending_viewport = (width, height)
|
|
135
|
+
self.set_viewport_size(width, height)
|
|
136
|
+
|
|
137
|
+
# -- platform primitives -------------------------------------------
|
|
138
|
+
|
|
139
|
+
def _initial_viewport_size(self) -> Optional[Tuple[float, float]]:
|
|
140
|
+
if self._pending_viewport is not None:
|
|
141
|
+
return self._pending_viewport
|
|
142
|
+
dims = platform_metrics.get_window_dimensions()
|
|
143
|
+
if dims.width > 0 and dims.height > 0:
|
|
144
|
+
return (dims.width, dims.height)
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
def _schedule_render_async(self) -> bool:
|
|
148
|
+
if self._render_scheduled:
|
|
149
|
+
return True
|
|
150
|
+
self._render_scheduled = True
|
|
151
|
+
_SCHEDULED[id(self)] = self
|
|
152
|
+
try:
|
|
153
|
+
_request_flush()
|
|
154
|
+
except Exception as exc:
|
|
155
|
+
self._render_scheduled = False
|
|
156
|
+
_SCHEDULED.pop(id(self), None)
|
|
157
|
+
log_pn(f"request_render: bridge defer failed ({exc!r}); rendering synchronously")
|
|
158
|
+
return False
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
def _attach_root(self, native_view: Any) -> None:
|
|
162
|
+
tag = getattr(native_view, "tag", None)
|
|
163
|
+
if tag is None:
|
|
164
|
+
return
|
|
165
|
+
# Native answers with the viewport the root now occupies, which
|
|
166
|
+
# is the earliest exact size available on iOS.
|
|
167
|
+
result = _host_module().call("attach_root", screen=self.screen_id, tag=int(tag))
|
|
168
|
+
if isinstance(result, dict):
|
|
169
|
+
self.apply_metrics(result)
|
|
170
|
+
|
|
171
|
+
def _detach_root(self, native_view: Any) -> None:
|
|
172
|
+
tag = getattr(native_view, "tag", None)
|
|
173
|
+
if tag is None:
|
|
174
|
+
return
|
|
175
|
+
try:
|
|
176
|
+
_host_module().call("detach_root", screen=self.screen_id, tag=int(tag))
|
|
177
|
+
except Exception:
|
|
178
|
+
diagnostics.swallowed("hosts.native.detach_root")
|
|
179
|
+
|
|
180
|
+
def _native_push(self, component_path: str, args: Dict[str, Any], options: Dict[str, Any]) -> None:
|
|
181
|
+
_host_module().call(
|
|
182
|
+
"push",
|
|
183
|
+
screen=self.screen_id,
|
|
184
|
+
path=component_path,
|
|
185
|
+
args=json.dumps(args) if args else None,
|
|
186
|
+
options=codec.to_jsonable(options or {}),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def _native_pop(self, count: int) -> None:
|
|
190
|
+
_host_module().call("pop", screen=self.screen_id, count=int(count))
|
|
191
|
+
|
|
192
|
+
def _native_pop_to_root(self) -> None:
|
|
193
|
+
_host_module().call("pop_to_root", screen=self.screen_id)
|
|
194
|
+
|
|
195
|
+
def _native_replace(self, component_path: str, args: Dict[str, Any], options: Dict[str, Any]) -> None:
|
|
196
|
+
_host_module().call(
|
|
197
|
+
"replace",
|
|
198
|
+
screen=self.screen_id,
|
|
199
|
+
path=component_path,
|
|
200
|
+
args=json.dumps(args) if args else None,
|
|
201
|
+
options=codec.to_jsonable(options or {}),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def _native_reset(self, component_path: str, screens: Sequence[Tuple[Dict[str, Any], Dict[str, Any]]]) -> None:
|
|
205
|
+
_host_module().call(
|
|
206
|
+
"reset",
|
|
207
|
+
screen=self.screen_id,
|
|
208
|
+
path=component_path,
|
|
209
|
+
screens=[
|
|
210
|
+
{
|
|
211
|
+
"path": component_path,
|
|
212
|
+
"args": json.dumps(args) if args else None,
|
|
213
|
+
"options": codec.to_jsonable(options or {}),
|
|
214
|
+
}
|
|
215
|
+
for args, options in screens
|
|
216
|
+
],
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def _native_set_options(self, options: Dict[str, Any]) -> None:
|
|
220
|
+
_host_module().call("set_options", screen=self.screen_id, options=codec.to_jsonable(options or {}))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ======================================================================
|
|
224
|
+
# Inbound host events
|
|
225
|
+
# ======================================================================
|
|
226
|
+
|
|
227
|
+
_create_lock = threading.Lock()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def dispatch_host_event(screen_id: int, event: str, payload: Any) -> Optional[str]:
|
|
231
|
+
"""Route ``callback("host", screen_id, event, payload)`` to a host.
|
|
232
|
+
|
|
233
|
+
Events (payloads are what ``PNViewController`` and
|
|
234
|
+
``PNScreenFragment`` send):
|
|
235
|
+
|
|
236
|
+
- ``create`` ``{"path", "args", "dev_root", "restored_state",
|
|
237
|
+
...metrics}``: import the component, create the host, and mount.
|
|
238
|
+
``path`` is the dotted component path (``None`` means the app
|
|
239
|
+
entry module), ``args`` a JSON string, ``dev_root`` the hot-reload
|
|
240
|
+
overlay directory for debug builds. Returns ``{"root": tag}``.
|
|
241
|
+
- ``start`` / ``resume`` / ``pause`` / ``stop`` / ``destroy``:
|
|
242
|
+
lifecycle. ``resume`` and ``layout`` carry metrics.
|
|
243
|
+
- ``layout`` ``{width, height, insets, keyboard_height, color_scheme}``.
|
|
244
|
+
- ``appearance`` ``{"color_scheme"}``.
|
|
245
|
+
- ``back_pressed``: returns ``"true"`` when a handler consumed it.
|
|
246
|
+
- ``hot_reload_tick``: poll the reload manifest; returns ``"true"``
|
|
247
|
+
when a reload ran.
|
|
248
|
+
- ``save_state`` / ``restore_state``: instance-state hooks.
|
|
249
|
+
- ``flush``: run deferred renders now.
|
|
250
|
+
|
|
251
|
+
Returns a JSON string for request-style events, else ``None``.
|
|
252
|
+
"""
|
|
253
|
+
screen_id = int(screen_id)
|
|
254
|
+
if event == "create":
|
|
255
|
+
return _create(screen_id, payload if isinstance(payload, dict) else {})
|
|
256
|
+
if event == "flush":
|
|
257
|
+
_flush_scheduled()
|
|
258
|
+
return None
|
|
259
|
+
if event == "appearance":
|
|
260
|
+
_publish_metrics(payload)
|
|
261
|
+
return None
|
|
262
|
+
host = _HOSTS.get(screen_id)
|
|
263
|
+
if host is None:
|
|
264
|
+
log_pn(f"dispatch_host_event: no host for screen={screen_id} event={event!r}")
|
|
265
|
+
return None
|
|
266
|
+
if event == "layout":
|
|
267
|
+
host.apply_metrics(payload)
|
|
268
|
+
host.on_layout()
|
|
269
|
+
return None
|
|
270
|
+
if event == "resume":
|
|
271
|
+
host.apply_metrics(payload)
|
|
272
|
+
host.on_resume()
|
|
273
|
+
return None
|
|
274
|
+
if event == "back_pressed":
|
|
275
|
+
return "true" if host.on_back_pressed() else "false"
|
|
276
|
+
if event in ("hot_reload_tick", "tick"):
|
|
277
|
+
return "true" if host.hot_reload_tick() else "false"
|
|
278
|
+
if event == "save_state":
|
|
279
|
+
host.on_save_instance_state()
|
|
280
|
+
return None
|
|
281
|
+
if event == "restore_state":
|
|
282
|
+
host.on_restore_instance_state()
|
|
283
|
+
return None
|
|
284
|
+
if event == "destroy":
|
|
285
|
+
host.on_destroy()
|
|
286
|
+
return None
|
|
287
|
+
handler = getattr(host, f"on_{event}", None)
|
|
288
|
+
if handler is None:
|
|
289
|
+
log_pn(f"dispatch_host_event: unknown event {event!r}")
|
|
290
|
+
return None
|
|
291
|
+
handler()
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _create(screen_id: int, payload: Dict[str, Any]) -> Optional[str]:
|
|
296
|
+
from . import import_component
|
|
297
|
+
|
|
298
|
+
component_path = str(payload.get("path") or payload.get("component") or _entry_module())
|
|
299
|
+
with _create_lock:
|
|
300
|
+
existing = _HOSTS.get(screen_id)
|
|
301
|
+
if existing is not None:
|
|
302
|
+
existing.apply_metrics(payload)
|
|
303
|
+
existing.on_create()
|
|
304
|
+
return _root_json(existing)
|
|
305
|
+
component = import_component(component_path)
|
|
306
|
+
host = NativeScreenHost(screen_id, component_path, component)
|
|
307
|
+
args = payload.get("args")
|
|
308
|
+
if args:
|
|
309
|
+
host.set_args(args)
|
|
310
|
+
dev_root = payload.get("dev_root")
|
|
311
|
+
if dev_root:
|
|
312
|
+
from .. import hot_reload
|
|
313
|
+
|
|
314
|
+
host.enable_hot_reload(hot_reload.manifest_path_for(str(dev_root)), str(dev_root))
|
|
315
|
+
if not (payload.get("width") and payload.get("height")):
|
|
316
|
+
# ``create`` is sent before the first layout pass; ask native for
|
|
317
|
+
# its best guess so the first commit lays out at the right size.
|
|
318
|
+
try:
|
|
319
|
+
viewport = _host_module().call("viewport", screen=screen_id)
|
|
320
|
+
except Exception:
|
|
321
|
+
viewport = None
|
|
322
|
+
if isinstance(viewport, dict):
|
|
323
|
+
payload = {**viewport, **payload}
|
|
324
|
+
_publish_metrics(payload)
|
|
325
|
+
width = float(payload.get("width") or 0.0)
|
|
326
|
+
height = float(payload.get("height") or 0.0)
|
|
327
|
+
if width > 0 and height > 0:
|
|
328
|
+
host._pending_viewport = (width, height)
|
|
329
|
+
host.on_create()
|
|
330
|
+
return _root_json(host)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _entry_module() -> str:
|
|
334
|
+
import os
|
|
335
|
+
|
|
336
|
+
return os.environ.get("PN_ENTRY_MODULE") or "app.main"
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _root_json(host: ScreenHost) -> str:
|
|
340
|
+
tag = getattr(host.root_native_view, "tag", None)
|
|
341
|
+
return codec.dumps({"root": None if tag is None else int(tag)})
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _reset_for_tests() -> None:
|
|
345
|
+
global _flush_pending
|
|
346
|
+
_HOSTS.clear()
|
|
347
|
+
_SCHEDULED.clear()
|
|
348
|
+
_flush_pending = False
|
pythonnative/images.py
CHANGED
|
@@ -38,13 +38,14 @@ def _default_cache_dir() -> str:
|
|
|
38
38
|
backups). Desktop: a per-user directory under the system temp dir.
|
|
39
39
|
"""
|
|
40
40
|
try:
|
|
41
|
-
from .utils import IS_ANDROID
|
|
41
|
+
from .utils import IS_ANDROID, IS_IOS
|
|
42
42
|
|
|
43
|
-
if IS_ANDROID:
|
|
44
|
-
from .
|
|
43
|
+
if IS_ANDROID or IS_IOS:
|
|
44
|
+
from .native_modules.registry import native_module
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
info = native_module("Device").call("info")
|
|
47
|
+
if isinstance(info, dict) and info.get("cache_dir"):
|
|
48
|
+
return os.path.join(str(info["cache_dir"]), "pn_images")
|
|
48
49
|
except Exception:
|
|
49
50
|
pass
|
|
50
51
|
home = os.path.expanduser("~")
|
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
"""Native API modules for device capabilities.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
Cross-platform Python interfaces to common device APIs. Every module
|
|
4
|
+
here is a thin facade over a *native module*: a Swift class in
|
|
5
|
+
``PythonNativeKit`` and a Kotlin class in the ``pythonnative`` Gradle
|
|
6
|
+
module registered under the same name (``"Camera"``, ``"Haptics"``,
|
|
7
|
+
...). Facades reach them through
|
|
8
|
+
[`native_module`][pythonnative.native_modules.registry.native_module]
|
|
9
|
+
and the bridge described in ``docs/concepts/bridge.md``; there is no
|
|
10
|
+
Python-side Objective-C or JNI anywhere in this package.
|
|
11
|
+
|
|
12
|
+
On a desktop machine (``pn preview``, unit tests) the same names
|
|
13
|
+
resolve to plain Python implementations in
|
|
14
|
+
[`pythonnative.native_modules.desktop`][pythonnative.native_modules.desktop]
|
|
15
|
+
with safe defaults (in-memory buffers, ``"unknown"`` states, no-op
|
|
16
|
+
feedback), so the same code stays runnable off device. Third-party
|
|
17
|
+
packages ship their own native modules the same way; see
|
|
18
|
+
``docs/guides/native-modules.md``.
|
|
19
|
+
|
|
20
|
+
Two rules hold across every facade, so you never have to look one up:
|
|
21
|
+
|
|
22
|
+
1. **Sync or async is decided by what the OS has to do.** A method is
|
|
23
|
+
a plain function when the answer is already on the device and
|
|
24
|
+
returns on the calling thread (read the pasteboard, check a
|
|
25
|
+
permission, read the battery level, Keychain get/set, file I/O).
|
|
26
|
+
It is a coroutine when the OS has to prompt the user, drive
|
|
27
|
+
hardware, or hand off to another process (camera and gallery
|
|
28
|
+
pickers, biometric prompts, permission requests, the share sheet,
|
|
29
|
+
GPS fixes, notification scheduling, remote push registration).
|
|
30
|
+
2. **Failures raise; "nothing happened" returns a value.** A native
|
|
31
|
+
error (a rejected promise, a missing module, a bad argument) is a
|
|
32
|
+
[`NativeModuleError`][pythonnative.native_modules.NativeModuleError]
|
|
33
|
+
and propagates to the caller like any other Python exception. Outcomes
|
|
34
|
+
that are not errors, such as the user cancelling a picker, denying a
|
|
35
|
+
permission, or dismissing the share sheet, come back as ``None``,
|
|
36
|
+
``False``, or a status string, and are documented per method. No
|
|
37
|
+
facade converts an exception into a default value.
|
|
9
38
|
|
|
10
39
|
Hardware / media:
|
|
11
40
|
|
|
@@ -43,8 +72,6 @@ Reactive state (with hooks):
|
|
|
43
72
|
[`use_net_info`][pythonnative.use_net_info]: connectivity.
|
|
44
73
|
"""
|
|
45
74
|
|
|
46
|
-
from typing import Any, Sequence
|
|
47
|
-
|
|
48
75
|
from .app_state import AppState, use_app_state
|
|
49
76
|
from .battery import Battery
|
|
50
77
|
from .biometrics import Biometrics
|
|
@@ -57,46 +84,39 @@ from .location import Location
|
|
|
57
84
|
from .net_info import NetInfo, use_net_info
|
|
58
85
|
from .notifications import Notifications
|
|
59
86
|
from .permissions import Permissions
|
|
87
|
+
from .registry import (
|
|
88
|
+
BridgeModule,
|
|
89
|
+
NativeModule,
|
|
90
|
+
NativeModuleError,
|
|
91
|
+
PythonModule,
|
|
92
|
+
native_module,
|
|
93
|
+
register_python_module,
|
|
94
|
+
)
|
|
60
95
|
from .secure_store import SecureStore
|
|
61
96
|
from .share import Share
|
|
62
97
|
|
|
63
|
-
|
|
64
|
-
def dispatch_activity_result(request_code: int, result_code: int, data: Any) -> bool:
|
|
65
|
-
"""Route an Android ``onActivityResult`` to whichever module is waiting on it.
|
|
66
|
-
|
|
67
|
-
Called by the screen host. Returns ``True`` when a pending request
|
|
68
|
-
consumed the result.
|
|
69
|
-
"""
|
|
70
|
-
from .camera import deliver_android_activity_result
|
|
71
|
-
|
|
72
|
-
return deliver_android_activity_result(request_code, result_code, data)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def dispatch_permissions_result(request_code: int, permissions: Sequence[str], grant_results: Sequence[int]) -> bool:
|
|
76
|
-
"""Route an Android ``onRequestPermissionsResult`` to the pending ``Permissions.request``."""
|
|
77
|
-
from .permissions import deliver_android_permission_result
|
|
78
|
-
|
|
79
|
-
return deliver_android_permission_result(request_code, permissions, grant_results)
|
|
80
|
-
|
|
81
|
-
|
|
82
98
|
__all__ = [
|
|
83
99
|
"AppState",
|
|
84
100
|
"Battery",
|
|
85
101
|
"Biometrics",
|
|
102
|
+
"BridgeModule",
|
|
86
103
|
"Camera",
|
|
87
104
|
"Clipboard",
|
|
88
105
|
"FileSystem",
|
|
89
106
|
"Haptics",
|
|
90
107
|
"Linking",
|
|
91
108
|
"Location",
|
|
109
|
+
"NativeModule",
|
|
110
|
+
"NativeModuleError",
|
|
92
111
|
"NetInfo",
|
|
93
112
|
"Notifications",
|
|
94
113
|
"Permissions",
|
|
114
|
+
"PythonModule",
|
|
95
115
|
"SecureStore",
|
|
96
116
|
"Share",
|
|
97
117
|
"Vibration",
|
|
98
|
-
"
|
|
99
|
-
"
|
|
118
|
+
"native_module",
|
|
119
|
+
"register_python_module",
|
|
100
120
|
"use_app_state",
|
|
101
121
|
"use_net_info",
|
|
102
122
|
]
|