diff --git a/client/src/components/PlatformAdaptiveExample.tsx b/client/src/components/PlatformAdaptiveExample.tsx deleted file mode 100644 index 07b03be..0000000 --- a/client/src/components/PlatformAdaptiveExample.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { usePlatformLayout, usePlatformClasses, PlatformSwitch } from '@/hooks/use-platform-layout'; -import { Home, Users, Settings, Plus } from 'lucide-react'; - -/** - * Example component showing how to adapt UI for different platforms - */ -export function PlatformAdaptiveExample() { - const layout = usePlatformLayout(); - const classes = usePlatformClasses(); - - return ( -
- {/* Platform-specific header */} - } - desktop={} - web={} - /> - - {/* Content that adapts to platform */} -
- - - - Platform: {layout.isMobile ? 'Mobile' : layout.isDesktop ? 'Desktop' : 'Web'} - - - -

- This component automatically adapts its layout and styling based on the platform. -

- - {/* Platform-specific buttons */} -
- -
-
-
- - {/* Grid that adapts to screen size and platform */} -
- - - -

Dashboard

-
-
- - - -

Team

-
-
- - - -

Settings

-
-
-
-
- - {/* Platform-specific navigation */} - } - desktop={} - web={} - /> -
- ); -} - -// Mobile: Bottom navigation bar -function MobileBottomNav() { - return ( - - ); -} - -// Desktop: Top navigation bar -function DesktopTopNav() { - return ( - - ); -} - -// Web: Sticky navigation -function WebStickyNav() { - return ( - - ); -} - -function NavItem({ icon, label }: { icon: React.ReactNode; label: string }) { - return ( - - ); -} - -// Mobile-specific header -function MobileHeader() { - return ( -
-

AeThex OS

-
- ); -} - -// Desktop-specific header -function DesktopHeader() { - return ( -
-

AeThex OS Desktop

-

Native desktop experience

-
- ); -} - -// Web-specific header -function WebHeader() { - return ( -
-

AeThex OS

-

Web desktop platform

-
- ); -} diff --git a/client/src/hooks/use-native-features.ts b/client/src/hooks/use-native-features.ts index 467dc0b..3350dc9 100644 --- a/client/src/hooks/use-native-features.ts +++ b/client/src/hooks/use-native-features.ts @@ -12,6 +12,7 @@ import { ScreenOrientation } from '@capacitor/screen-orientation'; import { Browser } from '@capacitor/browser'; import { App } from '@capacitor/app'; import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { isMobile } from '@/lib/platform'; interface UseNativeFeaturesReturn { // Camera @@ -59,19 +60,25 @@ interface UseNativeFeaturesReturn { export function useNativeFeatures(): UseNativeFeaturesReturn { const [networkStatus, setNetworkStatus] = useState({ connected: true, connectionType: 'unknown' }); - // Initialize network monitoring + // Initialize network monitoring - only on native platforms useEffect(() => { + if (!isMobile()) return; + const initNetwork = async () => { - const status = await Network.getStatus(); - setNetworkStatus({ connected: status.connected, connectionType: status.connectionType }); - - Network.addListener('networkStatusChange', status => { + try { + const status = await Network.getStatus(); setNetworkStatus({ connected: status.connected, connectionType: status.connectionType }); - }); + + Network.addListener('networkStatusChange', status => { + setNetworkStatus({ connected: status.connected, connectionType: status.connectionType }); + }); + } catch (error) { + console.debug('[NativeFeatures] Network init failed:', error); + } }; - + initNetwork(); - + return () => { Network.removeAllListeners(); }; @@ -79,6 +86,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { // Camera functions const takePhoto = async (): Promise => { + if (!isMobile()) return null; try { const image = await Camera.getPhoto({ quality: 90, @@ -94,6 +102,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { }; const pickPhoto = async (): Promise => { + if (!isMobile()) return null; try { const image = await Camera.getPhoto({ quality: 90, @@ -110,6 +119,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { // File system functions const saveFile = async (data: string, filename: string): Promise => { + if (!isMobile()) return false; try { await Filesystem.writeFile({ path: filename, @@ -126,6 +136,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { }; const readFile = async (filename: string): Promise => { + if (!isMobile()) return null; try { const result = await Filesystem.readFile({ path: filename, @@ -140,14 +151,14 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { }; const pickFile = async (): Promise => { - // Note: Capacitor doesn't have a built-in file picker - // You'd need to use a plugin like @capacitor-community/file-picker + if (!isMobile()) return null; console.log('File picker not implemented - need @capacitor-community/file-picker'); return null; }; // Share functions const shareText = async (text: string, title?: string): Promise => { + if (!isMobile()) return; try { await Share.share({ text: text, @@ -161,6 +172,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { }; const shareUrl = async (url: string, title?: string): Promise => { + if (!isMobile()) return; try { await Share.share({ url: url, @@ -175,6 +187,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { // Notification functions const requestNotificationPermission = async (): Promise => { + if (!isMobile()) return false; try { const result = await PushNotifications.requestPermissions(); if (result.receive === 'granted') { @@ -189,6 +202,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { }; const sendLocalNotification = async (title: string, body: string): Promise => { + if (!isMobile()) return; try { await LocalNotifications.schedule({ notifications: [ @@ -211,6 +225,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { // Location functions const getCurrentLocation = async (): Promise => { + if (!isMobile()) return null; try { const position = await Geolocation.getCurrentPosition({ enableHighAccuracy: true, @@ -224,20 +239,27 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { }; const watchLocation = (callback: (position: Position) => void) => { - const watchId = Geolocation.watchPosition( + if (!isMobile()) return () => {}; + let watchIdPromise: Promise | null = null; + watchIdPromise = Geolocation.watchPosition( { enableHighAccuracy: true }, (position, err) => { if (position) callback(position); } ); - + return () => { - if (watchId) Geolocation.clearWatch({ id: watchId }); + watchIdPromise?.then(id => Geolocation.clearWatch({ id })); }; }; // Clipboard functions const copyToClipboard = async (text: string): Promise => { + if (!isMobile()) { + // Fallback to web clipboard API + try { await navigator.clipboard.writeText(text); } catch {} + return; + } try { await Clipboard.write({ string: text }); await showToast('Copied to clipboard', 'short'); @@ -248,6 +270,10 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { }; const pasteFromClipboard = async (): Promise => { + if (!isMobile()) { + // Fallback to web clipboard API + try { return await navigator.clipboard.readText(); } catch { return ''; } + } try { const result = await Clipboard.read(); return result.value; @@ -259,6 +285,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { // Screen orientation const lockOrientation = async (orientation: 'portrait' | 'landscape'): Promise => { + if (!isMobile()) return; try { await ScreenOrientation.lock({ orientation: orientation }); } catch (error) { @@ -267,6 +294,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { }; const unlockOrientation = async (): Promise => { + if (!isMobile()) return; try { await ScreenOrientation.unlock(); } catch (error) { @@ -276,6 +304,11 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { // Browser const openInBrowser = async (url: string): Promise => { + if (!isMobile()) { + // Fallback to web: open in new tab + window.open(url, '_blank'); + return; + } try { await Browser.open({ url }); } catch (error) { @@ -285,6 +318,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { // Toast const showToast = async (text: string, duration: 'short' | 'long' = 'short'): Promise => { + if (!isMobile()) return; try { await Toast.show({ text: text, @@ -298,6 +332,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn { // Haptics const vibrate = async (style: 'light' | 'medium' | 'heavy' = 'medium'): Promise => { + if (!isMobile()) return; try { const styleMap = { light: ImpactStyle.Light, diff --git a/client/src/hooks/use-platform-layout.ts b/client/src/hooks/use-platform-layout.ts index 70fd7c5..98e9b3b 100644 --- a/client/src/hooks/use-platform-layout.ts +++ b/client/src/hooks/use-platform-layout.ts @@ -27,24 +27,9 @@ export function usePlatformLayout(): LayoutConfig { const config = useMemo((): LayoutConfig => { if (platformCheck.isMobile) { - // Tablet Optimization: Check if screen is large enough (e.g. iPad/Tablet) - const isTablet = typeof window !== 'undefined' && window.innerWidth >= 768; - - if (isTablet) { - return { - ...platformCheck, - // Tablet styling (Hybrid) - containerClass: 'px-6 py-4 max-w-2xl mx-auto', // Centered content for tablets - cardClass: 'rounded-xl shadow-md border p-5', - navClass: 'fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur border-t z-50', - spacing: 'space-y-4', - fontSize: 'text-base', - }; - } - return { ...platformCheck, - // Mobile-first container styling (Phone) + // Native mobile app styling (Capacitor/Flutter/Cordova) containerClass: 'px-4 py-3 max-w-full', cardClass: 'rounded-lg shadow-sm border p-4', navClass: 'fixed bottom-0 left-0 right-0 bg-background border-t', diff --git a/client/src/lib/embed-utils.ts b/client/src/lib/embed-utils.ts index 2479e91..363b671 100644 --- a/client/src/lib/embed-utils.ts +++ b/client/src/lib/embed-utils.ts @@ -12,9 +12,11 @@ export const isEmbedded = (): boolean => { }; /** - * Detect if running on mobile device + * Detect if viewport is small (phone-sized). + * This is a responsive/viewport check, NOT a platform check. + * For native platform detection, use isMobile() from '@/lib/platform'. */ -export const isMobileDevice = (): boolean => { +export const isSmallViewport = (): boolean => { return typeof window !== 'undefined' && window.innerWidth < 768; }; @@ -58,7 +60,7 @@ export const getMobileTheme = () => { */ export const getResponsiveStyles = () => { const embedded = isEmbedded(); - const mobile = isMobileDevice(); + const mobile = isSmallViewport(); const theme = getMobileTheme(); return { diff --git a/client/src/pages/os.tsx b/client/src/pages/os.tsx index 42449c4..a80ce3a 100644 --- a/client/src/pages/os.tsx +++ b/client/src/pages/os.tsx @@ -2034,13 +2034,13 @@ function DesktopWidgets({ time, weather, notifications }: { }); const [showWidgetSettings, setShowWidgetSettings] = useState(false); const [mobileWidgetsOpen, setMobileWidgetsOpen] = useState(false); - const [isMobile, setIsMobile] = useState(false); + const [isNarrowViewport, setIsNarrowViewport] = useState(false); useEffect(() => { - const checkMobile = () => setIsMobile(window.innerWidth < 768); - checkMobile(); - window.addEventListener('resize', checkMobile); - return () => window.removeEventListener('resize', checkMobile); + const checkViewport = () => setIsNarrowViewport(window.innerWidth < 768); + checkViewport(); + window.addEventListener('resize', checkViewport); + return () => window.removeEventListener('resize', checkViewport); }, []); const toggleWidgetVisibility = (id: string) => { @@ -2115,7 +2115,7 @@ function DesktopWidgets({ time, weather, notifications }: { return { color: 'text-cyan-400', icon: }; }; - if (isMobile) { + if (isNarrowViewport) { return ( <>