diff --git a/plugins/shell/android/.gitignore b/plugins/shell/android/.gitignore
new file mode 100644
index 00000000..c0f21ec2
--- /dev/null
+++ b/plugins/shell/android/.gitignore
@@ -0,0 +1,2 @@
+/build
+/.tauri
diff --git a/plugins/shell/android/build.gradle.kts b/plugins/shell/android/build.gradle.kts
new file mode 100644
index 00000000..31a1283e
--- /dev/null
+++ b/plugins/shell/android/build.gradle.kts
@@ -0,0 +1,40 @@
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "app.tauri.shell"
+ compileSdk = 33
+
+ defaultConfig {
+ minSdk = 19
+ targetSdk = 33
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+ implementation("androidx.core:core-ktx:1.9.0")
+ implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3")
+ implementation(project(":tauri-android"))
+}
diff --git a/plugins/shell/android/proguard-rules.pro b/plugins/shell/android/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/plugins/shell/android/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/plugins/shell/android/settings.gradle b/plugins/shell/android/settings.gradle
new file mode 100644
index 00000000..14a752e4
--- /dev/null
+++ b/plugins/shell/android/settings.gradle
@@ -0,0 +1,2 @@
+include ':tauri-android'
+project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
diff --git a/plugins/shell/android/src/main/AndroidManifest.xml b/plugins/shell/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..9a40236b
--- /dev/null
+++ b/plugins/shell/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/plugins/shell/android/src/main/java/ShellPlugin.kt b/plugins/shell/android/src/main/java/ShellPlugin.kt
new file mode 100644
index 00000000..4839483c
--- /dev/null
+++ b/plugins/shell/android/src/main/java/ShellPlugin.kt
@@ -0,0 +1,30 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+package app.tauri.shell
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import app.tauri.annotation.Command
+import app.tauri.annotation.TauriPlugin
+import app.tauri.plugin.Invoke
+import app.tauri.plugin.Plugin
+import java.io.File
+
+@TauriPlugin
+class ShellPlugin(private val activity: Activity) : Plugin(activity) {
+ @Command
+ fun open(invoke: Invoke) {
+ try {
+ val url = invoke.parseArgs(String::class.java)
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activity?.applicationContext?.startActivity(intent)
+ invoke.resolve()
+ } catch (ex: Exception) {
+ invoke.reject(ex.message)
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/shell/build.rs b/plugins/shell/build.rs
index 6a728570..fbfbb470 100644
--- a/plugins/shell/build.rs
+++ b/plugins/shell/build.rs
@@ -11,5 +11,20 @@ fn main() {
tauri_plugin::Builder::new(COMMANDS)
.global_api_script_path("./api-iife.js")
.global_scope_schema(schemars::schema_for!(scope_entry::Entry))
+ .android_path("android")
+ .ios_path("ios")
.build();
+
+ let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
+ let mobile = target_os == "ios" || target_os == "android";
+ alias("desktop", !mobile);
+ alias("mobile", mobile);
+}
+
+// creates a cfg alias if `has_feature` is true.
+// `alias` must be a snake case string.
+fn alias(alias: &str, has_feature: bool) {
+ if has_feature {
+ println!("cargo:rustc-cfg={alias}");
+ }
}
diff --git a/plugins/shell/ios/Package.resolved b/plugins/shell/ios/Package.resolved
new file mode 100644
index 00000000..5f998e0e
--- /dev/null
+++ b/plugins/shell/ios/Package.resolved
@@ -0,0 +1,16 @@
+{
+ "object": {
+ "pins": [
+ {
+ "package": "SwiftRs",
+ "repositoryURL": "https://github.com/Brendonovich/swift-rs",
+ "state": {
+ "branch": null,
+ "revision": "b5ed223fcdab165bc21219c1925dc1e77e2bef5e",
+ "version": "1.0.6"
+ }
+ }
+ ]
+ },
+ "version": 1
+}
diff --git a/plugins/shell/ios/Package.swift b/plugins/shell/ios/Package.swift
new file mode 100644
index 00000000..fa5d363d
--- /dev/null
+++ b/plugins/shell/ios/Package.swift
@@ -0,0 +1,33 @@
+// swift-tools-version:5.3
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+import PackageDescription
+
+let package = Package(
+ name: "tauri-plugin-shell",
+ platforms: [
+ .iOS(.v13),
+ ],
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "tauri-plugin-shell",
+ type: .static,
+ targets: ["tauri-plugin-shell"]),
+ ],
+ dependencies: [
+ .package(name: "Tauri", path: "../.tauri/tauri-api")
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages this package depends on.
+ .target(
+ name: "tauri-plugin-shell",
+ dependencies: [
+ .byName(name: "Tauri")
+ ],
+ path: "Sources")
+ ]
+)
diff --git a/plugins/shell/ios/Sources/ShellPlugin.swift b/plugins/shell/ios/Sources/ShellPlugin.swift
new file mode 100644
index 00000000..0fcb7dac
--- /dev/null
+++ b/plugins/shell/ios/Sources/ShellPlugin.swift
@@ -0,0 +1,34 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+import Foundation
+
+import SwiftRs
+import Tauri
+import UIKit
+import WebKit
+
+class ShellPlugin: Plugin {
+
+ @objc public func open(_ invoke: Invoke) throws {
+ do {
+ let urlString = try invoke.parseArgs(String.self)
+ if let url = URL(string: urlString) {
+ if #available(iOS 10, *) {
+ UIApplication.shared.open(url, options: [:])
+ } else {
+ UIApplication.shared.openURL(url)
+ }
+ }
+ invoke.resolve()
+ } catch {
+ invoke.reject(error.localizedDescription)
+ }
+ }
+}
+
+@_cdecl("init_plugin_shell")
+func initPlugin() -> Plugin {
+ return ShellPlugin()
+}
diff --git a/plugins/shell/src/error.rs b/plugins/shell/src/error.rs
index 99b13cfd..dfed22a1 100644
--- a/plugins/shell/src/error.rs
+++ b/plugins/shell/src/error.rs
@@ -8,6 +8,9 @@ use serde::{Serialize, Serializer};
#[derive(Debug, thiserror::Error)]
pub enum Error {
+ #[cfg(mobile)]
+ #[error(transparent)]
+ PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("current executable path has no parent")]
diff --git a/plugins/shell/src/lib.rs b/plugins/shell/src/lib.rs
index 1f92c6cc..e5f71688 100644
--- a/plugins/shell/src/lib.rs
+++ b/plugins/shell/src/lib.rs
@@ -35,11 +35,21 @@ mod scope_entry;
pub use error::Error;
type Result = std::result::Result;
+
+#[cfg(mobile)]
+use tauri::plugin::PluginHandle;
+#[cfg(target_os = "android")]
+const PLUGIN_IDENTIFIER: &str = "app.tauri.shell";
+#[cfg(target_os = "ios")]
+tauri::ios_plugin_binding!(init_plugin_shell);
+
type ChildStore = Arc>>;
pub struct Shell {
#[allow(dead_code)]
app: AppHandle,
+ #[cfg(mobile)]
+ mobile_plugin_handle: PluginHandle,
open_scope: scope::OpenScope,
children: ChildStore,
}
@@ -61,9 +71,20 @@ impl Shell {
/// Open a (url) path with a default or specific browser opening program.
///
/// See [`crate::open::open`] for how it handles security-related measures.
+ #[cfg(desktop)]
pub fn open(&self, path: impl Into, with: Option) -> Result<()> {
open::open(&self.open_scope, path.into(), with).map_err(Into::into)
}
+
+ /// Open a (url) path with a default or specific browser opening program.
+ ///
+ /// See [`crate::open::open`] for how it handles security-related measures.
+ #[cfg(mobile)]
+ pub fn open(&self, path: impl Into, _with: Option) -> Result<()> {
+ self.mobile_plugin_handle
+ .run_mobile_plugin("open", path.into())
+ .map_err(Into::into)
+ }
}
pub trait ShellExt {
@@ -89,10 +110,19 @@ pub fn init() -> TauriPlugin> {
.setup(|app, api| {
let default_config = config::Config::default();
let config = api.config().as_ref().unwrap_or(&default_config);
+
+ #[cfg(target_os = "android")]
+ let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ShellPlugin")?;
+ #[cfg(target_os = "ios")]
+ let handle = api.register_ios_plugin(init_plugin_shell)?;
+
app.manage(Shell {
app: app.clone(),
children: Default::default(),
open_scope: open_scope(&config.open),
+
+ #[cfg(mobile)]
+ mobile_plugin_handle: handle,
});
Ok(())
})
diff --git a/plugins/store/android/.gitignore b/plugins/store/android/.gitignore
new file mode 100644
index 00000000..c0f21ec2
--- /dev/null
+++ b/plugins/store/android/.gitignore
@@ -0,0 +1,2 @@
+/build
+/.tauri