Refactor mobile detection to use unified composable
All checks were successful
Build And Push Image / docker (push) Successful in 2m52s
All checks were successful
Build And Push Image / docker (push) Successful in 2m52s
- Add useMobileDetection composable to centralize device detection logic - Replace direct utility imports with composable usage across components - Update MultipleNationalityInput, PhoneInputWrapper, and auth pages - Simplify mobile-specific styling and behavior handling - Improve code maintainability by consolidating detection logic
This commit is contained in:
138
composables/useMobileDetection.ts
Normal file
138
composables/useMobileDetection.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Unified Mobile Detection Composable
|
||||
* Provides consistent mobile detection across the app
|
||||
*/
|
||||
|
||||
export interface MobileDetectionState {
|
||||
isMobile: boolean;
|
||||
isSafari: boolean;
|
||||
isMobileSafari: boolean;
|
||||
isIOS: boolean;
|
||||
isAndroid: boolean;
|
||||
safariVersion?: number;
|
||||
viewportHeight: number;
|
||||
isInitialized: boolean;
|
||||
}
|
||||
|
||||
// Global state to ensure single source of truth
|
||||
const globalState = reactive<MobileDetectionState>({
|
||||
isMobile: false,
|
||||
isSafari: false,
|
||||
isMobileSafari: false,
|
||||
isIOS: false,
|
||||
isAndroid: false,
|
||||
safariVersion: undefined,
|
||||
viewportHeight: 0,
|
||||
isInitialized: false
|
||||
});
|
||||
|
||||
// Track if listeners are already set up
|
||||
let listenersSetup = false;
|
||||
|
||||
export const useMobileDetection = () => {
|
||||
// Initialize detection on first use
|
||||
if (!globalState.isInitialized && typeof window !== 'undefined') {
|
||||
detectDevice();
|
||||
globalState.isInitialized = true;
|
||||
|
||||
// Set up listeners only once globally
|
||||
if (!listenersSetup) {
|
||||
setupListeners();
|
||||
listenersSetup = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return readonly state to prevent external modifications
|
||||
return readonly(globalState);
|
||||
};
|
||||
|
||||
function detectDevice() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const userAgent = navigator.userAgent;
|
||||
|
||||
// Detect mobile
|
||||
globalState.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
|
||||
|
||||
// Detect iOS
|
||||
globalState.isIOS = /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream;
|
||||
|
||||
// Detect Android
|
||||
globalState.isAndroid = /Android/i.test(userAgent);
|
||||
|
||||
// Detect Safari
|
||||
globalState.isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
|
||||
|
||||
// Detect Mobile Safari specifically
|
||||
globalState.isMobileSafari = globalState.isIOS && globalState.isSafari;
|
||||
|
||||
// Extract Safari version
|
||||
if (globalState.isSafari) {
|
||||
const match = userAgent.match(/Version\/(\d+)/);
|
||||
if (match) {
|
||||
globalState.safariVersion = parseInt(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Set initial viewport height
|
||||
globalState.viewportHeight = window.innerHeight;
|
||||
}
|
||||
|
||||
function setupListeners() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
let resizeTimeout: NodeJS.Timeout;
|
||||
let lastHeight = window.innerHeight;
|
||||
|
||||
const handleResize = () => {
|
||||
clearTimeout(resizeTimeout);
|
||||
|
||||
// Debounce and only update if height actually changed
|
||||
resizeTimeout = setTimeout(() => {
|
||||
const newHeight = window.innerHeight;
|
||||
|
||||
// Only update if there's a significant change (more than 20px)
|
||||
// This prevents minor fluctuations from triggering updates
|
||||
if (Math.abs(newHeight - lastHeight) > 20) {
|
||||
lastHeight = newHeight;
|
||||
globalState.viewportHeight = newHeight;
|
||||
|
||||
// Update CSS custom property for mobile Safari
|
||||
if (globalState.isMobileSafari) {
|
||||
const vh = newHeight * 0.01;
|
||||
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||
}
|
||||
}
|
||||
}, 150); // Increased debounce time
|
||||
};
|
||||
|
||||
// Add resize listener with passive option for better performance
|
||||
window.addEventListener('resize', handleResize, { passive: true });
|
||||
|
||||
// Also listen for orientation changes on mobile
|
||||
if (globalState.isMobile) {
|
||||
window.addEventListener('orientationchange', () => {
|
||||
// Wait for orientation change to complete
|
||||
setTimeout(handleResize, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up on app unmount (if needed)
|
||||
if (typeof window !== 'undefined') {
|
||||
const cleanup = () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (globalState.isMobile) {
|
||||
window.removeEventListener('orientationchange', handleResize);
|
||||
}
|
||||
};
|
||||
|
||||
// Store cleanup function for manual cleanup if needed
|
||||
(window as any).__mobileDetectionCleanup = cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
// Export helper functions
|
||||
export const isMobileSafari = () => globalState.isMobileSafari;
|
||||
export const isMobile = () => globalState.isMobile;
|
||||
export const isIOS = () => globalState.isIOS;
|
||||
export const getViewportHeight = () => globalState.viewportHeight;
|
||||
Reference in New Issue
Block a user