feat: enhance Tauri support with notification handling and safe area insets
This commit is contained in:
150
src/app/utils/tauri.ts
Normal file
150
src/app/utils/tauri.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Tauri-specific utilities for desktop and mobile platforms
|
||||
*/
|
||||
|
||||
/** Callback for notification tap actions */
|
||||
let notificationTapCallback: ((path: string) => void) | null = null;
|
||||
|
||||
/**
|
||||
* Set up listener for notification tap actions on mobile
|
||||
* Call this once at app startup with a navigation callback
|
||||
*/
|
||||
export const setupNotificationTapListener = async (onTap: (path: string) => void): Promise<void> => {
|
||||
if (!isTauri()) return;
|
||||
|
||||
notificationTapCallback = onTap;
|
||||
|
||||
try {
|
||||
const { onAction } = await import('@tauri-apps/plugin-notification');
|
||||
|
||||
await onAction((notification) => {
|
||||
// Get the path from extra data and navigate
|
||||
const path = notification.notification?.extra?.path;
|
||||
if (path && typeof path === 'string' && notificationTapCallback) {
|
||||
notificationTapCallback(path);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('Failed to set up notification tap listener:', err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if we're running inside a Tauri application
|
||||
*/
|
||||
export const isTauri = (): boolean =>
|
||||
'__TAURI__' in window || '__TAURI_INTERNALS__' in window;
|
||||
|
||||
/**
|
||||
* Check if we're running on a mobile platform (Android/iOS)
|
||||
*/
|
||||
export const isTauriMobile = (): boolean => {
|
||||
if (!isTauri()) return false;
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
return ua.includes('android') || ua.includes('iphone') || ua.includes('ipad');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if we're running on Android
|
||||
*/
|
||||
export const isAndroid = (): boolean => {
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
return ua.includes('android');
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply safe area insets for mobile devices
|
||||
* On Android, CSS env() may not work, so we apply fallback padding
|
||||
*/
|
||||
export const applySafeAreaInsets = (): void => {
|
||||
if (!isTauriMobile()) return;
|
||||
|
||||
const root = document.documentElement;
|
||||
|
||||
// Check if env() is working by testing if it returns a value
|
||||
const testValue = getComputedStyle(root).getPropertyValue('--safe-area-inset-top');
|
||||
const envWorking = testValue && testValue !== '0px' && testValue !== '';
|
||||
|
||||
if (!envWorking && isAndroid()) {
|
||||
// Apply fallback padding for Android status bar and navigation bar
|
||||
// Status bar is typically 24-48dp, navigation bar is typically 48dp
|
||||
// We use conservative values that work on most devices
|
||||
root.style.setProperty('--safe-area-inset-top', '28px');
|
||||
root.style.setProperty('--safe-area-inset-bottom', '24px');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a browser notification
|
||||
*/
|
||||
const sendBrowserNotification = (options: {
|
||||
title: string;
|
||||
body: string;
|
||||
icon?: string;
|
||||
onClick?: () => void;
|
||||
}): Notification | undefined => {
|
||||
const { title, body, icon, onClick } = options;
|
||||
|
||||
if (!('Notification' in window)) return undefined;
|
||||
if (Notification.permission !== 'granted') return undefined;
|
||||
|
||||
const notification = new Notification(title, {
|
||||
body,
|
||||
icon,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
if (onClick) {
|
||||
notification.onclick = () => {
|
||||
if (!window.closed) onClick();
|
||||
notification.close();
|
||||
};
|
||||
}
|
||||
|
||||
return notification;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a notification using Tauri's notification plugin
|
||||
* Falls back to browser Notification API if not in Tauri
|
||||
*/
|
||||
export const sendNotification = async (options: {
|
||||
title: string;
|
||||
body: string;
|
||||
icon?: string;
|
||||
path?: string;
|
||||
onClick?: () => void;
|
||||
}): Promise<void> => {
|
||||
const { title, body, icon, path, onClick } = options;
|
||||
|
||||
if (isTauri()) {
|
||||
try {
|
||||
const {
|
||||
sendNotification: tauriSendNotification,
|
||||
isPermissionGranted,
|
||||
requestPermission,
|
||||
} = await import('@tauri-apps/plugin-notification');
|
||||
|
||||
let permissionGranted = await isPermissionGranted();
|
||||
if (!permissionGranted) {
|
||||
const permission = await requestPermission();
|
||||
permissionGranted = permission === 'granted';
|
||||
}
|
||||
|
||||
if (permissionGranted) {
|
||||
await tauriSendNotification({
|
||||
title,
|
||||
body,
|
||||
// Store path in extra data for notification tap handling
|
||||
extra: path ? { path } : undefined,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Tauri notification failed:', err);
|
||||
// Fallback to browser notification on error
|
||||
sendBrowserNotification({ title, body, icon, onClick });
|
||||
}
|
||||
} else {
|
||||
sendBrowserNotification({ title, body, icon, onClick });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user