Files

206 lines
6.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
},
});