diff --git a/App.tsx b/App.tsx index 8b08269..2ffb0e4 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,7 @@ import { StatusBar } from 'expo-status-bar'; import React, { useEffect } from 'react'; +import { Platform } from 'react-native'; +import * as NavigationBar from 'expo-navigation-bar'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { SessionProvider } from './src/auth/session'; import { getDb } from './src/db/database'; @@ -9,13 +11,20 @@ export default function App() { // Abre y migra la BD local al arrancar. useEffect(() => { void getDb(); + + // Configuración global de la barra de navegación en Android (Edge-to-Edge) + if (Platform.OS === 'android') { + void NavigationBar.setPositionAsync('absolute'); + void NavigationBar.setBackgroundColorAsync('#ffffff00'); // Transparente + void NavigationBar.setButtonStyleAsync('dark'); // Iconos oscuros por defecto + } }, []); return ( - + ); diff --git a/package-lock.json b/package-lock.json index 0c5af0f..00ec35b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "expo-file-system": "~56.0.8", "expo-image-picker": "~56.0.18", "expo-location": "~56.0.18", + "expo-navigation-bar": "^57.0.2", "expo-screen-orientation": "^57.0.1", "expo-secure-store": "~56.0.4", "expo-sqlite": "~56.0.5", @@ -5911,6 +5912,20 @@ "react-native": "*" } }, + "node_modules/expo-navigation-bar": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/expo-navigation-bar/-/expo-navigation-bar-57.0.2.tgz", + "integrity": "sha512-Ufe49dsTZjarA7gMMwwwnNJWpXo+oTyl5Ud0g3lsGVrj2HXjyw9cb9098N8+CoWpjfIa5PghuRYYXrcCrGQIHg==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.2" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-screen-orientation": { "version": "57.0.1", "resolved": "https://registry.npmjs.org/expo-screen-orientation/-/expo-screen-orientation-57.0.1.tgz", diff --git a/package.json b/package.json index 524a458..d36e215 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "expo-file-system": "~56.0.8", "expo-image-picker": "~56.0.18", "expo-location": "~56.0.18", + "expo-navigation-bar": "^57.0.2", "expo-screen-orientation": "^57.0.1", "expo-secure-store": "~56.0.4", "expo-sqlite": "~56.0.5", diff --git a/src/screens/CameraScreen.tsx b/src/screens/CameraScreen.tsx index c754426..3c62795 100644 --- a/src/screens/CameraScreen.tsx +++ b/src/screens/CameraScreen.tsx @@ -1,6 +1,7 @@ import { CameraView, useCameraPermissions } from 'expo-camera'; import * as Location from 'expo-location'; import * as ScreenOrientation from 'expo-screen-orientation'; +import * as NavigationBar from 'expo-navigation-bar'; import { useFocusEffect } from '@react-navigation/native'; import React, { useCallback, useRef, useState } from 'react'; import { @@ -10,6 +11,8 @@ import { View, ActivityIndicator, useWindowDimensions, + Platform, + StatusBar, } from 'react-native'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { RootStackParamList } from '../navigation/types'; @@ -36,6 +39,13 @@ export function CameraScreen({ route, navigation }: Props) { useCallback(() => { let isMounted = true; + // Ocultar barras del sistema (Modo Inmersivo) + if (Platform.OS === 'android') { + void NavigationBar.setVisibilityAsync('hidden'); + void NavigationBar.setBehaviorAsync('inset-touch'); + } + StatusBar.setHidden(true); + const load = async () => { const [cfg, projectId] = await Promise.all([ loadFooterConfig(), @@ -95,6 +105,11 @@ export function CameraScreen({ route, navigation }: Props) { return () => { isMounted = false; ScreenOrientation.removeOrientationChangeListener(sub); + // Restaurar barras del sistema al salir + if (Platform.OS === 'android') { + void NavigationBar.setVisibilityAsync('visible'); + } + StatusBar.setHidden(false); }; }, []) ); diff --git a/src/screens/InspectionFormScreen.tsx b/src/screens/InspectionFormScreen.tsx index 4778988..28c7bc5 100644 --- a/src/screens/InspectionFormScreen.tsx +++ b/src/screens/InspectionFormScreen.tsx @@ -14,16 +14,9 @@ import { getTemplates } from '../db/repositories'; import { createInspection } from '../sync/mutations'; import { getDb } from '../db/database'; import { newUuid, nextTempId } from '../sync/uuid'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { RootStackParamList } from '../navigation/types'; import { MediaStrip } from '../ui/MediaStrip'; -import { - Card, - ChipSelect, - COLORS, - Field, - PrimaryButton, - SectionTitle, -} from '../ui/components'; type Props = NativeStackScreenProps; @@ -78,6 +71,7 @@ function groupFields(fields: NormField[]): { group: string; items: NormField[] } const RESULTS = ['pass', 'fail', 'na'] as const; export function InspectionFormScreen({ route, navigation }: Props) { + const insets = useSafeAreaInsets(); const { featureId, featureName, templateId: suggestedId } = route.params; // Paso 1: elegir plantilla (las asignadas al proyecto llegan en el bundle). @@ -204,7 +198,7 @@ export function InspectionFormScreen({ route, navigation }: Props) { // ── Paso 1: selector de plantilla ── if (!chosen) { return ( - + {featureName} Elige una plantilla @@ -241,7 +235,7 @@ export function InspectionFormScreen({ route, navigation }: Props) { // ── Paso 2: formulario ── return ( - + {featureName} {template?.name ?? 'Inspección libre'} diff --git a/src/screens/ProjectDetailScreen.tsx b/src/screens/ProjectDetailScreen.tsx index 6589e4d..1edea90 100644 --- a/src/screens/ProjectDetailScreen.tsx +++ b/src/screens/ProjectDetailScreen.tsx @@ -7,6 +7,7 @@ import { getOutboxCounts, OutboxCounts } from '../db/outbox'; import { isOnline } from '../net/connectivity'; import { runSync } from '../sync/engine'; import { useAutoSync } from '../sync/useAutoSync'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { RootStackParamList } from '../navigation/types'; import { COLORS, PrimaryButton } from '../ui/components'; import { FeaturesSection } from './sections/FeaturesSection'; @@ -19,6 +20,7 @@ const TABS = ['Features', 'Incidencias'] as const; type Tab = (typeof TABS)[number]; export function ProjectDetailScreen({ route, navigation }: Props) { + const insets = useSafeAreaInsets(); const { projectId } = route.params; const [tab, setTab] = useState('Features'); const [counts, setCounts] = useState(EMPTY); @@ -107,7 +109,7 @@ export function ProjectDetailScreen({ route, navigation }: Props) { {tab === 'Incidencias' && } - + void onSync()} diff --git a/src/screens/ProjectsScreen.tsx b/src/screens/ProjectsScreen.tsx index 5b334b2..c4449a3 100644 --- a/src/screens/ProjectsScreen.tsx +++ b/src/screens/ProjectsScreen.tsx @@ -18,6 +18,8 @@ import { runSync } from '../sync/engine'; import { RootStackParamList } from '../navigation/types'; import { COLORS } from '../ui/components'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + type Props = NativeStackScreenProps; /** Tolerante con la forma de la respuesta: `{projects}` (actual), `{data}` o array. */ @@ -28,6 +30,7 @@ function normalize(res: unknown): Project[] { } export function ProjectsScreen({ navigation }: Props) { + const insets = useSafeAreaInsets(); const { user, signOut } = useSession(); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); @@ -105,7 +108,7 @@ export function ProjectsScreen({ navigation }: Props) { }; return ( - + {/* Menu flotante (se posiciona relativo al contenedor principal) */} {showMenu && ( diff --git a/src/screens/detail/FeatureDetailContent.tsx b/src/screens/detail/FeatureDetailContent.tsx index 7b3b4fe..0f999ba 100644 --- a/src/screens/detail/FeatureDetailContent.tsx +++ b/src/screens/detail/FeatureDetailContent.tsx @@ -5,6 +5,7 @@ import { useFocusEffect, useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import React, { useCallback, useEffect, useState } from 'react'; import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { RootStackParamList } from '../../navigation/types'; import { Badge, @@ -17,6 +18,7 @@ import { type Nav = NativeStackNavigationProp; export function FeatureDetailContent({ featureId }: { featureId: number }) { + const insets = useSafeAreaInsets(); const navigation = useNavigation