pythonnative 0.25.0__py3-none-any.whl → 0.26.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 +172 -12
- pythonnative/diagnostics.py +27 -0
- pythonnative/native_modules/app_state.py +2 -1
- pythonnative/native_modules/battery.py +3 -2
- pythonnative/native_modules/biometrics.py +2 -1
- pythonnative/native_modules/camera.py +16 -7
- pythonnative/native_modules/clipboard.py +3 -2
- pythonnative/native_modules/file_system.py +3 -2
- pythonnative/native_modules/haptics.py +7 -6
- pythonnative/native_modules/linking.py +62 -6
- pythonnative/native_modules/location.py +11 -8
- pythonnative/native_modules/net_info.py +2 -1
- pythonnative/native_modules/notifications.py +97 -6
- pythonnative/native_modules/permissions.py +3 -2
- pythonnative/native_modules/share.py +2 -1
- pythonnative/native_views/android.py +142 -122
- pythonnative/native_views/desktop.py +50 -49
- pythonnative/native_views/ios.py +228 -160
- pythonnative/project/android.py +15 -3
- pythonnative/project/builder.py +172 -70
- pythonnative/project/config.py +28 -6
- pythonnative/project/devices.py +291 -0
- pythonnative/project/doctor.py +5 -4
- pythonnative/project/ios.py +51 -77
- pythonnative/project/permissions.py +19 -1
- pythonnative/project/runtime_assets.py +78 -166
- pythonnative/templates/android_template/app/build.gradle +5 -0
- pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +1 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +88 -12
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +41 -17
- pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
- pythonnative/templates/ios_template/ios_template/Info.plist +0 -2
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +397 -0
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +31 -20
- pythonnative/templates/ios_template/ios_template/ViewController.swift +77 -198
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +72 -47
- {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/METADATA +1 -1
- {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/RECORD +43 -41
- pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -24
- {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/top_level.txt +0 -0
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
iOS apps can't rely on a system Python, so PythonNative bundles a copy of
|
|
4
4
|
CPython built for iOS by the excellent
|
|
5
5
|
[Python-Apple-support](https://github.com/beeware/Python-Apple-support)
|
|
6
|
-
project. This module downloads the pinned release asset
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
``Python.xcframework
|
|
10
|
-
|
|
6
|
+
project. This module downloads the pinned release asset for the
|
|
7
|
+
project's ``app.python_version``, verifies its checksum, extracts it
|
|
8
|
+
once (cached under the build directory), and exposes the path to
|
|
9
|
+
``Python.xcframework``.
|
|
10
|
+
|
|
11
|
+
The xcframework is linked and embedded by the bundled Xcode template at
|
|
12
|
+
build time; its ``build/utils.sh`` helper (shipped inside the framework
|
|
13
|
+
by BeeWare) installs the standard library and converts binary modules
|
|
14
|
+
into signed frameworks during the Xcode build. There is no post-build
|
|
15
|
+
copy step.
|
|
11
16
|
|
|
12
17
|
Android doesn't need any of this: Chaquopy ships its own CPython via
|
|
13
18
|
Gradle, so there's no Android equivalent here.
|
|
@@ -16,23 +21,34 @@ Gradle, so there's no Android equivalent here.
|
|
|
16
21
|
from __future__ import annotations
|
|
17
22
|
|
|
18
23
|
import hashlib
|
|
19
|
-
import json
|
|
20
|
-
import os
|
|
21
24
|
import tarfile
|
|
22
25
|
import urllib.request
|
|
23
26
|
from dataclasses import dataclass
|
|
24
27
|
from pathlib import Path
|
|
25
|
-
from typing import Callable,
|
|
26
|
-
|
|
27
|
-
# Pinned, checksum-verified
|
|
28
|
-
|
|
28
|
+
from typing import Callable, Optional
|
|
29
|
+
|
|
30
|
+
# Pinned, checksum-verified Python-Apple-support assets. Every version in
|
|
31
|
+
# ``config.SUPPORTED_PYTHON_VERSIONS`` must have an entry here; iOS builds
|
|
32
|
+
# refuse to run against an unpinned, unverified runtime.
|
|
33
|
+
PINNED_ASSETS = {
|
|
34
|
+
"3.10": (
|
|
35
|
+
"3.10-b14",
|
|
36
|
+
"Python-3.10-iOS-support.b14.tar.gz",
|
|
37
|
+
"a6da479a67be74569813af77179dc8ac83a5e685324408110bfc04632166e404",
|
|
38
|
+
),
|
|
29
39
|
"3.11": (
|
|
30
|
-
"
|
|
31
|
-
"
|
|
40
|
+
"3.11-b9",
|
|
41
|
+
"Python-3.11-iOS-support.b9.tar.gz",
|
|
42
|
+
"56810335d2b73558f7a16b6b2f7ad855b88c3007b200cbaecae9d6a25a2d1ecc",
|
|
43
|
+
),
|
|
44
|
+
"3.12": (
|
|
45
|
+
"3.12-b9",
|
|
46
|
+
"Python-3.12-iOS-support.b9.tar.gz",
|
|
47
|
+
"a3be9e278c742911db54dd3045bd7451928813508771c9acf14b4af75294edd2",
|
|
32
48
|
),
|
|
33
49
|
}
|
|
34
50
|
|
|
35
|
-
|
|
51
|
+
_DOWNLOAD_URL = "https://github.com/beeware/Python-Apple-support/releases/download/{tag}/{name}"
|
|
36
52
|
_USER_AGENT = "pythonnative-cli"
|
|
37
53
|
|
|
38
54
|
Logger = Callable[[str], None]
|
|
@@ -40,98 +56,20 @@ Logger = Callable[[str], None]
|
|
|
40
56
|
|
|
41
57
|
@dataclass
|
|
42
58
|
class IOSRuntime:
|
|
43
|
-
"""
|
|
59
|
+
"""A resolved, extracted iOS CPython support package.
|
|
44
60
|
|
|
45
61
|
Attributes:
|
|
46
62
|
python_version: The CPython ``major.minor`` version.
|
|
47
|
-
xcframework_dir: Path to ``Python.xcframework``.
|
|
48
|
-
simulator_framework: Path to the simulator-slice
|
|
49
|
-
``Python.framework`` (embedded into the simulator ``.app``).
|
|
50
|
-
stdlib_dir: Path to the simulator standard library directory.
|
|
51
|
-
simulator_headers: Path to the simulator-slice ``Headers``
|
|
52
|
-
directory (used when the project links the static lib).
|
|
53
|
-
simulator_static_lib: Path to the simulator ``libPythonX.Y.a``.
|
|
54
|
-
device_framework: Path to the device-slice ``Python.framework``
|
|
55
|
-
(embedded when archiving for a real device).
|
|
56
|
-
device_stdlib: Path to the device standard library directory.
|
|
63
|
+
xcframework_dir: Path to the extracted ``Python.xcframework``.
|
|
57
64
|
"""
|
|
58
65
|
|
|
59
66
|
python_version: str
|
|
60
67
|
xcframework_dir: Path
|
|
61
|
-
simulator_framework: Optional[Path]
|
|
62
|
-
stdlib_dir: Optional[Path]
|
|
63
|
-
simulator_headers: Optional[Path]
|
|
64
|
-
simulator_static_lib: Optional[Path]
|
|
65
|
-
device_framework: Optional[Path] = None
|
|
66
|
-
device_stdlib: Optional[Path] = None
|
|
67
|
-
|
|
68
|
-
def framework_for(self, destination: str) -> Optional[Path]:
|
|
69
|
-
"""Return the ``Python.framework`` for a build destination.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
destination: ``"simulator"`` or ``"device"``.
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
The matching framework path, or ``None`` if unavailable.
|
|
76
|
-
"""
|
|
77
|
-
return self.device_framework if destination == "device" else self.simulator_framework
|
|
78
|
-
|
|
79
|
-
def stdlib_for(self, destination: str) -> Optional[Path]:
|
|
80
|
-
"""Return the standard library directory for a build destination.
|
|
81
|
-
|
|
82
|
-
Args:
|
|
83
|
-
destination: ``"simulator"`` or ``"device"``.
|
|
84
|
-
|
|
85
|
-
Returns:
|
|
86
|
-
The matching stdlib path, or ``None`` if unavailable.
|
|
87
|
-
"""
|
|
88
|
-
return self.device_stdlib if destination == "device" else self.stdlib_dir
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def _github_json(url: str) -> object:
|
|
92
|
-
headers = {"User-Agent": _USER_AGENT}
|
|
93
|
-
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
94
|
-
if token:
|
|
95
|
-
headers["Authorization"] = f"Bearer {token}"
|
|
96
|
-
req = urllib.request.Request(url, headers=headers)
|
|
97
|
-
with urllib.request.urlopen(req) as response:
|
|
98
|
-
return json.loads(response.read().decode("utf-8"))
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def resolve_asset_url(python_version: str, preferred_name: Optional[str] = None) -> Optional[str]:
|
|
102
|
-
"""Resolve a download URL for a Python-Apple-support iOS release asset.
|
|
103
|
-
|
|
104
|
-
Prefers an exact ``preferred_name`` match across all releases, then
|
|
105
|
-
falls back to the newest asset whose name contains
|
|
106
|
-
``Python-<version>-iOS-support`` and ends in ``.tar.gz``.
|
|
107
|
-
|
|
108
|
-
Args:
|
|
109
|
-
python_version: CPython ``major.minor`` (e.g., ``"3.11"``).
|
|
110
|
-
preferred_name: Exact asset filename to prefer.
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
A ``browser_download_url``, or ``None`` if resolution fails.
|
|
114
|
-
"""
|
|
115
|
-
try:
|
|
116
|
-
releases = _github_json(_RELEASES_API)
|
|
117
|
-
except Exception:
|
|
118
|
-
return None
|
|
119
|
-
if not isinstance(releases, list):
|
|
120
|
-
return None
|
|
121
|
-
|
|
122
|
-
if preferred_name:
|
|
123
|
-
for release in releases:
|
|
124
|
-
for asset in release.get("assets", []) or []:
|
|
125
|
-
if asset.get("name") == preferred_name:
|
|
126
|
-
return asset.get("browser_download_url")
|
|
127
68
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if needle in name and name.endswith(".tar.gz"):
|
|
133
|
-
return asset.get("browser_download_url")
|
|
134
|
-
return None
|
|
69
|
+
@property
|
|
70
|
+
def install_script(self) -> Path:
|
|
71
|
+
"""Path to BeeWare's ``utils.sh`` build helper inside the framework."""
|
|
72
|
+
return self.xcframework_dir / "build" / "utils.sh"
|
|
135
73
|
|
|
136
74
|
|
|
137
75
|
def _sha256(path: Path) -> str:
|
|
@@ -159,60 +97,31 @@ def _safe_extract(tar_path: Path, dest: Path) -> None:
|
|
|
159
97
|
tar.extractall(dest)
|
|
160
98
|
|
|
161
99
|
|
|
162
|
-
def _first_existing(candidates: List[Path]) -> Optional[Path]:
|
|
163
|
-
for candidate in candidates:
|
|
164
|
-
if candidate.exists():
|
|
165
|
-
return candidate
|
|
166
|
-
return None
|
|
167
|
-
|
|
168
|
-
|
|
169
100
|
def _locate_runtime(extract_root: Path, python_version: str) -> IOSRuntime:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
extract_root / "support" / "Python.xcframework",
|
|
173
|
-
]
|
|
174
|
-
xcframework = _first_existing(xc_candidates)
|
|
175
|
-
if xcframework is None:
|
|
101
|
+
xcframework = extract_root / "Python.xcframework"
|
|
102
|
+
if not xcframework.is_dir():
|
|
176
103
|
raise RuntimeError("Python.xcframework not found in extracted Python-Apple-support package.")
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
sim_slice / "libpython.a",
|
|
186
|
-
]
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
device_slice = xcframework / "ios-arm64"
|
|
190
|
-
device_framework = _first_existing([device_slice / "Python.framework"])
|
|
191
|
-
device_stdlib = _first_existing([device_slice / "lib" / f"python{python_version}"])
|
|
192
|
-
|
|
193
|
-
return IOSRuntime(
|
|
194
|
-
python_version=python_version,
|
|
195
|
-
xcframework_dir=xcframework,
|
|
196
|
-
simulator_framework=simulator_framework,
|
|
197
|
-
stdlib_dir=stdlib_dir,
|
|
198
|
-
simulator_headers=simulator_headers,
|
|
199
|
-
simulator_static_lib=simulator_static_lib,
|
|
200
|
-
device_framework=device_framework,
|
|
201
|
-
device_stdlib=device_stdlib,
|
|
202
|
-
)
|
|
104
|
+
runtime = IOSRuntime(python_version=python_version, xcframework_dir=xcframework)
|
|
105
|
+
if not runtime.install_script.is_file():
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
"The extracted Python.xcframework is missing build/utils.sh; the support "
|
|
108
|
+
"package layout is older than PythonNative expects. Delete the "
|
|
109
|
+
"build/ios_runtime cache and re-run to fetch the pinned asset."
|
|
110
|
+
)
|
|
111
|
+
return runtime
|
|
203
112
|
|
|
204
113
|
|
|
205
114
|
def prepare_ios_runtime(
|
|
206
115
|
cache_dir: Path,
|
|
207
|
-
python_version: str = "3.
|
|
116
|
+
python_version: str = "3.12",
|
|
208
117
|
*,
|
|
209
118
|
log: Optional[Logger] = None,
|
|
210
119
|
) -> IOSRuntime:
|
|
211
120
|
"""Download (if needed), verify, and extract the iOS CPython package.
|
|
212
121
|
|
|
213
122
|
The download and extraction are cached under ``cache_dir`` so repeat
|
|
214
|
-
builds are fast.
|
|
215
|
-
|
|
123
|
+
builds are fast. Only pinned, checksum-verified versions are
|
|
124
|
+
accepted; there is no unverified fallback.
|
|
216
125
|
|
|
217
126
|
Args:
|
|
218
127
|
cache_dir: Directory to store downloads and extractions in.
|
|
@@ -223,15 +132,20 @@ def prepare_ios_runtime(
|
|
|
223
132
|
A resolved [`IOSRuntime`][pythonnative.project.runtime_assets.IOSRuntime].
|
|
224
133
|
|
|
225
134
|
Raises:
|
|
226
|
-
RuntimeError: If the
|
|
227
|
-
doesn't match, or the package layout is
|
|
135
|
+
RuntimeError: If the version has no pinned asset, the download
|
|
136
|
+
fails, the checksum doesn't match, or the package layout is
|
|
137
|
+
unexpected.
|
|
228
138
|
"""
|
|
229
139
|
emit: Logger = log or (lambda _message: None)
|
|
230
140
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
231
141
|
|
|
232
|
-
pinned =
|
|
233
|
-
|
|
234
|
-
|
|
142
|
+
pinned = PINNED_ASSETS.get(python_version)
|
|
143
|
+
if pinned is None:
|
|
144
|
+
supported = ", ".join(sorted(PINNED_ASSETS))
|
|
145
|
+
raise RuntimeError(
|
|
146
|
+
f"No pinned iOS runtime for Python {python_version}. " f"Set app.python_version to one of: {supported}."
|
|
147
|
+
)
|
|
148
|
+
tag, asset_name, expected_sha = pinned
|
|
235
149
|
|
|
236
150
|
extract_root = cache_dir / f"python-{python_version}"
|
|
237
151
|
if extract_root.is_dir():
|
|
@@ -241,30 +155,28 @@ def prepare_ios_runtime(
|
|
|
241
155
|
# Stale/partial extraction: re-extract below.
|
|
242
156
|
pass
|
|
243
157
|
|
|
244
|
-
url =
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
"Check your network connection or set GITHUB_TOKEN to avoid rate limits."
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
tar_path = cache_dir / os.path.basename(url)
|
|
252
|
-
if not tar_path.exists():
|
|
253
|
-
emit(f"Downloading embedded Python runtime ({python_version} iOS): {os.path.basename(url)}")
|
|
158
|
+
url = _DOWNLOAD_URL.format(tag=tag, name=asset_name)
|
|
159
|
+
tar_path = cache_dir / asset_name
|
|
160
|
+
if not tar_path.exists() or _sha256(tar_path) != expected_sha:
|
|
161
|
+
emit(f"Downloading embedded Python runtime ({python_version} iOS): {asset_name}")
|
|
254
162
|
req = urllib.request.Request(url, headers={"User-Agent": _USER_AGENT})
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
actual = _sha256(tar_path)
|
|
260
|
-
if actual != expected_sha:
|
|
163
|
+
try:
|
|
164
|
+
with urllib.request.urlopen(req) as response, open(tar_path, "wb") as handle:
|
|
165
|
+
handle.write(response.read())
|
|
166
|
+
except OSError as exc:
|
|
261
167
|
tar_path.unlink(missing_ok=True)
|
|
262
168
|
raise RuntimeError(
|
|
263
|
-
f"
|
|
264
|
-
"
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
|
|
169
|
+
f"Could not download the iOS Python runtime from {url}: {exc}. "
|
|
170
|
+
"Check your network connection and re-run."
|
|
171
|
+
) from exc
|
|
172
|
+
|
|
173
|
+
actual = _sha256(tar_path)
|
|
174
|
+
if actual != expected_sha:
|
|
175
|
+
tar_path.unlink(missing_ok=True)
|
|
176
|
+
raise RuntimeError(
|
|
177
|
+
f"Checksum mismatch for {asset_name}: expected {expected_sha}, got {actual}. "
|
|
178
|
+
"The download may be corrupt; re-run to try again."
|
|
179
|
+
)
|
|
268
180
|
|
|
269
181
|
emit("Extracting embedded Python runtime...")
|
|
270
182
|
extract_root.mkdir(parents=True, exist_ok=True)
|
|
@@ -1,39 +1,115 @@
|
|
|
1
1
|
package com.pythonnative.android_template
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.Typeface
|
|
4
6
|
import android.os.Bundle
|
|
5
7
|
import android.util.Log
|
|
8
|
+
import android.util.TypedValue
|
|
9
|
+
import android.widget.ScrollView
|
|
6
10
|
import android.widget.TextView
|
|
11
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
7
12
|
import com.chaquo.python.Python
|
|
8
13
|
import com.chaquo.python.android.AndroidPlatform
|
|
9
14
|
|
|
10
15
|
class MainActivity : AppCompatActivity() {
|
|
11
16
|
private val TAG = javaClass.simpleName
|
|
17
|
+
private var pythonReady = false
|
|
12
18
|
|
|
13
19
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
14
20
|
super.onCreate(savedInstanceState)
|
|
15
21
|
Log.d(TAG, "onCreate() called")
|
|
16
22
|
|
|
17
|
-
// Initialize Chaquopy
|
|
18
23
|
if (!Python.isStarted()) {
|
|
19
24
|
Python.start(AndroidPlatform(this))
|
|
20
25
|
}
|
|
21
26
|
try {
|
|
22
|
-
// Set content view to the NavHost layout; the initial screen
|
|
27
|
+
// Set content view to the NavHost layout; the initial screen
|
|
28
|
+
// loads via nav_graph startDestination.
|
|
23
29
|
setContentView(R.layout.activity_main)
|
|
24
|
-
// Optionally, bootstrap Python so first fragment can create the initial screen onCreate
|
|
25
30
|
val py = Python.getInstance()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
if (BuildConfig.DEBUG) {
|
|
32
|
+
// Dev-only writable source overlay for hot reload.
|
|
33
|
+
py.getModule("pythonnative.hot_reload").callAttr(
|
|
34
|
+
"configure_dev_environment",
|
|
35
|
+
filesDir.absolutePath
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
// Import the entry module now so a broken app fails here,
|
|
39
|
+
// with a full traceback, instead of inside the first fragment.
|
|
31
40
|
py.getModule("app.main")
|
|
41
|
+
pythonReady = true
|
|
32
42
|
} catch (e: Exception) {
|
|
33
43
|
Log.e("PythonNative", "Bootstrap failed", e)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
showBootstrapError(e)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// A cold start from a deep link carries the URL on the launch intent.
|
|
49
|
+
intent?.dataString?.let { dispatchUrl(it) }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun onNewIntent(intent: Intent) {
|
|
53
|
+
super.onNewIntent(intent)
|
|
54
|
+
setIntent(intent)
|
|
55
|
+
intent.dataString?.let { dispatchUrl(it) }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// MARK: - AppState forwarding
|
|
59
|
+
|
|
60
|
+
override fun onResume() {
|
|
61
|
+
super.onResume()
|
|
62
|
+
dispatchAppState("active")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun onPause() {
|
|
66
|
+
super.onPause()
|
|
67
|
+
dispatchAppState("inactive")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun onStop() {
|
|
71
|
+
super.onStop()
|
|
72
|
+
dispatchAppState("background")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private fun dispatchAppState(state: String) {
|
|
76
|
+
if (!pythonReady) return
|
|
77
|
+
try {
|
|
78
|
+
Python.getInstance()
|
|
79
|
+
.getModule("pythonnative.native_modules.app_state")
|
|
80
|
+
.callAttr("dispatch_app_state", state)
|
|
81
|
+
} catch (e: Exception) {
|
|
82
|
+
Log.e("PythonNative", "dispatch_app_state($state) failed", e)
|
|
37
83
|
}
|
|
38
84
|
}
|
|
85
|
+
|
|
86
|
+
// MARK: - Deep links
|
|
87
|
+
|
|
88
|
+
private fun dispatchUrl(url: String) {
|
|
89
|
+
if (!pythonReady) return
|
|
90
|
+
try {
|
|
91
|
+
Python.getInstance()
|
|
92
|
+
.getModule("pythonnative.native_modules.linking")
|
|
93
|
+
.callAttr("dispatch_url", url)
|
|
94
|
+
} catch (e: Exception) {
|
|
95
|
+
Log.e("PythonNative", "dispatch_url failed", e)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// MARK: - Bootstrap error UI
|
|
100
|
+
|
|
101
|
+
private fun showBootstrapError(error: Exception) {
|
|
102
|
+
val text = TextView(this)
|
|
103
|
+
text.text = "PythonNative could not start\n\n" + Log.getStackTraceString(error)
|
|
104
|
+
text.setTextColor(Color.WHITE)
|
|
105
|
+
text.setBackgroundColor(Color.rgb(191, 26, 26))
|
|
106
|
+
text.typeface = Typeface.MONOSPACE
|
|
107
|
+
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
|
|
108
|
+
val pad = (16 * resources.displayMetrics.density).toInt()
|
|
109
|
+
text.setPadding(pad, pad * 3, pad, pad * 2)
|
|
110
|
+
val scroll = ScrollView(this)
|
|
111
|
+
scroll.setBackgroundColor(Color.rgb(191, 26, 26))
|
|
112
|
+
scroll.addView(text)
|
|
113
|
+
setContentView(scroll)
|
|
114
|
+
}
|
|
39
115
|
}
|
|
@@ -2,35 +2,59 @@
|
|
|
2
2
|
// AppDelegate.swift
|
|
3
3
|
// ios_template
|
|
4
4
|
//
|
|
5
|
-
//
|
|
5
|
+
// Application-level callbacks. Remote-notification registration
|
|
6
|
+
// results are forwarded to pythonnative.native_modules.notifications,
|
|
7
|
+
// which resolves the awaitable returned by
|
|
8
|
+
// Notifications.get_device_token().
|
|
6
9
|
//
|
|
7
10
|
|
|
8
11
|
import UIKit
|
|
9
12
|
|
|
10
13
|
@main
|
|
11
14
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// Override point for customization after application launch.
|
|
15
|
+
func application(
|
|
16
|
+
_ application: UIApplication,
|
|
17
|
+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
18
|
+
) -> Bool {
|
|
17
19
|
return true
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
// MARK:
|
|
21
|
-
|
|
22
|
-
func application(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
// MARK: - Remote notifications (APNs)
|
|
23
|
+
|
|
24
|
+
func application(
|
|
25
|
+
_ application: UIApplication,
|
|
26
|
+
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
|
|
27
|
+
) {
|
|
28
|
+
let token = deviceToken.map { String(format: "%02x", $0) }.joined()
|
|
29
|
+
guard PythonRuntime.shared.started else { return }
|
|
30
|
+
PythonRuntime.shared.notify(
|
|
31
|
+
module: "pythonnative.native_modules.notifications",
|
|
32
|
+
function: "dispatch_device_token",
|
|
33
|
+
token
|
|
34
|
+
)
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
func application(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
func application(
|
|
38
|
+
_ application: UIApplication,
|
|
39
|
+
didFailToRegisterForRemoteNotificationsWithError error: Error
|
|
40
|
+
) {
|
|
41
|
+
guard PythonRuntime.shared.started else { return }
|
|
42
|
+
PythonRuntime.shared.notify(
|
|
43
|
+
module: "pythonnative.native_modules.notifications",
|
|
44
|
+
function: "dispatch_device_token_error",
|
|
45
|
+
String(describing: error)
|
|
46
|
+
)
|
|
32
47
|
}
|
|
33
48
|
|
|
49
|
+
// MARK: - UISceneSession lifecycle
|
|
34
50
|
|
|
35
|
-
|
|
51
|
+
func application(
|
|
52
|
+
_ application: UIApplication,
|
|
53
|
+
configurationForConnecting connectingSceneSession: UISceneSession,
|
|
54
|
+
options: UIScene.ConnectionOptions
|
|
55
|
+
) -> UISceneConfiguration {
|
|
56
|
+
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
|
57
|
+
}
|
|
36
58
|
|
|
59
|
+
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {}
|
|
60
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BridgingHeader.h
|
|
3
|
+
// ios_template
|
|
4
|
+
//
|
|
5
|
+
// Exposes the embedded CPython C API to Swift. The header resolves
|
|
6
|
+
// through the linked Python.xcframework's framework search path.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#ifndef ios_template_BridgingHeader_h
|
|
10
|
+
#define ios_template_BridgingHeader_h
|
|
11
|
+
|
|
12
|
+
#include <Python/Python.h>
|
|
13
|
+
|
|
14
|
+
#endif /* ios_template_BridgingHeader_h */
|