Преглед изворни кода

feat(cli): update plugin template including trait to access mobile APIs (#6293)

Lucas Fernandes Nogueira пре 2 година
родитељ
комит
897a1547a1

+ 4 - 0
core/tauri-build/src/mobile.rs

@@ -33,6 +33,10 @@ impl PluginBuilder {
   /// Injects the mobile templates in the given path relative to the manifest root.
   pub fn run(self) -> Result<()> {
     let target_os = var("CARGO_CFG_TARGET_OS").unwrap();
+    let mobile = target_os == "android" || target_os == "ios";
+    crate::cfg_alias("mobile", mobile);
+    crate::cfg_alias("desktop", !mobile);
+
     match target_os.as_str() {
       "android" => {
         if let Some(path) = self.android_path {

+ 2 - 0
examples/api/src-tauri/Cargo.lock

@@ -3159,8 +3159,10 @@ name = "tauri-plugin-sample"
 version = "0.1.0"
 dependencies = [
  "log",
+ "serde",
  "tauri",
  "tauri-build",
+ "thiserror",
 ]
 
 [[package]]

+ 10 - 0
examples/api/src-tauri/src/lib.rs

@@ -16,6 +16,7 @@ pub use mobile::*;
 
 use serde::Serialize;
 use tauri::{window::WindowBuilder, App, AppHandle, RunEvent, WindowUrl};
+use tauri_plugin_sample::{PingRequest, SampleExt};
 
 #[derive(Clone, Serialize)]
 struct Reply {
@@ -95,6 +96,15 @@ impl AppBuilder {
         #[cfg(debug_assertions)]
         window.open_devtools();
 
+        let value = Some("test".to_string());
+        let response = app.sample().ping(PingRequest {
+          value: value.clone(),
+        });
+        println!("got response: {:?}", response);
+        if let Ok(res) = response {
+          assert_eq!(res.value, value);
+        }
+
         #[cfg(desktop)]
         std::thread::spawn(|| {
           let server = match tiny_http::Server::http("localhost:3003") {

+ 2 - 0
examples/api/src-tauri/tauri-plugin-sample/Cargo.toml

@@ -6,6 +6,8 @@ edition = "2021"
 [dependencies]
 tauri = { path = "../../../../core/tauri" }
 log = "0.4"
+serde = "1"
+thiserror = "1"
 
 [build-dependencies]
 tauri-build = { path = "../../../../core/tauri-build/" }

+ 22 - 0
examples/api/src-tauri/tauri-plugin-sample/src/desktop.rs

@@ -0,0 +1,22 @@
+use serde::de::DeserializeOwned;
+use tauri::{plugin::PluginApi, AppHandle, Runtime};
+
+use crate::models::*;
+
+pub fn init<R: Runtime, C: DeserializeOwned>(
+  app: &AppHandle<R>,
+  _api: PluginApi<R, C>,
+) -> crate::Result<Sample<R>> {
+  Ok(Sample(app.clone()))
+}
+
+/// A helper class to access the sample APIs.
+pub struct Sample<R: Runtime>(AppHandle<R>);
+
+impl<R: Runtime> Sample<R> {
+  pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
+    Ok(PingResponse {
+      value: payload.value,
+    })
+  }
+}

+ 8 - 0
examples/api/src-tauri/tauri-plugin-sample/src/error.rs

@@ -0,0 +1,8 @@
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+  #[cfg(mobile)]
+  #[error(transparent)]
+  PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
+}
+
+pub type Result<T> = std::result::Result<T, Error>;

+ 30 - 15
examples/api/src-tauri/tauri-plugin-sample/src/lib.rs

@@ -1,29 +1,44 @@
 use tauri::{
-  plugin::{Builder, PluginHandle, TauriPlugin},
+  plugin::{Builder, TauriPlugin},
   Manager, Runtime,
 };
 
-#[cfg(target_os = "android")]
-const PLUGIN_IDENTIFIER: &str = "com.plugin.sample";
+pub use models::*;
 
-#[cfg(target_os = "ios")]
-extern "C" {
-  fn init_plugin_sample(webview: tauri::cocoa::base::id);
+#[cfg(desktop)]
+mod desktop;
+#[cfg(mobile)]
+mod mobile;
+
+mod error;
+mod models;
+
+#[cfg(desktop)]
+use desktop::Sample;
+#[cfg(mobile)]
+use mobile::Sample;
+
+pub use error::*;
+
+/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the sample APIs.
+pub trait SampleExt<R: Runtime> {
+  fn sample(&self) -> &Sample<R>;
 }
 
-pub struct SamplePlugin<R: Runtime>(PluginHandle<R>);
+impl<R: Runtime, T: Manager<R>> crate::SampleExt<R> for T {
+  fn sample(&self) -> &Sample<R> {
+    self.state::<Sample<R>>().inner()
+  }
+}
 
 pub fn init<R: Runtime>() -> TauriPlugin<R> {
   Builder::new("sample")
     .setup(|app, api| {
-      #[cfg(any(target_os = "android", target_os = "ios"))]
-      {
-        #[cfg(target_os = "android")]
-        let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?;
-        #[cfg(target_os = "ios")]
-        let handle = api.register_ios_plugin(init_plugin_sample)?;
-        app.manage(SamplePlugin(handle));
-      }
+      #[cfg(mobile)]
+      let sample = mobile::init(app, api)?;
+      #[cfg(desktop)]
+      let sample = desktop::init(app, api)?;
+      app.manage(sample);
 
       Ok(())
     })

+ 39 - 0
examples/api/src-tauri/tauri-plugin-sample/src/mobile.rs

@@ -0,0 +1,39 @@
+use serde::de::DeserializeOwned;
+use tauri::{
+  plugin::{PluginApi, PluginHandle},
+  AppHandle, Runtime,
+};
+
+use crate::models::*;
+
+#[cfg(target_os = "android")]
+const PLUGIN_IDENTIFIER: &str = "com.plugin.sample";
+
+#[cfg(target_os = "ios")]
+extern "C" {
+  fn init_plugin_sample(webview: tauri::cocoa::base::id);
+}
+
+// initializes the Kotlin or Swift plugin classes
+pub fn init<R: Runtime, C: DeserializeOwned>(
+  _app: &AppHandle<R>,
+  api: PluginApi<R, C>,
+) -> crate::Result<Sample<R>> {
+  #[cfg(target_os = "android")]
+  let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?;
+  #[cfg(target_os = "ios")]
+  let handle = api.register_ios_plugin(init_plugin_sample)?;
+  Ok(Sample(handle))
+}
+
+/// A helper class to access the sample APIs.
+pub struct Sample<R: Runtime>(PluginHandle<R>);
+
+impl<R: Runtime> Sample<R> {
+  pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
+    self
+      .0
+      .run_mobile_plugin("ping", payload)
+      .map_err(Into::into)
+  }
+}

+ 11 - 0
examples/api/src-tauri/tauri-plugin-sample/src/models.rs

@@ -0,0 +1,11 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize)]
+pub struct PingRequest {
+  pub value: Option<String>,
+}
+
+#[derive(Debug, Clone, Default, Deserialize)]
+pub struct PingResponse {
+  pub value: Option<String>,
+}

+ 16 - 0
tooling/cli/templates/plugin/src/commands.rs

@@ -0,0 +1,16 @@
+{{#if license_header}}
+{{ license_header }}
+{{/if}}
+use tauri::{AppHandle, command, Runtime, State, Window};
+
+use crate::{MyState, Result};
+
+#[command]
+pub(crate) async fn execute<R: Runtime>(
+  _app: AppHandle<R>,
+  _window: Window<R>,
+  state: State<'_, MyState>,
+) -> Result<String> {
+  state.0.lock().unwrap().insert("key".into(), "value".into());
+  Ok("success".to_string())
+}

+ 25 - 0
tooling/cli/templates/plugin/src/desktop.rs

@@ -0,0 +1,25 @@
+{{#if license_header}}
+{{ license_header }}
+{{/if}}
+use serde::de::DeserializeOwned;
+use tauri::{plugin::PluginApi, AppHandle, Runtime};
+
+use crate::models::*;
+
+pub fn init<R: Runtime, C: DeserializeOwned>(
+  app: &AppHandle<R>,
+  _api: PluginApi<R, C>,
+) -> crate::Result<{{ plugin_name_pascal_case }}<R>> {
+  Ok({{ plugin_name_pascal_case }}(app.clone()))
+}
+
+/// Access to the {{ plugin_name }} APIs.
+pub struct {{ plugin_name_pascal_case }}<R: Runtime>(AppHandle<R>);
+
+impl<R: Runtime> {{ plugin_name_pascal_case }}<R> {
+  pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
+    Ok(PingResponse {
+      value: payload.value,
+    })
+  }
+}

+ 24 - 0
tooling/cli/templates/plugin/src/error.rs

@@ -0,0 +1,24 @@
+{{#if license_header}}
+{{ license_header }}
+{{/if}}
+use serde::{ser::Serializer, Serialize};
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+  #[error(transparent)]
+  Io(#[from] std::io::Error),
+  #[cfg(mobile)]
+  #[error(transparent)]
+  PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
+}
+
+impl Serialize for Error {
+  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+  where
+    S: Serializer,
+  {
+    serializer.serialize_str(self.to_string().as_ref())
+  }
+}

+ 29 - 44
tooling/cli/templates/plugin/src/lib.rs

@@ -1,70 +1,55 @@
 {{#if license_header}}
 {{ license_header }}
 {{/if}}
-
-use serde::{ser::Serializer, Serialize};
 use tauri::{
-  command,
-  plugin::{Builder, PluginHandle, TauriPlugin},
-  AppHandle, Manager, Runtime, State, Window,
+  plugin::{Builder, TauriPlugin},
+  Manager, Runtime,
 };
 
 use std::{collections::HashMap, sync::Mutex};
 
-type Result<T> = std::result::Result<T, Error>;
+pub use models::*;
 
-#[cfg(target_os = "android")]
-const PLUGIN_IDENTIFIER: &str = "{{ android_package_id }}";
+#[cfg(desktop)]
+mod desktop;
+#[cfg(mobile)]
+mod mobile;
 
-#[cfg(target_os = "ios")]
-extern "C" {
-  fn init_plugin_{{ plugin_name }}(webview: tauri::cocoa::base::id);
-}
+mod commands;
+mod error;
+mod models;
 
-#[derive(Debug, thiserror::Error)]
-pub enum Error {
-  #[error(transparent)]
-  Io(#[from] std::io::Error),
-}
+pub use error::{Error, Result};
 
-impl Serialize for Error {
-  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
-  where
-    S: Serializer,
-  {
-    serializer.serialize_str(self.to_string().as_ref())
-  }
-}
+#[cfg(desktop)]
+use desktop::{{ plugin_name_pascal_case }};
+#[cfg(mobile)]
+use mobile::{{ plugin_name_pascal_case }};
 
 #[derive(Default)]
 struct MyState(Mutex<HashMap<String, String>>);
 
-pub struct {{ plugin_name_pascal_case }}Plugin<R: Runtime>(PluginHandle<R>);
+/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the {{ plugin_name }} APIs.
+pub trait {{ plugin_name_pascal_case }}Ext<R: Runtime> {
+  fn {{ plugin_name_snake_case }}(&self) -> &{{ plugin_name_pascal_case }}<R>;
+}
 
-#[command]
-async fn execute<R: Runtime>(
-  _app: AppHandle<R>,
-  _window: Window<R>,
-  state: State<'_, MyState>,
-) -> Result<String> {
-  state.0.lock().unwrap().insert("key".into(), "value".into());
-  Ok("success".to_string())
+impl<R: Runtime, T: Manager<R>> crate::{{ plugin_name_pascal_case }}Ext<R> for T {
+  fn {{ plugin_name_snake_case }}(&self) -> &{{ plugin_name_pascal_case }}<R> {
+    self.state::<{{ plugin_name_pascal_case }}<R>>().inner()
+  }
 }
 
 /// Initializes the plugin.
 pub fn init<R: Runtime>() -> TauriPlugin<R> {
   Builder::new("{{ plugin_name }}")
-    .invoke_handler(tauri::generate_handler![execute])
+    .invoke_handler(tauri::generate_handler![commands::execute])
     .setup(|app, api| {
-      // initialize mobile plugins
-      #[cfg(any(target_os = "android", target_os = "ios"))]
-      {
-        #[cfg(target_os = "android")]
-        let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?;
-        #[cfg(target_os = "ios")]
-        let handle = api.register_ios_plugin(init_plugin_{{ plugin_name }})?;
-        app.manage({{ plugin_name_pascal_case }}Plugin(handle));
-      }
+      #[cfg(mobile)]
+      let {{ plugin_name_snake_case }} = mobile::init(app, api)?;
+      #[cfg(desktop)]
+      let {{ plugin_name_snake_case }} = desktop::init(app, api)?;
+      app.manage({{ plugin_name_snake_case }});
 
       // manage state so it is accessible by the commands
       app.manage(MyState::default());

+ 42 - 0
tooling/cli/templates/plugin/src/mobile.rs

@@ -0,0 +1,42 @@
+{{#if license_header}}
+{{ license_header }}
+{{/if}}
+use serde::de::DeserializeOwned;
+use tauri::{
+  plugin::{PluginApi, PluginHandle},
+  AppHandle, Runtime,
+};
+
+use crate::models::*;
+
+#[cfg(target_os = "android")]
+const PLUGIN_IDENTIFIER: &str = "{{ android_package_id }}";
+
+#[cfg(target_os = "ios")]
+extern "C" {
+  fn init_plugin_{{ plugin_name }}(webview: tauri::cocoa::base::id);
+}
+
+// initializes the Kotlin or Swift plugin classes
+pub fn init<R: Runtime, C: DeserializeOwned>(
+  _app: &AppHandle<R>,
+  api: PluginApi<R, C>,
+) -> crate::Result<{{ plugin_name_pascal_case }}<R>> {
+  #[cfg(target_os = "android")]
+  let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?;
+  #[cfg(target_os = "ios")]
+  let handle = api.register_ios_plugin(init_plugin_{{ plugin_name }})?;
+  Ok({{ plugin_name_pascal_case }}(handle))
+}
+
+/// Access to the {{ plugin_name }} APIs.
+pub struct {{ plugin_name_pascal_case }}<R: Runtime>(PluginHandle<R>);
+
+impl<R: Runtime> {{ plugin_name_pascal_case }}<R> {
+  pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
+    self
+      .0
+      .run_mobile_plugin("ping", payload)
+      .map_err(Into::into)
+  }
+}

+ 16 - 0
tooling/cli/templates/plugin/src/models.rs

@@ -0,0 +1,16 @@
+{{#if license_header}}
+{{ license_header }}
+{{/if}}
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PingRequest {
+  pub value: Option<String>,
+}
+
+#[derive(Debug, Clone, Default, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PingResponse {
+  pub value: Option<String>,
+}