pythonnative 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonnative/__init__.py +1 -1
- pythonnative/cli/pn.py +1 -8
- pythonnative/page.py +197 -10
- pythonnative/templates/android_template/app/build.gradle +3 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +10 -7
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +26 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PageFragment.kt +111 -0
- pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +6 -14
- pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +22 -0
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +7 -4
- pythonnative/templates/ios_template/ios_template/ViewController.swift +128 -28
- pythonnative/utils.py +25 -1
- {pythonnative-0.2.0.dist-info → pythonnative-0.3.0.dist-info}/METADATA +1 -1
- {pythonnative-0.2.0.dist-info → pythonnative-0.3.0.dist-info}/RECORD +18 -15
- {pythonnative-0.2.0.dist-info → pythonnative-0.3.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.2.0.dist-info → pythonnative-0.3.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.2.0.dist-info → pythonnative-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.2.0.dist-info → pythonnative-0.3.0.dist-info}/top_level.txt +0 -0
pythonnative/__init__.py
CHANGED
pythonnative/cli/pn.py
CHANGED
|
@@ -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:
|
|
@@ -61,13 +61,6 @@ class MainPage(pn.Page):
|
|
|
61
61
|
button.set_on_click(lambda: print("Button clicked"))
|
|
62
62
|
stack.add_view(button)
|
|
63
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
|
|
71
64
|
"""
|
|
72
65
|
)
|
|
73
66
|
|
pythonnative/page.py
CHANGED
|
@@ -29,7 +29,9 @@ Just ensure that your PythonNative UI framework is aware of these platform
|
|
|
29
29
|
differences and handles them appropriately.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
+
import json
|
|
32
33
|
from abc import ABC, abstractmethod
|
|
34
|
+
from typing import Any, Optional, Union
|
|
33
35
|
|
|
34
36
|
from .utils import IS_ANDROID, set_android_context
|
|
35
37
|
from .view import ViewBase
|
|
@@ -85,7 +87,25 @@ class PageBase(ABC):
|
|
|
85
87
|
pass
|
|
86
88
|
|
|
87
89
|
@abstractmethod
|
|
90
|
+
def set_args(self, args: Optional[dict]) -> None:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
@abstractmethod
|
|
94
|
+
def push(self, page: Union[str, Any], args: Optional[dict] = None) -> None:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def pop(self) -> None:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
def get_args(self) -> dict:
|
|
102
|
+
"""Return arguments provided to this Page (empty dict if none)."""
|
|
103
|
+
# Concrete classes should set self._args; default empty
|
|
104
|
+
return getattr(self, "_args", {})
|
|
105
|
+
|
|
106
|
+
# Back-compat: navigate_to delegates to push
|
|
88
107
|
def navigate_to(self, page) -> None:
|
|
108
|
+
self.push(page)
|
|
89
109
|
pass
|
|
90
110
|
|
|
91
111
|
|
|
@@ -105,9 +125,23 @@ if IS_ANDROID:
|
|
|
105
125
|
# self.native_instance = self.native_class()
|
|
106
126
|
# Stash the Activity so child views can implicitly acquire a Context
|
|
107
127
|
set_android_context(native_instance)
|
|
128
|
+
self._args: dict = {}
|
|
108
129
|
|
|
109
130
|
def set_root_view(self, view) -> None:
|
|
110
|
-
|
|
131
|
+
# In fragment-based navigation, attach child view to the current fragment container.
|
|
132
|
+
try:
|
|
133
|
+
from .utils import get_android_fragment_container
|
|
134
|
+
|
|
135
|
+
container = get_android_fragment_container()
|
|
136
|
+
# Remove previous children if any, then add the new root
|
|
137
|
+
try:
|
|
138
|
+
container.removeAllViews()
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
container.addView(view.native_instance)
|
|
142
|
+
except Exception:
|
|
143
|
+
# Fallback to setting content view directly on the Activity
|
|
144
|
+
self.native_instance.setContentView(view.native_instance)
|
|
111
145
|
|
|
112
146
|
def on_create(self) -> None:
|
|
113
147
|
print("Android on_create() called")
|
|
@@ -136,13 +170,53 @@ if IS_ANDROID:
|
|
|
136
170
|
def on_restore_instance_state(self) -> None:
|
|
137
171
|
print("Android on_restore_instance_state() called")
|
|
138
172
|
|
|
139
|
-
def
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
173
|
+
def set_args(self, args: Optional[dict]) -> None:
|
|
174
|
+
# Accept dict or JSON string for convenience when crossing language boundaries
|
|
175
|
+
if isinstance(args, str):
|
|
176
|
+
try:
|
|
177
|
+
self._args = json.loads(args) or {}
|
|
178
|
+
return
|
|
179
|
+
except Exception:
|
|
180
|
+
self._args = {}
|
|
181
|
+
return
|
|
182
|
+
self._args = args or {}
|
|
183
|
+
|
|
184
|
+
def _resolve_page_path(self, page: Union[str, Any]) -> str:
|
|
185
|
+
if isinstance(page, str):
|
|
186
|
+
return page
|
|
187
|
+
# If a class or instance is passed, derive dotted path
|
|
188
|
+
try:
|
|
189
|
+
module = getattr(page, "__module__", None)
|
|
190
|
+
name = getattr(page, "__name__", None)
|
|
191
|
+
if module and name:
|
|
192
|
+
return f"{module}.{name}"
|
|
193
|
+
# Instance: use its class
|
|
194
|
+
cls = page.__class__
|
|
195
|
+
return f"{cls.__module__}.{cls.__name__}"
|
|
196
|
+
except Exception:
|
|
197
|
+
raise ValueError("Unsupported page reference; expected dotted string or class/instance")
|
|
198
|
+
|
|
199
|
+
def push(self, page: Union[str, Any], args: Optional[dict] = None) -> None:
|
|
200
|
+
# Delegate to Navigator.push to navigate to PageFragment with arguments
|
|
201
|
+
page_path = self._resolve_page_path(page)
|
|
202
|
+
try:
|
|
203
|
+
Navigator = jclass(f"{self.native_instance.getPackageName()}.Navigator")
|
|
204
|
+
args_json = json.dumps(args) if args else None
|
|
205
|
+
Navigator.push(self.native_instance, page_path, args_json)
|
|
206
|
+
except Exception:
|
|
207
|
+
# As a last resort, do nothing rather than crash
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
def pop(self) -> None:
|
|
211
|
+
# Delegate to Navigator.pop for back-stack pop
|
|
212
|
+
try:
|
|
213
|
+
Navigator = jclass(f"{self.native_instance.getPackageName()}.Navigator")
|
|
214
|
+
Navigator.pop(self.native_instance)
|
|
215
|
+
except Exception:
|
|
216
|
+
try:
|
|
217
|
+
self.native_instance.finish()
|
|
218
|
+
except Exception:
|
|
219
|
+
pass
|
|
146
220
|
|
|
147
221
|
else:
|
|
148
222
|
# ========================================
|
|
@@ -150,8 +224,45 @@ else:
|
|
|
150
224
|
# https://developer.apple.com/documentation/uikit/uiviewcontroller
|
|
151
225
|
# ========================================
|
|
152
226
|
|
|
227
|
+
from typing import Dict
|
|
228
|
+
|
|
153
229
|
from rubicon.objc import ObjCClass, ObjCInstance
|
|
154
230
|
|
|
231
|
+
# Global registry mapping native UIViewController pointer address to Page instances.
|
|
232
|
+
_IOS_PAGE_REGISTRY: Dict[int, Any] = {}
|
|
233
|
+
|
|
234
|
+
def _ios_register_page(vc_instance: Any, page_obj: Any) -> None:
|
|
235
|
+
try:
|
|
236
|
+
ptr = int(vc_instance.ptr) # rubicon ObjCInstance -> c_void_p convertible to int
|
|
237
|
+
_IOS_PAGE_REGISTRY[ptr] = page_obj
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
def _ios_unregister_page(vc_instance: Any) -> None:
|
|
242
|
+
try:
|
|
243
|
+
ptr = int(vc_instance.ptr)
|
|
244
|
+
_IOS_PAGE_REGISTRY.pop(ptr, None)
|
|
245
|
+
except Exception:
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
def forward_lifecycle(native_addr: int, event: str) -> None:
|
|
249
|
+
"""Forward a lifecycle event from Swift ViewController to the registered Page.
|
|
250
|
+
|
|
251
|
+
:param native_addr: Integer pointer address of the UIViewController
|
|
252
|
+
:param event: One of 'on_start', 'on_resume', 'on_pause', 'on_stop', 'on_destroy',
|
|
253
|
+
'on_save_instance_state', 'on_restore_instance_state'.
|
|
254
|
+
"""
|
|
255
|
+
page = _IOS_PAGE_REGISTRY.get(int(native_addr))
|
|
256
|
+
if not page:
|
|
257
|
+
return
|
|
258
|
+
try:
|
|
259
|
+
handler = getattr(page, event, None)
|
|
260
|
+
if handler:
|
|
261
|
+
handler()
|
|
262
|
+
except Exception:
|
|
263
|
+
# Avoid surfacing exceptions across the Swift/Python boundary in lifecycle
|
|
264
|
+
pass
|
|
265
|
+
|
|
155
266
|
class Page(PageBase, ViewBase):
|
|
156
267
|
def __init__(self, native_instance) -> None:
|
|
157
268
|
super().__init__()
|
|
@@ -164,6 +275,10 @@ else:
|
|
|
164
275
|
native_instance = None
|
|
165
276
|
self.native_instance = native_instance
|
|
166
277
|
# self.native_instance = self.native_class.alloc().init()
|
|
278
|
+
self._args: dict = {}
|
|
279
|
+
# Register for lifecycle forwarding
|
|
280
|
+
if self.native_instance is not None:
|
|
281
|
+
_ios_register_page(self.native_instance, self)
|
|
167
282
|
|
|
168
283
|
def set_root_view(self, view) -> None:
|
|
169
284
|
# UIViewController.view is a property; access without calling.
|
|
@@ -195,6 +310,8 @@ else:
|
|
|
195
310
|
|
|
196
311
|
def on_destroy(self) -> None:
|
|
197
312
|
print("iOS on_destroy() called")
|
|
313
|
+
if self.native_instance is not None:
|
|
314
|
+
_ios_unregister_page(self.native_instance)
|
|
198
315
|
|
|
199
316
|
def on_restart(self) -> None:
|
|
200
317
|
print("iOS on_restart() called")
|
|
@@ -205,5 +322,75 @@ else:
|
|
|
205
322
|
def on_restore_instance_state(self) -> None:
|
|
206
323
|
print("iOS on_restore_instance_state() called")
|
|
207
324
|
|
|
208
|
-
def
|
|
209
|
-
|
|
325
|
+
def set_args(self, args: Optional[dict]) -> None:
|
|
326
|
+
if isinstance(args, str):
|
|
327
|
+
try:
|
|
328
|
+
self._args = json.loads(args) or {}
|
|
329
|
+
return
|
|
330
|
+
except Exception:
|
|
331
|
+
self._args = {}
|
|
332
|
+
return
|
|
333
|
+
self._args = args or {}
|
|
334
|
+
|
|
335
|
+
def _resolve_page_path(self, page: Union[str, Any]) -> str:
|
|
336
|
+
if isinstance(page, str):
|
|
337
|
+
return page
|
|
338
|
+
try:
|
|
339
|
+
module = getattr(page, "__module__", None)
|
|
340
|
+
name = getattr(page, "__name__", None)
|
|
341
|
+
if module and name:
|
|
342
|
+
return f"{module}.{name}"
|
|
343
|
+
cls = page.__class__
|
|
344
|
+
return f"{cls.__module__}.{cls.__name__}"
|
|
345
|
+
except Exception:
|
|
346
|
+
raise ValueError("Unsupported page reference; expected dotted string or class/instance")
|
|
347
|
+
|
|
348
|
+
def push(self, page: Union[str, Any], args: Optional[dict] = None) -> None:
|
|
349
|
+
page_path = self._resolve_page_path(page)
|
|
350
|
+
# Resolve the Swift ViewController class. Swift classes are namespaced by
|
|
351
|
+
# the module name (CFBundleName). Try plain name first, then Module.Name.
|
|
352
|
+
ViewController = None
|
|
353
|
+
try:
|
|
354
|
+
ViewController = ObjCClass("ViewController")
|
|
355
|
+
except Exception:
|
|
356
|
+
try:
|
|
357
|
+
NSBundle = ObjCClass("NSBundle")
|
|
358
|
+
bundle = NSBundle.mainBundle
|
|
359
|
+
module_name = None
|
|
360
|
+
try:
|
|
361
|
+
# Prefer CFBundleName; fallback to CFBundleExecutable
|
|
362
|
+
module_name = bundle.objectForInfoDictionaryKey_("CFBundleName")
|
|
363
|
+
if module_name is None:
|
|
364
|
+
module_name = bundle.objectForInfoDictionaryKey_("CFBundleExecutable")
|
|
365
|
+
except Exception:
|
|
366
|
+
module_name = None
|
|
367
|
+
if module_name:
|
|
368
|
+
ViewController = ObjCClass(f"{module_name}.ViewController")
|
|
369
|
+
except Exception:
|
|
370
|
+
ViewController = None
|
|
371
|
+
|
|
372
|
+
if ViewController is None:
|
|
373
|
+
raise NameError("ViewController class not found; ensure Swift class is ObjC-visible")
|
|
374
|
+
|
|
375
|
+
next_vc = ViewController.alloc().init()
|
|
376
|
+
try:
|
|
377
|
+
# Use KVC to pass metadata to Swift
|
|
378
|
+
next_vc.setValue_forKey_(page_path, "requestedPagePath")
|
|
379
|
+
if args:
|
|
380
|
+
next_vc.setValue_forKey_(json.dumps(args), "requestedPageArgsJSON")
|
|
381
|
+
except Exception:
|
|
382
|
+
pass
|
|
383
|
+
# On iOS, `navigationController` is exposed as a property; treat it as such.
|
|
384
|
+
nav = getattr(self.native_instance, "navigationController", None)
|
|
385
|
+
if nav is None:
|
|
386
|
+
# If no navigation controller, this push will be a no-op; rely on template to embed one.
|
|
387
|
+
raise RuntimeError(
|
|
388
|
+
"No UINavigationController available; ensure template embeds root in navigation controller"
|
|
389
|
+
)
|
|
390
|
+
# Method name maps from pushViewController:animated:
|
|
391
|
+
nav.pushViewController_animated_(next_vc, True)
|
|
392
|
+
|
|
393
|
+
def pop(self) -> None:
|
|
394
|
+
nav = getattr(self.native_instance, "navigationController", None)
|
|
395
|
+
if nav is not None:
|
|
396
|
+
nav.popViewControllerAnimated_(True)
|
|
@@ -53,6 +53,9 @@ dependencies {
|
|
|
53
53
|
implementation 'androidx.appcompat:appcompat:1.4.1'
|
|
54
54
|
implementation 'com.google.android.material:material:1.5.0'
|
|
55
55
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
|
56
|
+
// AndroidX Navigation for Fragment-based navigation
|
|
57
|
+
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
|
|
58
|
+
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
|
|
56
59
|
testImplementation 'junit:junit:4.13.2'
|
|
57
60
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
|
58
61
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
|
@@ -8,25 +8,28 @@ import com.chaquo.python.Python
|
|
|
8
8
|
import com.chaquo.python.android.AndroidPlatform
|
|
9
9
|
|
|
10
10
|
class MainActivity : AppCompatActivity() {
|
|
11
|
+
private val TAG = javaClass.simpleName
|
|
12
|
+
|
|
11
13
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
12
14
|
super.onCreate(savedInstanceState)
|
|
13
|
-
|
|
15
|
+
Log.d(TAG, "onCreate() called")
|
|
14
16
|
|
|
15
17
|
// Initialize Chaquopy
|
|
16
18
|
if (!Python.isStarted()) {
|
|
17
19
|
Python.start(AndroidPlatform(this))
|
|
18
20
|
}
|
|
19
21
|
try {
|
|
22
|
+
// Set content view to the NavHost layout; the initial page loads via nav_graph startDestination
|
|
23
|
+
setContentView(R.layout.activity_main)
|
|
24
|
+
// Optionally, bootstrap Python so first fragment can create the initial page onCreate
|
|
20
25
|
val py = Python.getInstance()
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// Python Page will set the content view via set_root_view
|
|
26
|
+
// Touch module to ensure bundled Python code is available; actual instantiation happens in PageFragment
|
|
27
|
+
py.getModule("app.main_page")
|
|
24
28
|
} catch (e: Exception) {
|
|
25
|
-
Log.e("PythonNative", "
|
|
26
|
-
// Fallback: show a simple native label if Python bootstrap fails
|
|
29
|
+
Log.e("PythonNative", "Bootstrap failed", e)
|
|
27
30
|
val tv = TextView(this)
|
|
28
31
|
tv.text = "Hello from PythonNative (Android template)"
|
|
29
32
|
setContentView(tv)
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
|
-
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
package com.pythonnative.android_template
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import androidx.core.os.bundleOf
|
|
5
|
+
import androidx.fragment.app.FragmentActivity
|
|
6
|
+
import androidx.navigation.fragment.NavHostFragment
|
|
7
|
+
|
|
8
|
+
object Navigator {
|
|
9
|
+
@JvmStatic
|
|
10
|
+
fun push(activity: FragmentActivity, pagePath: String, argsJson: String?) {
|
|
11
|
+
val navHost = activity.supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
|
|
12
|
+
val navController = navHost.navController
|
|
13
|
+
val args = Bundle()
|
|
14
|
+
args.putString("page_path", pagePath)
|
|
15
|
+
if (argsJson != null) {
|
|
16
|
+
args.putString("args_json", argsJson)
|
|
17
|
+
}
|
|
18
|
+
navController.navigate(R.id.pageFragment, args)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@JvmStatic
|
|
22
|
+
fun pop(activity: FragmentActivity) {
|
|
23
|
+
val navHost = activity.supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
|
|
24
|
+
navHost.navController.popBackStack()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package com.pythonnative.android_template
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import android.view.LayoutInflater
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import android.widget.FrameLayout
|
|
9
|
+
import androidx.core.os.bundleOf
|
|
10
|
+
import androidx.fragment.app.Fragment
|
|
11
|
+
import com.chaquo.python.PyObject
|
|
12
|
+
import com.chaquo.python.Python
|
|
13
|
+
import com.chaquo.python.android.AndroidPlatform
|
|
14
|
+
|
|
15
|
+
class PageFragment : Fragment() {
|
|
16
|
+
private val TAG = javaClass.simpleName
|
|
17
|
+
private var page: PyObject? = null
|
|
18
|
+
|
|
19
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
20
|
+
super.onCreate(savedInstanceState)
|
|
21
|
+
if (!Python.isStarted()) {
|
|
22
|
+
context?.let { Python.start(AndroidPlatform(it)) }
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
val py = Python.getInstance()
|
|
26
|
+
val pagePath = arguments?.getString("page_path") ?: "app.main_page.MainPage"
|
|
27
|
+
val argsJson = arguments?.getString("args_json")
|
|
28
|
+
val moduleName = pagePath.substringBeforeLast('.')
|
|
29
|
+
val className = pagePath.substringAfterLast('.')
|
|
30
|
+
val pyModule = py.getModule(moduleName)
|
|
31
|
+
val pageClass = pyModule.get(className)
|
|
32
|
+
// Pass the hosting Activity as native_instance for context
|
|
33
|
+
page = pageClass?.call(requireActivity())
|
|
34
|
+
if (!argsJson.isNullOrEmpty()) {
|
|
35
|
+
page?.callAttr("set_args", argsJson)
|
|
36
|
+
}
|
|
37
|
+
} catch (e: Exception) {
|
|
38
|
+
Log.e(TAG, "Failed to instantiate page", e)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override fun onCreateView(
|
|
43
|
+
inflater: LayoutInflater,
|
|
44
|
+
container: ViewGroup?,
|
|
45
|
+
savedInstanceState: Bundle?
|
|
46
|
+
): View? {
|
|
47
|
+
// Create a simple container which Python-native views can be attached to.
|
|
48
|
+
val frame = FrameLayout(requireContext())
|
|
49
|
+
frame.layoutParams = ViewGroup.LayoutParams(
|
|
50
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
51
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
52
|
+
)
|
|
53
|
+
return frame
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
57
|
+
super.onViewCreated(view, savedInstanceState)
|
|
58
|
+
// Python side will call set_root_view to attach a native view to Activity.
|
|
59
|
+
// In fragment-based architecture, the Activity will set contentView once,
|
|
60
|
+
// so we ensure the fragment's container is available for Python to target.
|
|
61
|
+
// Expose the fragment container to Python so Page.set_root_view can attach into it
|
|
62
|
+
try {
|
|
63
|
+
val py = Python.getInstance()
|
|
64
|
+
val utils = py.getModule("pythonnative.utils")
|
|
65
|
+
utils.callAttr("set_android_fragment_container", view)
|
|
66
|
+
// Now that container exists, invoke on_create so Python can attach its root view
|
|
67
|
+
page?.callAttr("on_create")
|
|
68
|
+
} catch (_: Exception) {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
override fun onStart() {
|
|
73
|
+
super.onStart()
|
|
74
|
+
try { page?.callAttr("on_start") } catch (e: Exception) { Log.w(TAG, "on_start failed", e) }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
override fun onResume() {
|
|
78
|
+
super.onResume()
|
|
79
|
+
try { page?.callAttr("on_resume") } catch (e: Exception) { Log.w(TAG, "on_resume failed", e) }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
override fun onPause() {
|
|
83
|
+
super.onPause()
|
|
84
|
+
try { page?.callAttr("on_pause") } catch (e: Exception) { Log.w(TAG, "on_pause failed", e) }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override fun onStop() {
|
|
88
|
+
super.onStop()
|
|
89
|
+
try { page?.callAttr("on_stop") } catch (e: Exception) { Log.w(TAG, "on_stop failed", e) }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override fun onDestroyView() {
|
|
93
|
+
super.onDestroyView()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override fun onDestroy() {
|
|
97
|
+
super.onDestroy()
|
|
98
|
+
try { page?.callAttr("on_destroy") } catch (e: Exception) { Log.w(TAG, "on_destroy failed", e) }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
companion object {
|
|
102
|
+
fun newInstance(pagePath: String, argsJson: String?): PageFragment {
|
|
103
|
+
val f = PageFragment()
|
|
104
|
+
f.arguments = bundleOf(
|
|
105
|
+
"page_path" to pagePath,
|
|
106
|
+
"args_json" to argsJson
|
|
107
|
+
)
|
|
108
|
+
return f
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
-
<androidx.
|
|
2
|
+
<androidx.fragment.app.FragmentContainerView
|
|
3
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
4
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
4
|
-
|
|
5
|
+
android:id="@+id/nav_host_fragment"
|
|
6
|
+
android:name="androidx.navigation.fragment.NavHostFragment"
|
|
5
7
|
android:layout_width="match_parent"
|
|
6
8
|
android:layout_height="match_parent"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<TextView
|
|
10
|
-
android:layout_width="wrap_content"
|
|
11
|
-
android:layout_height="wrap_content"
|
|
12
|
-
android:text="Hello World!"
|
|
13
|
-
app:layout_constraintBottom_toBottomOf="parent"
|
|
14
|
-
app:layout_constraintEnd_toEndOf="parent"
|
|
15
|
-
app:layout_constraintStart_toStartOf="parent"
|
|
16
|
-
app:layout_constraintTop_toTopOf="parent" />
|
|
17
|
-
|
|
18
|
-
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
9
|
+
app:defaultNavHost="true"
|
|
10
|
+
app:navGraph="@navigation/nav_graph" />
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
4
|
+
xmlns:tools="http://schemas.android.com/tools"
|
|
5
|
+
android:id="@+id/nav_graph"
|
|
6
|
+
app:startDestination="@id/pageFragment">
|
|
7
|
+
|
|
8
|
+
<fragment
|
|
9
|
+
android:id="@+id/pageFragment"
|
|
10
|
+
android:name="com.pythonnative.android_template.PageFragment"
|
|
11
|
+
android:label="PageFragment">
|
|
12
|
+
<argument
|
|
13
|
+
android:name="page_path"
|
|
14
|
+
app:argType="string"
|
|
15
|
+
android:defaultValue="app.main_page.MainPage" />
|
|
16
|
+
<argument
|
|
17
|
+
android:name="args_json"
|
|
18
|
+
app:argType="string"
|
|
19
|
+
android:nullable="true" />
|
|
20
|
+
</fragment>
|
|
21
|
+
|
|
22
|
+
</navigation>
|
|
@@ -13,10 +13,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
guard let windowScene = (scene as? UIWindowScene) else { return }
|
|
17
|
+
let window = UIWindow(windowScene: windowScene)
|
|
18
|
+
let root = ViewController()
|
|
19
|
+
let nav = UINavigationController(rootViewController: root)
|
|
20
|
+
window.rootViewController = nav
|
|
21
|
+
self.window = window
|
|
22
|
+
window.makeKeyAndVisible()
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
func sceneDidDisconnect(_ scene: UIScene) {
|
|
@@ -16,9 +16,17 @@ import Python
|
|
|
16
16
|
#endif
|
|
17
17
|
|
|
18
18
|
class ViewController: UIViewController {
|
|
19
|
+
// Ensure Python.framework is configured only once per process
|
|
20
|
+
private static var hasInitializedPython: Bool = false
|
|
21
|
+
// Optional keys for dynamic page navigation
|
|
22
|
+
@objc dynamic var requestedPagePath: String? = nil
|
|
23
|
+
@objc dynamic var requestedPageArgsJSON: String? = nil
|
|
24
|
+
private var pythonReady: Bool = false
|
|
19
25
|
|
|
20
26
|
override func viewDidLoad() {
|
|
21
27
|
super.viewDidLoad()
|
|
28
|
+
// Ensure a visible background when created programmatically (storyboards set this automatically)
|
|
29
|
+
view.backgroundColor = .systemBackground
|
|
22
30
|
NSLog("[PN][ViewController] viewDidLoad")
|
|
23
31
|
if let bundleId = Bundle.main.bundleIdentifier {
|
|
24
32
|
NSLog("[PN] Bundle Identifier: \(bundleId)")
|
|
@@ -45,14 +53,19 @@ class ViewController: UIViewController {
|
|
|
45
53
|
let frameworkLib = "\(bundlePath)/Frameworks/Python.framework/Python"
|
|
46
54
|
setenv("PYTHON_LIBRARY", frameworkLib, 1)
|
|
47
55
|
if FileManager.default.fileExists(atPath: frameworkLib) {
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
if !ViewController.hasInitializedPython {
|
|
57
|
+
NSLog("[PN] Using embedded Python lib at: \(frameworkLib)")
|
|
58
|
+
PythonLibrary.useLibrary(at: frameworkLib)
|
|
59
|
+
ViewController.hasInitializedPython = true
|
|
60
|
+
} else {
|
|
61
|
+
NSLog("[PN] Python library already initialized; skipping useLibrary")
|
|
62
|
+
}
|
|
63
|
+
pythonReady = true
|
|
50
64
|
} else {
|
|
51
65
|
NSLog("[PN] Embedded Python library not found at: \(frameworkLib)")
|
|
52
66
|
}
|
|
53
67
|
}
|
|
54
|
-
NSLog("[PN] PythonKit available; attempting Python bootstrap
|
|
55
|
-
// Attempt Python bootstrap of app.main_page.bootstrap(self)
|
|
68
|
+
NSLog("[PN] PythonKit available; attempting Python bootstrap")
|
|
56
69
|
let sys = Python.import("sys")
|
|
57
70
|
NSLog("[PN] Python version: \(sys.version)")
|
|
58
71
|
NSLog("[PN] Initial sys.path: \(sys.path)")
|
|
@@ -69,38 +82,35 @@ class ViewController: UIViewController {
|
|
|
69
82
|
NSLog("[PN] Could not list contents of \(appDir).")
|
|
70
83
|
}
|
|
71
84
|
}
|
|
85
|
+
// Determine which Python page to load
|
|
86
|
+
let pagePath: String = requestedPagePath ?? "app.main_page.MainPage"
|
|
72
87
|
do {
|
|
73
|
-
let
|
|
74
|
-
let
|
|
88
|
+
let moduleName = String(pagePath.split(separator: ".").dropLast().joined(separator: "."))
|
|
89
|
+
let className = String(pagePath.split(separator: ".").last ?? "MainPage")
|
|
90
|
+
let pyModule = try Python.attemptImport(moduleName)
|
|
91
|
+
// Resolve class by name via builtins.getattr to avoid subscripting issues
|
|
75
92
|
let builtins = Python.import("builtins")
|
|
76
93
|
let getattrFn = builtins.getattr
|
|
77
|
-
let
|
|
78
|
-
|
|
94
|
+
let pageClass = try getattrFn.throwing.dynamicallyCall(withArguments: [pyModule, className])
|
|
95
|
+
// Pass native pointer so Python Page can wrap via rubicon.objc
|
|
96
|
+
let ptr = Unmanaged.passUnretained(self).toOpaque()
|
|
97
|
+
let addr = UInt(bitPattern: ptr)
|
|
98
|
+
let page = try pageClass.throwing.dynamicallyCall(withArguments: [addr])
|
|
99
|
+
// If args provided, pass into Page via set_args(dict)
|
|
100
|
+
if let jsonStr = requestedPageArgsJSON {
|
|
101
|
+
let json = Python.import("json")
|
|
79
102
|
do {
|
|
80
|
-
let
|
|
81
|
-
|
|
82
|
-
// Pass the native UIViewController pointer into Python so it can be wrapped by rubicon.objc
|
|
83
|
-
let ptr = Unmanaged.passUnretained(self).toOpaque()
|
|
84
|
-
let addr = UInt(bitPattern: ptr)
|
|
85
|
-
NSLog("[PN] Passing native UIViewController pointer to Python: 0x%llx", addr)
|
|
86
|
-
_ = try bootstrap.throwing.dynamicallyCall(withArguments: [addr])
|
|
87
|
-
NSLog("[PN] Python bootstrap succeeded; returning early from viewDidLoad")
|
|
88
|
-
return
|
|
89
|
-
} else {
|
|
90
|
-
NSLog("[PN] 'bootstrap' exists but is not callable")
|
|
91
|
-
}
|
|
103
|
+
let args = try json.loads.throwing.dynamicallyCall(withArguments: [jsonStr])
|
|
104
|
+
_ = try page.set_args.throwing.dynamicallyCall(withArguments: [args])
|
|
92
105
|
} catch {
|
|
93
|
-
NSLog("[PN]
|
|
94
|
-
let sys = Python.import("sys")
|
|
95
|
-
NSLog("[PN] sys.path at call error: \(sys.path)")
|
|
106
|
+
NSLog("[PN] Failed to decode requestedPageArgsJSON: \(error)")
|
|
96
107
|
}
|
|
97
|
-
} else {
|
|
98
|
-
NSLog("[PN] Python bootstrap function not found on app.main_page")
|
|
99
108
|
}
|
|
109
|
+
// Call on_create immediately so Python can insert its root view
|
|
110
|
+
_ = try page.on_create.throwing.dynamicallyCall(withArguments: [])
|
|
111
|
+
return
|
|
100
112
|
} catch {
|
|
101
|
-
NSLog("[PN] Python bootstrap failed
|
|
102
|
-
let sys = Python.import("sys")
|
|
103
|
-
NSLog("[PN] sys.path at failure: \(sys.path)")
|
|
113
|
+
NSLog("[PN] Python bootstrap failed: \(error)")
|
|
104
114
|
}
|
|
105
115
|
#endif
|
|
106
116
|
|
|
@@ -113,6 +123,96 @@ class ViewController: UIViewController {
|
|
|
113
123
|
view.addSubview(label)
|
|
114
124
|
}
|
|
115
125
|
|
|
126
|
+
override func viewWillAppear(_ animated: Bool) {
|
|
127
|
+
super.viewWillAppear(animated)
|
|
128
|
+
#if canImport(PythonKit)
|
|
129
|
+
if pythonReady {
|
|
130
|
+
let ptr = UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque())
|
|
131
|
+
do {
|
|
132
|
+
let pn = try Python.attemptImport("pythonnative.page")
|
|
133
|
+
_ = try pn.forward_lifecycle.throwing.dynamicallyCall(withArguments: [ptr, "on_start"])
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
136
|
+
#endif
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
override func viewDidAppear(_ animated: Bool) {
|
|
140
|
+
super.viewDidAppear(animated)
|
|
141
|
+
#if canImport(PythonKit)
|
|
142
|
+
if pythonReady {
|
|
143
|
+
let ptr = UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque())
|
|
144
|
+
do {
|
|
145
|
+
let pn = try Python.attemptImport("pythonnative.page")
|
|
146
|
+
_ = try pn.forward_lifecycle.throwing.dynamicallyCall(withArguments: [ptr, "on_resume"])
|
|
147
|
+
} catch {}
|
|
148
|
+
}
|
|
149
|
+
#endif
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
override func viewWillDisappear(_ animated: Bool) {
|
|
153
|
+
super.viewWillDisappear(animated)
|
|
154
|
+
#if canImport(PythonKit)
|
|
155
|
+
if pythonReady {
|
|
156
|
+
let ptr = UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque())
|
|
157
|
+
do {
|
|
158
|
+
let pn = try Python.attemptImport("pythonnative.page")
|
|
159
|
+
_ = try pn.forward_lifecycle.throwing.dynamicallyCall(withArguments: [ptr, "on_pause"])
|
|
160
|
+
} catch {}
|
|
161
|
+
}
|
|
162
|
+
#endif
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
override func viewDidDisappear(_ animated: Bool) {
|
|
166
|
+
super.viewDidDisappear(animated)
|
|
167
|
+
#if canImport(PythonKit)
|
|
168
|
+
if pythonReady {
|
|
169
|
+
let ptr = UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque())
|
|
170
|
+
do {
|
|
171
|
+
let pn = try Python.attemptImport("pythonnative.page")
|
|
172
|
+
_ = try pn.forward_lifecycle.throwing.dynamicallyCall(withArguments: [ptr, "on_stop"])
|
|
173
|
+
} catch {}
|
|
174
|
+
}
|
|
175
|
+
#endif
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
override func encodeRestorableState(with coder: NSCoder) {
|
|
179
|
+
super.encodeRestorableState(with: coder)
|
|
180
|
+
#if canImport(PythonKit)
|
|
181
|
+
if pythonReady {
|
|
182
|
+
let ptr = UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque())
|
|
183
|
+
do {
|
|
184
|
+
let pn = try Python.attemptImport("pythonnative.page")
|
|
185
|
+
_ = try pn.forward_lifecycle.throwing.dynamicallyCall(withArguments: [ptr, "on_save_instance_state"])
|
|
186
|
+
} catch {}
|
|
187
|
+
}
|
|
188
|
+
#endif
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
override func decodeRestorableState(with coder: NSCoder) {
|
|
192
|
+
super.decodeRestorableState(with: coder)
|
|
193
|
+
#if canImport(PythonKit)
|
|
194
|
+
if pythonReady {
|
|
195
|
+
let ptr = UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque())
|
|
196
|
+
do {
|
|
197
|
+
let pn = try Python.attemptImport("pythonnative.page")
|
|
198
|
+
_ = try pn.forward_lifecycle.throwing.dynamicallyCall(withArguments: [ptr, "on_restore_instance_state"])
|
|
199
|
+
} catch {}
|
|
200
|
+
}
|
|
201
|
+
#endif
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
deinit {
|
|
205
|
+
#if canImport(PythonKit)
|
|
206
|
+
if pythonReady {
|
|
207
|
+
let ptr = UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque())
|
|
208
|
+
do {
|
|
209
|
+
let pn = try Python.attemptImport("pythonnative.page")
|
|
210
|
+
_ = try pn.forward_lifecycle.throwing.dynamicallyCall(withArguments: [ptr, "on_destroy"])
|
|
211
|
+
} catch {}
|
|
212
|
+
}
|
|
213
|
+
#endif
|
|
214
|
+
}
|
|
215
|
+
|
|
116
216
|
|
|
117
217
|
}
|
|
118
218
|
|
pythonnative/utils.py
CHANGED
|
@@ -39,8 +39,9 @@ def _get_is_android() -> bool:
|
|
|
39
39
|
|
|
40
40
|
IS_ANDROID: bool = _get_is_android()
|
|
41
41
|
|
|
42
|
-
# Global
|
|
42
|
+
# Global hooks to access current Android Activity/Context and Fragment container from Python code
|
|
43
43
|
_android_context: Any = None
|
|
44
|
+
_android_fragment_container: Any = None
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
def set_android_context(context: Any) -> None:
|
|
@@ -55,6 +56,15 @@ def set_android_context(context: Any) -> None:
|
|
|
55
56
|
_android_context = context
|
|
56
57
|
|
|
57
58
|
|
|
59
|
+
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
|
+
"""
|
|
64
|
+
global _android_fragment_container
|
|
65
|
+
_android_fragment_container = container_view
|
|
66
|
+
|
|
67
|
+
|
|
58
68
|
def get_android_context() -> Any:
|
|
59
69
|
"""Return the previously set Android Activity/Context or raise if missing."""
|
|
60
70
|
|
|
@@ -65,3 +75,17 @@ def get_android_context() -> Any:
|
|
|
65
75
|
"Android context is not set. Ensure Page is initialized from an Activity " "before constructing views."
|
|
66
76
|
)
|
|
67
77
|
return _android_context
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
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
|
+
"""
|
|
85
|
+
if not IS_ANDROID:
|
|
86
|
+
raise RuntimeError("get_android_fragment_container() called on non-Android platform")
|
|
87
|
+
if _android_fragment_container is None:
|
|
88
|
+
raise RuntimeError(
|
|
89
|
+
"Android fragment container is not set. Ensure PageFragment has been created before set_root_view."
|
|
90
|
+
)
|
|
91
|
+
return _android_fragment_container
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
pythonnative/__init__.py,sha256=
|
|
1
|
+
pythonnative/__init__.py,sha256=wU7uPSQH-YZsJs1nCdZ9XveG3QTKETbFQjUzQiDY06s,2050
|
|
2
2
|
pythonnative/activity_indicator_view.py,sha256=cYRiyGf5o4dlEy7v6v9yUt212E4cK6Hpn85P7uRIin0,2314
|
|
3
3
|
pythonnative/button.py,sha256=UMwgjb6EhxLQgf3OIXtnoydy5pVEg_1B3fsOp2dLISo,3766
|
|
4
4
|
pythonnative/collection_view.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -15,7 +15,7 @@ pythonnative/material_search_bar.py,sha256=j8a52smIUAem48AJ693g-aeNswNHpHp3N8M0q
|
|
|
15
15
|
pythonnative/material_switch.py,sha256=b4K6pKX_RQbmlEiL9nNEZiyOhkvJd5t_m4Ak7RoD7FE,1944
|
|
16
16
|
pythonnative/material_time_picker.py,sha256=bCUcrqsIRbFnt4g-vXfiSuuJrbaJJJjLrSSV_nSdOe8,2319
|
|
17
17
|
pythonnative/material_toolbar.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
pythonnative/page.py,sha256=
|
|
18
|
+
pythonnative/page.py,sha256=pJyEmpbMWRC3-iJnZbkBfXr0DcDQK9uYSgdd2u3yzAA,14962
|
|
19
19
|
pythonnative/picker_view.py,sha256=gGryk2SmBi0aWY_faJapWcWGGFCuabx_FaRidnXzIhU,1960
|
|
20
20
|
pythonnative/progress_view.py,sha256=Kyq4Ed4DzDXBr64KYEbyUJJW9WLxN-01QWsHgpU7ymM,2251
|
|
21
21
|
pythonnative/scroll_view.py,sha256=elvqWPihQBREqUPQtAwdPtHVp2prIkg5arKhI-u8His,2024
|
|
@@ -25,24 +25,26 @@ pythonnative/switch.py,sha256=Ca-XJnHMQdqJrLzDMRZGmm2d7J7GIZ_B0F1xxXF85Zs,1896
|
|
|
25
25
|
pythonnative/text_field.py,sha256=CaLH9erxY0hfKWYkkrF9rTkzgmt7Nax8qjqy-qW_Lmc,1962
|
|
26
26
|
pythonnative/text_view.py,sha256=ZO3Ff_vl4CX1evYYTHZqzgYhpxyvk0bSKI2oIMHW9bs,2140
|
|
27
27
|
pythonnative/time_picker.py,sha256=StGElEDNKnuw1j6iVPyIMwBAyZ5yxEwx2XTzfam2a0M,2258
|
|
28
|
-
pythonnative/utils.py,sha256=
|
|
28
|
+
pythonnative/utils.py,sha256=0Ub6DDg4uX4ToqoyioQzUJ-_ERzXlJ2r70pBSmeiaYY,3038
|
|
29
29
|
pythonnative/view.py,sha256=PJ0vNdRkyKKtiIxTYYDU0iDvR2r4WG8ZVUmMSeeJhmE,586
|
|
30
30
|
pythonnative/web_view.py,sha256=knBh-R1jP_JChADIo1SqwI5mCbonJuI81Q6vdWot7UU,1775
|
|
31
31
|
pythonnative/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
-
pythonnative/cli/pn.py,sha256=
|
|
32
|
+
pythonnative/cli/pn.py,sha256=UbkZtkYMNuuC9HylEhF44vQ39JRG4UTdA8Hb1lOLDrI,27136
|
|
33
33
|
pythonnative/templates/android_template/build.gradle,sha256=nM8OKenPZzdTJjzZlpl3EpLAoj1YL9A1arX0UPSvaL4,352
|
|
34
34
|
pythonnative/templates/android_template/gradle.properties,sha256=REPaKLRfQiiVfIV8wYmgwzPWvF1f3bhh_kAMV9p4HME,1358
|
|
35
35
|
pythonnative/templates/android_template/gradlew,sha256=YxNShxF6Hm0SyEWA8fScYdG6AiGOzShmBgXpf5dufWU,5766
|
|
36
36
|
pythonnative/templates/android_template/gradlew.bat,sha256=xGonx5AHdG3lkisXq7YjDWStixujrRWF7lxlQ8KpsSk,2674
|
|
37
37
|
pythonnative/templates/android_template/settings.gradle,sha256=GKZiYUYWsaXxaiKOB65xnOs4jLmf0rhvI_3f8x0ic-o,333
|
|
38
|
-
pythonnative/templates/android_template/app/build.gradle,sha256=
|
|
38
|
+
pythonnative/templates/android_template/app/build.gradle,sha256=y0-v6bUcVV1nlm0N90h_jbnYplAhIJTEhSoGiTC3k3Q,1863
|
|
39
39
|
pythonnative/templates/android_template/app/proguard-rules.pro,sha256=Vv2WDPIl9spA-YKxOl27DYvD394T_3ZCKCXGBw0KGJA,750
|
|
40
40
|
pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt,sha256=Am8Yla3i1eR_ac5FVgPU_RsuMrCbyT79h1BcajGE-zI,693
|
|
41
41
|
pythonnative/templates/android_template/app/src/main/AndroidManifest.xml,sha256=MdWrXxOrwUjnqtDbV952NI4nVF2dTUX9xwSS8chhd9I,940
|
|
42
|
-
pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt,sha256=
|
|
42
|
+
pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt,sha256=sqOQ4k--WVyfnrhfNkzqAEQ211uYNPxhmIUXDrIb0zY,1321
|
|
43
|
+
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
|
|
43
45
|
pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml,sha256=7UI8c6b0Ck0pCfCQHmBSezqAfNWeG1WTvKrhgIscYyE,5606
|
|
44
46
|
pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml,sha256=AdGmpsEjTrf-Jw0JfrKD1yucla5RGIhvG2VzqtKA8fc,1702
|
|
45
|
-
pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml,sha256=
|
|
47
|
+
pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml,sha256=HIgdCNktb3YoJC8QOTIv-0qZRtMRoPdARK59nyYFO6g,461
|
|
46
48
|
pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml,sha256=iPdlNJnvUkEm6lAYqZuvnMMmnn5YTWIF39122znznMA,343
|
|
47
49
|
pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml,sha256=iPdlNJnvUkEm6lAYqZuvnMMmnn5YTWIF39122znznMA,343
|
|
48
50
|
pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp,sha256=3QCZYZhkDtKPvAnNzXo4B8-HB_PrJVtlljTaPKam_wE,1404
|
|
@@ -55,6 +57,7 @@ pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launch
|
|
|
55
57
|
pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp,sha256=MAn60Hn1dy8w7Ndn-YkkNn--D4HDAEjWctT9otXKfRI,5914
|
|
56
58
|
pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp,sha256=-Y_vW8O_5bZWksQK0cuuK-xPr58bJJw5diZ0DbcbYtg,3844
|
|
57
59
|
pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp,sha256=X68DN0XIyILEO7GzctTkpYkMrsY9mJKi51bJVBCLhdw,7778
|
|
60
|
+
pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml,sha256=QGvhfqtiWKIu9U_MQboPV3leLByyRgDvyWMoyGCFXKc,763
|
|
58
61
|
pythonnative/templates/android_template/app/src/main/res/values/colors.xml,sha256=77cNdJlUmlfOoysA55DvBjLLDJXNru_RaQPIzRLIieQ,147
|
|
59
62
|
pythonnative/templates/android_template/app/src/main/res/values/strings.xml,sha256=y212ihQZPuuegFVU54kyOMwU50J759yi6h1CNAo-RDc,78
|
|
60
63
|
pythonnative/templates/android_template/app/src/main/res/values/themes.xml,sha256=8Amp6l23WClwtnLrfXYI2UFvC1eS6JzMRivN57rQBzw,420
|
|
@@ -66,8 +69,8 @@ pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar,sha256
|
|
|
66
69
|
pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties,sha256=EWegb_Gq2up5HPSrDpYbDQ5NwrRIWmTFTdkBSDNia_4,230
|
|
67
70
|
pythonnative/templates/ios_template/ios_template/AppDelegate.swift,sha256=_6G8GNcw4idXd75qKgQKTDCr45Ez73QB8WTvhBqqcMw,1349
|
|
68
71
|
pythonnative/templates/ios_template/ios_template/Info.plist,sha256=ZQIJGpo8Y2qP0j29xqOsIEGvPpEVICLTAw2NehC5CSo,704
|
|
69
|
-
pythonnative/templates/ios_template/ios_template/SceneDelegate.swift,sha256=
|
|
70
|
-
pythonnative/templates/ios_template/ios_template/ViewController.swift,sha256=
|
|
72
|
+
pythonnative/templates/ios_template/ios_template/SceneDelegate.swift,sha256=lqtre92dc6d6s-f4ieh_M_4xmc_zMGW79j46tDu9cOY,2177
|
|
73
|
+
pythonnative/templates/ios_template/ios_template/ViewController.swift,sha256=CvIpVOLRY3WSmm-_25N441b_8kaYKgmfkkQChNcBU7M,9241
|
|
71
74
|
pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json,sha256=D9Sbo8NYXHCWeOAEaoIcPGBoXscGNyDTDTo0SL46IIs,63
|
|
72
75
|
pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json,sha256=mvZQhvowtJJS-uGhIlcxaR3nlPd3WvdNcb7-tQfRK3w,123
|
|
73
76
|
pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json,sha256=VUwGr7K_geOvQjFh5VKB6iVXV1mi0tjGMinUmB2JvQs,177
|
|
@@ -78,9 +81,9 @@ pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/x
|
|
|
78
81
|
pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift,sha256=YnwzZx7yXB13xKAXEGNgz17VuhWeqkHTRTtBJ2Vu3_E,1238
|
|
79
82
|
pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift,sha256=l2Pwa50F_rv-qPu2go6e4bQernM6PTQJeNPFl_c4ivY,1387
|
|
80
83
|
pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift,sha256=f5JrG0uVtLMeJQy26Yyz7Om-JUkT220osqcbeIVkj2g,815
|
|
81
|
-
pythonnative-0.
|
|
82
|
-
pythonnative-0.
|
|
83
|
-
pythonnative-0.
|
|
84
|
-
pythonnative-0.
|
|
85
|
-
pythonnative-0.
|
|
86
|
-
pythonnative-0.
|
|
84
|
+
pythonnative-0.3.0.dist-info/licenses/LICENSE,sha256=O7jIzERBe5XxsbuZaWnPBneOk6JoSYNj6g2M0noRTws,1069
|
|
85
|
+
pythonnative-0.3.0.dist-info/METADATA,sha256=jaDrFxaYR4ZQvhmSMSP7FXIDPKisfMXZh1eAUL9y5ts,5083
|
|
86
|
+
pythonnative-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
87
|
+
pythonnative-0.3.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
|
|
88
|
+
pythonnative-0.3.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
|
|
89
|
+
pythonnative-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|