pyxpo-sharing 0.1.0a1__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.
- pyxpo_sharing-0.1.0a1/LICENSE +21 -0
- pyxpo_sharing-0.1.0a1/PKG-INFO +58 -0
- pyxpo_sharing-0.1.0a1/README.md +39 -0
- pyxpo_sharing-0.1.0a1/pyproject.toml +42 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing/__init__.py +68 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing/android/src/main/AndroidManifest.xml +10 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing/android/src/main/java/dev/pyxpo/modules/sharing/PxShareFileProvider.kt +61 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing/android/src/main/java/dev/pyxpo/modules/sharing/ShareModule.kt +59 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing/ios/ShareModule.swift +33 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing/py.typed +0 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing/pyxpo-module.json +13 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing.egg-info/PKG-INFO +58 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing.egg-info/SOURCES.txt +16 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing.egg-info/dependency_links.txt +1 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing.egg-info/entry_points.txt +2 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing.egg-info/requires.txt +1 -0
- pyxpo_sharing-0.1.0a1/pyxpo_sharing.egg-info/top_level.txt +1 -0
- pyxpo_sharing-0.1.0a1/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vishal Chaubey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyxpo-sharing
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: System share sheet for pyxpo apps (react-native Share and expo-sharing shapes).
|
|
5
|
+
Author: Vishal Chaubey
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pyxpo/pyxpo/tree/main/packages/sharing
|
|
8
|
+
Project-URL: Issues, https://github.com/pyxpo/pyxpo/issues
|
|
9
|
+
Keywords: pyxpo,pyxpo-module,mobile,android,ios
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: pyxpo<0.2,>=0.1.0a1
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# pyxpo-sharing
|
|
21
|
+
|
|
22
|
+
System share sheet for pyxpo apps (react-native Share shape).
|
|
23
|
+
|
|
24
|
+
This is an optional [pyxpo](https://github.com/pyxpo/pyxpo) native module. Apps that do not
|
|
25
|
+
list it carry none of its code, permissions or dependencies.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install pyxpo-sharing
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then link it into your app in `pyproject.toml`:
|
|
34
|
+
|
|
35
|
+
```toml
|
|
36
|
+
[tool.pyxpo]
|
|
37
|
+
modules = ["sharing"]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
and rebuild (`pyxpo run ios`, `pyxpo build android`, ...). The build copies the module's Swift and Kotlin
|
|
41
|
+
sources into the native project and its Python package into the app.
|
|
42
|
+
|
|
43
|
+
## Use
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from pyxpo_sharing import Share
|
|
47
|
+
|
|
48
|
+
await Share.share({"url": path, "message": "Made with pyxpo"})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Permissions and Info.plist keys: none (Android shares through a FileProvider declared in the module manifest).
|
|
52
|
+
|
|
53
|
+
## What is in the package
|
|
54
|
+
|
|
55
|
+
- `pyxpo_sharing/__init__.py`: the Python API.
|
|
56
|
+
- `pyxpo_sharing/pyxpo-module.json`: autolink metadata (bridge name, Swift and Kotlin classes, Android namespace and dependencies, Info.plist keys).
|
|
57
|
+
- `pyxpo_sharing/ios/`: Swift sources compiled into the iOS app.
|
|
58
|
+
- `pyxpo_sharing/android/src/main/`: Kotlin sources and the manifest merged into the Android app.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# pyxpo-sharing
|
|
2
|
+
|
|
3
|
+
System share sheet for pyxpo apps (react-native Share shape).
|
|
4
|
+
|
|
5
|
+
This is an optional [pyxpo](https://github.com/pyxpo/pyxpo) native module. Apps that do not
|
|
6
|
+
list it carry none of its code, permissions or dependencies.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install pyxpo-sharing
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then link it into your app in `pyproject.toml`:
|
|
15
|
+
|
|
16
|
+
```toml
|
|
17
|
+
[tool.pyxpo]
|
|
18
|
+
modules = ["sharing"]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
and rebuild (`pyxpo run ios`, `pyxpo build android`, ...). The build copies the module's Swift and Kotlin
|
|
22
|
+
sources into the native project and its Python package into the app.
|
|
23
|
+
|
|
24
|
+
## Use
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from pyxpo_sharing import Share
|
|
28
|
+
|
|
29
|
+
await Share.share({"url": path, "message": "Made with pyxpo"})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Permissions and Info.plist keys: none (Android shares through a FileProvider declared in the module manifest).
|
|
33
|
+
|
|
34
|
+
## What is in the package
|
|
35
|
+
|
|
36
|
+
- `pyxpo_sharing/__init__.py`: the Python API.
|
|
37
|
+
- `pyxpo_sharing/pyxpo-module.json`: autolink metadata (bridge name, Swift and Kotlin classes, Android namespace and dependencies, Info.plist keys).
|
|
38
|
+
- `pyxpo_sharing/ios/`: Swift sources compiled into the iOS app.
|
|
39
|
+
- `pyxpo_sharing/android/src/main/`: Kotlin sources and the manifest merged into the Android app.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyxpo-sharing"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "System share sheet for pyxpo apps (react-native Share and expo-sharing shapes)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Vishal Chaubey" }]
|
|
13
|
+
keywords = ["pyxpo", "pyxpo-module", "mobile", "android", "ios"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: Software Development :: User Interfaces",
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.9"
|
|
21
|
+
dependencies = ["pyxpo>=0.1.0a1,<0.2"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/pyxpo/pyxpo/tree/main/packages/sharing"
|
|
25
|
+
Issues = "https://github.com/pyxpo/pyxpo/issues"
|
|
26
|
+
|
|
27
|
+
# How `pyxpo build` finds this module: the entry point name is the module name apps list in
|
|
28
|
+
# `[tool.pyxpo] modules`, its value the import package holding pyxpo-module.json.
|
|
29
|
+
[project.entry-points."pyxpo.modules"]
|
|
30
|
+
sharing = "pyxpo_sharing"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools]
|
|
33
|
+
packages = ["pyxpo_sharing"]
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.dynamic]
|
|
36
|
+
version = { attr = "pyxpo_sharing.__version__" }
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.package-data]
|
|
39
|
+
pyxpo_sharing = ["pyxpo-module.json", "py.typed", "ios/**/*", "android/**/*"]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.exclude-package-data]
|
|
42
|
+
"*" = ["__pycache__", "*.pyc", ".DS_Store"]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""System share sheet: react-native `Share` and expo-sharing shapes. Shares local files, links and an optional message."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from pyxpo.module_api import call, require_linked, snake_aliases
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0a1"
|
|
9
|
+
|
|
10
|
+
_in_flight = False
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _is_link(s: str) -> bool:
|
|
14
|
+
return s.lower().startswith(("http://", "https://"))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def _share(files: List[str], message: str, mime: str, title: str) -> Dict[str, str]:
|
|
18
|
+
"""One share sheet at a time; a second call while one is open raises instead of stacking sheets."""
|
|
19
|
+
global _in_flight
|
|
20
|
+
if _in_flight:
|
|
21
|
+
raise RuntimeError("a share sheet is already open")
|
|
22
|
+
_in_flight = True
|
|
23
|
+
try:
|
|
24
|
+
res = await call("Share", "share", files, message, mime, title, reply=True)
|
|
25
|
+
finally:
|
|
26
|
+
_in_flight = False
|
|
27
|
+
return {"action": (res or {}).get("action", "dismissedAction")}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@snake_aliases
|
|
31
|
+
class Share:
|
|
32
|
+
@staticmethod
|
|
33
|
+
async def share(content: Dict[str, Any], options: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
|
|
34
|
+
"""Opens the system share sheet. `content`: `{"url": path_or_link}` or `{"urls": [...]}`, plus optional `message`.
|
|
35
|
+
Local paths are shared as files; http(s) links are appended to the message, the same on both OSes.
|
|
36
|
+
`options`: `{"dialogTitle": str}` (Android chooser title; iOS has none).
|
|
37
|
+
Returns `{"action": "sharedAction" | "dismissedAction"}` (Android always reports sharedAction, as react-native does).
|
|
38
|
+
Android shares files from the app's own cache and files folders."""
|
|
39
|
+
require_linked("Share", "sharing", package="pyxpo-sharing")
|
|
40
|
+
items = [str(u) for u in (content.get("urls") or ([content["url"]] if content.get("url") else []))]
|
|
41
|
+
files = [u for u in items if not _is_link(u)]
|
|
42
|
+
message = "\n".join([m for m in [content.get("message") or ""] + [u for u in items if _is_link(u)] if m])
|
|
43
|
+
if not files and not message:
|
|
44
|
+
raise ValueError("Share.share needs a url, urls or message")
|
|
45
|
+
return await _share(files, message, "", str((options or {}).get("dialogTitle") or ""))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@snake_aliases
|
|
49
|
+
class Sharing:
|
|
50
|
+
"""expo-sharing shape."""
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
async def isAvailableAsync() -> bool:
|
|
54
|
+
"""True when the share sheet module is linked (both iOS and Android always have a share sheet)."""
|
|
55
|
+
try:
|
|
56
|
+
require_linked("Share", "sharing", package="pyxpo-sharing")
|
|
57
|
+
except Exception: # noqa: BLE001 - not linked means unavailable, not an error
|
|
58
|
+
return False
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
async def shareAsync(url: str, options: Optional[Dict[str, Any]] = None) -> None:
|
|
63
|
+
"""Shares one local file. `options`: `mimeType` and `dialogTitle` (Android only; iOS derives both from the file)."""
|
|
64
|
+
require_linked("Share", "sharing", package="pyxpo-sharing")
|
|
65
|
+
if not url or _is_link(str(url)):
|
|
66
|
+
raise ValueError("Sharing.shareAsync needs a local file path; use Share.share for links")
|
|
67
|
+
o = options or {}
|
|
68
|
+
await _share([str(url)], "", str(o.get("mimeType") or ""), str(o.get("dialogTitle") or ""))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<application>
|
|
3
|
+
<!-- Hands files from the app's cache and files folders to the share sheet without exposing file:// paths (no AndroidX needed). -->
|
|
4
|
+
<provider
|
|
5
|
+
android:name="dev.pyxpo.modules.sharing.PxShareFileProvider"
|
|
6
|
+
android:authorities="${applicationId}.pyxpo.share"
|
|
7
|
+
android:exported="false"
|
|
8
|
+
android:grantUriPermissions="true" />
|
|
9
|
+
</application>
|
|
10
|
+
</manifest>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package dev.pyxpo.modules.sharing
|
|
2
|
+
|
|
3
|
+
import android.content.ContentProvider
|
|
4
|
+
import android.content.ContentValues
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.database.Cursor
|
|
7
|
+
import android.database.MatrixCursor
|
|
8
|
+
import android.net.Uri
|
|
9
|
+
import android.os.ParcelFileDescriptor
|
|
10
|
+
import android.provider.OpenableColumns
|
|
11
|
+
import android.webkit.MimeTypeMap
|
|
12
|
+
import java.io.File
|
|
13
|
+
import java.io.FileNotFoundException
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Read-only provider for the share sheet. Serves only files inside the app's cache and files folders,
|
|
17
|
+
* addressed as content://<applicationId>.pyxpo.share/<absolute path>. Plain ContentProvider, so no AndroidX is needed.
|
|
18
|
+
*/
|
|
19
|
+
class PxShareFileProvider : ContentProvider() {
|
|
20
|
+
override fun onCreate() = true
|
|
21
|
+
|
|
22
|
+
private fun resolve(uri: Uri): File {
|
|
23
|
+
val ctx = context ?: throw FileNotFoundException("no context")
|
|
24
|
+
val f = File("/" + uri.pathSegments.joinToString("/")).canonicalFile
|
|
25
|
+
if (!allowedRoots(ctx).any { f.path.startsWith(it.path + File.separator) } || !f.isFile) throw FileNotFoundException(uri.toString())
|
|
26
|
+
return f
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor {
|
|
30
|
+
if (mode != "r") throw FileNotFoundException("read-only")
|
|
31
|
+
return ParcelFileDescriptor.open(resolve(uri), ParcelFileDescriptor.MODE_READ_ONLY)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun getType(uri: Uri): String? = try {
|
|
35
|
+
MimeTypeMap.getSingleton().getMimeTypeFromExtension(resolve(uri).extension.lowercase()) ?: "application/octet-stream"
|
|
36
|
+
} catch (e: FileNotFoundException) { null }
|
|
37
|
+
|
|
38
|
+
override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor {
|
|
39
|
+
val f = resolve(uri)
|
|
40
|
+
val cols = projection ?: arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)
|
|
41
|
+
val cur = MatrixCursor(cols)
|
|
42
|
+
cur.addRow(Array<Any?>(cols.size) { i -> when (cols[i]) { OpenableColumns.DISPLAY_NAME -> f.name; OpenableColumns.SIZE -> f.length(); else -> null } })
|
|
43
|
+
return cur
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override fun insert(uri: Uri, values: ContentValues?): Uri? = throw UnsupportedOperationException("read-only")
|
|
47
|
+
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int = throw UnsupportedOperationException("read-only")
|
|
48
|
+
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = throw UnsupportedOperationException("read-only")
|
|
49
|
+
|
|
50
|
+
companion object {
|
|
51
|
+
private fun allowedRoots(ctx: Context) = listOf(ctx.cacheDir.canonicalFile, ctx.filesDir.canonicalFile)
|
|
52
|
+
|
|
53
|
+
fun uriFor(ctx: Context, file: File): Uri {
|
|
54
|
+
val f = file.canonicalFile
|
|
55
|
+
if (!allowedRoots(ctx).any { f.path.startsWith(it.path + File.separator) }) {
|
|
56
|
+
throw IllegalArgumentException("only files in the app's cache or files folder can be shared")
|
|
57
|
+
}
|
|
58
|
+
return Uri.Builder().scheme("content").authority(ctx.packageName + ".pyxpo.share").path(f.path).build()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
pyxpo_sharing-0.1.0a1/pyxpo_sharing/android/src/main/java/dev/pyxpo/modules/sharing/ShareModule.kt
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package dev.pyxpo.modules.sharing
|
|
2
|
+
|
|
3
|
+
import android.content.ClipData
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import android.webkit.MimeTypeMap
|
|
7
|
+
import dev.pyxpo.moduleapi.PxModule
|
|
8
|
+
import dev.pyxpo.moduleapi.PxModuleContext
|
|
9
|
+
import dev.pyxpo.moduleapi.PxReply
|
|
10
|
+
import org.json.JSONArray
|
|
11
|
+
import java.io.File
|
|
12
|
+
|
|
13
|
+
/** System share sheet. Files must live in the app's cache or files folder. Like react-native, Android always reports sharedAction. */
|
|
14
|
+
class ShareModule : PxModule() {
|
|
15
|
+
override val name = "Share"
|
|
16
|
+
|
|
17
|
+
override fun call(ctx: PxModuleContext, method: String, args: JSONArray, reply: PxReply) {
|
|
18
|
+
if (method != "share") { reply(null, "unknown native call Share.$method"); return }
|
|
19
|
+
val paths = args.optJSONArray(0) ?: JSONArray()
|
|
20
|
+
val message = args.optString(1, "")
|
|
21
|
+
val mimeOverride = args.optString(2, "")
|
|
22
|
+
val title = args.optString(3, "")
|
|
23
|
+
val app = ctx.activity
|
|
24
|
+
val uris = ArrayList<Uri>()
|
|
25
|
+
val mimes = HashSet<String>()
|
|
26
|
+
try {
|
|
27
|
+
for (i in 0 until paths.length()) {
|
|
28
|
+
val raw = paths.optString(i)
|
|
29
|
+
val f = File(if (raw.startsWith("file://")) Uri.parse(raw).path ?: raw else raw)
|
|
30
|
+
if (!f.isFile) { reply(null, "file not found: ${f.name}"); return }
|
|
31
|
+
uris.add(PxShareFileProvider.uriFor(app, f))
|
|
32
|
+
mimes.add(MimeTypeMap.getSingleton().getMimeTypeFromExtension(f.extension.lowercase()) ?: "application/octet-stream")
|
|
33
|
+
}
|
|
34
|
+
} catch (e: IllegalArgumentException) {
|
|
35
|
+
reply(null, e.message ?: "file can not be shared"); return
|
|
36
|
+
}
|
|
37
|
+
val intent = Intent(if (uris.size > 1) Intent.ACTION_SEND_MULTIPLE else Intent.ACTION_SEND)
|
|
38
|
+
intent.type = when {
|
|
39
|
+
mimeOverride.isNotEmpty() -> mimeOverride
|
|
40
|
+
uris.isEmpty() -> "text/plain"
|
|
41
|
+
mimes.size == 1 -> mimes.first()
|
|
42
|
+
mimes.all { it.startsWith("image/") } -> "image/*"
|
|
43
|
+
else -> "*/*"
|
|
44
|
+
}
|
|
45
|
+
if (message.isNotEmpty()) intent.putExtra(Intent.EXTRA_TEXT, message)
|
|
46
|
+
if (uris.size == 1) intent.putExtra(Intent.EXTRA_STREAM, uris[0])
|
|
47
|
+
else if (uris.size > 1) intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
|
48
|
+
if (uris.isNotEmpty()) {
|
|
49
|
+
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
50
|
+
intent.clipData = ClipData.newRawUri("", uris[0]).also { c -> for (u in uris.drop(1)) c.addItem(ClipData.Item(u)) }
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
app.startActivity(Intent.createChooser(intent, title.ifEmpty { null }))
|
|
54
|
+
reply(mapOf("action" to "sharedAction"), null)
|
|
55
|
+
} catch (e: Throwable) {
|
|
56
|
+
reply(null, e.message ?: e.toString())
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// System share sheet (UIActivityViewController). Replies once the sheet closes.
|
|
4
|
+
final class ShareModule: PxModule {
|
|
5
|
+
static let moduleName = "Share"
|
|
6
|
+
|
|
7
|
+
required init() {}
|
|
8
|
+
|
|
9
|
+
func call(_ ctx: PxModuleContext, _ method: String, _ args: [Any], _ reply: @escaping PxReply) {
|
|
10
|
+
guard method == "share" else { reply(nil, "unknown native call Share.\(method)"); return }
|
|
11
|
+
let paths = (args.first as? [Any])?.compactMap { $0 as? String } ?? []
|
|
12
|
+
let message = (args.count > 1 ? args[1] as? String : nil) ?? ""
|
|
13
|
+
var items: [Any] = []
|
|
14
|
+
if !message.isEmpty { items.append(message) }
|
|
15
|
+
for p in paths {
|
|
16
|
+
let path = p.hasPrefix("file://") ? (URL(string: p)?.path ?? p) : p
|
|
17
|
+
guard FileManager.default.fileExists(atPath: path) else { reply(nil, "file not found: \((path as NSString).lastPathComponent)"); return }
|
|
18
|
+
items.append(URL(fileURLWithPath: path))
|
|
19
|
+
}
|
|
20
|
+
guard let top = ctx.presenter() else { reply(nil, "no view controller to present the share sheet"); return }
|
|
21
|
+
let vc = UIActivityViewController(activityItems: items, applicationActivities: nil)
|
|
22
|
+
vc.completionWithItemsHandler = { _, completed, _, err in
|
|
23
|
+
if let err = err { reply(nil, err.localizedDescription); return }
|
|
24
|
+
reply(["action": completed ? "sharedAction" : "dismissedAction"], nil)
|
|
25
|
+
}
|
|
26
|
+
if let pop = vc.popoverPresentationController {
|
|
27
|
+
pop.sourceView = top.view
|
|
28
|
+
pop.sourceRect = CGRect(x: top.view.bounds.midX, y: top.view.bounds.midY, width: 0, height: 0)
|
|
29
|
+
pop.permittedArrowDirections = []
|
|
30
|
+
}
|
|
31
|
+
top.present(vc, animated: true)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"nativeContract": 1,
|
|
4
|
+
"name": "sharing",
|
|
5
|
+
"native": "Share",
|
|
6
|
+
"ios": { "classes": ["ShareModule"] },
|
|
7
|
+
"android": {
|
|
8
|
+
"namespace": "dev.pyxpo.modules.sharing",
|
|
9
|
+
"classes": ["dev.pyxpo.modules.sharing.ShareModule"],
|
|
10
|
+
"dependencies": [],
|
|
11
|
+
"minSdk": 24
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyxpo-sharing
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: System share sheet for pyxpo apps (react-native Share and expo-sharing shapes).
|
|
5
|
+
Author: Vishal Chaubey
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pyxpo/pyxpo/tree/main/packages/sharing
|
|
8
|
+
Project-URL: Issues, https://github.com/pyxpo/pyxpo/issues
|
|
9
|
+
Keywords: pyxpo,pyxpo-module,mobile,android,ios
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: pyxpo<0.2,>=0.1.0a1
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# pyxpo-sharing
|
|
21
|
+
|
|
22
|
+
System share sheet for pyxpo apps (react-native Share shape).
|
|
23
|
+
|
|
24
|
+
This is an optional [pyxpo](https://github.com/pyxpo/pyxpo) native module. Apps that do not
|
|
25
|
+
list it carry none of its code, permissions or dependencies.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install pyxpo-sharing
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then link it into your app in `pyproject.toml`:
|
|
34
|
+
|
|
35
|
+
```toml
|
|
36
|
+
[tool.pyxpo]
|
|
37
|
+
modules = ["sharing"]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
and rebuild (`pyxpo run ios`, `pyxpo build android`, ...). The build copies the module's Swift and Kotlin
|
|
41
|
+
sources into the native project and its Python package into the app.
|
|
42
|
+
|
|
43
|
+
## Use
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from pyxpo_sharing import Share
|
|
47
|
+
|
|
48
|
+
await Share.share({"url": path, "message": "Made with pyxpo"})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Permissions and Info.plist keys: none (Android shares through a FileProvider declared in the module manifest).
|
|
52
|
+
|
|
53
|
+
## What is in the package
|
|
54
|
+
|
|
55
|
+
- `pyxpo_sharing/__init__.py`: the Python API.
|
|
56
|
+
- `pyxpo_sharing/pyxpo-module.json`: autolink metadata (bridge name, Swift and Kotlin classes, Android namespace and dependencies, Info.plist keys).
|
|
57
|
+
- `pyxpo_sharing/ios/`: Swift sources compiled into the iOS app.
|
|
58
|
+
- `pyxpo_sharing/android/src/main/`: Kotlin sources and the manifest merged into the Android app.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
pyxpo_sharing/__init__.py
|
|
5
|
+
pyxpo_sharing/py.typed
|
|
6
|
+
pyxpo_sharing/pyxpo-module.json
|
|
7
|
+
pyxpo_sharing.egg-info/PKG-INFO
|
|
8
|
+
pyxpo_sharing.egg-info/SOURCES.txt
|
|
9
|
+
pyxpo_sharing.egg-info/dependency_links.txt
|
|
10
|
+
pyxpo_sharing.egg-info/entry_points.txt
|
|
11
|
+
pyxpo_sharing.egg-info/requires.txt
|
|
12
|
+
pyxpo_sharing.egg-info/top_level.txt
|
|
13
|
+
pyxpo_sharing/android/src/main/AndroidManifest.xml
|
|
14
|
+
pyxpo_sharing/android/src/main/java/dev/pyxpo/modules/sharing/PxShareFileProvider.kt
|
|
15
|
+
pyxpo_sharing/android/src/main/java/dev/pyxpo/modules/sharing/ShareModule.kt
|
|
16
|
+
pyxpo_sharing/ios/ShareModule.swift
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyxpo<0.2,>=0.1.0a1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyxpo_sharing
|