fix: add Tauri opener plugin for external URL handling and improve notification focus behavior
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"@tauri-apps/api": "2.9.1",
|
"@tauri-apps/api": "2.9.1",
|
||||||
"@tauri-apps/plugin-fs": "2.4.5",
|
"@tauri-apps/plugin-fs": "2.4.5",
|
||||||
"@tauri-apps/plugin-notification": "2.3.3",
|
"@tauri-apps/plugin-notification": "2.3.3",
|
||||||
|
"@tauri-apps/plugin-opener": "2",
|
||||||
"@vanilla-extract/css": "1.9.3",
|
"@vanilla-extract/css": "1.9.3",
|
||||||
"@vanilla-extract/recipes": "0.3.0",
|
"@vanilla-extract/recipes": "0.3.0",
|
||||||
"@vanilla-extract/vite-plugin": "3.7.1",
|
"@vanilla-extract/vite-plugin": "3.7.1",
|
||||||
|
|||||||
@@ -90,13 +90,15 @@ function InviteNotifications() {
|
|||||||
const notify = useCallback(
|
const notify = useCallback(
|
||||||
(count: number) => {
|
(count: number) => {
|
||||||
const body = `You have ${count} new invitation request.`;
|
const body = `You have ${count} new invitation request.`;
|
||||||
|
const invitesPath = getInboxInvitesPath();
|
||||||
|
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
sendNotification({
|
sendNotification({
|
||||||
title: 'Invitation',
|
title: 'Invitation',
|
||||||
body,
|
body,
|
||||||
|
path: invitesPath,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (!window.closed) navigate(getInboxInvitesPath());
|
if (!window.closed) navigate(invitesPath);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -108,7 +110,8 @@ function InviteNotifications() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
noti.onclick = () => {
|
noti.onclick = () => {
|
||||||
if (!window.closed) navigate(getInboxInvitesPath());
|
window.focus();
|
||||||
|
if (!window.closed) navigate(invitesPath);
|
||||||
noti.close();
|
noti.close();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -207,6 +210,7 @@ function MessageNotifications() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
noti.onclick = () => {
|
noti.onclick = () => {
|
||||||
|
window.focus();
|
||||||
if (!window.closed) navigate(roomPath);
|
if (!window.closed) navigate(roomPath);
|
||||||
noti.close();
|
noti.close();
|
||||||
notifRef.current = undefined;
|
notifRef.current = undefined;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Open an external URL using Tauri shell on desktop/mobile, or window.open in browser
|
* Open an external URL using Tauri opener plugin on desktop/mobile, or window.open in browser
|
||||||
* @param url The URL to open
|
* @param url The URL to open
|
||||||
*/
|
*/
|
||||||
export const openExternalUrl = async (url: string): Promise<void> => {
|
export const openExternalUrl = async (url: string): Promise<void> => {
|
||||||
@@ -9,11 +9,11 @@ export const openExternalUrl = async (url: string): Promise<void> => {
|
|||||||
// Use new Function to avoid static analysis by Vite/Rollup
|
// Use new Function to avoid static analysis by Vite/Rollup
|
||||||
await new Function(
|
await new Function(
|
||||||
'url',
|
'url',
|
||||||
"return import('@tauri-apps/plugin-shell').then(m => m.open(url));"
|
"return import('@tauri-apps/plugin-opener').then(m => m.openUrl(url));"
|
||||||
)(url);
|
)(url);
|
||||||
return;
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Tauri shell.open failed, falling back to window.open:', err);
|
console.warn('Tauri opener.openUrl failed, falling back to window.open:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
@@ -26,7 +26,23 @@ export const openExternalUrl = async (url: string): Promise<void> => {
|
|||||||
let notificationTapCallback: ((path: string) => void) | null = null;
|
let notificationTapCallback: ((path: string) => void) | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up listener for notification tap actions on mobile
|
* Bring the Tauri window to the front and focus it
|
||||||
|
*/
|
||||||
|
export const focusWindow = async (): Promise<void> => {
|
||||||
|
if (!isTauri()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
||||||
|
const window = getCurrentWindow();
|
||||||
|
await window.unminimize();
|
||||||
|
await window.setFocus();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to focus window:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up listener for notification tap/click actions
|
||||||
* Call this once at app startup with a navigation callback
|
* Call this once at app startup with a navigation callback
|
||||||
*/
|
*/
|
||||||
export const setupNotificationTapListener = async (onTap: (path: string) => void): Promise<void> => {
|
export const setupNotificationTapListener = async (onTap: (path: string) => void): Promise<void> => {
|
||||||
@@ -37,7 +53,10 @@ export const setupNotificationTapListener = async (onTap: (path: string) => void
|
|||||||
try {
|
try {
|
||||||
const { onAction } = await import('@tauri-apps/plugin-notification');
|
const { onAction } = await import('@tauri-apps/plugin-notification');
|
||||||
|
|
||||||
await onAction((notification) => {
|
await onAction(async (notification) => {
|
||||||
|
// Bring window to front first
|
||||||
|
await focusWindow();
|
||||||
|
|
||||||
// Get the path from extra data and navigate
|
// Get the path from extra data and navigate
|
||||||
const path = notification.notification?.extra?.path;
|
const path = notification.notification?.extra?.path;
|
||||||
if (path && typeof path === 'string' && notificationTapCallback) {
|
if (path && typeof path === 'string' && notificationTapCallback) {
|
||||||
@@ -115,7 +134,11 @@ const sendBrowserNotification = (options: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
notification.onclick = () => {
|
notification.onclick = async () => {
|
||||||
|
// Bring Tauri window to front if in Tauri
|
||||||
|
await focusWindow();
|
||||||
|
// Also use standard window.focus for browser
|
||||||
|
window.focus();
|
||||||
if (!window.closed) onClick();
|
if (!window.closed) onClick();
|
||||||
notification.close();
|
notification.close();
|
||||||
};
|
};
|
||||||
@@ -124,6 +147,34 @@ const sendBrowserNotification = (options: {
|
|||||||
return notification;
|
return notification;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Flag to track if notification channel has been created on Android */
|
||||||
|
let notificationChannelCreated = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create notification channel for Android
|
||||||
|
* Required for Android 8.0+ to show notifications
|
||||||
|
*/
|
||||||
|
const ensureNotificationChannel = async (): Promise<void> => {
|
||||||
|
if (notificationChannelCreated || !isAndroid()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { createChannel, Importance } = await import('@tauri-apps/plugin-notification');
|
||||||
|
|
||||||
|
await createChannel({
|
||||||
|
id: 'messages',
|
||||||
|
name: 'Messages',
|
||||||
|
description: 'Message notifications from Matrix',
|
||||||
|
importance: Importance.High,
|
||||||
|
vibration: true,
|
||||||
|
sound: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationChannelCreated = true;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to create notification channel:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a notification using Tauri's notification plugin
|
* Send a notification using Tauri's notification plugin
|
||||||
* Falls back to browser Notification API if not in Tauri
|
* Falls back to browser Notification API if not in Tauri
|
||||||
@@ -152,9 +203,14 @@ export const sendNotification = async (options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (permissionGranted) {
|
if (permissionGranted) {
|
||||||
|
// Ensure notification channel exists on Android
|
||||||
|
await ensureNotificationChannel();
|
||||||
|
|
||||||
await tauriSendNotification({
|
await tauriSendNotification({
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
|
// Use the channel on Android
|
||||||
|
channelId: isAndroid() ? 'messages' : undefined,
|
||||||
// Store path in extra data for notification tap handling
|
// Store path in extra data for notification tap handling
|
||||||
extra: path ? { path } : undefined,
|
extra: path ? { path } : undefined,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user