mobile download

This commit is contained in:
Macbook
2026-05-28 14:44:19 +09:00
parent e2816b037f
commit 3503ef33f5
152 changed files with 11021 additions and 687 deletions
+125
View File
@@ -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>
);
}