206 lines
6.1 KiB
TypeScript
206 lines
6.1 KiB
TypeScript
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||
import {
|
||
ActivityIndicator,
|
||
Alert,
|
||
StatusBar,
|
||
StyleSheet,
|
||
Text,
|
||
TouchableOpacity,
|
||
View,
|
||
} from 'react-native';
|
||
import { NavigationContainer } from '@react-navigation/native';
|
||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||
import { Ionicons } from '@expo/vector-icons';
|
||
import { orderBoxApi } from './api';
|
||
import { theme } from './theme';
|
||
import { OrderBoxState } from './types';
|
||
import { CardsScreen, HistoryScreen } from './kits/motion_reminder';
|
||
|
||
const Tab = createBottomTabNavigator();
|
||
|
||
type ScreenMode = 'hub' | 'motion_reminder';
|
||
|
||
const HubScreen = ({
|
||
onEnterMotionReminder,
|
||
loading,
|
||
}: {
|
||
onEnterMotionReminder: () => void;
|
||
loading: boolean;
|
||
}) => (
|
||
<View style={styles.hubContainer}>
|
||
<StatusBar barStyle="light-content" backgroundColor={theme.colors.background} />
|
||
<View style={styles.hubHeader}>
|
||
<Text style={styles.hubTitle}>框子</Text>
|
||
<Text style={styles.hubSubtitle}>kit 聚合壳。当前阶段先落地第一个工具:运动提醒宝。</Text>
|
||
</View>
|
||
|
||
<TouchableOpacity style={styles.kitCard} disabled={loading} onPress={onEnterMotionReminder}>
|
||
<View style={styles.kitCardIcon}>
|
||
<Ionicons name="barbell-outline" size={28} color={theme.colors.primary} />
|
||
</View>
|
||
<View style={styles.kitCardBody}>
|
||
<Text style={styles.kitCardTitle}>运动提醒宝</Text>
|
||
<Text style={styles.kitCardText}>流程卡片、自动切步、语音播报、大字倒计时、下一个动作常驻显示。</Text>
|
||
</View>
|
||
<Ionicons name="chevron-forward" size={24} color={theme.colors.textSecondary} />
|
||
</TouchableOpacity>
|
||
</View>
|
||
);
|
||
|
||
function MotionReminderKit({ onExit }: { onExit: () => void }) {
|
||
const [serverState, setServerState] = useState<OrderBoxState | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
const refreshData = useCallback(async () => {
|
||
try {
|
||
const nextState = await orderBoxApi.getState();
|
||
setServerState(nextState);
|
||
} catch (error) {
|
||
const message = error instanceof Error ? error.message : '加载失败';
|
||
Alert.alert('加载失败', message);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
void refreshData();
|
||
}, [refreshData]);
|
||
|
||
return (
|
||
<NavigationContainer independent>
|
||
<Tab.Navigator
|
||
screenOptions={{
|
||
headerStyle: { backgroundColor: theme.colors.background },
|
||
headerTintColor: theme.colors.text,
|
||
tabBarStyle: { backgroundColor: theme.colors.surface, borderTopColor: theme.colors.border },
|
||
tabBarActiveTintColor: theme.colors.primary,
|
||
tabBarInactiveTintColor: theme.colors.textSecondary,
|
||
}}
|
||
>
|
||
<Tab.Screen
|
||
name="Cards"
|
||
children={() => <CardsScreen onExit={onExit} serverState={serverState} onRefresh={refreshData} />}
|
||
options={{
|
||
title: '运动提醒宝',
|
||
tabBarLabel: '卡片',
|
||
tabBarIcon: ({ color, size }) => <Ionicons name="albums-outline" size={size} color={color} />,
|
||
}}
|
||
/>
|
||
<Tab.Screen
|
||
name="History"
|
||
children={() => <HistoryScreen onRefresh={refreshData} />}
|
||
options={{
|
||
title: '执行历史',
|
||
tabBarLabel: '历史',
|
||
tabBarIcon: ({ color, size }) => <Ionicons name="time-outline" size={size} color={color} />,
|
||
}}
|
||
/>
|
||
</Tab.Navigator>
|
||
</NavigationContainer>
|
||
);
|
||
}
|
||
|
||
export default function App() {
|
||
const [screenMode, setScreenMode] = useState<ScreenMode>('hub');
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
const initialize = useCallback(async () => {
|
||
try {
|
||
await orderBoxApi.getMeta();
|
||
const state = await orderBoxApi.getState();
|
||
if (state.activeKit === 'motion_reminder') {
|
||
setScreenMode('motion_reminder');
|
||
}
|
||
} catch (error) {
|
||
const message = error instanceof Error ? error.message : '初始化失败';
|
||
Alert.alert('初始化失败', message);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
void initialize();
|
||
}, [initialize]);
|
||
|
||
const enterMotionReminder = async () => {
|
||
try {
|
||
setLoading(true);
|
||
await orderBoxApi.setActiveKit('motion_reminder');
|
||
setScreenMode('motion_reminder');
|
||
} catch (error) {
|
||
const message = error instanceof Error ? error.message : '进入失败';
|
||
Alert.alert('进入失败', message);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const content = useMemo(() => {
|
||
if (screenMode === 'motion_reminder') {
|
||
return <MotionReminderKit onExit={() => setScreenMode('hub')} />;
|
||
}
|
||
return <HubScreen loading={loading} onEnterMotionReminder={() => void enterMotionReminder()} />;
|
||
}, [loading, screenMode]);
|
||
|
||
return <View style={styles.appContainer}>{content}</View>;
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
appContainer: {
|
||
flex: 1,
|
||
backgroundColor: theme.colors.background,
|
||
},
|
||
hubContainer: {
|
||
flex: 1,
|
||
backgroundColor: theme.colors.background,
|
||
padding: theme.spacing.lg,
|
||
paddingTop: 72,
|
||
},
|
||
hubHeader: {
|
||
marginBottom: theme.spacing.xl,
|
||
},
|
||
hubTitle: {
|
||
...theme.typography.h1,
|
||
color: theme.colors.text,
|
||
marginBottom: theme.spacing.sm,
|
||
},
|
||
hubSubtitle: {
|
||
...theme.typography.body,
|
||
color: theme.colors.textSecondary,
|
||
lineHeight: 24,
|
||
},
|
||
kitCard: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
backgroundColor: theme.colors.surface,
|
||
borderRadius: theme.borderRadius.xl,
|
||
padding: theme.spacing.lg,
|
||
borderWidth: 1,
|
||
borderColor: theme.colors.border,
|
||
gap: theme.spacing.md,
|
||
},
|
||
kitCardIcon: {
|
||
width: 56,
|
||
height: 56,
|
||
borderRadius: 28,
|
||
backgroundColor: theme.colors.primarySoft,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
kitCardBody: {
|
||
flex: 1,
|
||
},
|
||
kitCardTitle: {
|
||
...theme.typography.h3,
|
||
color: theme.colors.text,
|
||
marginBottom: theme.spacing.xs,
|
||
},
|
||
kitCardText: {
|
||
...theme.typography.bodySmall,
|
||
color: theme.colors.textSecondary,
|
||
lineHeight: 20,
|
||
},
|
||
});
|