diff --git a/apps/readest-app/src-tauri/capabilities/default.json b/apps/readest-app/src-tauri/capabilities/default.json index c5884ad0..f9b6ba8a 100644 --- a/apps/readest-app/src-tauri/capabilities/default.json +++ b/apps/readest-app/src-tauri/capabilities/default.json @@ -134,6 +134,14 @@ "oauth:allow-cancel", "sign-in-with-apple:default", "opener:default", + { + "identifier": "opener:allow-open-url", + "allow": [ + { + "url": "alipays:*" + } + ] + }, "haptics:allow-vibrate", "haptics:allow-impact-feedback", "haptics:allow-notification-feedback", diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt index 7c888351..1bacc83b 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt @@ -75,6 +75,11 @@ class SetScreenBrightnessRequestArgs { var brightness: Double? = null // 0.0 to 1.0 } +@InvokeArg +class OpenExternalUrlArgs { + var url: String? = null +} + @InvokeArg class FetchProductsRequestArgs { val productIds: List? = null @@ -660,4 +665,21 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) { } } } + + @Command + fun open_external_url(invoke: Invoke) { + val args = invoke.parseArgs(OpenExternalUrlArgs::class.java) + val url = args.url ?: "" + + try { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + activity.startActivity(intent) + val ret = JSObject() + ret.put("success", true) + invoke.resolve(ret) + } catch (e: Exception) { + invoke.reject("Failed to open URL: ${e.message}") + } + } } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs index c94c4af8..8e615e5a 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs @@ -18,6 +18,7 @@ const COMMANDS: &[&str] = &[ "get_screen_brightness", "set_screen_brightness", "get_external_sdcard_path", + "open_external_url", "request_manage_storage_permission", "check_permissions", "request_permissions", diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/open_external_url.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/open_external_url.toml new file mode 100644 index 00000000..4cc8c763 --- /dev/null +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/open_external_url.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-open-external-url" +description = "Enables the open_external_url command without any pre-configured scope." +commands.allow = ["open_external_url"] + +[[permission]] +identifier = "deny-open-external-url" +description = "Denies the open_external_url command without any pre-configured scope." +commands.deny = ["open_external_url"] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md index 3ba40b26..d08c0626 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md @@ -23,6 +23,7 @@ Default permissions for the plugin - `allow-get-screen-brightness` - `allow-set-screen-brightness` - `allow-get-external-sdcard-path` +- `allow-open-external-url` - `allow-request-manage-storage-permission` - `allow-check-permissions` - `allow-request-permissions` @@ -535,6 +536,32 @@ Denies the lock_screen_orientation command without any pre-configured scope. +`native-bridge:allow-open-external-url` + + + + +Enables the open_external_url command without any pre-configured scope. + + + + + + + +`native-bridge:deny-open-external-url` + + + + +Denies the open_external_url command without any pre-configured scope. + + + + + + + `native-bridge:allow-request-permissions` diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml index 1d308b03..d20ca78c 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml @@ -20,6 +20,7 @@ permissions = [ "allow-get-screen-brightness", "allow-set-screen-brightness", "allow-get-external-sdcard-path", + "allow-open-external-url", "allow-request-manage-storage-permission", "allow-check-permissions", "allow-request-permissions", diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json index fd6b27ab..23756183 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json @@ -522,6 +522,18 @@ "const": "deny-lock-screen-orientation", "markdownDescription": "Denies the lock_screen_orientation command without any pre-configured scope." }, + { + "description": "Enables the open_external_url command without any pre-configured scope.", + "type": "string", + "const": "allow-open-external-url", + "markdownDescription": "Enables the open_external_url command without any pre-configured scope." + }, + { + "description": "Denies the open_external_url command without any pre-configured scope.", + "type": "string", + "const": "deny-open-external-url", + "markdownDescription": "Denies the open_external_url command without any pre-configured scope." + }, { "description": "Enables the request-permissions command without any pre-configured scope.", "type": "string", @@ -607,10 +619,10 @@ "markdownDescription": "Denies the use_background_audio command without any pre-configured scope." }, { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-request-manage-storage-permission`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`", + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-request-manage-storage-permission`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`", "type": "string", "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-request-manage-storage-permission`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`" + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-request-manage-storage-permission`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`" } ] } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs index d05ce20a..54ed1f57 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs @@ -149,6 +149,14 @@ pub(crate) async fn get_external_sdcard_path( app.native_bridge().get_external_sdcard_path() } +#[command] +pub(crate) async fn open_external_url( + app: AppHandle, + payload: OpenExternalUrlRequest, +) -> Result { + app.native_bridge().open_external_url(payload) +} + #[command] pub(crate) async fn request_manage_storage_permission( app: AppHandle, diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs index e1ab8cf7..84eefae3 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs @@ -122,6 +122,13 @@ impl NativeBridge { Err(crate::Error::UnsupportedPlatformError) } + pub fn open_external_url( + &self, + _payload: OpenExternalUrlRequest, + ) -> crate::Result { + Err(crate::Error::UnsupportedPlatformError) + } + pub fn request_manage_storage_permission( &self, ) -> crate::Result { diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs index c2fa1240..1c6cf0a3 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs @@ -56,6 +56,7 @@ pub fn init() -> TauriPlugin { commands::get_screen_brightness, commands::set_screen_brightness, commands::get_external_sdcard_path, + commands::open_external_url, commands::request_manage_storage_permission, ]) .setup(|app, api| { diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs index 994dc133..99a1cc2d 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs @@ -197,6 +197,17 @@ impl NativeBridge { } } +impl NativeBridge { + pub fn open_external_url( + &self, + payload: OpenExternalUrlRequest, + ) -> crate::Result { + self.0 + .run_mobile_plugin("open_external_url", payload) + .map_err(Into::into) + } +} + impl NativeBridge { pub fn request_manage_storage_permission( &self, diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs index c28f561c..e11c9e7e 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs @@ -202,3 +202,16 @@ pub struct GetExternalSDCardPathResponse { pub struct RequestManageStoragePermissionResponse { pub manage_storage: String, // "granted", "denied", or "prompt" } + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OpenExternalUrlRequest { + pub url: String, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OpenExternalUrlResponse { + pub success: bool, + pub error: Option, +} diff --git a/apps/readest-app/src-tauri/src/lib.rs b/apps/readest-app/src-tauri/src/lib.rs index 573b3874..1a86d9d7 100644 --- a/apps/readest-app/src-tauri/src/lib.rs +++ b/apps/readest-app/src-tauri/src/lib.rs @@ -21,7 +21,11 @@ use tauri_plugin_fs::FsExt; mod macos; mod transfer_file; use tauri::{command, Emitter, WebviewUrl, WebviewWindowBuilder, Window}; +#[cfg(target_os = "android")] +use tauri_plugin_native_bridge::{NativeBridgeExt, OpenExternalUrlRequest}; use tauri_plugin_oauth::start; +#[cfg(not(target_os = "android"))] +use tauri_plugin_opener::OpenerExt; use transfer_file::{download_file, upload_file}; #[cfg(desktop)] @@ -257,9 +261,56 @@ pub fn run() { eprintln!("Failed to initialize tauri_plugin_log: {e}"); }; + let app_handle = app.handle().clone(); let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default()) .background_throttling(BackgroundThrottlingPolicy::Disabled) - .background_color(tauri::window::Color(50, 49, 48, 255)); + .background_color(tauri::window::Color(50, 49, 48, 255)) + .initialization_script( + r#" + window.addEventListener('DOMContentLoaded', function() { + const isTauriLocal = window.location.protocol === 'tauri:' || + window.location.hostname === 'tauri.localhost'; + const needsSafeArea = !isTauriLocal; + if (needsSafeArea && !document.getElementById('safe-area-style')) { + const style = document.createElement('style'); + style.id = 'safe-area-style'; + style.textContent = ` + body { + padding-top: env(safe-area-inset-top) !important; + padding-bottom: env(safe-area-inset-bottom) !important; + padding-left: env(safe-area-inset-left) !important; + padding-right: env(safe-area-inset-right) !important; + } + `; + document.head.appendChild(style); + } + }); + "#, + ) + .on_navigation(move |url| { + if url.scheme() == "alipays" || url.scheme() == "alipay" { + let url_str = url.as_str().to_string(); + #[cfg(target_os = "android")] + { + let handle = app_handle.clone(); + tauri::async_runtime::spawn(async move { + match handle + .native_bridge() + .open_external_url(OpenExternalUrlRequest { url: url_str }) + { + Ok(result) => println!("Result: {:?}", result), + Err(e) => eprintln!("Error: {:?}", e), + } + }); + } + #[cfg(not(target_os = "android"))] + { + let _ = app_handle.opener().open_url(url_str, None::<&str>); + } + return false; + } + true + }); #[cfg(desktop)] let win_builder = win_builder.inner_size(800.0, 600.0).resizable(true); diff --git a/apps/readest-app/src/app/library/components/Bookshelf.tsx b/apps/readest-app/src/app/library/components/Bookshelf.tsx index 4e664ee7..75f68a1c 100644 --- a/apps/readest-app/src/app/library/components/Bookshelf.tsx +++ b/apps/readest-app/src/app/library/components/Bookshelf.tsx @@ -328,7 +328,7 @@ const Bookshelf: React.FC = ({ /> ))} {viewMode === 'grid' && currentBookshelfItems.length > 0 && ( -
+