import React from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; import { Bluetooth, RadioReceiver, } from 'lucide-react-native'; import { type HeartRateMonitorState } from '../ble/useHeartRateMonitor'; import { formatRssi } from '../ble/rssi'; import { ActionButton } from '../components/ActionButton'; import { DialTile } from '../components/DialTile'; import { LiveDot } from '../components/LiveDot'; import { colors, radius, spacing, typography } from '../theme'; type Props = { readonly monitor: HeartRateMonitorState; readonly onMonitorAction: () => Promise; }; export function SensorScreen({ monitor, onMonitorAction }: Props) { const device = monitor.connectedDevice; const services = monitor.services.length ? monitor.services : ['Heart Rate Service \· 0x180D \· aguardando conex\ão']; const characteristics = monitor.characteristics.length ? monitor.characteristics : ['Heart Rate Measurement \· 0x2A37 \· notify esperado']; const isReceiving = monitor.phase === 'receiving'; return ( {'\•'} SENSOR REAL {friendlyDeviceTitle(device?.name)} {phaseLabel(monitor.phase)} {isReceiving ? : null} {monitor.errorMessage ?? sensorExplanation(monitor)} {!monitor.isMonitoring ? ( { onMonitorAction().catch(() => undefined); }} showIcon={false} variant="primary" /> ) : ( Monitor cont\ínuo ativo )} {!monitor.batteryOptimizationIgnored ? ( monitor.openBatterySettings().catch(() => undefined) } style={styles.secondaryButton} > Bateria ) : null} {monitor.discoveredDevices.length > 0 ? ( ENCONTRADOS AGORA {monitor.discoveredDevices.map((candidate) => ( monitor.connectToDevice(candidate.id).catch(() => undefined) } style={styles.deviceRow} > {friendlyDeviceTitle(candidate.name)} {candidate.name} \· {formatRssi(candidate.rssi)} Conectar ))} ) : null} ); } function BleInventoryBlock({ title, rows, }: { readonly title: string; readonly rows: readonly string[]; }) { return ( {title} {rows.slice(0, 8).map((row) => { const parsed = parseBleRow(row); return ( {parsed.name} {parsed.uuid} \· {parsed.detail} ); })} ); } function friendlyDeviceTitle(name?: string | null) { if (!name) { return 'Cinta card\íaca BLE'; } if (/magene|h003/i.test(name)) { return 'Magene H003'; } if (/heart|hrm/i.test(name)) { return 'Cinta card\íaca BLE'; } return name; } function parseBleRow(row: string) { const [name = row, uuid = 'BLE', ...rest] = row.split(' \· '); return { name, uuid, detail: rest.join(' \· ') || 'aguardando leitura', }; } function phaseLabel(phase: HeartRateMonitorState['phase']) { const labels: Record = { idle: 'parado', 'requesting-permission': 'permiss\ões', 'bluetooth-off': 'bluetooth off', scanning: 'buscando', connecting: 'conectando', connected: 'conectado', receiving: 'recebendo', 'permission-denied': 'sem permiss\ão', 'sensor-not-found': 'sem sensor', error: 'erro', }; return labels[phase]; } function sensorExplanation(monitor: HeartRateMonitorState) { if (monitor.phase === 'receiving' && monitor.samples.length > 0) { return `Amostras reais chegando: ${monitor.samples.length}. Cada BPM reconhecido est\á sendo salvo em SQLite.`; } if (monitor.phase === 'receiving' || monitor.phase === 'connected') { return 'Sensor reconhecido. O app est\á armando a leitura 2A37; se n\ão vier BPM, reativa notify e reconecta sozinho.'; } if (monitor.phase === 'scanning') { return 'Busca nativa BLE ativa. Se o app for bloqueado, a notifica\ç\ão mant\ém o servi\ço vivo.'; } return 'A tela traduz nomes t\écnicos: 180D \é o servi\ço card\íaco; 2A37 \é o dado de BPM.'; } function formatBattery(percent?: number | null) { if (typeof percent === 'number') { return `${percent}%`; } return '--'; } const styles = StyleSheet.create({ screen: { gap: spacing.sm, }, sensorConsole: { borderTopWidth: 1, borderColor: colors.line, paddingVertical: spacing.md, gap: spacing.md, }, consoleTopline: { flexDirection: 'row', justifyContent: 'space-between', gap: spacing.md, alignItems: 'center', }, consoleLeft: { flex: 1, }, eyebrowRow: { flexDirection: 'row', alignItems: 'center', gap: 6, }, eyebrowDot: { fontSize: 18, lineHeight: 18, fontWeight: '900', }, eyebrow: { ...typography.label, color: colors.action, }, title: { ...typography.detailNumeric, color: colors.text, fontSize: 22, lineHeight: 26, letterSpacing: -0.8, marginTop: spacing.xs, }, phaseBadge: { paddingVertical: spacing.xs, paddingHorizontal: spacing.sm, flexDirection: 'row', alignItems: 'center', gap: 6, height: 32, borderRadius: radius.xl, borderWidth: 1, borderColor: colors.line, backgroundColor: colors.surface, }, phaseText: { ...typography.label, color: colors.text, }, signalColumn: { borderTopWidth: 1, borderColor: colors.line, }, explainer: { ...typography.body, color: colors.textMuted, }, actionRow: { flexDirection: 'row', gap: spacing.sm, alignItems: 'center', }, livePill: { minHeight: 48, flex: 1, borderWidth: 1, borderColor: colors.line, paddingHorizontal: spacing.md, flexDirection: 'row', alignItems: 'center', gap: 6, }, livePillText: { ...typography.label, color: colors.success, }, secondaryButton: { minHeight: 48, borderWidth: 1, borderColor: colors.line, borderRadius: radius.xl, paddingHorizontal: spacing.md, justifyContent: 'center', }, secondaryButtonText: { ...typography.label, color: colors.warning, }, discoveryBlock: { borderTopWidth: 1, borderTopColor: colors.line, paddingTop: spacing.md, gap: spacing.sm, }, blockLabel: { ...typography.label, color: colors.warning, }, deviceRow: { flexDirection: 'row', gap: spacing.sm, alignItems: 'center', paddingVertical: spacing.sm, borderBottomWidth: 1, borderBottomColor: colors.line, }, deviceDot: { width: 10, height: 10, borderRadius: 5, backgroundColor: colors.success, }, deviceInfo: { flex: 1, }, deviceName: { color: colors.text, fontSize: 18, lineHeight: 22, fontWeight: '900', }, deviceMeta: { ...typography.body, color: colors.textMuted, marginTop: spacing.xxs, }, connectPill: { borderWidth: 1, borderColor: colors.line, paddingHorizontal: spacing.sm, paddingVertical: spacing.xs, }, connectPillText: { ...typography.label, color: colors.warning, }, diagnosticBlock: { borderTopWidth: 1, borderTopColor: colors.line, paddingTop: spacing.sm, gap: spacing.xs, }, diagnosticRow: { flexDirection: 'row', alignItems: 'center', gap: spacing.xs, paddingVertical: spacing.xs, borderBottomWidth: 1, borderBottomColor: colors.line, }, diagnosticBody: { flex: 1, gap: 2, }, diagnosticTitle: { ...typography.label, color: colors.text, }, diagnosticText: { flex: 1, color: colors.textMuted, fontSize: 12, lineHeight: 16, fontWeight: '700', }, });