1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
| | import React, { useEffect, useRef } from "react";
| | import { Animated, Pressable, Text, View } from "react-native";
| | import { useTheme } from "../../contexts/ThemeContext";
| |
| | interface IncomingToastProps {
| | sessionName: string;
| | preview: string;
| | onTap: () => void;
| | onDismiss: () => void;
| | }
| |
| | const DISPLAY_MS = 4000;
| |
| | export function IncomingToast({ sessionName, preview, onTap, onDismiss }: IncomingToastProps) {
| | const { colors } = useTheme();
| | const translateY = useRef(new Animated.Value(-60)).current;
| |
| | useEffect(() => {
| | // Slide in from above (no opacity — keeps background solid)
| | Animated.spring(translateY, {
| | toValue: 0,
| | useNativeDriver: true,
| | tension: 80,
| | friction: 10,
| | }).start();
| |
| | // Auto-dismiss
| | const timer = setTimeout(() => {
| | Animated.timing(translateY, {
| | toValue: -60,
| | duration: 200,
| | useNativeDriver: true,
| | }).start(() => onDismiss());
| | }, DISPLAY_MS);
| |
| | return () => clearTimeout(timer);
| | }, []);
| |
| | return (
| | <Animated.View
| | style={{
| | position: "absolute",
| | top: 4,
| | left: 12,
| | right: 12,
| | zIndex: 100,
| | transform: [{ translateY }],
| | }}
| | >
| | <Pressable
| | onPress={onTap}
| | style={({ pressed }) => ({
| | flexDirection: "row",
| | alignItems: "center",
| | gap: 10,
| | paddingHorizontal: 14,
| | paddingVertical: 10,
| | borderRadius: 12,
| | backgroundColor: pressed ? colors.bgTertiary : colors.bgSecondary,
| | borderWidth: 1,
| | borderColor: colors.accent,
| | shadowColor: "#000",
| | shadowOffset: { width: 0, height: 4 },
| | shadowOpacity: 0.4,
| | shadowRadius: 8,
| | elevation: 8,
| | })}
| | >
| | <View
| | style={{
| | width: 10,
| | height: 10,
| | borderRadius: 5,
| | backgroundColor: colors.accent,
| | }}
| | />
| | <View style={{ flex: 1 }}>
| | <Text style={{ color: colors.text, fontSize: 14, fontWeight: "700" }} numberOfLines={1}>
| | {sessionName}
| | </Text>
| | <Text style={{ color: colors.textMuted, fontSize: 12, marginTop: 2 }} numberOfLines={1}>
| | {preview}
| | </Text>
| | </View>
| | <Text style={{ color: colors.accent, fontSize: 11, fontWeight: "600" }}>switch</Text>
| | </Pressable>
| | </Animated.View>
| | );
| | }
|
|