pythonnative 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. pythonnative/__init__.py +45 -65
  2. pythonnative/cli/pn.py +15 -14
  3. pythonnative/components.py +241 -0
  4. pythonnative/element.py +47 -0
  5. pythonnative/native_views.py +800 -0
  6. pythonnative/page.py +319 -247
  7. pythonnative/reconciler.py +129 -0
  8. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PageFragment.kt +2 -1
  9. pythonnative/utils.py +21 -29
  10. {pythonnative-0.4.0.dist-info → pythonnative-0.5.0.dist-info}/METADATA +35 -17
  11. {pythonnative-0.4.0.dist-info → pythonnative-0.5.0.dist-info}/RECORD +15 -35
  12. pythonnative/activity_indicator_view.py +0 -71
  13. pythonnative/button.py +0 -113
  14. pythonnative/date_picker.py +0 -76
  15. pythonnative/image_view.py +0 -78
  16. pythonnative/label.py +0 -133
  17. pythonnative/list_view.py +0 -76
  18. pythonnative/material_activity_indicator_view.py +0 -71
  19. pythonnative/material_button.py +0 -69
  20. pythonnative/material_date_picker.py +0 -87
  21. pythonnative/material_progress_view.py +0 -70
  22. pythonnative/material_search_bar.py +0 -69
  23. pythonnative/material_switch.py +0 -69
  24. pythonnative/material_time_picker.py +0 -76
  25. pythonnative/picker_view.py +0 -69
  26. pythonnative/progress_view.py +0 -70
  27. pythonnative/scroll_view.py +0 -101
  28. pythonnative/search_bar.py +0 -69
  29. pythonnative/stack_view.py +0 -199
  30. pythonnative/switch.py +0 -68
  31. pythonnative/text_field.py +0 -132
  32. pythonnative/text_view.py +0 -135
  33. pythonnative/time_picker.py +0 -77
  34. pythonnative/view.py +0 -173
  35. pythonnative/web_view.py +0 -60
  36. {pythonnative-0.4.0.dist-info → pythonnative-0.5.0.dist-info}/WHEEL +0 -0
  37. {pythonnative-0.4.0.dist-info → pythonnative-0.5.0.dist-info}/entry_points.txt +0 -0
  38. {pythonnative-0.4.0.dist-info → pythonnative-0.5.0.dist-info}/licenses/LICENSE +0 -0
  39. {pythonnative-0.4.0.dist-info → pythonnative-0.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,129 @@
1
+ """Virtual-tree reconciler.
2
+
3
+ Maintains a tree of :class:`VNode` objects (each wrapping a native view)
4
+ and diffs incoming :class:`Element` trees to apply the minimal set of
5
+ native mutations.
6
+ """
7
+
8
+ from typing import Any, List, Optional
9
+
10
+ from .element import Element
11
+
12
+
13
+ class VNode:
14
+ """A mounted element paired with its native view and child VNodes."""
15
+
16
+ __slots__ = ("element", "native_view", "children")
17
+
18
+ def __init__(self, element: Element, native_view: Any, children: List["VNode"]) -> None:
19
+ self.element = element
20
+ self.native_view = native_view
21
+ self.children = children
22
+
23
+
24
+ class Reconciler:
25
+ """Create, diff, and patch native view trees from Element descriptors.
26
+
27
+ Parameters
28
+ ----------
29
+ backend:
30
+ An object implementing the :class:`NativeViewRegistry` protocol
31
+ (``create_view``, ``update_view``, ``add_child``, ``remove_child``,
32
+ ``insert_child``).
33
+ """
34
+
35
+ def __init__(self, backend: Any) -> None:
36
+ self.backend = backend
37
+ self._tree: Optional[VNode] = None
38
+
39
+ # ------------------------------------------------------------------
40
+ # Public API
41
+ # ------------------------------------------------------------------
42
+
43
+ def mount(self, element: Element) -> Any:
44
+ """Build native views from *element* and return the root native view."""
45
+ self._tree = self._create_tree(element)
46
+ return self._tree.native_view
47
+
48
+ def reconcile(self, new_element: Element) -> Any:
49
+ """Diff *new_element* against the current tree and patch native views.
50
+
51
+ Returns the (possibly replaced) root native view.
52
+ """
53
+ if self._tree is None:
54
+ self._tree = self._create_tree(new_element)
55
+ return self._tree.native_view
56
+
57
+ self._tree = self._reconcile_node(self._tree, new_element)
58
+ return self._tree.native_view
59
+
60
+ # ------------------------------------------------------------------
61
+ # Internal helpers
62
+ # ------------------------------------------------------------------
63
+
64
+ def _create_tree(self, element: Element) -> VNode:
65
+ native_view = self.backend.create_view(element.type, element.props)
66
+ children: List[VNode] = []
67
+ for child_el in element.children:
68
+ child_node = self._create_tree(child_el)
69
+ self.backend.add_child(native_view, child_node.native_view, element.type)
70
+ children.append(child_node)
71
+ return VNode(element, native_view, children)
72
+
73
+ def _reconcile_node(self, old: VNode, new_el: Element) -> VNode:
74
+ if old.element.type != new_el.type:
75
+ new_node = self._create_tree(new_el)
76
+ self._destroy_tree(old)
77
+ return new_node
78
+
79
+ changed = self._diff_props(old.element.props, new_el.props)
80
+ if changed:
81
+ self.backend.update_view(old.native_view, old.element.type, changed)
82
+
83
+ self._reconcile_children(old, new_el.children)
84
+ old.element = new_el
85
+ return old
86
+
87
+ def _reconcile_children(self, parent: VNode, new_children: List[Element]) -> None:
88
+ old_children = parent.children
89
+ new_child_nodes: List[VNode] = []
90
+ max_len = max(len(old_children), len(new_children))
91
+
92
+ for i in range(max_len):
93
+ if i >= len(new_children):
94
+ self.backend.remove_child(parent.native_view, old_children[i].native_view, parent.element.type)
95
+ self._destroy_tree(old_children[i])
96
+ elif i >= len(old_children):
97
+ node = self._create_tree(new_children[i])
98
+ self.backend.add_child(parent.native_view, node.native_view, parent.element.type)
99
+ new_child_nodes.append(node)
100
+ else:
101
+ if old_children[i].element.type != new_children[i].type:
102
+ self.backend.remove_child(parent.native_view, old_children[i].native_view, parent.element.type)
103
+ self._destroy_tree(old_children[i])
104
+ node = self._create_tree(new_children[i])
105
+ self.backend.insert_child(parent.native_view, node.native_view, parent.element.type, i)
106
+ new_child_nodes.append(node)
107
+ else:
108
+ updated = self._reconcile_node(old_children[i], new_children[i])
109
+ new_child_nodes.append(updated)
110
+
111
+ parent.children = new_child_nodes
112
+
113
+ def _destroy_tree(self, node: VNode) -> None:
114
+ for child in node.children:
115
+ self._destroy_tree(child)
116
+ node.children = []
117
+
118
+ @staticmethod
119
+ def _diff_props(old: dict, new: dict) -> dict:
120
+ """Return props that changed (callables always count as changed)."""
121
+ changed = {}
122
+ for key, new_val in new.items():
123
+ old_val = old.get(key)
124
+ if callable(new_val) or old_val != new_val:
125
+ changed[key] = new_val
126
+ for key in old:
127
+ if key not in new:
128
+ changed[key] = None
129
+ return changed
@@ -65,7 +65,8 @@ class PageFragment : Fragment() {
65
65
  utils.callAttr("set_android_fragment_container", view)
66
66
  // Now that container exists, invoke on_create so Python can attach its root view
67
67
  page?.callAttr("on_create")
68
- } catch (_: Exception) {
68
+ } catch (e: Exception) {
69
+ Log.e(TAG, "on_create failed", e)
69
70
  }
70
71
  }
71
72
 
pythonnative/utils.py CHANGED
@@ -1,27 +1,29 @@
1
+ """Platform detection and shared helpers.
2
+
3
+ This module is imported early by most other modules, so it avoids
4
+ importing platform-specific packages at module level.
5
+ """
6
+
1
7
  import os
2
8
  from typing import Any, Optional
3
9
 
4
- # Platform detection with multiple fallbacks suitable for Chaquopy/Android
10
+ # ======================================================================
11
+ # Platform detection
12
+ # ======================================================================
13
+
5
14
  _is_android: Optional[bool] = None
6
15
 
7
16
 
8
17
  def _detect_android() -> bool:
9
- # 1) Direct environment hints commonly present on Android
10
18
  env = os.environ
11
19
  if "ANDROID_BOOTLOGO" in env or "ANDROID_ROOT" in env or "ANDROID_DATA" in env or "ANDROID_ARGUMENT" in env:
12
20
  return True
13
-
14
- # 2) Chaquopy-specific: the builtin 'java' package is available
15
21
  try:
16
- # Import inside try so importing this module doesn't explode off-device
17
- from java import jclass
22
+ from java import jclass # noqa: F401
18
23
 
19
- _ = jclass # silence linter unused
20
24
  return True
21
25
  except Exception:
22
26
  pass
23
-
24
- # 3) Last resort: some Android Python dists set os.name/others, but avoid false positives
25
27
  return False
26
28
 
27
29
 
@@ -39,53 +41,43 @@ def _get_is_android() -> bool:
39
41
 
40
42
  IS_ANDROID: bool = _get_is_android()
41
43
 
42
- # Global hooks to access current Android Activity/Context and Fragment container from Python code
44
+ # ======================================================================
45
+ # Android context management
46
+ # ======================================================================
47
+
43
48
  _android_context: Any = None
44
49
  _android_fragment_container: Any = None
45
50
 
46
51
 
47
52
  def set_android_context(context: Any) -> None:
48
- """Record the current Android Activity/Context for implicit constructor use.
49
-
50
- On Android, Python UI components require a Context to create native views.
51
- We capture it when a Page is constructed from the host Activity so component
52
- constructors can be platform-consistent and avoid explicit context params.
53
- """
54
-
53
+ """Record the current Android Activity/Context for view construction."""
55
54
  global _android_context
56
55
  _android_context = context
57
56
 
58
57
 
59
58
  def set_android_fragment_container(container_view: Any) -> None:
60
- """Record the current Fragment root container ViewGroup for rendering pages.
61
-
62
- The current Page's `set_root_view` will attach its native view to this container.
63
- """
59
+ """Record the current Fragment root container ViewGroup."""
64
60
  global _android_fragment_container
65
61
  _android_fragment_container = container_view
66
62
 
67
63
 
68
64
  def get_android_context() -> Any:
69
- """Return the previously set Android Activity/Context or raise if missing."""
70
-
65
+ """Return the current Android Activity/Context."""
71
66
  if not IS_ANDROID:
72
67
  raise RuntimeError("get_android_context() called on non-Android platform")
73
68
  if _android_context is None:
74
69
  raise RuntimeError(
75
- "Android context is not set. Ensure Page is initialized from an Activity " "before constructing views."
70
+ "Android context not set. Ensure Page is initialized from an Activity before constructing views."
76
71
  )
77
72
  return _android_context
78
73
 
79
74
 
80
75
  def get_android_fragment_container() -> Any:
81
- """Return the previously set Fragment container ViewGroup or raise if missing.
82
-
83
- This is set by the host `PageFragment` when its view is created.
84
- """
76
+ """Return the current Fragment container ViewGroup."""
85
77
  if not IS_ANDROID:
86
78
  raise RuntimeError("get_android_fragment_container() called on non-Android platform")
87
79
  if _android_fragment_container is None:
88
80
  raise RuntimeError(
89
- "Android fragment container is not set. Ensure PageFragment has been created before set_root_view."
81
+ "Android fragment container not set. Ensure PageFragment has been created before set_root_view."
90
82
  )
91
83
  return _android_fragment_container
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Cross-platform native UI toolkit for Android and iOS
5
5
  Author: Owen Carey
6
6
  License: MIT License
@@ -89,18 +89,17 @@ Dynamic: license-file
89
89
 
90
90
  ## Overview
91
91
 
92
- PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a Pythonic API for native UI components, lifecycle events, and device capabilities, powered by Chaquopy on Android and rubicon-objc on iOS. Write your app once in Python and run it on both platforms with genuinely native interfaces.
92
+ PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS. Describe your UI as a tree of elements, manage state with `set_state()`, and let PythonNative handle creating and updating native views.
93
93
 
94
94
  ## Features
95
95
 
96
- - **Cross-platform native UI:** Build Android and iOS apps from a single Python codebase with truly native rendering.
96
+ - **Declarative UI:** Describe *what* your UI should look like with element functions (`Text`, `Button`, `Column`, `Row`, etc.). PythonNative creates and updates native views automatically.
97
+ - **Reactive state:** Call `self.set_state(key=value)` and the framework re-renders only what changed — no manual view mutation.
98
+ - **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation.
97
99
  - **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
98
- - **Unified component API:** Components like `Page`, `StackView`, `Label`, `Button`, and `WebView` share a consistent interface across platforms.
99
- - **CLI scaffolding:** `pn init` creates a ready-to-run project structure; `pn run android` and `pn run ios` build and launch your app.
100
- - **Page lifecycle:** Hooks for `on_create`, `on_start`, `on_resume`, `on_pause`, `on_stop`, and `on_destroy`, with state save and restore.
100
+ - **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
101
101
  - **Navigation:** Push and pop screens with argument passing for multi-page apps.
102
- - **Rich component set:** Core views (Label, Button, TextField, ImageView, WebView, Switch, DatePicker, and more) plus Material Design variants.
103
- - **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access.
102
+ - **Bundled templates:** Android Gradle and iOS Xcode templates are included scaffolding requires no network access.
104
103
 
105
104
  ## Quick Start
106
105
 
@@ -119,17 +118,36 @@ import pythonnative as pn
119
118
  class MainPage(pn.Page):
120
119
  def __init__(self, native_instance):
121
120
  super().__init__(native_instance)
122
-
123
- def on_create(self):
124
- super().on_create()
125
- stack = pn.StackView()
126
- stack.add_view(pn.Label("Hello from PythonNative!"))
127
- button = pn.Button("Tap me")
128
- button.set_on_click(lambda: print("Button tapped"))
129
- stack.add_view(button)
130
- self.set_root_view(stack)
121
+ self.state = {"count": 0}
122
+
123
+ def render(self):
124
+ return pn.Column(
125
+ pn.Text(f"Count: {self.state['count']}", font_size=24),
126
+ pn.Button(
127
+ "Tap me",
128
+ on_click=lambda: self.set_state(count=self.state["count"] + 1),
129
+ ),
130
+ spacing=12,
131
+ padding=16,
132
+ )
131
133
  ```
132
134
 
135
+ ### Available Components
136
+
137
+ | Component | Description |
138
+ |---|---|
139
+ | `Text` | Display text |
140
+ | `Button` | Tappable button with `on_click` callback |
141
+ | `Column` / `Row` | Vertical / horizontal layout containers |
142
+ | `ScrollView` | Scrollable wrapper |
143
+ | `TextInput` | Text entry field with `on_change` callback |
144
+ | `Image` | Display images |
145
+ | `Switch` | Toggle with `on_change` callback |
146
+ | `ProgressBar` | Determinate progress (0.0–1.0) |
147
+ | `ActivityIndicator` | Indeterminate loading spinner |
148
+ | `WebView` | Embedded web content |
149
+ | `Spacer` | Empty space |
150
+
133
151
  ## Documentation
134
152
 
135
153
  Visit [docs.pythonnative.com](https://docs.pythonnative.com/) for the full documentation, including getting started guides, platform-specific instructions for Android and iOS, API reference, and working examples.
@@ -1,35 +1,15 @@
1
- pythonnative/__init__.py,sha256=Z4Iqe-CTiRVKh9Wynd8PFy_qxTqJIxLsxdBqWSTBp_c,2050
2
- pythonnative/activity_indicator_view.py,sha256=cYRiyGf5o4dlEy7v6v9yUt212E4cK6Hpn85P7uRIin0,2314
3
- pythonnative/button.py,sha256=B7smRmQs3J202nhg9ec4vitDjlpFZdTuLCUfG2qkfqs,3948
1
+ pythonnative/__init__.py,sha256=E0OXTarwvCIL7DcnuAC8GhLe_EY-7MrQJzwaBH8yVYY,1040
4
2
  pythonnative/collection_view.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- pythonnative/date_picker.py,sha256=rcU5WThaBe0PTr8KR47krrOTlIbjgyvNL0QWNq2GvYo,2406
6
- pythonnative/image_view.py,sha256=NddNM7wfUhgE23rPAQwcu-oi01y3fmWTDafD9FhWc6Y,2591
7
- pythonnative/label.py,sha256=SKx4XpctoDr1N-E3xaFVqT-_5AwV9zStyEy2oXvXVKc,4125
8
- pythonnative/list_view.py,sha256=nk3aOWpvjr5Htg2rTIwdkOolf0dRFmR69F0cbuzEPNg,2452
9
- pythonnative/material_activity_indicator_view.py,sha256=GYUo1tkdPyb4NnCrEZqHViNOk2mH6fT1E7Ezvc8TgYQ,2313
3
+ pythonnative/components.py,sha256=TASfQZS1u_tEF0QvK3E1Uj0Dzb26GA3kvGqOLOpbuxY,6756
4
+ pythonnative/element.py,sha256=7gfdjtCAcz4YBrWmUkve3zeyM_495yiPKAJZEXq2QZM,1453
10
5
  pythonnative/material_bottom_navigation_view.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- pythonnative/material_button.py,sha256=goRoILCKBXSOPTAfaAtfDOasedBJpCDxU2sIMIJvBKI,2148
12
- pythonnative/material_date_picker.py,sha256=zOcnSyoNWjLGpkNlQjRZwIyc9RY95Ya_A2yd_S7EwLA,2988
13
- pythonnative/material_progress_view.py,sha256=h1lmaab-nqwIyGjmU8L-4l7Zm8jG5x3Dx-SWTZ4DQpk,2288
14
- pythonnative/material_search_bar.py,sha256=kHYOYTTBjPTYOtQyxjH8ZpVPOJRWoCu2wyWIzkk-DVY,2104
15
- pythonnative/material_switch.py,sha256=3pct4sIfFvC6LJ1XXh4SCIik087hzxUyvHVrnE8eQDI,2065
16
- pythonnative/material_time_picker.py,sha256=2UwYugsUqX6sGLo-Uco_yLtLtvrPgCh64Hqg4j8EA1Q,2452
17
6
  pythonnative/material_toolbar.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- pythonnative/page.py,sha256=AaAAy4cY8goxq3dtSZvsd84eOjmYQ4yoLe3xmHcVsgM,14992
19
- pythonnative/picker_view.py,sha256=Is_GNRn22TmNWu77soo7mrQ-jxXuEbaJ9nciVdZvIbE,2069
20
- pythonnative/progress_view.py,sha256=_NxSqagfYcdiOQ4gtVhrmgAhFNUFGEcMZ7pxyvHSAFs,2333
21
- pythonnative/scroll_view.py,sha256=DQf4k3H6PQaSTeMWYE9z7-s3K4FKfuFXstwVNSBENWQ,3697
22
- pythonnative/search_bar.py,sha256=mkCE7qxMwqCzJO8-Kk9sCwoq0yXDNqGnG0E6H81qq08,1990
23
- pythonnative/stack_view.py,sha256=mTypUjpOoI12KSDmTgku0JepGYj_aBPyGvDIWPG3PtQ,7923
24
- pythonnative/switch.py,sha256=OGyd_QEprf5_M9ZMV6DlP2lePC7AstiFTHINjwTAjtw,1960
25
- pythonnative/text_field.py,sha256=bQFbC-Bk78BxS9hwy9aj3hePm5IsOOm_wZ6qEp4ZpsE,4154
26
- pythonnative/text_view.py,sha256=kcOskJghS0cUJeVBtXTht6Tj4P0RzMWT33ANenps3RQ,4323
27
- pythonnative/time_picker.py,sha256=OPQ_44QYA9dtB1bQu5vfQ8rMM44cmxE0m5eOz050kzI,2367
28
- pythonnative/utils.py,sha256=0Ub6DDg4uX4ToqoyioQzUJ-_ERzXlJ2r70pBSmeiaYY,3038
29
- pythonnative/view.py,sha256=nsoXnX1HsVKQ3ppuO9PkGNf3cDr48JBRrP0dryKYURA,6528
30
- pythonnative/web_view.py,sha256=OxduGbNgzfI6YNGzEPEGVHdLNhmCqUpTS57QZ7kVHms,1842
7
+ pythonnative/native_views.py,sha256=lQK7EMQhG2foBSjonH8iIQbCAkqwPj-QV6xg5e8l9HQ,36357
8
+ pythonnative/page.py,sha256=agVamXmMAbOlYB1MLp7GGosiH75p6C2CYvNPVTWg930,15150
9
+ pythonnative/reconciler.py,sha256=P7KM71BoQGpsU2wsvB-o2QxHAfOy9zXZwCx2lBBLyz8,4976
10
+ pythonnative/utils.py,sha256=IqR_GYknveM_NfAblcaizg9S66hCZfrfiH08HzpOc-4,2537
31
11
  pythonnative/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- pythonnative/cli/pn.py,sha256=Ild9WKASdOGXviOiC9aW9avg9knP8Mnc23lENc6Aodc,27311
12
+ pythonnative/cli/pn.py,sha256=m6mwfS2SAu9W53yhouzFHfnALXtrCQB_04uFXMev5dc,27239
33
13
  pythonnative/templates/android_template/build.gradle,sha256=metF5S4LveW05kDE2e-nzVG5rtwe2HESYs-lGNl390A,352
34
14
  pythonnative/templates/android_template/gradle.properties,sha256=REPaKLRfQiiVfIV8wYmgwzPWvF1f3bhh_kAMV9p4HME,1358
35
15
  pythonnative/templates/android_template/gradlew,sha256=YxNShxF6Hm0SyEWA8fScYdG6AiGOzShmBgXpf5dufWU,5766
@@ -41,7 +21,7 @@ pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnativ
41
21
  pythonnative/templates/android_template/app/src/main/AndroidManifest.xml,sha256=MdWrXxOrwUjnqtDbV952NI4nVF2dTUX9xwSS8chhd9I,940
42
22
  pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt,sha256=sqOQ4k--WVyfnrhfNkzqAEQ211uYNPxhmIUXDrIb0zY,1321
43
23
  pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt,sha256=dWOpdJFuGO2CWZZQjYPmSNxljjDyGUuys7-ehHhAqyM,931
44
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PageFragment.kt,sha256=lnIgCfAifZWp9mlJvSqO5mUOfMhH-RWaK11WfkE7uZ8,4001
24
+ pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PageFragment.kt,sha256=fMQUugt9WqUIA9CbE4BafrVdPAbBTR_K_LH6bLed7XQ,4047
45
25
  pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml,sha256=7UI8c6b0Ck0pCfCQHmBSezqAfNWeG1WTvKrhgIscYyE,5606
46
26
  pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml,sha256=AdGmpsEjTrf-Jw0JfrKD1yucla5RGIhvG2VzqtKA8fc,1702
47
27
  pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml,sha256=HIgdCNktb3YoJC8QOTIv-0qZRtMRoPdARK59nyYFO6g,461
@@ -81,9 +61,9 @@ pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/x
81
61
  pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift,sha256=YnwzZx7yXB13xKAXEGNgz17VuhWeqkHTRTtBJ2Vu3_E,1238
82
62
  pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift,sha256=l2Pwa50F_rv-qPu2go6e4bQernM6PTQJeNPFl_c4ivY,1387
83
63
  pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift,sha256=f5JrG0uVtLMeJQy26Yyz7Om-JUkT220osqcbeIVkj2g,815
84
- pythonnative-0.4.0.dist-info/licenses/LICENSE,sha256=A69iG7TIAe6KkGQf6xoVHkc5JSZtOr5eRSvC5iuivnI,1067
85
- pythonnative-0.4.0.dist-info/METADATA,sha256=ZhXR_1Md8wDjUar8ERWA9t2tIubdoHOAqZpu23YUclQ,6712
86
- pythonnative-0.4.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
87
- pythonnative-0.4.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
88
- pythonnative-0.4.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
89
- pythonnative-0.4.0.dist-info/RECORD,,
64
+ pythonnative-0.5.0.dist-info/licenses/LICENSE,sha256=A69iG7TIAe6KkGQf6xoVHkc5JSZtOr5eRSvC5iuivnI,1067
65
+ pythonnative-0.5.0.dist-info/METADATA,sha256=TVhRDPT4qFBuijk8eEIgDTXugnxw6G-B_1_JBGpMCpM,7256
66
+ pythonnative-0.5.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
67
+ pythonnative-0.5.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
68
+ pythonnative-0.5.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
69
+ pythonnative-0.5.0.dist-info/RECORD,,
@@ -1,71 +0,0 @@
1
- from abc import ABC, abstractmethod
2
-
3
- from .utils import IS_ANDROID, get_android_context
4
- from .view import ViewBase
5
-
6
- # ========================================
7
- # Base class
8
- # ========================================
9
-
10
-
11
- class ActivityIndicatorViewBase(ABC):
12
- @abstractmethod
13
- def __init__(self) -> None:
14
- super().__init__()
15
-
16
- @abstractmethod
17
- def start_animating(self) -> None:
18
- pass
19
-
20
- @abstractmethod
21
- def stop_animating(self) -> None:
22
- pass
23
-
24
-
25
- if IS_ANDROID:
26
- # ========================================
27
- # Android class
28
- # https://developer.android.com/reference/android/widget/ProgressBar
29
- # ========================================
30
-
31
- from java import jclass
32
-
33
- class ActivityIndicatorView(ActivityIndicatorViewBase, ViewBase):
34
- def __init__(self) -> None:
35
- super().__init__()
36
- self.native_class = jclass("android.widget.ProgressBar")
37
- # self.native_instance = self.native_class(context, None, android.R.attr.progressBarStyleLarge)
38
- context = get_android_context()
39
- self.native_instance = self.native_class(context)
40
- self.native_instance.setIndeterminate(True)
41
-
42
- def start_animating(self) -> None:
43
- # self.native_instance.setVisibility(android.view.View.VISIBLE)
44
- return
45
-
46
- def stop_animating(self) -> None:
47
- # self.native_instance.setVisibility(android.view.View.GONE)
48
- return
49
-
50
- else:
51
- # ========================================
52
- # iOS class
53
- # https://developer.apple.com/documentation/uikit/uiactivityindicatorview
54
- # ========================================
55
-
56
- from rubicon.objc import ObjCClass
57
-
58
- class ActivityIndicatorView(ActivityIndicatorViewBase, ViewBase):
59
- def __init__(self) -> None:
60
- super().__init__()
61
- self.native_class = ObjCClass("UIActivityIndicatorView")
62
- self.native_instance = self.native_class.alloc().initWithActivityIndicatorStyle_(
63
- 0
64
- ) # 0: UIActivityIndicatorViewStyleLarge
65
- self.native_instance.hidesWhenStopped = True
66
-
67
- def start_animating(self) -> None:
68
- self.native_instance.startAnimating()
69
-
70
- def stop_animating(self) -> None:
71
- self.native_instance.stopAnimating()
pythonnative/button.py DELETED
@@ -1,113 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from typing import Any, Callable, Optional
3
-
4
- from .utils import IS_ANDROID, get_android_context
5
- from .view import ViewBase
6
-
7
- # ========================================
8
- # Base class
9
- # ========================================
10
-
11
-
12
- class ButtonBase(ABC):
13
- @abstractmethod
14
- def __init__(self) -> None:
15
- super().__init__()
16
-
17
- @abstractmethod
18
- def set_title(self, title: str) -> "ButtonBase":
19
- pass
20
-
21
- @abstractmethod
22
- def get_title(self) -> str:
23
- pass
24
-
25
- @abstractmethod
26
- def set_on_click(self, callback: Callable[[], None]) -> "ButtonBase":
27
- pass
28
-
29
-
30
- if IS_ANDROID:
31
- # ========================================
32
- # Android class
33
- # https://developer.android.com/reference/android/widget/Button
34
- # ========================================
35
-
36
- from java import dynamic_proxy, jclass
37
-
38
- class Button(ButtonBase, ViewBase):
39
- def __init__(self, title: str = "") -> None:
40
- super().__init__()
41
- self.native_class = jclass("android.widget.Button")
42
- context = get_android_context()
43
- self.native_instance = self.native_class(context)
44
- self.set_title(title)
45
-
46
- def set_title(self, title: str) -> "Button":
47
- self.native_instance.setText(title)
48
- return self
49
-
50
- def get_title(self) -> str:
51
- return self.native_instance.getText().toString()
52
-
53
- def set_on_click(self, callback: Callable[[], None]) -> "Button":
54
- class OnClickListener(dynamic_proxy(jclass("android.view.View").OnClickListener)):
55
- def __init__(self, callback: Callable[[], None]) -> None:
56
- super().__init__()
57
- self.callback = callback
58
-
59
- def onClick(self, view: Any) -> None:
60
- self.callback()
61
-
62
- listener = OnClickListener(callback)
63
- self.native_instance.setOnClickListener(listener)
64
- return self
65
-
66
- else:
67
- # ========================================
68
- # iOS class
69
- # https://developer.apple.com/documentation/uikit/uibutton
70
- # ========================================
71
-
72
- from rubicon.objc import SEL, ObjCClass, objc_method
73
-
74
- NSObject = ObjCClass("NSObject")
75
-
76
- # Mypy cannot understand Rubicon's dynamic subclassing; ignore the base type here.
77
- class _PNButtonHandler(NSObject): # type: ignore[valid-type]
78
- # Set by the Button when wiring up the target/action callback.
79
- _callback: Optional[Callable[[], None]] = None
80
-
81
- @objc_method
82
- def onTap_(self, sender: object) -> None:
83
- try:
84
- callback = self._callback
85
- if callback is not None:
86
- callback()
87
- except Exception:
88
- # Swallow exceptions to avoid crashing the app; logging is handled at higher levels
89
- pass
90
-
91
- class Button(ButtonBase, ViewBase):
92
- def __init__(self, title: str = "") -> None:
93
- super().__init__()
94
- self.native_class = ObjCClass("UIButton")
95
- self.native_instance = self.native_class.alloc().init()
96
- self.set_title(title)
97
-
98
- def set_title(self, title: str) -> "Button":
99
- self.native_instance.setTitle_forState_(title, 0)
100
- return self
101
-
102
- def get_title(self) -> str:
103
- return self.native_instance.titleForState_(0)
104
-
105
- def set_on_click(self, callback: Callable[[], None]) -> "Button":
106
- # Create a handler object with an Objective-C method `onTap:` and attach the Python callback
107
- handler = _PNButtonHandler.new()
108
- # Keep strong references to the handler and callback
109
- self._click_handler = handler
110
- handler._callback = callback
111
- # UIControlEventTouchUpInside = 1 << 6
112
- self.native_instance.addTarget_action_forControlEvents_(handler, SEL("onTap:"), 1 << 6)
113
- return self
@@ -1,76 +0,0 @@
1
- from abc import ABC, abstractmethod
2
-
3
- from .utils import IS_ANDROID
4
- from .view import ViewBase
5
-
6
- # ========================================
7
- # Base class
8
- # ========================================
9
-
10
-
11
- class DatePickerBase(ABC):
12
- @abstractmethod
13
- def __init__(self) -> None:
14
- super().__init__()
15
-
16
- @abstractmethod
17
- def set_date(self, year: int, month: int, day: int) -> "DatePickerBase":
18
- pass
19
-
20
- @abstractmethod
21
- def get_date(self) -> tuple:
22
- pass
23
-
24
-
25
- if IS_ANDROID:
26
- # ========================================
27
- # Android class
28
- # https://developer.android.com/reference/android/widget/DatePicker
29
- # ========================================
30
-
31
- from typing import Any
32
-
33
- from java import jclass
34
-
35
- class DatePicker(DatePickerBase, ViewBase):
36
- def __init__(self, context: Any, year: int = 0, month: int = 0, day: int = 0) -> None:
37
- super().__init__()
38
- self.native_class = jclass("android.widget.DatePicker")
39
- self.native_instance = self.native_class(context)
40
- self.set_date(year, month, day)
41
-
42
- def set_date(self, year: int, month: int, day: int) -> "DatePicker":
43
- self.native_instance.updateDate(year, month, day)
44
- return self
45
-
46
- def get_date(self) -> tuple:
47
- year = self.native_instance.getYear()
48
- month = self.native_instance.getMonth()
49
- day = self.native_instance.getDayOfMonth()
50
- return year, month, day
51
-
52
- else:
53
- # ========================================
54
- # iOS class
55
- # https://developer.apple.com/documentation/uikit/uidatepicker
56
- # ========================================
57
-
58
- from datetime import datetime
59
-
60
- from rubicon.objc import ObjCClass
61
-
62
- class DatePicker(DatePickerBase, ViewBase):
63
- def __init__(self, year: int = 0, month: int = 0, day: int = 0) -> None:
64
- super().__init__()
65
- self.native_class = ObjCClass("UIDatePicker")
66
- self.native_instance = self.native_class.alloc().init()
67
- self.set_date(year, month, day)
68
-
69
- def set_date(self, year: int, month: int, day: int) -> "DatePicker":
70
- date = datetime(year, month, day)
71
- self.native_instance.setDate_(date)
72
- return self
73
-
74
- def get_date(self) -> tuple:
75
- date = self.native_instance.date()
76
- return date.year, date.month, date.day