feat: notificaciones push, APK nativo, cleartext traffic habilitado
This commit is contained in:
@@ -2,6 +2,8 @@ import { StatusBar } from 'expo-status-bar';
|
|||||||
import { StyleSheet, Text, View, TextInput, TouchableOpacity, ActivityIndicator, ScrollView, Alert, RefreshControl } from 'react-native';
|
import { StyleSheet, Text, View, TextInput, TouchableOpacity, ActivityIndicator, ScrollView, Alert, RefreshControl } from 'react-native';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
import * as Notifications from 'expo-notifications';
|
||||||
|
import * as Device from 'expo-device';
|
||||||
|
|
||||||
const API_URL = 'http://10.137.112.65:8000';
|
const API_URL = 'http://10.137.112.65:8000';
|
||||||
|
|
||||||
@@ -9,6 +11,13 @@ const COLONIAS = [
|
|||||||
'Zona Centro', 'Las Arboledas', 'Trojes', 'San Juanico',
|
'Zona Centro', 'Las Arboledas', 'Trojes', 'San Juanico',
|
||||||
'Los Olivos', 'Rancho Seco', 'Las Insurgentes'
|
'Los Olivos', 'Rancho Seco', 'Las Insurgentes'
|
||||||
];
|
];
|
||||||
|
Notifications.setNotificationHandler({
|
||||||
|
handleNotification: async () => ({
|
||||||
|
shouldShowAlert: true,
|
||||||
|
shouldPlaySound: true,
|
||||||
|
shouldSetBadge: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [screen, setScreen] = useState('splash');
|
const [screen, setScreen] = useState('splash');
|
||||||
@@ -27,7 +36,10 @@ export default function App() {
|
|||||||
const [direccion, setDireccion] = useState('');
|
const [direccion, setDireccion] = useState('');
|
||||||
const [codigoPostal, setCodigoPostal] = useState('');
|
const [codigoPostal, setCodigoPostal] = useState('');
|
||||||
|
|
||||||
useEffect(() => { cargarSesion(); }, []);
|
useEffect(() => {
|
||||||
|
cargarSesion();
|
||||||
|
registrarNotificaciones();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (screen === 'eta' && domicilioActivo && token) {
|
if (screen === 'eta' && domicilioActivo && token) {
|
||||||
@@ -167,9 +179,29 @@ export default function App() {
|
|||||||
headers: { 'Authorization': `Bearer ${t || token}` }
|
headers: { 'Authorization': `Bearer ${t || token}` }
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.mensaje) setEta(data);
|
if (data.mensaje) {
|
||||||
|
if (data.evento === 'TRUCK_PROXIMITY' && eta?.evento !== 'TRUCK_PROXIMITY') {
|
||||||
|
enviarNotificacionLocal(
|
||||||
|
'🚨 ¡Camión cercano!',
|
||||||
|
`El camión está a menos de 15 minutos de ${data.colonia}. Saca tus bolsas a la acera.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.evento === 'ROUTE_START' && eta?.evento !== 'ROUTE_START') {
|
||||||
|
enviarNotificacionLocal(
|
||||||
|
'🟢 Ruta iniciada',
|
||||||
|
`El camión de ${data.colonia} ha salido. Prepara tus residuos.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.evento === 'ROUTE_COMPLETED' && eta?.evento !== 'ROUTE_COMPLETED') {
|
||||||
|
enviarNotificacionLocal(
|
||||||
|
'✅ Servicio finalizado',
|
||||||
|
`El camión de ${data.colonia} ha concluido su jornada.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setEta(data);
|
||||||
|
}
|
||||||
} catch { if (!silencioso) Alert.alert('Error', 'No se pudo obtener el ETA'); }
|
} catch { if (!silencioso) Alert.alert('Error', 'No se pudo obtener el ETA'); }
|
||||||
if (!silencioso) setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRefresh = useCallback(async () => {
|
const onRefresh = useCallback(async () => {
|
||||||
@@ -178,10 +210,23 @@ export default function App() {
|
|||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
}, [domicilioActivo, token]);
|
}, [domicilioActivo, token]);
|
||||||
|
|
||||||
|
const registrarNotificaciones = async () => {
|
||||||
|
if (!Device.isDevice) return;
|
||||||
|
const { status } = await Notifications.requestPermissionsAsync();
|
||||||
|
if (status !== 'granted') return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const enviarNotificacionLocal = async (titulo, cuerpo) => {
|
||||||
|
await Notifications.scheduleNotificationAsync({
|
||||||
|
content: { title: titulo, body: cuerpo, sound: true },
|
||||||
|
trigger: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const seleccionarDomicilio = async (dom) => {
|
const seleccionarDomicilio = async (dom) => {
|
||||||
setDomicilioActivo(dom);
|
setDomicilioActivo(dom);
|
||||||
await AsyncStorage.setItem('domicilioId', String(dom.id));
|
await AsyncStorage.setItem('domicilioId', String(dom.id));
|
||||||
consultarETA(dom.id, token);
|
consultarETA(dom.id, token, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (screen === 'splash') return (
|
if (screen === 'splash') return (
|
||||||
@@ -302,7 +347,8 @@ export default function App() {
|
|||||||
|
|
||||||
{domicilios.length > 1 && (
|
{domicilios.length > 1 && (
|
||||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}
|
<ScrollView horizontal showsHorizontalScrollIndicator={false}
|
||||||
style={{ width: '100%', marginBottom: 12 }}>
|
style={{ width: '100%', marginBottom: 12 }}
|
||||||
|
contentContainerStyle={{ alignItems: 'center', paddingVertical: 4 }}>
|
||||||
{domicilios.map(d => (
|
{domicilios.map(d => (
|
||||||
<TouchableOpacity key={d.id}
|
<TouchableOpacity key={d.id}
|
||||||
style={[styles.domicilioTab, domicilioActivo?.id === d.id && styles.domicilioTabActivo]}
|
style={[styles.domicilioTab, domicilioActivo?.id === d.id && styles.domicilioTabActivo]}
|
||||||
@@ -426,8 +472,8 @@ const styles = StyleSheet.create({
|
|||||||
domicilioChip: { padding: 12, borderRadius: 8, borderWidth: 1, borderColor: '#ddd', backgroundColor: '#fff', marginBottom: 6 },
|
domicilioChip: { padding: 12, borderRadius: 8, borderWidth: 1, borderColor: '#ddd', backgroundColor: '#fff', marginBottom: 6 },
|
||||||
domicilioChipActivo: { borderColor: '#1a7a4a', backgroundColor: '#e8f5ee' },
|
domicilioChipActivo: { borderColor: '#1a7a4a', backgroundColor: '#e8f5ee' },
|
||||||
domicilioChipText: { fontSize: 13, color: '#333' },
|
domicilioChipText: { fontSize: 13, color: '#333' },
|
||||||
domicilioTab: { paddingHorizontal: 14, paddingVertical: 8, borderRadius: 20, borderWidth: 1, borderColor: '#ddd', backgroundColor: '#fff', marginRight: 8 },
|
domicilioTab: { paddingHorizontal: 14, paddingVertical: 8, borderRadius: 20, borderWidth: 1, borderColor: '#ddd', backgroundColor: '#fff', marginRight: 8, height: 36, justifyContent: 'center' },
|
||||||
domicilioTabActivo: { backgroundColor: '#1a7a4a', borderColor: '#1a7a4a' },
|
domicilioTabActivo: { backgroundColor: '#1a7a4a', borderColor: '#1a7a4a', height: 36 },
|
||||||
domicilioTabText: { fontSize: 13, color: '#555' },
|
domicilioTabText: { fontSize: 13, color: '#555' },
|
||||||
domicilioTabTextActivo: { color: '#fff', fontWeight: '500' },
|
domicilioTabTextActivo: { color: '#fff', fontWeight: '500' },
|
||||||
etaCard: { width: '100%', backgroundColor: '#fff', borderRadius: 16, padding: 20, marginBottom: 16, borderWidth: 1, borderColor: '#d0e8d8' },
|
etaCard: { width: '100%', backgroundColor: '#fff', borderRadius: 16, padding: 20, marginBottom: 16, borderWidth: 1, borderColor: '#d0e8d8' },
|
||||||
|
|||||||
@@ -20,10 +20,16 @@
|
|||||||
"foregroundImage": "./assets/adaptive-icon.png",
|
"foregroundImage": "./assets/adaptive-icon.png",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"edgeToEdgeEnabled": true
|
"edgeToEdgeEnabled": true,
|
||||||
|
"package": "com.hackonlinces.basuraapp"
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"favicon": "./assets/favicon.png"
|
"favicon": "./assets/favicon.png"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"eas": {
|
||||||
|
"projectId": "4aec7244-5fe6-49e9-90fd-e57f93d91073"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user