feat: add notification push

This commit is contained in:
Diego Mireles
2026-05-23 07:34:21 -06:00
parent 7de53482b1
commit b6addb411a
6 changed files with 262 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
/**
* NotificationToast.tsx
*
* Banner animado que cae desde arriba cuando llega una notificación
* nueva del backend. Se autodescarta a los 5 segundos.
*
* Limitación: solo se muestra cuando la app está en primer plano. Para
* notificaciones del SO con la app cerrada se necesita un Development
* Build + expo-notifications (Expo Go SDK 53+ no lo soporta).
*/
import { useEffect, useRef } from "react";
import {
Animated,
Pressable,
StyleSheet,
Text,
View,
Platform,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { useApp } from "../context/AppContext";
const AUTO_DISMISS_MS = 5000;
export default function NotificationToast() {
const { toast, dismissToast } = useApp();
const translateY = useRef(new Animated.Value(-180)).current;
useEffect(() => {
if (!toast) return;
// Slide in
Animated.spring(translateY, {
toValue: 0,
useNativeDriver: true,
damping: 18,
}).start();
// Auto dismiss después de N segundos
const timer = setTimeout(() => {
Animated.timing(translateY, {
toValue: -180,
duration: 250,
useNativeDriver: true,
}).start(() => dismissToast());
}, AUTO_DISMISS_MS);
return () => clearTimeout(timer);
}, [toast, translateY, dismissToast]);
if (!toast) return null;
return (
<Animated.View
style={[
styles.container,
{ transform: [{ translateY }] },
]}
pointerEvents="box-none"
>
<Pressable style={styles.card} onPress={dismissToast}>
<View style={styles.iconWrap}>
<Ionicons name="notifications" size={22} color="#FFFFFF" />
</View>
<View style={{ flex: 1 }}>
<Text style={styles.title} numberOfLines={1}>
{toast.title}
</Text>
<Text style={styles.body} numberOfLines={2}>
{toast.body}
</Text>
</View>
<Ionicons name="close" size={18} color="#FFFFFF" />
</Pressable>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
position: "absolute",
top: Platform.OS === "ios" ? 50 : 30,
left: 16,
right: 16,
zIndex: 9999,
},
card: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "#0E8A61",
borderRadius: 14,
padding: 14,
shadowColor: "#000",
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.2,
shadowRadius: 10,
elevation: 10,
},
iconWrap: {
width: 38,
height: 38,
borderRadius: 19,
backgroundColor: "rgba(255,255,255,0.2)",
justifyContent: "center",
alignItems: "center",
marginRight: 12,
},
title: {
fontSize: 14,
fontWeight: "800",
color: "#FFFFFF",
marginBottom: 2,
},
body: {
fontSize: 12,
color: "rgba(255,255,255,0.95)",
lineHeight: 16,
},
});