pythonnative 0.2.0__tar.gz → 0.4.0__tar.gz

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 (110) hide show
  1. {pythonnative-0.2.0 → pythonnative-0.4.0}/LICENSE +1 -1
  2. pythonnative-0.4.0/PKG-INFO +143 -0
  3. pythonnative-0.4.0/README.md +80 -0
  4. {pythonnative-0.2.0 → pythonnative-0.4.0}/pyproject.toml +28 -1
  5. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/__init__.py +1 -1
  6. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/button.py +14 -10
  7. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/cli/pn.py +11 -13
  8. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/date_picker.py +8 -4
  9. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/image_view.py +5 -3
  10. pythonnative-0.4.0/src/pythonnative/label.py +133 -0
  11. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/list_view.py +7 -4
  12. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/material_activity_indicator_view.py +3 -1
  13. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/material_button.py +8 -4
  14. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/material_date_picker.py +5 -3
  15. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/material_progress_view.py +8 -4
  16. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/material_search_bar.py +8 -4
  17. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/material_switch.py +8 -4
  18. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/material_time_picker.py +8 -4
  19. pythonnative-0.4.0/src/pythonnative/page.py +396 -0
  20. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/picker_view.py +8 -4
  21. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/progress_view.py +5 -3
  22. pythonnative-0.4.0/src/pythonnative/scroll_view.py +101 -0
  23. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/search_bar.py +8 -4
  24. pythonnative-0.4.0/src/pythonnative/stack_view.py +199 -0
  25. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/switch.py +5 -3
  26. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/build.gradle +5 -2
  27. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +10 -7
  28. pythonnative-0.4.0/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +26 -0
  29. pythonnative-0.4.0/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PageFragment.kt +111 -0
  30. pythonnative-0.4.0/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +10 -0
  31. pythonnative-0.4.0/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +22 -0
  32. pythonnative-0.4.0/src/pythonnative/templates/android_template/build.gradle +7 -0
  33. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +1 -1
  34. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +7 -4
  35. pythonnative-0.4.0/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +218 -0
  36. pythonnative-0.4.0/src/pythonnative/text_field.py +132 -0
  37. pythonnative-0.4.0/src/pythonnative/text_view.py +135 -0
  38. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/time_picker.py +8 -4
  39. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/utils.py +25 -1
  40. pythonnative-0.4.0/src/pythonnative/view.py +173 -0
  41. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/web_view.py +5 -3
  42. pythonnative-0.4.0/src/pythonnative.egg-info/PKG-INFO +143 -0
  43. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative.egg-info/SOURCES.txt +3 -0
  44. {pythonnative-0.2.0 → pythonnative-0.4.0}/tests/test_cli.py +30 -5
  45. pythonnative-0.4.0/tests/test_smoke.py +2 -0
  46. pythonnative-0.2.0/PKG-INFO +0 -137
  47. pythonnative-0.2.0/README.md +0 -74
  48. pythonnative-0.2.0/src/pythonnative/label.py +0 -66
  49. pythonnative-0.2.0/src/pythonnative/page.py +0 -209
  50. pythonnative-0.2.0/src/pythonnative/scroll_view.py +0 -63
  51. pythonnative-0.2.0/src/pythonnative/stack_view.py +0 -60
  52. pythonnative-0.2.0/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -18
  53. pythonnative-0.2.0/src/pythonnative/templates/android_template/build.gradle +0 -7
  54. pythonnative-0.2.0/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +0 -118
  55. pythonnative-0.2.0/src/pythonnative/text_field.py +0 -67
  56. pythonnative-0.2.0/src/pythonnative/text_view.py +0 -70
  57. pythonnative-0.2.0/src/pythonnative/view.py +0 -25
  58. pythonnative-0.2.0/src/pythonnative.egg-info/PKG-INFO +0 -137
  59. pythonnative-0.2.0/tests/test_smoke.py +0 -2
  60. {pythonnative-0.2.0 → pythonnative-0.4.0}/setup.cfg +0 -0
  61. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/activity_indicator_view.py +0 -0
  62. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/cli/__init__.py +0 -0
  63. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/collection_view.py +0 -0
  64. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/material_bottom_navigation_view.py +0 -0
  65. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/material_toolbar.py +0 -0
  66. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
  67. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
  68. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
  69. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
  70. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
  71. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
  72. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
  73. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  74. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  75. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  76. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  77. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  78. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  79. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  80. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  81. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  82. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  83. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
  84. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
  85. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
  86. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
  87. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
  88. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
  89. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
  90. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
  91. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
  92. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/gradlew +0 -0
  93. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
  94. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
  95. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
  96. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
  97. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
  98. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
  99. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
  100. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -0
  101. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
  102. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
  103. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
  104. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
  105. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
  106. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
  107. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
  108. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
  109. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative.egg-info/requires.txt +0 -0
  110. {pythonnative-0.2.0 → pythonnative-0.4.0}/src/pythonnative.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 PythonNative
3
+ Copyright (c) 2026 Owen Carey
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: pythonnative
3
+ Version: 0.4.0
4
+ Summary: Cross-platform native UI toolkit for Android and iOS
5
+ Author: Owen Carey
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Owen Carey
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in
18
+ all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ THE SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/pythonnative/pythonnative
29
+ Project-URL: Repository, https://github.com/pythonnative/pythonnative
30
+ Project-URL: Issues, https://github.com/pythonnative/pythonnative/issues
31
+ Project-URL: Documentation, https://docs.pythonnative.com/
32
+ Classifier: Development Status :: 2 - Pre-Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3 :: Only
37
+ Classifier: Programming Language :: Python :: 3.9
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Topic :: Software Development :: User Interfaces
42
+ Requires-Python: >=3.9
43
+ Description-Content-Type: text/markdown
44
+ License-File: LICENSE
45
+ Requires-Dist: requests>=2.31.0
46
+ Provides-Extra: ios
47
+ Requires-Dist: rubicon-objc<0.5.0,>=0.4.6; extra == "ios"
48
+ Provides-Extra: docs
49
+ Requires-Dist: mkdocs>=1.5; extra == "docs"
50
+ Requires-Dist: mkdocs-material[imaging]>=9.5; extra == "docs"
51
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
52
+ Provides-Extra: dev
53
+ Requires-Dist: black>=24.0; extra == "dev"
54
+ Requires-Dist: ruff>=0.5; extra == "dev"
55
+ Requires-Dist: mypy>=1.10; extra == "dev"
56
+ Requires-Dist: pytest>=8.0; extra == "dev"
57
+ Provides-Extra: ci
58
+ Requires-Dist: black>=24.0; extra == "ci"
59
+ Requires-Dist: ruff>=0.5; extra == "ci"
60
+ Requires-Dist: mypy>=1.10; extra == "ci"
61
+ Requires-Dist: pytest>=8.0; extra == "ci"
62
+ Dynamic: license-file
63
+
64
+ <p align="center">
65
+ <img src="docs/assets/banner.jpg" alt="PythonNative" width="800" />
66
+ </p>
67
+
68
+ <p align="center">
69
+ <em>Build native Android and iOS apps in Python.</em>
70
+ </p>
71
+
72
+ <p align="center">
73
+ <a href="https://github.com/pythonnative/pythonnative/actions/workflows/ci.yml"><img src="https://github.com/pythonnative/pythonnative/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
74
+ <a href="https://github.com/pythonnative/pythonnative/actions/workflows/release.yml"><img src="https://github.com/pythonnative/pythonnative/actions/workflows/release.yml/badge.svg" alt="Release" /></a>
75
+ <a href="https://pypi.org/project/pythonnative/"><img src="https://img.shields.io/pypi/v/pythonnative" alt="PyPI Version" /></a>
76
+ <a href="https://pypi.org/project/pythonnative/"><img src="https://img.shields.io/pypi/pyversions/pythonnative" alt="Python Versions" /></a>
77
+ <a href="LICENSE"><img src="https://img.shields.io/pypi/l/pythonnative" alt="License: MIT" /></a>
78
+ <a href="https://docs.pythonnative.com/"><img src="https://img.shields.io/website?url=https%3A%2F%2Fdocs.pythonnative.com&label=docs" alt="Docs" /></a>
79
+ </p>
80
+
81
+ <p align="center">
82
+ <a href="https://docs.pythonnative.com/">Documentation</a> ·
83
+ <a href="https://docs.pythonnative.com/getting-started/">Getting Started</a> ·
84
+ <a href="https://docs.pythonnative.com/examples/">Examples</a> ·
85
+ <a href="CONTRIBUTING.md">Contributing</a>
86
+ </p>
87
+
88
+ ---
89
+
90
+ ## Overview
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.
93
+
94
+ ## Features
95
+
96
+ - **Cross-platform native UI:** Build Android and iOS apps from a single Python codebase with truly native rendering.
97
+ - **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.
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.
104
+
105
+ ## Quick Start
106
+
107
+ ### Installation
108
+
109
+ ```bash
110
+ pip install pythonnative
111
+ ```
112
+
113
+ ### Usage
114
+
115
+ ```python
116
+ import pythonnative as pn
117
+
118
+
119
+ class MainPage(pn.Page):
120
+ def __init__(self, native_instance):
121
+ 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)
131
+ ```
132
+
133
+ ## Documentation
134
+
135
+ 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.
136
+
137
+ ## Contributing
138
+
139
+ Contributions are welcome. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions, coding standards, and guidelines for submitting pull requests.
140
+
141
+ ## License
142
+
143
+ [MIT](LICENSE)
@@ -0,0 +1,80 @@
1
+ <p align="center">
2
+ <img src="docs/assets/banner.jpg" alt="PythonNative" width="800" />
3
+ </p>
4
+
5
+ <p align="center">
6
+ <em>Build native Android and iOS apps in Python.</em>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/pythonnative/pythonnative/actions/workflows/ci.yml"><img src="https://github.com/pythonnative/pythonnative/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
11
+ <a href="https://github.com/pythonnative/pythonnative/actions/workflows/release.yml"><img src="https://github.com/pythonnative/pythonnative/actions/workflows/release.yml/badge.svg" alt="Release" /></a>
12
+ <a href="https://pypi.org/project/pythonnative/"><img src="https://img.shields.io/pypi/v/pythonnative" alt="PyPI Version" /></a>
13
+ <a href="https://pypi.org/project/pythonnative/"><img src="https://img.shields.io/pypi/pyversions/pythonnative" alt="Python Versions" /></a>
14
+ <a href="LICENSE"><img src="https://img.shields.io/pypi/l/pythonnative" alt="License: MIT" /></a>
15
+ <a href="https://docs.pythonnative.com/"><img src="https://img.shields.io/website?url=https%3A%2F%2Fdocs.pythonnative.com&label=docs" alt="Docs" /></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="https://docs.pythonnative.com/">Documentation</a> ·
20
+ <a href="https://docs.pythonnative.com/getting-started/">Getting Started</a> ·
21
+ <a href="https://docs.pythonnative.com/examples/">Examples</a> ·
22
+ <a href="CONTRIBUTING.md">Contributing</a>
23
+ </p>
24
+
25
+ ---
26
+
27
+ ## Overview
28
+
29
+ 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.
30
+
31
+ ## Features
32
+
33
+ - **Cross-platform native UI:** Build Android and iOS apps from a single Python codebase with truly native rendering.
34
+ - **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
35
+ - **Unified component API:** Components like `Page`, `StackView`, `Label`, `Button`, and `WebView` share a consistent interface across platforms.
36
+ - **CLI scaffolding:** `pn init` creates a ready-to-run project structure; `pn run android` and `pn run ios` build and launch your app.
37
+ - **Page lifecycle:** Hooks for `on_create`, `on_start`, `on_resume`, `on_pause`, `on_stop`, and `on_destroy`, with state save and restore.
38
+ - **Navigation:** Push and pop screens with argument passing for multi-page apps.
39
+ - **Rich component set:** Core views (Label, Button, TextField, ImageView, WebView, Switch, DatePicker, and more) plus Material Design variants.
40
+ - **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access.
41
+
42
+ ## Quick Start
43
+
44
+ ### Installation
45
+
46
+ ```bash
47
+ pip install pythonnative
48
+ ```
49
+
50
+ ### Usage
51
+
52
+ ```python
53
+ import pythonnative as pn
54
+
55
+
56
+ class MainPage(pn.Page):
57
+ def __init__(self, native_instance):
58
+ super().__init__(native_instance)
59
+
60
+ def on_create(self):
61
+ super().on_create()
62
+ stack = pn.StackView()
63
+ stack.add_view(pn.Label("Hello from PythonNative!"))
64
+ button = pn.Button("Tap me")
65
+ button.set_on_click(lambda: print("Button tapped"))
66
+ stack.add_view(button)
67
+ self.set_root_view(stack)
68
+ ```
69
+
70
+ ## Documentation
71
+
72
+ 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.
73
+
74
+ ## Contributing
75
+
76
+ Contributions are welcome. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions, coding standards, and guidelines for submitting pull requests.
77
+
78
+ ## License
79
+
80
+ [MIT](LICENSE)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pythonnative"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "Cross-platform native UI toolkit for Android and iOS"
9
9
  authors = [
10
10
  { name = "Owen Carey" }
@@ -88,3 +88,30 @@ ignore = []
88
88
  [tool.black]
89
89
  line-length = 120
90
90
  target-version = ['py39']
91
+
92
+ # ── Semantic Release ────────────────────────────────────────────────
93
+
94
+ [tool.semantic_release]
95
+ version_toml = ["pyproject.toml:project.version"]
96
+ version_variables = ["src/pythonnative/__init__.py:__version__"]
97
+ commit_message = "chore(release): v{version}"
98
+ tag_format = "v{version}"
99
+ major_on_zero = false
100
+
101
+ [tool.semantic_release.branches.main]
102
+ match = "main"
103
+ prerelease = false
104
+
105
+ [tool.semantic_release.changelog]
106
+ changelog_file = "CHANGELOG.md"
107
+ exclude_commit_patterns = [
108
+ "^chore\\(release\\):",
109
+ ]
110
+
111
+ [tool.semantic_release.commit_parser_options]
112
+ allowed_tags = [
113
+ "build", "chore", "ci", "docs", "feat", "fix",
114
+ "perf", "refactor", "revert", "style", "test",
115
+ ]
116
+ minor_tags = ["feat"]
117
+ patch_tags = ["fix", "perf"]
@@ -1,7 +1,7 @@
1
1
  from importlib import import_module
2
2
  from typing import Any, Dict
3
3
 
4
- __version__ = "0.2.0"
4
+ __version__ = "0.4.0"
5
5
 
6
6
  __all__ = [
7
7
  "ActivityIndicatorView",
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Callable, Optional
2
+ from typing import Any, Callable, Optional
3
3
 
4
4
  from .utils import IS_ANDROID, get_android_context
5
5
  from .view import ViewBase
@@ -15,7 +15,7 @@ class ButtonBase(ABC):
15
15
  super().__init__()
16
16
 
17
17
  @abstractmethod
18
- def set_title(self, title: str) -> None:
18
+ def set_title(self, title: str) -> "ButtonBase":
19
19
  pass
20
20
 
21
21
  @abstractmethod
@@ -23,7 +23,7 @@ class ButtonBase(ABC):
23
23
  pass
24
24
 
25
25
  @abstractmethod
26
- def set_on_click(self, callback: Callable[[], None]) -> None:
26
+ def set_on_click(self, callback: Callable[[], None]) -> "ButtonBase":
27
27
  pass
28
28
 
29
29
 
@@ -43,23 +43,25 @@ if IS_ANDROID:
43
43
  self.native_instance = self.native_class(context)
44
44
  self.set_title(title)
45
45
 
46
- def set_title(self, title: str) -> None:
46
+ def set_title(self, title: str) -> "Button":
47
47
  self.native_instance.setText(title)
48
+ return self
48
49
 
49
50
  def get_title(self) -> str:
50
51
  return self.native_instance.getText().toString()
51
52
 
52
- def set_on_click(self, callback: Callable[[], None]) -> None:
53
+ def set_on_click(self, callback: Callable[[], None]) -> "Button":
53
54
  class OnClickListener(dynamic_proxy(jclass("android.view.View").OnClickListener)):
54
- def __init__(self, callback):
55
+ def __init__(self, callback: Callable[[], None]) -> None:
55
56
  super().__init__()
56
57
  self.callback = callback
57
58
 
58
- def onClick(self, view):
59
+ def onClick(self, view: Any) -> None:
59
60
  self.callback()
60
61
 
61
62
  listener = OnClickListener(callback)
62
63
  self.native_instance.setOnClickListener(listener)
64
+ return self
63
65
 
64
66
  else:
65
67
  # ========================================
@@ -77,7 +79,7 @@ else:
77
79
  _callback: Optional[Callable[[], None]] = None
78
80
 
79
81
  @objc_method
80
- def onTap_(self, sender) -> None:
82
+ def onTap_(self, sender: object) -> None:
81
83
  try:
82
84
  callback = self._callback
83
85
  if callback is not None:
@@ -93,13 +95,14 @@ else:
93
95
  self.native_instance = self.native_class.alloc().init()
94
96
  self.set_title(title)
95
97
 
96
- def set_title(self, title: str) -> None:
98
+ def set_title(self, title: str) -> "Button":
97
99
  self.native_instance.setTitle_forState_(title, 0)
100
+ return self
98
101
 
99
102
  def get_title(self) -> str:
100
103
  return self.native_instance.titleForState_(0)
101
104
 
102
- def set_on_click(self, callback: Callable[[], None]) -> None:
105
+ def set_on_click(self, callback: Callable[[], None]) -> "Button":
103
106
  # Create a handler object with an Objective-C method `onTap:` and attach the Python callback
104
107
  handler = _PNButtonHandler.new()
105
108
  # Keep strong references to the handler and callback
@@ -107,3 +110,4 @@ else:
107
110
  handler._callback = callback
108
111
  # UIControlEventTouchUpInside = 1 << 6
109
112
  self.native_instance.addTarget_action_forControlEvents_(handler, SEL("onTap:"), 1 << 6)
113
+ return self
@@ -41,7 +41,7 @@ def init_project(args: argparse.Namespace) -> None:
41
41
 
42
42
  os.makedirs(app_dir, exist_ok=True)
43
43
 
44
- # Minimal hello world app scaffold
44
+ # Minimal hello world app scaffold (no bootstrap function; host instantiates Page directly)
45
45
  main_page_py = os.path.join(app_dir, "main_page.py")
46
46
  if not os.path.exists(main_page_py) or args.force:
47
47
  with open(main_page_py, "w", encoding="utf-8") as f:
@@ -55,19 +55,17 @@ class MainPage(pn.Page):
55
55
 
56
56
  def on_create(self):
57
57
  super().on_create()
58
- stack = pn.StackView()
59
- stack.add_view(pn.Label("Hello from PythonNative!"))
60
- button = pn.Button("Tap me")
61
- button.set_on_click(lambda: print("Button clicked"))
58
+ stack = (
59
+ pn.StackView()
60
+ .set_axis("vertical")
61
+ .set_spacing(12)
62
+ .set_alignment("fill")
63
+ .set_padding(all=16)
64
+ )
65
+ stack.add_view(pn.Label("Hello from PythonNative!").set_text_size(18))
66
+ button = pn.Button("Tap me").set_on_click(lambda: print("Button clicked"))
62
67
  stack.add_view(button)
63
- self.set_root_view(stack)
64
-
65
-
66
- def bootstrap(native_instance):
67
- '''Entry point called by the host app (Android Activity or iOS ViewController).'''
68
- page = MainPage(native_instance)
69
- page.on_create()
70
- return page
68
+ self.set_root_view(stack.wrap_in_scroll())
71
69
  """
72
70
  )
73
71
 
@@ -14,7 +14,7 @@ class DatePickerBase(ABC):
14
14
  super().__init__()
15
15
 
16
16
  @abstractmethod
17
- def set_date(self, year: int, month: int, day: int) -> None:
17
+ def set_date(self, year: int, month: int, day: int) -> "DatePickerBase":
18
18
  pass
19
19
 
20
20
  @abstractmethod
@@ -28,17 +28,20 @@ if IS_ANDROID:
28
28
  # https://developer.android.com/reference/android/widget/DatePicker
29
29
  # ========================================
30
30
 
31
+ from typing import Any
32
+
31
33
  from java import jclass
32
34
 
33
35
  class DatePicker(DatePickerBase, ViewBase):
34
- def __init__(self, context, year: int = 0, month: int = 0, day: int = 0) -> None:
36
+ def __init__(self, context: Any, year: int = 0, month: int = 0, day: int = 0) -> None:
35
37
  super().__init__()
36
38
  self.native_class = jclass("android.widget.DatePicker")
37
39
  self.native_instance = self.native_class(context)
38
40
  self.set_date(year, month, day)
39
41
 
40
- def set_date(self, year: int, month: int, day: int) -> None:
42
+ def set_date(self, year: int, month: int, day: int) -> "DatePicker":
41
43
  self.native_instance.updateDate(year, month, day)
44
+ return self
42
45
 
43
46
  def get_date(self) -> tuple:
44
47
  year = self.native_instance.getYear()
@@ -63,9 +66,10 @@ else:
63
66
  self.native_instance = self.native_class.alloc().init()
64
67
  self.set_date(year, month, day)
65
68
 
66
- def set_date(self, year: int, month: int, day: int) -> None:
69
+ def set_date(self, year: int, month: int, day: int) -> "DatePicker":
67
70
  date = datetime(year, month, day)
68
71
  self.native_instance.setDate_(date)
72
+ return self
69
73
 
70
74
  def get_date(self) -> tuple:
71
75
  date = self.native_instance.date()
@@ -14,7 +14,7 @@ class ImageViewBase(ABC):
14
14
  super().__init__()
15
15
 
16
16
  @abstractmethod
17
- def set_image(self, image: str) -> None:
17
+ def set_image(self, image: str) -> "ImageViewBase":
18
18
  pass
19
19
 
20
20
  @abstractmethod
@@ -40,9 +40,10 @@ if IS_ANDROID:
40
40
  if image:
41
41
  self.set_image(image)
42
42
 
43
- def set_image(self, image: str) -> None:
43
+ def set_image(self, image: str) -> "ImageView":
44
44
  bitmap = BitmapFactory.decodeFile(image)
45
45
  self.native_instance.setImageBitmap(bitmap)
46
+ return self
46
47
 
47
48
  def get_image(self) -> str:
48
49
  # Please note that this is a simplistic representation, getting image from ImageView
@@ -66,10 +67,11 @@ else:
66
67
  if image:
67
68
  self.set_image(image)
68
69
 
69
- def set_image(self, image: str) -> None:
70
+ def set_image(self, image: str) -> "ImageView":
70
71
  ns_str = NSString.alloc().initWithUTF8String_(image)
71
72
  ui_image = UIImage.imageNamed_(ns_str)
72
73
  self.native_instance.setImage_(ui_image)
74
+ return self
73
75
 
74
76
  def get_image(self) -> str:
75
77
  # Similar to Android, getting the image from UIImageView isn't straightforward.
@@ -0,0 +1,133 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
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 LabelBase(ABC):
13
+ @abstractmethod
14
+ def __init__(self) -> None:
15
+ super().__init__()
16
+
17
+ @abstractmethod
18
+ def set_text(self, text: str) -> "LabelBase":
19
+ pass
20
+
21
+ @abstractmethod
22
+ def get_text(self) -> str:
23
+ pass
24
+
25
+ @abstractmethod
26
+ def set_text_color(self, color: Any) -> "LabelBase":
27
+ pass
28
+
29
+ @abstractmethod
30
+ def set_text_size(self, size: float) -> "LabelBase":
31
+ pass
32
+
33
+
34
+ if IS_ANDROID:
35
+ # ========================================
36
+ # Android class
37
+ # https://developer.android.com/reference/android/widget/TextView
38
+ # ========================================
39
+
40
+ from java import jclass
41
+
42
+ class Label(LabelBase, ViewBase):
43
+ def __init__(self, text: str = "") -> None:
44
+ super().__init__()
45
+ self.native_class = jclass("android.widget.TextView")
46
+ context = get_android_context()
47
+ self.native_instance = self.native_class(context)
48
+ self.set_text(text)
49
+
50
+ def set_text(self, text: str) -> "Label":
51
+ self.native_instance.setText(text)
52
+ return self
53
+
54
+ def get_text(self) -> str:
55
+ return self.native_instance.getText().toString()
56
+
57
+ def set_text_color(self, color: Any) -> "Label":
58
+ # Accept int ARGB or hex string
59
+ if isinstance(color, str):
60
+ c = color.strip()
61
+ if c.startswith("#"):
62
+ c = c[1:]
63
+ if len(c) == 6:
64
+ c = "FF" + c
65
+ color_int = int(c, 16)
66
+ else:
67
+ color_int = int(color)
68
+ try:
69
+ self.native_instance.setTextColor(color_int)
70
+ except Exception:
71
+ pass
72
+ return self
73
+
74
+ def set_text_size(self, size_sp: float) -> "Label":
75
+ try:
76
+ self.native_instance.setTextSize(float(size_sp))
77
+ except Exception:
78
+ pass
79
+ return self
80
+
81
+ else:
82
+ # ========================================
83
+ # iOS class
84
+ # https://developer.apple.com/documentation/uikit/uilabel
85
+ # ========================================
86
+
87
+ from rubicon.objc import ObjCClass
88
+
89
+ class Label(LabelBase, ViewBase):
90
+ def __init__(self, text: str = "") -> None:
91
+ super().__init__()
92
+ self.native_class = ObjCClass("UILabel")
93
+ self.native_instance = self.native_class.alloc().init()
94
+ self.set_text(text)
95
+
96
+ def set_text(self, text: str) -> "Label":
97
+ self.native_instance.setText_(text)
98
+ return self
99
+
100
+ def get_text(self) -> str:
101
+ return self.native_instance.text()
102
+
103
+ def set_text_color(self, color: Any) -> "Label":
104
+ # Accept int ARGB or hex string
105
+ if isinstance(color, str):
106
+ c = color.strip()
107
+ if c.startswith("#"):
108
+ c = c[1:]
109
+ if len(c) == 6:
110
+ c = "FF" + c
111
+ color_int = int(c, 16)
112
+ else:
113
+ color_int = int(color)
114
+ try:
115
+ UIColor = ObjCClass("UIColor")
116
+ a = ((color_int >> 24) & 0xFF) / 255.0
117
+ r = ((color_int >> 16) & 0xFF) / 255.0
118
+ g = ((color_int >> 8) & 0xFF) / 255.0
119
+ b = (color_int & 0xFF) / 255.0
120
+ color_obj = UIColor.colorWithRed_green_blue_alpha_(r, g, b, a)
121
+ self.native_instance.setTextColor_(color_obj)
122
+ except Exception:
123
+ pass
124
+ return self
125
+
126
+ def set_text_size(self, size: float) -> "Label":
127
+ try:
128
+ UIFont = ObjCClass("UIFont")
129
+ font = UIFont.systemFontOfSize_(float(size))
130
+ self.native_instance.setFont_(font)
131
+ except Exception:
132
+ pass
133
+ return self
@@ -1,4 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
+ from typing import Any
2
3
 
3
4
  from .utils import IS_ANDROID
4
5
  from .view import ViewBase
@@ -14,7 +15,7 @@ class ListViewBase(ABC):
14
15
  super().__init__()
15
16
 
16
17
  @abstractmethod
17
- def set_data(self, data: list) -> None:
18
+ def set_data(self, data: list) -> "ListViewBase":
18
19
  pass
19
20
 
20
21
  @abstractmethod
@@ -31,18 +32,19 @@ if IS_ANDROID:
31
32
  from java import jclass
32
33
 
33
34
  class ListView(ListViewBase, ViewBase):
34
- def __init__(self, context, data: list = []) -> None:
35
+ def __init__(self, context: Any, data: list = []) -> None:
35
36
  super().__init__()
36
37
  self.context = context
37
38
  self.native_class = jclass("android.widget.ListView")
38
39
  self.native_instance = self.native_class(context)
39
40
  self.set_data(data)
40
41
 
41
- def set_data(self, data: list) -> None:
42
+ def set_data(self, data: list) -> "ListView":
42
43
  adapter = jclass("android.widget.ArrayAdapter")(
43
44
  self.context, jclass("android.R$layout").simple_list_item_1, data
44
45
  )
45
46
  self.native_instance.setAdapter(adapter)
47
+ return self
46
48
 
47
49
  def get_data(self) -> list:
48
50
  adapter = self.native_instance.getAdapter()
@@ -63,9 +65,10 @@ else:
63
65
  self.native_instance = self.native_class.alloc().init()
64
66
  self.set_data(data)
65
67
 
66
- def set_data(self, data: list) -> None:
68
+ def set_data(self, data: list) -> "ListView":
67
69
  # Note: This is a simplified representation. Normally, you would need to create a UITableViewDataSource.
68
70
  self.native_instance.reloadData()
71
+ return self
69
72
 
70
73
  def get_data(self) -> list:
71
74
  # Note: This is a simplified representation.