eletrotupi / tcc/ commit / 1177778

frontend: patch notifications receiving and draft notification handling

Pedro Lucas Porcellis porcellis@eletrotupi.com 1 month ago 1177778ae361b58371a1dabb3aeec3b0b03e0ff9
Parents: d139cd1
8 file(s) changed
  • frontend/app/(tabs)/actions.tsx +0 -1
  • frontend/app/(tabs)/history.tsx +0 -1
  • frontend/app/(tabs)/insights.tsx +0 -1
  • frontend/app/(tabs)/new/index.tsx +0 -5
  • frontend/app/(tabs)/settings.tsx +1 -4
  • frontend/app/notifications.tsx +27 -16
  • frontend/components/ui/ScreenLayout.tsx +3 -1
  • frontend/hooks/useNotificationObserver.ts +9 -0
frontend/app/(tabs)/actions.tsx
1 1
import { View, Text, StyleSheet } from "react-native";
2 2
import { ThemedText } from "@/components/misc/themed-text";
3 3
import { ThemedView } from "@/components/misc/themed-view";
4 4
import { ScreenLayout } from "@/components/ui/ScreenLayout";
5 5
import { useAuth } from "@/context/AuthContext";
▸ 24 unchanged lines
30 30

31 31
  return (
32 32
    <ScreenLayout
33 33
      userName={user.firstName}
34 34
      userAvatar={user.avatarURL}
35 -
      onNotificationPress={() => console.log("Notifications")}
36 35
      showNotificationBadge={true}
37 36
    >
38 37
      <Section>
39 38
        <SectionHeader
40 39
          title="Ações de Cuidado"
▸ 132 unchanged lines
173 172
    fontSize: 12,
174 173
    lineHeight: 16,
175 174
    alignItems: "center",
176 175
  },
177 176
});
frontend/app/(tabs)/history.tsx
1 1
import { useState } from "react";
2 2
import { ScrollView, View, Text } from "react-native";
3 3
import { ScreenLayout } from "@/components/ui/ScreenLayout";
4 4
import { Section, SectionHeader } from "@/components/ui/Sections";
5 5
import { FilterPills } from "@/components/ui/FilterPills";
▸ 31 unchanged lines
37 37

38 38
  return (
39 39
    <ScreenLayout
40 40
      userName={user.firstName}
41 41
      userAvatar={user.avatarURL}
42 -
      onNotificationPress={() => console.log("Notifications")}
43 42
      showNotificationBadge={true}
44 43
    >
45 44
      <ScrollView>
46 45
        <FilterPills active={activeFilter} onChange={setActiveFilter} />
47 46

▸ 9 unchanged lines
57 56
        ))}
58 57
      </ScrollView>
59 58
    </ScreenLayout>
60 59
  );
61 60
}
frontend/app/(tabs)/insights.tsx
1 1
import { View, Text, ScrollView, StyleSheet } from "react-native";
2 2

3 3
import { ThemedText } from "@/components/misc/themed-text";
4 4
import { ThemedView } from "@/components/misc/themed-view";
5 5
import { ScreenLayout } from "@/components/ui/ScreenLayout";
▸ 12 unchanged lines
18 18

19 19
  return (
20 20
    <ScreenLayout
21 21
      userName={user.firstName}
22 22
      userAvatar={user.avatarURL}
23 -
      onNotificationPress={() => console.log("Notifications")}
24 23
      showNotificationBadge={true}
25 24
    >
26 25
      <WeeklyResume
27 26
        avgMood="Neutro"
28 27
        stats={[
▸ 111 unchanged lines
140 139
  insightMetric: {
141 140
    ...Typography.headlineLg,
142 141
    fontSize: 22,
143 142
  },
144 143
});
frontend/app/(tabs)/new/index.tsx
1 1
import { useEffect, useState } from "react";
2 2
import { StyleSheet, View, Text, ActivityIndicator } from "react-native";
3 3

4 4
import { ThemedText } from "@/components/misc/themed-text";
5 5
import { ThemedView } from "@/components/misc/themed-view";
▸ 33 unchanged lines
39 39
  const initQuickRegister = () => {
40 40
    // TODO: Set down on camelCase vs kebab case
41 41
    router.navigate(`/new/entry?initialMood=${mood}`);
42 42
  };
43 43

44 -
  const onNotificationPress = () => {
45 -
    router.push("/notifications");
46 -
  };
47 -

48 44
  return (
49 45
    <ScreenLayout
50 46
      userName={user.firstName}
51 47
      userAvatar={user.avatarURL}
52 -
      onNotificationPress={onNotificationPress}
53 48
      showNotificationBadge={true}
54 49
    >
55 50
      <Section>
56 51
        <SectionHeader
57 52
          title="Como você está hoje?"
▸ 102 unchanged lines
160 155
    fontSize: 11,
161 156
    fontWeight: 600,
162 157
    lineHeight: 16.5,
163 158
  },
164 159
});
frontend/app/(tabs)/settings.tsx
1 1
import {
2 2
  TouchableOpacity,
3 3
  StyleSheet,
4 4
  Text,
5 5
  SectionList,
▸ 18 unchanged lines
24 24
  const [allowNotifications, setAllowNotifications] = useState(false);
25 25
  const [allowWeeklyInsights, setAllowWeeklyInsights] = useState(false);
26 26

27 27
  const textColor = useThemeColor({}, "text");
28 28

29 -
  const onNotificationPress = () => {};
30 -

31 29
  const handleLogout = async () => {
32 30
    Alert.alert("Sair", "Tem certeza que quer sair?", [
33 31
      { text: "Cancelar", style: "cancel" },
34 32
      {
35 33
        text: "Sair",
▸ 28 unchanged lines
64 62

65 63
  return (
66 64
    <ScreenLayout
67 65
      userName={user.firstName}
68 66
      userAvatar={user.avatarURL}
69 -
      onNotificationPress={onNotificationPress}
70 67
      showNotificationBadge={true}
71 68
    >
72 69
      <Section>
73 70
        <SectionHeader title="Perfil" />
74 71

▸ 138 unchanged lines
213 210
                  color: Colors.light.text,
214 211
                  fontSize: 14,
215 212
                  fontWeight: "500" as const,
216 213
                }}
217 214
              >
218 -
                Criptografia (EE2E)
215 +
                Privacidade
219 216
              </Text>
220 217
            </View>
221 218

222 219
            <Ionicons name="chevron-forward" size={24} color={textColor} />
223 220
          </TouchableOpacity>
▸ 82 unchanged lines
306 303
    height: StyleSheet.hairlineWidth,
307 304
    backgroundColor: "#D1D5DB",
308 305
    marginVertical: 16,
309 306
  },
310 307
});
frontend/app/notifications.tsx
1 1
import {
2 2
  View,
3 3
  Text,
4 4
  StyleSheet,
5 5
  ScrollView,
▸ 18 unchanged lines
24 24
  title?: string;
25 25
  body?: string;
26 26
  notificationId?: string;
27 27
};
28 28

29 -
Notifications.setNotificationHandler({
30 -
  handleNotification: async () => ({
31 -
    shouldPlaySound: true,
32 -
    shouldSetBadge: false,
33 -
    shouldShowBanner: true,
34 -
    shouldShowList: true,
35 -
  }),
36 -
});
29 +
type ReceivedNotification = {
30 +
  title?: string;
31 +
  body?: string;
32 +
};
37 33

38 34
export default function NotificationsPage() {
39 35
  const { user } = useAuth();
40 36
  const params = useLocalSearchParams<NotificationParams>();
41 37
  const [expoPushToken, setExpoPushToken] = useState(user?.pushToken);
38 +
  const [liveNotification, setLiveNotification] = useState<ReceivedNotification | null>(null);
42 39

43 40
  useEffect(() => {
44 41
    if (user) {
45 42
      setExpoPushToken(user.pushToken);
46 43
    }
47 44
  }, [user]);
48 45

46 +
  useEffect(() => {
47 +
    const sub = Notifications.addNotificationReceivedListener((n) => {
48 +
      setLiveNotification({
49 +
        title: n.request.content.title ?? undefined,
50 +
        body: n.request.content.body ?? undefined,
51 +
      });
52 +
    });
53 +
    return () => sub.remove();
54 +
  }, []);
55 +

56 +
  const displayed = liveNotification ?? (params.title || params.body ? { title: params.title, body: params.body } : null);
57 +

49 58
  async function sendPushNotification() {
50 59
    if (expoPushToken) {
51 60
      const message = {
52 61
        to: expoPushToken,
53 62
        sound: "default",
▸ 23 unchanged lines
77 86
          showsVerticalScrollIndicator={false}
78 87
        >
79 88
          <Section>
80 89
            <SectionHeader title="Recebidas" />
81 90

82 -
            <Card style={{ padding: Spacing.cardGap }}>
83 -
              <Text>Title: {params.title}</Text>
84 -
              <Text>Body: {params.body}</Text>
85 -
              <Text>
86 -
                Data:{" "}
87 -
                {params.notificationId && JSON.stringify(params.notificationId)}
88 -
              </Text>
89 -
            </Card>
91 +
            {displayed ? (
92 +
              <Card style={{ padding: Spacing.cardGap }}>
93 +
                <Text>Title: {displayed.title}</Text>
94 +
                <Text>Body: {displayed.body}</Text>
95 +
              </Card>
96 +
            ) : (
97 +
              <Card style={{ padding: Spacing.cardGap }}>
98 +
                <Text>Nenhuma notificação recebida.</Text>
99 +
              </Card>
100 +
            )}
90 101
          </Section>
91 102

92 103
          <Button title="Quer ver uma coisa?" onPress={sendPushNotification} />
93 104
        </ScrollView>
94 105
      </KeyboardAvoidingView>
▸ 18 unchanged lines
113 124
  header: {
114 125
    alignItems: "left",
115 126
    marginBottom: 40,
116 127
  },
117 128
});
frontend/components/ui/ScreenLayout.tsx
1 1
import React from "react";
2 2

3 3
import {
4 4
  View,
5 5
  ScrollView,
▸ 2 unchanged lines
8 8
  Pressable,
9 9
  StatusBar,
10 10
} from "react-native";
11 11

12 12
import { SafeAreaView } from "react-native-safe-area-context";
13 +
import { useRouter } from "expo-router";
13 14
import { IconSymbol } from "@/components/ui/icon-symbol";
14 15
import { useThemeColor } from "@/hooks/use-theme-color";
15 16
import { Typography, Spacing, BorderRadius } from "@/constants/theme";
16 17
import { ThemedText } from "@/components/misc/themed-text";
17 18

▸ 12 unchanged lines
30 31
  userAvatar,
31 32
  onNotificationPress,
32 33
  showNotificationBadge = false,
33 34
  scrollEnabled = true,
34 35
}) => {
36 +
  const router = useRouter();
35 37
  const textColor = useThemeColor({}, "text");
36 38
  const backgroundColor = useThemeColor({}, "background");
37 39
  const surfaceColor = useThemeColor({}, "surface");
38 40
  const dividerColor = useThemeColor({}, "divider");
39 41
  const accentBlue = useThemeColor({}, "accentBlue");
▸ 76 unchanged lines
116 118
            </View>
117 119
          </View>
118 120

119 121
          <Pressable
120 122
            style={styles.notificationButton}
121 -
            onPress={onNotificationPress}
123 +
            onPress={onNotificationPress ?? (() => router.push("/notifications"))}
122 124
            hitSlop={8}
123 125
          >
124 126
            <View
125 127
              style={{
126 128
                fontSize: 24,
▸ 18 unchanged lines
145 147
    </View>
146 148
  );
147 149
};
148 150

149 151
export default ScreenLayout;
frontend/hooks/useNotificationObserver.ts
1 1
import { useEffect, useRef } from "react";
2 2
import * as Notifications from "expo-notifications";
3 3
import { useRouter, useRootNavigationState } from "expo-router";
4 4

5 +
Notifications.setNotificationHandler({
6 +
  handleNotification: async () => ({
7 +
    shouldPlaySound: true,
8 +
    shouldSetBadge: false,
9 +
    shouldShowBanner: true,
10 +
    shouldShowList: true,
11 +
  }),
12 +
});
13 +

5 14
export function useNotificationObserver() {
6 15
  const router = useRouter();
7 16
  const rootNavState = useRootNavigationState();
8 17
  const routerReady = !!rootNavState?.key;
9 18
  const handled = useRef(false);
▸ 58 unchanged lines
68 77
      isMounted = false;
69 78
      subscription.remove();
70 79
    };
71 80
  }, [routerReady, router]);
72 81
}