mobile download
This commit is contained in:
+125
@@ -0,0 +1,125 @@
|
||||
import React, { lazy, Suspense, useCallback, useEffect, useState } from 'react';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { StatusBar, Style } from '@capacitor/status-bar';
|
||||
import { SplashScreen } from '@capacitor/splash-screen';
|
||||
import { initStorage } from './lib/shared';
|
||||
import { NavigationProvider, useNavigation } from './contexts/NavigationContext';
|
||||
import { TradeNotificationProvider, useTradeNotification } from './contexts/TradeNotificationContext';
|
||||
import { useAppSettings, resolveAppDefaults } from './hooks/useAppSettings';
|
||||
import TabBar, { type TabId } from './components/TabBar';
|
||||
import { initFcmPush, type FcmPayload } from './services/fcm';
|
||||
import LiveSignalBridge from './components/LiveSignalBridge';
|
||||
import './theme/global.css';
|
||||
|
||||
const VirtualTradingScreen = lazy(() => import('./screens/virtual/VirtualTradingScreen'));
|
||||
const StrategyEditorScreen = lazy(() => import('./screens/strategy/StrategyEditorScreen'));
|
||||
const NotificationsScreen = lazy(() => import('./screens/notifications/NotificationsScreen'));
|
||||
const SettingsScreen = lazy(() => import('./screens/settings/SettingsScreen'));
|
||||
|
||||
function ToastStack() {
|
||||
const { toastNotifications, dismissToast } = useTradeNotification();
|
||||
if (toastNotifications.length === 0) return null;
|
||||
return (
|
||||
<div className="toast-stack">
|
||||
{toastNotifications.slice(0, 3).map(t => (
|
||||
<div key={t.id} className="toast-item" role="button" tabIndex={0} onClick={() => dismissToast(t.id)}>
|
||||
<strong className={t.signalType === 'BUY' ? 'text-green' : 'text-red'}>
|
||||
{t.signalType === 'BUY' ? '매수' : '매도'}
|
||||
</strong>
|
||||
{' '}{t.market} · ₩{t.price?.toLocaleString()}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AppInner() {
|
||||
const { tab, setTab, openVirtualFocus } = useNavigation();
|
||||
const { addNotification, refreshHistory, unreadCount } = useTradeNotification();
|
||||
const { settings, isLoaded } = useAppSettings();
|
||||
const defaults = resolveAppDefaults(settings);
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
await initStorage();
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
try {
|
||||
await StatusBar.setStyle({ style: Style.Dark });
|
||||
await StatusBar.setBackgroundColor({ color: '#0f0f23' });
|
||||
await SplashScreen.hide();
|
||||
} catch { /* web */ }
|
||||
}
|
||||
setReady(true);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoaded || !defaults.fcmPushEnabled) return;
|
||||
void initFcmPush({
|
||||
onForeground: (title, body, data) => {
|
||||
if (data?.market && data?.signalType) {
|
||||
addNotification({
|
||||
market: data.market,
|
||||
signalType: data.signalType,
|
||||
price: Number(data.price) || 0,
|
||||
candleTime: Math.floor(Date.now() / 1000),
|
||||
dbId: data.signalId ? Number(data.signalId) : undefined,
|
||||
});
|
||||
} else {
|
||||
void refreshHistory();
|
||||
}
|
||||
},
|
||||
onTap: (data: FcmPayload) => {
|
||||
if (data.market) {
|
||||
setTab('virtual');
|
||||
openVirtualFocus(data.market);
|
||||
} else {
|
||||
setTab('notifications');
|
||||
}
|
||||
},
|
||||
});
|
||||
}, [isLoaded, defaults.fcmPushEnabled, addNotification, refreshHistory, setTab, openVirtualFocus]);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute('data-theme', defaults.theme ?? 'dark');
|
||||
}, [defaults.theme]);
|
||||
|
||||
if (!ready) {
|
||||
return <div className="loading-center">GoldenChart</div>;
|
||||
}
|
||||
|
||||
const screens: Record<TabId, React.ReactNode> = {
|
||||
virtual: <VirtualTradingScreen />,
|
||||
strategy: <StrategyEditorScreen />,
|
||||
notifications: <NotificationsScreen />,
|
||||
settings: <SettingsScreen />,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<LiveSignalBridge />
|
||||
<ToastStack />
|
||||
<main className="app-content">
|
||||
<Suspense fallback={<div className="loading-center">로딩…</div>}>
|
||||
{screens[tab]}
|
||||
</Suspense>
|
||||
</main>
|
||||
<TabBar
|
||||
active={tab}
|
||||
onChange={setTab}
|
||||
unreadCount={unreadCount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NavigationProvider>
|
||||
<TradeNotificationProvider soundEnabled={true}>
|
||||
<AppInner />
|
||||
</TradeNotificationProvider>
|
||||
</NavigationProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user