mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-17 22:27:19 +00:00
fix: separate platform detection from responsive design, fix boot screen crash on web
The boot screen was rendering as a black screen on web because useNativeFeatures() called Capacitor's Network.getStatus() without checking if the app was running on a native platform. This crashed the entire AeThexOS component during mount. Additionally, tablet testing code in use-platform-layout.ts was mixing viewport width checks (responsive design) with native platform detection, causing layout confusion between web and mobile builds. Changes: - Add isMobile() guards to all Capacitor plugin calls in useNativeFeatures - Remove tablet viewport-width branch from usePlatformLayout (platform detection should not check window.innerWidth) - Rename isMobileDevice() to isSmallViewport() in embed-utils to clarify it's a responsive check, not a platform check - Rename local isMobile state to isNarrowViewport in os.tsx DesktopWidgets to prevent shadowing the platform.ts isMobile() import - Remove dead PlatformAdaptiveExample.tsx (not imported anywhere) - Fix watchLocation TypeScript error (watchId is Promise<string>) - Add web fallbacks for clipboard and browser in useNativeFeatures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
33e0a26d35
commit
51ed8371b9
5 changed files with 60 additions and 201 deletions
|
|
@ -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 (
|
|
||||||
<div className={classes.container}>
|
|
||||||
{/* Platform-specific header */}
|
|
||||||
<PlatformSwitch
|
|
||||||
mobile={<MobileHeader />}
|
|
||||||
desktop={<DesktopHeader />}
|
|
||||||
web={<WebHeader />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Content that adapts to platform */}
|
|
||||||
<div className={classes.spacing}>
|
|
||||||
<Card className={classes.card}>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className={classes.heading}>
|
|
||||||
Platform: {layout.isMobile ? 'Mobile' : layout.isDesktop ? 'Desktop' : 'Web'}
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className={classes.spacing}>
|
|
||||||
<p className={classes.fontSize}>
|
|
||||||
This component automatically adapts its layout and styling based on the platform.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Platform-specific buttons */}
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button className={classes.button}>
|
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
|
||||||
{layout.isMobile ? 'Add' : 'Add New Item'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Grid that adapts to screen size and platform */}
|
|
||||||
<div className={`grid gap-4 ${
|
|
||||||
layout.isMobile ? 'grid-cols-1' :
|
|
||||||
layout.isDesktop ? 'grid-cols-3' :
|
|
||||||
'grid-cols-2'
|
|
||||||
}`}>
|
|
||||||
<Card className={classes.card}>
|
|
||||||
<CardContent className="pt-6">
|
|
||||||
<Home className="h-8 w-8 mb-2" />
|
|
||||||
<h3 className={classes.subheading}>Dashboard</h3>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card className={classes.card}>
|
|
||||||
<CardContent className="pt-6">
|
|
||||||
<Users className="h-8 w-8 mb-2" />
|
|
||||||
<h3 className={classes.subheading}>Team</h3>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card className={classes.card}>
|
|
||||||
<CardContent className="pt-6">
|
|
||||||
<Settings className="h-8 w-8 mb-2" />
|
|
||||||
<h3 className={classes.subheading}>Settings</h3>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Platform-specific navigation */}
|
|
||||||
<PlatformSwitch
|
|
||||||
mobile={<MobileBottomNav />}
|
|
||||||
desktop={<DesktopTopNav />}
|
|
||||||
web={<WebStickyNav />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobile: Bottom navigation bar
|
|
||||||
function MobileBottomNav() {
|
|
||||||
return (
|
|
||||||
<nav className="fixed bottom-0 left-0 right-0 bg-background border-t">
|
|
||||||
<div className="flex justify-around items-center h-16 px-4">
|
|
||||||
<NavItem icon={<Home />} label="Home" />
|
|
||||||
<NavItem icon={<Users />} label="Team" />
|
|
||||||
<NavItem icon={<Settings />} label="Settings" />
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Desktop: Top navigation bar
|
|
||||||
function DesktopTopNav() {
|
|
||||||
return (
|
|
||||||
<nav className="fixed top-0 left-0 right-0 bg-background border-b">
|
|
||||||
<div className="flex items-center justify-between h-16 px-8">
|
|
||||||
<div className="flex items-center gap-8">
|
|
||||||
<span className="text-xl font-bold">AeThex OS</span>
|
|
||||||
<NavItem icon={<Home />} label="Dashboard" />
|
|
||||||
<NavItem icon={<Users />} label="Team" />
|
|
||||||
</div>
|
|
||||||
<NavItem icon={<Settings />} label="Settings" />
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Web: Sticky navigation
|
|
||||||
function WebStickyNav() {
|
|
||||||
return (
|
|
||||||
<nav className="sticky top-0 bg-background/95 backdrop-blur border-b z-50">
|
|
||||||
<div className="flex items-center justify-between h-14 px-6">
|
|
||||||
<div className="flex items-center gap-6">
|
|
||||||
<span className="text-lg font-bold">AeThex OS</span>
|
|
||||||
<NavItem icon={<Home />} label="Home" />
|
|
||||||
<NavItem icon={<Users />} label="Team" />
|
|
||||||
</div>
|
|
||||||
<NavItem icon={<Settings />} label="Settings" />
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function NavItem({ icon, label }: { icon: React.ReactNode; label: string }) {
|
|
||||||
return (
|
|
||||||
<button className="flex flex-col items-center gap-1 text-muted-foreground hover:text-foreground transition-colors">
|
|
||||||
{icon}
|
|
||||||
<span className="text-xs">{label}</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobile-specific header
|
|
||||||
function MobileHeader() {
|
|
||||||
return (
|
|
||||||
<header className="sticky top-0 bg-background border-b z-10 px-4 py-3">
|
|
||||||
<h1 className="text-xl font-bold">AeThex OS</h1>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Desktop-specific header
|
|
||||||
function DesktopHeader() {
|
|
||||||
return (
|
|
||||||
<header className="mb-6">
|
|
||||||
<h1 className="text-3xl font-bold mb-2">AeThex OS Desktop</h1>
|
|
||||||
<p className="text-muted-foreground">Native desktop experience</p>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Web-specific header
|
|
||||||
function WebHeader() {
|
|
||||||
return (
|
|
||||||
<header className="mb-4">
|
|
||||||
<h1 className="text-2xl font-bold mb-1">AeThex OS</h1>
|
|
||||||
<p className="text-sm text-muted-foreground">Web desktop platform</p>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { ScreenOrientation } from '@capacitor/screen-orientation';
|
||||||
import { Browser } from '@capacitor/browser';
|
import { Browser } from '@capacitor/browser';
|
||||||
import { App } from '@capacitor/app';
|
import { App } from '@capacitor/app';
|
||||||
import { Haptics, ImpactStyle } from '@capacitor/haptics';
|
import { Haptics, ImpactStyle } from '@capacitor/haptics';
|
||||||
|
import { isMobile } from '@/lib/platform';
|
||||||
|
|
||||||
interface UseNativeFeaturesReturn {
|
interface UseNativeFeaturesReturn {
|
||||||
// Camera
|
// Camera
|
||||||
|
|
@ -59,19 +60,25 @@ interface UseNativeFeaturesReturn {
|
||||||
export function useNativeFeatures(): UseNativeFeaturesReturn {
|
export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
const [networkStatus, setNetworkStatus] = useState({ connected: true, connectionType: 'unknown' });
|
const [networkStatus, setNetworkStatus] = useState({ connected: true, connectionType: 'unknown' });
|
||||||
|
|
||||||
// Initialize network monitoring
|
// Initialize network monitoring - only on native platforms
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isMobile()) return;
|
||||||
|
|
||||||
const initNetwork = async () => {
|
const initNetwork = async () => {
|
||||||
const status = await Network.getStatus();
|
try {
|
||||||
setNetworkStatus({ connected: status.connected, connectionType: status.connectionType });
|
const status = await Network.getStatus();
|
||||||
|
|
||||||
Network.addListener('networkStatusChange', status => {
|
|
||||||
setNetworkStatus({ connected: status.connected, connectionType: status.connectionType });
|
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();
|
initNetwork();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
Network.removeAllListeners();
|
Network.removeAllListeners();
|
||||||
};
|
};
|
||||||
|
|
@ -79,6 +86,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
|
|
||||||
// Camera functions
|
// Camera functions
|
||||||
const takePhoto = async (): Promise<string | null> => {
|
const takePhoto = async (): Promise<string | null> => {
|
||||||
|
if (!isMobile()) return null;
|
||||||
try {
|
try {
|
||||||
const image = await Camera.getPhoto({
|
const image = await Camera.getPhoto({
|
||||||
quality: 90,
|
quality: 90,
|
||||||
|
|
@ -94,6 +102,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
};
|
};
|
||||||
|
|
||||||
const pickPhoto = async (): Promise<string | null> => {
|
const pickPhoto = async (): Promise<string | null> => {
|
||||||
|
if (!isMobile()) return null;
|
||||||
try {
|
try {
|
||||||
const image = await Camera.getPhoto({
|
const image = await Camera.getPhoto({
|
||||||
quality: 90,
|
quality: 90,
|
||||||
|
|
@ -110,6 +119,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
|
|
||||||
// File system functions
|
// File system functions
|
||||||
const saveFile = async (data: string, filename: string): Promise<boolean> => {
|
const saveFile = async (data: string, filename: string): Promise<boolean> => {
|
||||||
|
if (!isMobile()) return false;
|
||||||
try {
|
try {
|
||||||
await Filesystem.writeFile({
|
await Filesystem.writeFile({
|
||||||
path: filename,
|
path: filename,
|
||||||
|
|
@ -126,6 +136,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
};
|
};
|
||||||
|
|
||||||
const readFile = async (filename: string): Promise<string | null> => {
|
const readFile = async (filename: string): Promise<string | null> => {
|
||||||
|
if (!isMobile()) return null;
|
||||||
try {
|
try {
|
||||||
const result = await Filesystem.readFile({
|
const result = await Filesystem.readFile({
|
||||||
path: filename,
|
path: filename,
|
||||||
|
|
@ -140,14 +151,14 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
};
|
};
|
||||||
|
|
||||||
const pickFile = async (): Promise<string | null> => {
|
const pickFile = async (): Promise<string | null> => {
|
||||||
// Note: Capacitor doesn't have a built-in file picker
|
if (!isMobile()) return null;
|
||||||
// You'd need to use a plugin like @capacitor-community/file-picker
|
|
||||||
console.log('File picker not implemented - need @capacitor-community/file-picker');
|
console.log('File picker not implemented - need @capacitor-community/file-picker');
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Share functions
|
// Share functions
|
||||||
const shareText = async (text: string, title?: string): Promise<void> => {
|
const shareText = async (text: string, title?: string): Promise<void> => {
|
||||||
|
if (!isMobile()) return;
|
||||||
try {
|
try {
|
||||||
await Share.share({
|
await Share.share({
|
||||||
text: text,
|
text: text,
|
||||||
|
|
@ -161,6 +172,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
};
|
};
|
||||||
|
|
||||||
const shareUrl = async (url: string, title?: string): Promise<void> => {
|
const shareUrl = async (url: string, title?: string): Promise<void> => {
|
||||||
|
if (!isMobile()) return;
|
||||||
try {
|
try {
|
||||||
await Share.share({
|
await Share.share({
|
||||||
url: url,
|
url: url,
|
||||||
|
|
@ -175,6 +187,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
|
|
||||||
// Notification functions
|
// Notification functions
|
||||||
const requestNotificationPermission = async (): Promise<boolean> => {
|
const requestNotificationPermission = async (): Promise<boolean> => {
|
||||||
|
if (!isMobile()) return false;
|
||||||
try {
|
try {
|
||||||
const result = await PushNotifications.requestPermissions();
|
const result = await PushNotifications.requestPermissions();
|
||||||
if (result.receive === 'granted') {
|
if (result.receive === 'granted') {
|
||||||
|
|
@ -189,6 +202,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendLocalNotification = async (title: string, body: string): Promise<void> => {
|
const sendLocalNotification = async (title: string, body: string): Promise<void> => {
|
||||||
|
if (!isMobile()) return;
|
||||||
try {
|
try {
|
||||||
await LocalNotifications.schedule({
|
await LocalNotifications.schedule({
|
||||||
notifications: [
|
notifications: [
|
||||||
|
|
@ -211,6 +225,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
|
|
||||||
// Location functions
|
// Location functions
|
||||||
const getCurrentLocation = async (): Promise<Position | null> => {
|
const getCurrentLocation = async (): Promise<Position | null> => {
|
||||||
|
if (!isMobile()) return null;
|
||||||
try {
|
try {
|
||||||
const position = await Geolocation.getCurrentPosition({
|
const position = await Geolocation.getCurrentPosition({
|
||||||
enableHighAccuracy: true,
|
enableHighAccuracy: true,
|
||||||
|
|
@ -224,20 +239,27 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
};
|
};
|
||||||
|
|
||||||
const watchLocation = (callback: (position: Position) => void) => {
|
const watchLocation = (callback: (position: Position) => void) => {
|
||||||
const watchId = Geolocation.watchPosition(
|
if (!isMobile()) return () => {};
|
||||||
|
let watchIdPromise: Promise<string> | null = null;
|
||||||
|
watchIdPromise = Geolocation.watchPosition(
|
||||||
{ enableHighAccuracy: true },
|
{ enableHighAccuracy: true },
|
||||||
(position, err) => {
|
(position, err) => {
|
||||||
if (position) callback(position);
|
if (position) callback(position);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (watchId) Geolocation.clearWatch({ id: watchId });
|
watchIdPromise?.then(id => Geolocation.clearWatch({ id }));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clipboard functions
|
// Clipboard functions
|
||||||
const copyToClipboard = async (text: string): Promise<void> => {
|
const copyToClipboard = async (text: string): Promise<void> => {
|
||||||
|
if (!isMobile()) {
|
||||||
|
// Fallback to web clipboard API
|
||||||
|
try { await navigator.clipboard.writeText(text); } catch {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await Clipboard.write({ string: text });
|
await Clipboard.write({ string: text });
|
||||||
await showToast('Copied to clipboard', 'short');
|
await showToast('Copied to clipboard', 'short');
|
||||||
|
|
@ -248,6 +270,10 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
};
|
};
|
||||||
|
|
||||||
const pasteFromClipboard = async (): Promise<string> => {
|
const pasteFromClipboard = async (): Promise<string> => {
|
||||||
|
if (!isMobile()) {
|
||||||
|
// Fallback to web clipboard API
|
||||||
|
try { return await navigator.clipboard.readText(); } catch { return ''; }
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const result = await Clipboard.read();
|
const result = await Clipboard.read();
|
||||||
return result.value;
|
return result.value;
|
||||||
|
|
@ -259,6 +285,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
|
|
||||||
// Screen orientation
|
// Screen orientation
|
||||||
const lockOrientation = async (orientation: 'portrait' | 'landscape'): Promise<void> => {
|
const lockOrientation = async (orientation: 'portrait' | 'landscape'): Promise<void> => {
|
||||||
|
if (!isMobile()) return;
|
||||||
try {
|
try {
|
||||||
await ScreenOrientation.lock({ orientation: orientation });
|
await ScreenOrientation.lock({ orientation: orientation });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -267,6 +294,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
};
|
};
|
||||||
|
|
||||||
const unlockOrientation = async (): Promise<void> => {
|
const unlockOrientation = async (): Promise<void> => {
|
||||||
|
if (!isMobile()) return;
|
||||||
try {
|
try {
|
||||||
await ScreenOrientation.unlock();
|
await ScreenOrientation.unlock();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -276,6 +304,11 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
|
|
||||||
// Browser
|
// Browser
|
||||||
const openInBrowser = async (url: string): Promise<void> => {
|
const openInBrowser = async (url: string): Promise<void> => {
|
||||||
|
if (!isMobile()) {
|
||||||
|
// Fallback to web: open in new tab
|
||||||
|
window.open(url, '_blank');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await Browser.open({ url });
|
await Browser.open({ url });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -285,6 +318,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
|
|
||||||
// Toast
|
// Toast
|
||||||
const showToast = async (text: string, duration: 'short' | 'long' = 'short'): Promise<void> => {
|
const showToast = async (text: string, duration: 'short' | 'long' = 'short'): Promise<void> => {
|
||||||
|
if (!isMobile()) return;
|
||||||
try {
|
try {
|
||||||
await Toast.show({
|
await Toast.show({
|
||||||
text: text,
|
text: text,
|
||||||
|
|
@ -298,6 +332,7 @@ export function useNativeFeatures(): UseNativeFeaturesReturn {
|
||||||
|
|
||||||
// Haptics
|
// Haptics
|
||||||
const vibrate = async (style: 'light' | 'medium' | 'heavy' = 'medium'): Promise<void> => {
|
const vibrate = async (style: 'light' | 'medium' | 'heavy' = 'medium'): Promise<void> => {
|
||||||
|
if (!isMobile()) return;
|
||||||
try {
|
try {
|
||||||
const styleMap = {
|
const styleMap = {
|
||||||
light: ImpactStyle.Light,
|
light: ImpactStyle.Light,
|
||||||
|
|
|
||||||
|
|
@ -27,24 +27,9 @@ export function usePlatformLayout(): LayoutConfig {
|
||||||
|
|
||||||
const config = useMemo((): LayoutConfig => {
|
const config = useMemo((): LayoutConfig => {
|
||||||
if (platformCheck.isMobile) {
|
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 {
|
return {
|
||||||
...platformCheck,
|
...platformCheck,
|
||||||
// Mobile-first container styling (Phone)
|
// Native mobile app styling (Capacitor/Flutter/Cordova)
|
||||||
containerClass: 'px-4 py-3 max-w-full',
|
containerClass: 'px-4 py-3 max-w-full',
|
||||||
cardClass: 'rounded-lg shadow-sm border p-4',
|
cardClass: 'rounded-lg shadow-sm border p-4',
|
||||||
navClass: 'fixed bottom-0 left-0 right-0 bg-background border-t',
|
navClass: 'fixed bottom-0 left-0 right-0 bg-background border-t',
|
||||||
|
|
|
||||||
|
|
@ -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;
|
return typeof window !== 'undefined' && window.innerWidth < 768;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -58,7 +60,7 @@ export const getMobileTheme = () => {
|
||||||
*/
|
*/
|
||||||
export const getResponsiveStyles = () => {
|
export const getResponsiveStyles = () => {
|
||||||
const embedded = isEmbedded();
|
const embedded = isEmbedded();
|
||||||
const mobile = isMobileDevice();
|
const mobile = isSmallViewport();
|
||||||
const theme = getMobileTheme();
|
const theme = getMobileTheme();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -2034,13 +2034,13 @@ function DesktopWidgets({ time, weather, notifications }: {
|
||||||
});
|
});
|
||||||
const [showWidgetSettings, setShowWidgetSettings] = useState(false);
|
const [showWidgetSettings, setShowWidgetSettings] = useState(false);
|
||||||
const [mobileWidgetsOpen, setMobileWidgetsOpen] = useState(false);
|
const [mobileWidgetsOpen, setMobileWidgetsOpen] = useState(false);
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const [isNarrowViewport, setIsNarrowViewport] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkMobile = () => setIsMobile(window.innerWidth < 768);
|
const checkViewport = () => setIsNarrowViewport(window.innerWidth < 768);
|
||||||
checkMobile();
|
checkViewport();
|
||||||
window.addEventListener('resize', checkMobile);
|
window.addEventListener('resize', checkViewport);
|
||||||
return () => window.removeEventListener('resize', checkMobile);
|
return () => window.removeEventListener('resize', checkViewport);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleWidgetVisibility = (id: string) => {
|
const toggleWidgetVisibility = (id: string) => {
|
||||||
|
|
@ -2115,7 +2115,7 @@ function DesktopWidgets({ time, weather, notifications }: {
|
||||||
return { color: 'text-cyan-400', icon: <Users className="w-3 h-3" /> };
|
return { color: 'text-cyan-400', icon: <Users className="w-3 h-3" /> };
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isMobile) {
|
if (isNarrowViewport) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue