pythonnative 0.18.0__py3-none-any.whl → 0.20.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/screen.py CHANGED
@@ -50,7 +50,7 @@ import sys
50
50
  import threading
51
51
  from typing import Any, Dict, Optional, Sequence
52
52
 
53
- from .utils import IS_ANDROID, IS_IOS, set_android_context
53
+ from .utils import IS_ANDROID, IS_DESKTOP, IS_IOS, set_android_context
54
54
 
55
55
  _MAX_RENDER_PASSES = 25
56
56
  _DEBUG_ENV = "PYTHONNATIVE_DEBUG"
@@ -87,6 +87,20 @@ def _resolve_component_path(component_ref: Any) -> str:
87
87
  raise ValueError(f"Cannot resolve component path for {component_ref!r}")
88
88
 
89
89
 
90
+ def _missing_module_is_target(exc: ModuleNotFoundError, dotted: str) -> bool:
91
+ """Return ``True`` when ``exc`` means ``dotted`` itself is absent.
92
+
93
+ Distinguishes "the component module/package cannot be found" (so the
94
+ caller should fall through to the next resolution strategy) from "the
95
+ module exists but raised :class:`ModuleNotFoundError` while importing
96
+ one of *its own* dependencies". The latter must propagate so the
97
+ developer sees the real missing import (e.g. ``No module named
98
+ 'emoji'``) instead of a misleading "could not resolve component".
99
+ """
100
+ missing = exc.name or ""
101
+ return missing == dotted or dotted.startswith(missing + ".")
102
+
103
+
90
104
  def _import_component(component_path: str) -> Any:
91
105
  """Import a component by module or dotted-attribute path.
92
106
 
@@ -117,11 +131,16 @@ def _import_component(component_path: str) -> Any:
117
131
  The resolved component callable.
118
132
 
119
133
  Raises:
120
- ImportError: If neither resolution path succeeds.
134
+ ImportError: If the module or dotted path cannot be found.
135
+ Errors raised *inside* a resolvable module (such as a
136
+ missing third-party dependency it imports) propagate
137
+ unchanged so the real cause stays visible.
121
138
  """
122
139
  try:
123
140
  module = importlib.import_module(component_path)
124
- except ModuleNotFoundError:
141
+ except ModuleNotFoundError as exc:
142
+ if not _missing_module_is_target(exc, component_path):
143
+ raise
125
144
  module = None
126
145
  if module is not None:
127
146
  component = getattr(module, "App", None)
@@ -132,7 +151,9 @@ def _import_component(component_path: str) -> Any:
132
151
  module_path, attr = component_path.rsplit(".", 1)
133
152
  try:
134
153
  parent = importlib.import_module(module_path)
135
- except ModuleNotFoundError:
154
+ except ModuleNotFoundError as exc:
155
+ if not _missing_module_is_target(exc, module_path):
156
+ raise
136
157
  parent = None
137
158
  if parent is not None:
138
159
  component = getattr(parent, attr, None)
@@ -301,53 +322,49 @@ def _request_render(host: Any) -> None:
301
322
 
302
323
 
303
324
  def _re_render(host: Any) -> None:
304
- """Run one render pass, then drain any renders queued during it."""
305
- from .hooks import Provider, _NavigationContext
306
-
307
- _log_pn("_re_render: starting render pass")
325
+ """Run one *local* render pass, then drain any renders queued during it.
326
+
327
+ State setters mark only their own component subtree dirty (see
328
+ [`mark_dirty`][pythonnative.reconciler.Reconciler.mark_dirty]), so
329
+ this drains the reconciler's dirty set via
330
+ [`flush_dirty`][pythonnative.reconciler.Reconciler.flush_dirty]
331
+ instead of re-running the whole ``App`` from the root. The app's
332
+ element tree is only rebuilt from scratch on mount, navigation, and
333
+ hot reload.
334
+ """
335
+ _log_pn("_re_render: starting local render pass")
308
336
  host._is_rendering = True
309
337
  try:
310
338
  host._render_queued = False
311
-
312
- app_element = _render_app(host)
313
- provider_element = Provider(_NavigationContext, host._nav_handle, app_element)
314
-
315
- new_root = host._reconciler.reconcile(provider_element)
316
- if new_root is not host._root_native_view:
317
- _log_pn(f"_re_render: ROOT VIEW CHANGED ({id(host._root_native_view)} -> {id(new_root)}); reattaching")
318
- host._detach_root(host._root_native_view)
319
- host._root_native_view = new_root
320
- host._attach_root(new_root)
321
-
339
+ _commit_dirty(host)
322
340
  _drain_renders(host)
323
341
  finally:
324
342
  host._is_rendering = False
325
343
  _log_pn("_re_render: done")
326
344
 
327
345
 
346
+ def _commit_dirty(host: Any) -> None:
347
+ """Flush the reconciler's dirty components and re-attach the root if it changed."""
348
+ new_root = host._reconciler.flush_dirty()
349
+ if new_root is not host._root_native_view:
350
+ _log_pn(f"_commit_dirty: ROOT VIEW CHANGED ({id(host._root_native_view)} -> {id(new_root)}); reattaching")
351
+ host._detach_root(host._root_native_view)
352
+ host._root_native_view = new_root
353
+ host._attach_root(new_root)
354
+
355
+
328
356
  def _drain_renders(host: Any) -> None:
329
357
  """Flush additional renders queued by effects that set state.
330
358
 
331
359
  Capped at `_MAX_RENDER_PASSES` to break runaway feedback loops
332
360
  (e.g., an effect that unconditionally calls a setter).
333
361
  """
334
- from .hooks import Provider, _NavigationContext
335
-
336
362
  for i in range(_MAX_RENDER_PASSES):
337
363
  if not host._render_queued:
338
364
  break
339
365
  _log_pn(f"_drain_renders: draining pass #{i + 1}")
340
366
  host._render_queued = False
341
-
342
- app_element = _render_app(host)
343
- provider_element = Provider(_NavigationContext, host._nav_handle, app_element)
344
-
345
- new_root = host._reconciler.reconcile(provider_element)
346
- if new_root is not host._root_native_view:
347
- _log_pn(f"_drain_renders: ROOT VIEW CHANGED ({id(host._root_native_view)} -> {id(new_root)}); reattaching")
348
- host._detach_root(host._root_native_view)
349
- host._root_native_view = new_root
350
- host._attach_root(new_root)
367
+ _commit_dirty(host)
351
368
 
352
369
 
353
370
  def _set_args(host: Any, args: Any) -> None:
@@ -868,6 +885,165 @@ if IS_ANDROID:
868
885
  """Public hook for native code to push viewport sizes (Maestro/tests)."""
869
886
  _push_viewport_size(self, width, height)
870
887
 
888
+ elif IS_DESKTOP:
889
+ # ------------------------------------------------------------------
890
+ # Desktop preview host (Tkinter), driven by ``pn preview``.
891
+ #
892
+ # The screen host owns the reconciler + lifecycle just like the
893
+ # device hosts; placement of the root view and the navigation stack
894
+ # are delegated to the ``DesktopApp`` controller in
895
+ # ``pythonnative.preview`` (passed in as ``native_instance``). The
896
+ # controller runs the Tk event loop on the main thread and polls
897
+ # ``drain_desktop_scheduled_renders`` so renders requested from the
898
+ # asyncio worker thread are applied on the main thread.
899
+ # ------------------------------------------------------------------
900
+
901
+ _DESKTOP_SCHEDULED_RENDER_HOSTS: Dict[int, Any] = {}
902
+ _desktop_render_lock = threading.Lock()
903
+
904
+ def _schedule_render_async(host: Any) -> bool:
905
+ """Queue an off-main-thread render for the Tk poll loop to drain.
906
+
907
+ Renders requested on the Tk main thread (button handlers, etc.)
908
+ run synchronously (returns ``False``); requests from the asyncio
909
+ worker thread are queued and applied by
910
+ [`drain_desktop_scheduled_renders`][pythonnative.screen.drain_desktop_scheduled_renders].
911
+ """
912
+ if not IS_DESKTOP:
913
+ return False
914
+ if threading.current_thread() is threading.main_thread():
915
+ return False
916
+ if getattr(host, "_render_scheduled", False):
917
+ return True
918
+ host._render_scheduled = True
919
+ with _desktop_render_lock:
920
+ _DESKTOP_SCHEDULED_RENDER_HOSTS[id(host)] = host
921
+ return True
922
+
923
+ def drain_desktop_scheduled_renders() -> None:
924
+ """Apply renders queued from worker threads (called on the main thread)."""
925
+ with _desktop_render_lock:
926
+ hosts = list(_DESKTOP_SCHEDULED_RENDER_HOSTS.values())
927
+ _DESKTOP_SCHEDULED_RENDER_HOSTS.clear()
928
+ _flush_scheduled_renders(hosts)
929
+
930
+ class _ScreenHost:
931
+ """Desktop host backed by a Tk window and an in-process nav stack.
932
+
933
+ Created by ``pythonnative.preview`` for
934
+ each screen on the navigation stack. ``native_instance`` is the
935
+ ``DesktopApp`` controller, which provides the stage frame,
936
+ viewport size, and push/pop primitives.
937
+ """
938
+
939
+ def __init__(self, native_instance: Any = None, component_path: str = "", component_func: Any = None) -> None:
940
+ self.native_instance = native_instance
941
+ _init_host_common(self, component_path, component_func)
942
+
943
+ def on_create(self) -> None:
944
+ _on_create(self)
945
+
946
+ def on_start(self) -> None:
947
+ pass
948
+
949
+ def on_resume(self) -> None:
950
+ _set_host_focused(self, True)
951
+
952
+ def on_layout(self) -> None:
953
+ pass
954
+
955
+ def on_pause(self) -> None:
956
+ _set_host_focused(self, False)
957
+
958
+ def on_stop(self) -> None:
959
+ pass
960
+
961
+ def on_destroy(self) -> None:
962
+ pass
963
+
964
+ def enable_hot_reload(self, manifest_path: str, source_root: Optional[str] = None) -> None:
965
+ _enable_hot_reload(self, manifest_path)
966
+
967
+ def hot_reload_tick(self) -> bool:
968
+ return _hot_reload_tick(self)
969
+
970
+ def reload(self, changed_modules: Optional[Sequence[str]] = None) -> None:
971
+ _reload_host(self, changed_modules)
972
+
973
+ def on_restart(self) -> None:
974
+ pass
975
+
976
+ def on_save_instance_state(self) -> None:
977
+ pass
978
+
979
+ def on_restore_instance_state(self) -> None:
980
+ pass
981
+
982
+ def set_args(self, args: Any) -> None:
983
+ _set_args(self, args)
984
+
985
+ def _get_nav_args(self) -> Dict[str, Any]:
986
+ return self._args
987
+
988
+ def _push(self, component: Any, args: Optional[Dict[str, Any]] = None) -> None:
989
+ screen_path = _resolve_component_path(component)
990
+ app = self.native_instance
991
+ if app is None or not hasattr(app, "push_screen"):
992
+ raise RuntimeError("desktop navigation requires a running `pn preview` session")
993
+ app.push_screen(screen_path, args)
994
+
995
+ def _pop(self) -> None:
996
+ app = self.native_instance
997
+ if app is not None and hasattr(app, "pop_screen"):
998
+ app.pop_screen()
999
+
1000
+ def _reset_to_root(self) -> None:
1001
+ app = self.native_instance
1002
+ if app is not None and hasattr(app, "reset_to_root"):
1003
+ try:
1004
+ app.reset_to_root()
1005
+ except Exception:
1006
+ pass
1007
+
1008
+ def _set_screen_options(self, options: Dict[str, Any]) -> None:
1009
+ title = options.get("title") if isinstance(options, dict) else None
1010
+ app = self.native_instance
1011
+ if title and app is not None and hasattr(app, "set_title"):
1012
+ try:
1013
+ app.set_title(str(title))
1014
+ except Exception:
1015
+ pass
1016
+
1017
+ def _attach_root(self, native_view: Any) -> None:
1018
+ from .native_views import desktop as _desktop_backend
1019
+
1020
+ stage = _desktop_backend.get_root_container()
1021
+ if stage is not None and native_view is not None:
1022
+ try:
1023
+ native_view.place(in_=stage, x=0, y=0, relwidth=1.0, relheight=1.0)
1024
+ native_view.lift()
1025
+ except Exception:
1026
+ pass
1027
+ app = self.native_instance
1028
+ if app is not None and hasattr(app, "viewport_size"):
1029
+ try:
1030
+ width, height = app.viewport_size()
1031
+ if width > 0 and height > 0:
1032
+ _push_viewport_size(self, float(width), float(height))
1033
+ except Exception:
1034
+ pass
1035
+
1036
+ def _detach_root(self, native_view: Any) -> None:
1037
+ if native_view is not None:
1038
+ try:
1039
+ native_view.place_forget()
1040
+ except Exception:
1041
+ pass
1042
+
1043
+ def set_viewport_size(self, width: float, height: float) -> None:
1044
+ """Push a viewport-size change (called on window resize)."""
1045
+ _push_viewport_size(self, width, height)
1046
+
871
1047
  else:
872
1048
  from typing import Dict as _Dict
873
1049
 
pythonnative/utils.py CHANGED
@@ -19,6 +19,10 @@ Attributes:
19
19
  `PN_PLATFORM=ios`, `sys.platform == "ios"`, or a Simulator
20
20
  `HOME` path). Importing `rubicon-objc` alone is intentionally
21
21
  not enough to trigger this flag.
22
+ IS_DESKTOP: `True` when running the desktop preview backend
23
+ (signaled by `PN_PLATFORM=desktop`, set by ``pn preview``).
24
+ This drives the Tkinter native-view registry so a PythonNative
25
+ app can render in a real OS window for fast local iteration.
22
26
  """
23
27
 
24
28
  import os
@@ -31,6 +35,7 @@ from typing import Any, Optional
31
35
 
32
36
  _is_android: Optional[bool] = None
33
37
  _is_ios: Optional[bool] = None
38
+ _is_desktop: Optional[bool] = None
34
39
 
35
40
 
36
41
  def _detect_android() -> bool:
@@ -75,13 +80,29 @@ def _detect_ios() -> bool:
75
80
  return False
76
81
 
77
82
 
83
+ def _detect_desktop() -> bool:
84
+ """Detect whether we're running the desktop (Tkinter) preview backend.
85
+
86
+ The only signal is the explicit ``PN_PLATFORM=desktop`` env var,
87
+ set by ``pn preview`` before importing PythonNative. Desktop is a
88
+ *development* target: it renders the app in a native OS window via
89
+ the pure-Python Tkinter registry so the inner dev loop doesn't
90
+ require a device build. Off-device unit tests deliberately leave
91
+ this flag ``False`` so they keep using an injected mock registry
92
+ and ``Platform.OS == "test"``.
93
+ """
94
+ return os.environ.get("PN_PLATFORM") == "desktop"
95
+
96
+
78
97
  def _ensure_platform_detection() -> None:
79
- """Populate `_is_android` / `_is_ios` once, then reuse."""
80
- global _is_android, _is_ios
98
+ """Populate `_is_android` / `_is_ios` / `_is_desktop` once, then reuse."""
99
+ global _is_android, _is_ios, _is_desktop
81
100
  if _is_android is None:
82
101
  _is_android = _detect_android()
83
102
  if _is_ios is None:
84
103
  _is_ios = (not _is_android) and _detect_ios()
104
+ if _is_desktop is None:
105
+ _is_desktop = (not _is_android) and (not _is_ios) and _detect_desktop()
85
106
 
86
107
 
87
108
  def _get_is_android() -> bool:
@@ -98,6 +119,13 @@ def _get_is_ios() -> bool:
98
119
  return _is_ios
99
120
 
100
121
 
122
+ def _get_is_desktop() -> bool:
123
+ """Return the cached desktop-detection result."""
124
+ _ensure_platform_detection()
125
+ assert _is_desktop is not None
126
+ return _is_desktop
127
+
128
+
101
129
  IS_ANDROID: bool = _get_is_android()
102
130
  """``True`` when running inside an Android process.
103
131
 
@@ -113,6 +141,14 @@ The flag is computed once at import time, by checking
113
141
  `HOME` path.
114
142
  """
115
143
 
144
+ IS_DESKTOP: bool = _get_is_desktop()
145
+ """``True`` when running the desktop (Tkinter) preview backend.
146
+
147
+ Set by ``pn preview`` via ``PN_PLATFORM=desktop``. Mutually exclusive
148
+ with `IS_ANDROID` / `IS_IOS`. Off-device unit tests leave this
149
+ ``False`` and inject a mock registry instead.
150
+ """
151
+
116
152
  # ======================================================================
117
153
  # Android context management
118
154
  # ======================================================================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.18.0
3
+ Version: 0.20.0
4
4
  Summary: Cross-platform native UI toolkit for Android and iOS
5
5
  Author: Owen Carey
6
6
  License: MIT License
@@ -102,10 +102,11 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app
102
102
  - **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect`, and navigation with `use_navigation`, all through one consistent pattern.
103
103
  - **Typed `style` prop:** Pass all visual and layout properties through a single `style` dict, fully described by the `pn.Style` `TypedDict` and the ergonomic `pn.style(...)` helper for IDE autocomplete and static checking. Compose reusable styles with `StyleSheet`.
104
104
  - **Cross-platform flexbox engine:** A pure-Python, Yoga-style layout engine computes frames once and applies them to native views, so `flex`, `padding`, `aspect_ratio`, and `position: "absolute"` produce the same geometry on Android and iOS.
105
- - **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation.
105
+ - **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation. State updates re-render **locally** — only the component whose state changed (and its subtree) re-runs, and unchanged leaves reuse cached intrinsic measurements — so deep UIs stay responsive instead of re-rendering the whole app from the root on every tap.
106
106
  - **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
107
107
  - **Custom-component SDK:** Wrap any platform widget as a first-class element with type-checked props via `pythonnative.sdk` (`Props`, `@native_component`, `element_factory`). Plugins distributed on PyPI auto-register through the `pythonnative.handlers` entry-point group.
108
108
  - **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
109
+ - **Instant desktop preview:** `pn preview` renders your app in a native desktop window via Tkinter with Fast Refresh on every save — iterate on layout, state, and navigation in milliseconds without booting a simulator or device. The reconciler, hooks, layout engine, and navigation are the same code that ships to the phone.
109
110
  - **Native-backed navigation:** Declarative `Stack`, `Tab`, and `Drawer` navigators inspired by React Navigation. The root stack drives the platform's native navigation controller (`UINavigationController` on iOS, AndroidX Navigation Component on Android), so transitions, back gestures, and the hardware back button match what users expect.
110
111
  - **Fast Refresh hot reload:** `pn run --hot-reload` watches `app/` and patches edits into the running app on save, preserving component state across most changes.
111
112
  - **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access.
@@ -1,24 +1,25 @@
1
- pythonnative/__init__.py,sha256=EI_-TSD1LQJDBMpGt94xZ6ht9htAM46FV1eOcxJK_ts,8042
1
+ pythonnative/__init__.py,sha256=tfRm60oMZNS2TXgluc8QfIi2uV3fHncDYVNB9ir8voY,8042
2
2
  pythonnative/_ios_log.py,sha256=Oi7V28VxcVoZyrpAirvLeEmUW18McqnU87V4d37Zzlw,2582
3
3
  pythonnative/alerts.py,sha256=mIANysFlaHwL5EqKnvNcyiJN9rGiZi9XDrD9Jpz1RFM,9340
4
4
  pythonnative/animated.py,sha256=bAgG_sGODAdl5eVQjX_vryaKI1hyjI92QH1PNx7Tqyg,24491
5
5
  pythonnative/components.py,sha256=7vkoMaKVTVKZNBut1UKET3xawnJzuIc-Zt7m__3w9X8,72503
6
6
  pythonnative/element.py,sha256=W9varJj0Cl9HpckL8BcsC1u4ryUQOPVMrvetro4ilAE,2725
7
- pythonnative/hooks.py,sha256=Zt1AiK5kOtHJIhk8_FetAKKt-8n21UDig1_DC-XcUow,37948
7
+ pythonnative/hooks.py,sha256=_XkoyK0aTqu3A7BiiXalN3R-qXTiOh1UbL_ZmUcIaDA,38996
8
8
  pythonnative/hot_reload.py,sha256=j7z2c7o2Hdoyd-p4nQY15LTW7CBH_1z0TSAzLCer-aA,25036
9
9
  pythonnative/layout.py,sha256=siU7PeVOjL_G1f-1q31ssrKWlxz2UBmvMwNXtmqyOxw,36586
10
10
  pythonnative/navigation.py,sha256=skMZFh3AXEJgTU6qQpATFN1Lp4GB94K_ACNNXZjEOEE,35579
11
11
  pythonnative/net.py,sha256=UI-39-BmGYWLE_vMAFoAbkzWZvfhIFj9yX_gexp1loc,8091
12
- pythonnative/platform.py,sha256=jEya1KTDc3WfwpmrQkk3DIFyt7CWO4Vc3pej_wDiSR8,4629
12
+ pythonnative/platform.py,sha256=cqG37hPY1fh6QzgO_AfHObg8R1fiMZmDeMu_2TIXqO0,4997
13
13
  pythonnative/platform_metrics.py,sha256=m2u8M8x52n5THNsYdspcaI9mlWWMbfSJWai1svjD0NM,8976
14
- pythonnative/reconciler.py,sha256=dJUSXX65Ckdj5iSmpPtXYnNk0pfg54aesmSlAV0vLbM,43996
15
- pythonnative/runtime.py,sha256=wQnMMG7ibDGR4zFtwSbh7pR6o5nRe1R_vyPYI0dVcfk,17116
16
- pythonnative/screen.py,sha256=hcOjs1ZP2jeag55NkbyzOCXiae8cUs5IUhSDwHKIFLQ,57666
14
+ pythonnative/preview.py,sha256=ZtDupXfQdW07i2JD-fnoikAd3zHKVPYoPSMob1K8KQs,16990
15
+ pythonnative/reconciler.py,sha256=cy6OtPXlxHzMafpNuMUfTMqIVobrxCnkwYd2MaiqOXw,56815
16
+ pythonnative/runtime.py,sha256=8xQvhZvMQJYJ0eozWTwKvC-H1GN5vikl6OCxt8f5uI0,18267
17
+ pythonnative/screen.py,sha256=Vm6tdc9YM9P1eORmEo-6clODeaTf6rF0aVFDgSxg6NE,64745
17
18
  pythonnative/storage.py,sha256=hLgSI44ADq6wj29eeYbHaAUNpxYPzJ2ZLn1L7AHkPZY,12010
18
19
  pythonnative/style.py,sha256=yDJv-G6iZIgrscpc-IZS_cbEQvY2o7R02PTQZ4BV8RA,15162
19
- pythonnative/utils.py,sha256=pQSxa3QW06_Y9JzBSnK0g0eMV1VhXww8Qym6HPOGzgM,6064
20
+ pythonnative/utils.py,sha256=-hwe_YS19ebpjeygdl3dGeVsYzO4G74rYD53svSi0rI,7593
20
21
  pythonnative/cli/__init__.py,sha256=NM1psvKe8jT0vzp8Ak4MMoygZz4P_msk5g-YEsY8xLk,232
21
- pythonnative/cli/pn.py,sha256=yWhDELAN7-UOE3qiv3ir29plcbXUp_gRT23Y7sRSVZQ,49133
22
+ pythonnative/cli/pn.py,sha256=wd6mpPxO5nMzt_jJWbw_Vp4Rz-BceQZHHu2o3CaE6KE,53795
22
23
  pythonnative/native_modules/__init__.py,sha256=pgigpHuzT-rqwcjlwJvu93_4L8Nozal1HJid0S_JlmM,2636
23
24
  pythonnative/native_modules/app_state.py,sha256=EnfChi_YWEgUpZosUiBQh0CmblBZFw8ZGaW1NKIJ4WM,2666
24
25
  pythonnative/native_modules/battery.py,sha256=gOU9aN5fCmWfHgTXPD-BgvatdQnyUjfVfsJM7PQ_ARA,4317
@@ -34,9 +35,10 @@ pythonnative/native_modules/notifications.py,sha256=WVtzdimc_aGfnxU6syCFPkjHF9YR
34
35
  pythonnative/native_modules/permissions.py,sha256=adRiO_LGxZ1PSJaCoJx_3nheuh9lLWNrPIb47O_A6-Y,6710
35
36
  pythonnative/native_modules/secure_store.py,sha256=YC25T2n2Nkfgks_qrCM1CR232AeSkh1_78NOJEBQ800,5956
36
37
  pythonnative/native_modules/share.py,sha256=B9ovknmnjeQvGQ8MQ14Hmq6E1x-JA469APMburJ0KUg,4727
37
- pythonnative/native_views/__init__.py,sha256=yP0IdOmQ3Cco4kJKBcgkMBUPX30ditM_Sp6ZotKAen4,11820
38
+ pythonnative/native_views/__init__.py,sha256=PCb2twVEIwEgMS5NZxsjoe4DeZwm1LRh0Q4QKYHS--A,12347
38
39
  pythonnative/native_views/android.py,sha256=z2ypAqMcgL9WNjVcTw-oINgbcFZJR27awC2jS_cWqJ0,114036
39
40
  pythonnative/native_views/base.py,sha256=LXDQYRM8wJa3MmGPwslkVyvlu36_s1_J6aO7wwhwYpA,6173
41
+ pythonnative/native_views/desktop.py,sha256=Z4r-72B3zbuvnqbJ5um35UqPM_iTj8wrEz61qcg0SZA,54931
40
42
  pythonnative/native_views/ios.py,sha256=F-DTStyva9fsESHAR5qZkNwrQKH_q2BoSGzgKPKJCBA,142727
41
43
  pythonnative/sdk/__init__.py,sha256=btIRfW2yy2d2LzjdpFnlc6ym-G3iJj9sVUbb2IlFMOI,3384
42
44
  pythonnative/sdk/_components.py,sha256=Hw0cqiyJ1NEzUrhOIT8zsC_mnVtf0jgpPUsireQG2qM,14644
@@ -92,9 +94,9 @@ pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/x
92
94
  pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift,sha256=YnwzZx7yXB13xKAXEGNgz17VuhWeqkHTRTtBJ2Vu3_E,1238
93
95
  pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift,sha256=l2Pwa50F_rv-qPu2go6e4bQernM6PTQJeNPFl_c4ivY,1387
94
96
  pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift,sha256=f5JrG0uVtLMeJQy26Yyz7Om-JUkT220osqcbeIVkj2g,815
95
- pythonnative-0.18.0.dist-info/licenses/LICENSE,sha256=A69iG7TIAe6KkGQf6xoVHkc5JSZtOr5eRSvC5iuivnI,1067
96
- pythonnative-0.18.0.dist-info/METADATA,sha256=wDTaZFQc6eK0lCtvBIoBVJHIVrsx0LRm5H3saFg13UE,8644
97
- pythonnative-0.18.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
98
- pythonnative-0.18.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
99
- pythonnative-0.18.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
100
- pythonnative-0.18.0.dist-info/RECORD,,
97
+ pythonnative-0.20.0.dist-info/licenses/LICENSE,sha256=A69iG7TIAe6KkGQf6xoVHkc5JSZtOr5eRSvC5iuivnI,1067
98
+ pythonnative-0.20.0.dist-info/METADATA,sha256=XlOYAungaXEGKHMhG9xRQV2IUCcWrgKoHCci7hwou54,9233
99
+ pythonnative-0.20.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
100
+ pythonnative-0.20.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
101
+ pythonnative-0.20.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
102
+ pythonnative-0.20.0.dist-info/RECORD,,