mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-01-31 00:35:19 +01:00
* feat(core): back button event and exit on Android, closes #8142 I've used https://github.com/ionic-team/capacitor-plugins/blob/main/app/android/src/main/java/com/capacitorjs/plugins/app/AppPlugin.java as a reference here, checking if there's a back button event handler with a default of webview's goBack implementation * missing change file * remove exit impl * fmt * update wry * fix default back press * add remove_listener
This commit is contained in:
committed by
GitHub
parent
3b4fac2017
commit
3397fd9bfe
5
.changes/android-app-plugin.md
Normal file
5
.changes/android-app-plugin.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": minor:feat
|
||||
---
|
||||
|
||||
Added mobile app plugin to support exit and back button press event.
|
||||
5
.changes/back-button-press-api.md
Normal file
5
.changes/back-button-press-api.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tauri-apps/api": minor:feat
|
||||
---
|
||||
|
||||
Added `app > onBackButtonPress` for Android back button handling.
|
||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -10947,9 +10947,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.53.2"
|
||||
version = "0.53.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b6763512fe4b51c80b3ce9b50939d682acb4de335dfabbdb20d7a2642199b7"
|
||||
checksum = "6d78ec082b80fa088569a970d043bb3050abaabf4454101d44514ee8d9a8c9f6"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.0",
|
||||
|
||||
@@ -17,7 +17,7 @@ rustc-args = ["--cfg", "docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
wry = { version = "0.53.2", default-features = false, features = [
|
||||
wry = { version = "0.53.4", default-features = false, features = [
|
||||
"drag-drop",
|
||||
"protocol",
|
||||
"os-webview",
|
||||
|
||||
@@ -164,6 +164,8 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
|
||||
("set_app_theme", false),
|
||||
("set_dock_visibility", false),
|
||||
("bundle_type", true),
|
||||
("register_listener", true),
|
||||
("remove_listener", true),
|
||||
],
|
||||
),
|
||||
(
|
||||
|
||||
@@ -12,6 +12,7 @@ import app.tauri.plugin.PluginManager
|
||||
|
||||
abstract class TauriActivity : WryActivity() {
|
||||
var pluginManager: PluginManager = PluginManager(this)
|
||||
override val handleBackNavigation: Boolean = false
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package app.tauri
|
||||
|
||||
import android.app.Activity
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import app.tauri.annotation.Command
|
||||
import app.tauri.annotation.TauriPlugin
|
||||
import app.tauri.plugin.Plugin
|
||||
import app.tauri.plugin.Invoke
|
||||
import app.tauri.plugin.JSObject
|
||||
|
||||
@TauriPlugin
|
||||
class AppPlugin(private val activity: Activity): Plugin(activity) {
|
||||
private val BACK_BUTTON_EVENT = "back-button"
|
||||
|
||||
private var webView: WebView? = null
|
||||
|
||||
override fun load(webView: WebView) {
|
||||
this.webView = webView
|
||||
}
|
||||
|
||||
init {
|
||||
val callback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (!hasListener(BACK_BUTTON_EVENT)) {
|
||||
if (this@AppPlugin.webView?.canGoBack() == true) {
|
||||
this@AppPlugin.webView!!.goBack()
|
||||
} else {
|
||||
this.isEnabled = false
|
||||
this@AppPlugin.activity.onBackPressed()
|
||||
this.isEnabled = true
|
||||
}
|
||||
} else {
|
||||
val data = JSObject().apply {
|
||||
put("canGoBack", this@AppPlugin.webView?.canGoBack() ?: false)
|
||||
}
|
||||
trigger(BACK_BUTTON_EVENT, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
(activity as AppCompatActivity).onBackPressedDispatcher.addCallback(activity, callback)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun exit(invoke: Invoke) {
|
||||
invoke.resolve()
|
||||
activity.finish()
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.core.app.ActivityCompat
|
||||
@@ -22,7 +21,6 @@ import app.tauri.annotation.InvokeArg
|
||||
import app.tauri.annotation.PermissionCallback
|
||||
import app.tauri.annotation.TauriPlugin
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.json.JSONException
|
||||
import java.util.*
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@@ -148,6 +146,10 @@ abstract class Plugin(private val activity: Activity) {
|
||||
}
|
||||
}
|
||||
|
||||
fun hasListener(event: String): Boolean {
|
||||
return !listeners[event].isNullOrEmpty()
|
||||
}
|
||||
|
||||
@Command
|
||||
open fun registerListener(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(RegisterListenerArgs::class.java)
|
||||
|
||||
@@ -9,6 +9,8 @@ Default permissions for the plugin.
|
||||
- `allow-tauri-version`
|
||||
- `allow-identifier`
|
||||
- `allow-bundle-type`
|
||||
- `allow-register-listener`
|
||||
- `allow-remove-listener`
|
||||
|
||||
## Permission Table
|
||||
|
||||
@@ -204,6 +206,32 @@ Denies the name command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`core:app:allow-register-listener`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the register_listener command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`core:app:deny-register-listener`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the register_listener command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`core:app:allow-remove-data-store`
|
||||
|
||||
</td>
|
||||
@@ -230,6 +258,32 @@ Denies the remove_data_store command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`core:app:allow-remove-listener`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the remove_listener command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`core:app:deny-remove-listener`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the remove_listener command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`core:app:allow-set-app-theme`
|
||||
|
||||
</td>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -132,5 +132,16 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
set_dock_visibility,
|
||||
bundle_type,
|
||||
])
|
||||
.setup(|_app, _api| {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
let handle = _api.register_android_plugin("app.tauri", "AppPlugin")?;
|
||||
_app.manage(AppPlugin(handle));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub(crate) struct AppPlugin<R: Runtime>(pub crate::plugin::PluginHandle<R>);
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
|
||||
use super::Result;
|
||||
use crate::{plugin::PluginHandle, Runtime};
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// A helper class to access the mobile path APIs.
|
||||
pub struct PathResolver<R: Runtime>(pub(crate) PluginHandle<R>);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { invoke } from './core'
|
||||
import { addPluginListener, invoke, PluginListener } from './core'
|
||||
import { Image } from './image'
|
||||
import { Theme } from './window'
|
||||
|
||||
@@ -252,6 +252,28 @@ async function getBundleType(): Promise<BundleType> {
|
||||
return invoke('plugin:app|bundle_type')
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload for the onBackButtonPress event.
|
||||
*/
|
||||
type OnBackButtonPressPayload = {
|
||||
/** Whether the webview canGoBack property is true. */
|
||||
canGoBack: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to the backButton event on Android.
|
||||
* @param handler
|
||||
*/
|
||||
async function onBackButtonPress(
|
||||
handler: (payload: OnBackButtonPressPayload) => void
|
||||
): Promise<PluginListener> {
|
||||
return addPluginListener<OnBackButtonPressPayload>(
|
||||
'app',
|
||||
'back-button',
|
||||
handler
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
getName,
|
||||
getVersion,
|
||||
@@ -264,5 +286,7 @@ export {
|
||||
fetchDataStoreIdentifiers,
|
||||
removeDataStore,
|
||||
setDockVisibility,
|
||||
getBundleType
|
||||
getBundleType,
|
||||
type OnBackButtonPressPayload,
|
||||
onBackButtonPress
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user