diff --git a/src/app/FightRateApp.tsx b/src/app/FightRateApp.tsx index 776c478..bba849d 100644 --- a/src/app/FightRateApp.tsx +++ b/src/app/FightRateApp.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Image, Pressable, @@ -16,12 +16,19 @@ import { type LucideIcon, } from 'lucide-react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import Animated, { + Easing, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; import { useHeartRateMonitor } from '../ble/useHeartRateMonitor'; +import { LiveDot } from '../components/LiveDot'; import { CaptureScreen } from '../screens/CaptureScreen'; import { HistoryScreen } from '../screens/HistoryScreen'; import { InsightsScreen } from '../screens/InsightsScreen'; import { SensorScreen } from '../screens/SensorScreen'; -import { colors, radius, spacing, typography } from '../theme'; +import { colors, motion, radius, spacing, typography } from '../theme'; type TabKey = 'capture' | 'history' | 'sensor' | 'insights'; @@ -33,7 +40,7 @@ type Tab = { const tabs: readonly Tab[] = [ { key: 'capture', label: 'Captura', icon: Radio }, - { key: 'history', label: 'Histórico', icon: History }, + { key: 'history', label: 'Hist\órico', icon: History }, { key: 'sensor', label: 'Sensor', icon: Bluetooth }, { key: 'insights', label: 'Insights', icon: ChartNoAxesCombined }, ]; @@ -42,13 +49,23 @@ export function FightRateApp() { const insets = useSafeAreaInsets(); const monitor = useHeartRateMonitor(); const [activeTab, setActiveTab] = useState('capture'); + const opacity = useSharedValue(1); + + useEffect(() => { + opacity.value = 0; + opacity.value = withTiming(1, { + duration: motion.duration.fast, + easing: Easing.out(Easing.quad), + }); + }, [activeTab, opacity]); + + const fadeStyle = useAnimatedStyle(() => ({ opacity: opacity.value })); const onCaptureAction = useCallback(async () => { if (monitor.isCapturing) { await monitor.stopCapture(); return; } - await monitor.startCapture(); }, [monitor]); @@ -57,31 +74,22 @@ export function FightRateApp() { await monitor.stopMonitoring(); return; } - await monitor.startMonitoring(); }, [monitor]); + const activeTitle = useMemo(() => { if (activeTab === 'capture') { - if (monitor.isCapturing) { - return 'Janela de treino ativa'; - } - if (monitor.isMonitoring) { - return 'Monitor contínuo'; - } + if (monitor.isCapturing) return 'Janela de treino ativa'; + if (monitor.isMonitoring) return 'Monitor cont\ínuo'; return 'Ativar sensor'; } - - if (activeTab === 'history') { - return 'Histórico real'; - } - - if (activeTab === 'sensor') { - return 'Sensor BLE'; - } - - return 'Análise real'; + if (activeTab === 'history') return 'Hist\órico real'; + if (activeTab === 'sensor') return 'Sensor BLE'; + return 'An\álise real'; }, [activeTab, monitor.isCapturing, monitor.isMonitoring]); + const isAnyLive = monitor.isMonitoring || monitor.isCapturing; + return ( @@ -101,43 +109,54 @@ export function FightRateApp() { style={styles.logo} /> - FightRate + + {isAnyLive ? : null} + FightRate + {activeTitle} - {activeTab === 'capture' ? ( - - ) : null} - {activeTab === 'history' ? ( - - ) : null} - {activeTab === 'sensor' ? ( - - ) : null} - {activeTab === 'insights' ? ( - - ) : null} + + {activeTab === 'capture' ? ( + + ) : null} + {activeTab === 'history' ? ( + + ) : null} + {activeTab === 'sensor' ? ( + + ) : null} + {activeTab === 'insights' ? ( + + ) : null} + - {tabs.map(tab => { + {tabs.map((tab) => { const selected = tab.key === activeTab; const Icon = tab.icon; + const isTabLive = + (tab.key === 'capture' && monitor.isCapturing) || + (tab.key === 'sensor' && monitor.isMonitoring); return ( setActiveTab(tab.key)} style={[styles.navItem, selected ? styles.navItemActive : null]} > - + + + {isTabLive ? ( + + + + ) : null} +