Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Multiple notifications fixes (#2154)

* Dont reset notifications feed on push notification event

* Dont separate notifications by read state to avoid jank

* On notifications screen focus, check latest and only rerender if not scrolled down

* Reuse the cached notifs page when its not stale

* Bump ios build number

* Improve comments

* Change the 'mark all read' condition to avoid firing too early

authored by

Paul Frazee and committed by
GitHub
6b3eb401 d854e882

+162 -146
+1 -1
app.config.js
··· 9 9 /** 10 10 * iOS build number. Must be incremented for each TestFlight version. 11 11 */ 12 - const IOS_BUILD_NUMBER = '12' 12 + const IOS_BUILD_NUMBER = '13' 13 13 14 14 /** 15 15 * Android build number. Must be incremented for each release.
+1 -2
src/lib/notifications/notifications.ts
··· 81 81 82 82 export function init(queryClient: QueryClient) { 83 83 // handle notifications that are received, both in the foreground or background 84 + // NOTE: currently just here for debug logging 84 85 Notifications.addNotificationReceivedListener(event => { 85 86 logger.debug( 86 87 'Notifications: received', ··· 88 89 logger.DebugContext.notifications, 89 90 ) 90 91 if (event.request.trigger.type === 'push') { 91 - // refresh notifications in the background 92 - truncateAndInvalidate(queryClient, RQKEY_NOTIFS()) 93 92 // handle payload-based deeplinks 94 93 let payload 95 94 if (isIOS) {
+45 -45
src/locale/locales/en/messages.po
··· 38 38 msgstr "" 39 39 40 40 #: src/view/screens/Settings.tsx:407 41 - #: src/view/shell/Drawer.tsx:648 41 + #: src/view/shell/Drawer.tsx:640 42 42 msgid "{invitesAvailable} invite code available" 43 43 msgstr "" 44 44 45 45 #: src/view/screens/Settings.tsx:409 46 - #: src/view/shell/Drawer.tsx:650 46 + #: src/view/shell/Drawer.tsx:642 47 47 msgid "{invitesAvailable} invite codes available" 48 48 msgstr "" 49 49 ··· 830 830 msgstr "" 831 831 832 832 #: src/view/shell/desktop/RightNav.tsx:64 833 - #: src/view/shell/Drawer.tsx:300 833 + #: src/view/shell/Drawer.tsx:292 834 834 msgid "Feedback" 835 835 msgstr "" 836 836 837 837 #: src/view/screens/Feeds.tsx:475 838 838 #: src/view/screens/Profile.tsx:164 839 - #: src/view/shell/bottom-bar/BottomBar.tsx:168 840 - #: src/view/shell/desktop/LeftNav.tsx:341 841 - #: src/view/shell/Drawer.tsx:463 842 - #: src/view/shell/Drawer.tsx:464 839 + #: src/view/shell/bottom-bar/BottomBar.tsx:160 840 + #: src/view/shell/desktop/LeftNav.tsx:333 841 + #: src/view/shell/Drawer.tsx:455 842 + #: src/view/shell/Drawer.tsx:456 843 843 msgid "Feeds" 844 844 msgstr "" 845 845 ··· 928 928 #: src/view/com/auth/LoggedOut.tsx:68 929 929 #: src/view/com/auth/LoggedOut.tsx:69 930 930 #: src/view/com/util/moderation/ScreenHider.tsx:105 931 - #: src/view/shell/desktop/LeftNav.tsx:106 931 + #: src/view/shell/desktop/LeftNav.tsx:103 932 932 msgid "Go back" 933 933 msgstr "" 934 934 ··· 950 950 msgstr "" 951 951 952 952 #: src/view/shell/desktop/RightNav.tsx:93 953 - #: src/view/shell/Drawer.tsx:310 953 + #: src/view/shell/Drawer.tsx:302 954 954 msgid "Help" 955 955 msgstr "" 956 956 ··· 994 994 #~ msgid "Hmmm, we're having trouble finding this feed. It may have been deleted." 995 995 #~ msgstr "" 996 996 997 - #: src/view/shell/bottom-bar/BottomBar.tsx:124 998 - #: src/view/shell/desktop/LeftNav.tsx:305 999 - #: src/view/shell/Drawer.tsx:387 1000 - #: src/view/shell/Drawer.tsx:388 997 + #: src/view/shell/bottom-bar/BottomBar.tsx:116 998 + #: src/view/shell/desktop/LeftNav.tsx:297 999 + #: src/view/shell/Drawer.tsx:379 1000 + #: src/view/shell/Drawer.tsx:380 1001 1001 msgid "Home" 1002 1002 msgstr "" 1003 1003 ··· 1063 1063 msgid "Invite code not accepted. Check that you input it correctly and try again." 1064 1064 msgstr "" 1065 1065 1066 - #: src/view/shell/Drawer.tsx:629 1066 + #: src/view/shell/Drawer.tsx:621 1067 1067 msgid "Invite codes: {invitesAvailable} available" 1068 1068 msgstr "" 1069 1069 ··· 1162 1162 msgstr "" 1163 1163 1164 1164 #: src/view/screens/Profile.tsx:165 1165 - #: src/view/shell/desktop/LeftNav.tsx:381 1166 - #: src/view/shell/Drawer.tsx:479 1167 - #: src/view/shell/Drawer.tsx:480 1165 + #: src/view/shell/desktop/LeftNav.tsx:373 1166 + #: src/view/shell/Drawer.tsx:471 1167 + #: src/view/shell/Drawer.tsx:472 1168 1168 msgid "Lists" 1169 1169 msgstr "" 1170 1170 ··· 1173 1173 msgid "Load more posts" 1174 1174 msgstr "" 1175 1175 1176 - #: src/view/screens/Notifications.tsx:130 1176 + #: src/view/screens/Notifications.tsx:141 1177 1177 msgid "Load new notifications" 1178 1178 msgstr "" 1179 1179 ··· 1224 1224 1225 1225 #: src/view/screens/Moderation.tsx:63 1226 1226 #: src/view/screens/Settings.tsx:563 1227 - #: src/view/shell/desktop/LeftNav.tsx:399 1228 - #: src/view/shell/Drawer.tsx:498 1229 - #: src/view/shell/Drawer.tsx:499 1227 + #: src/view/shell/desktop/LeftNav.tsx:391 1228 + #: src/view/shell/Drawer.tsx:490 1229 + #: src/view/shell/Drawer.tsx:491 1230 1230 msgid "Moderation" 1231 1231 msgstr "" 1232 1232 ··· 1300 1300 msgid "My Feeds" 1301 1301 msgstr "" 1302 1302 1303 - #: src/view/shell/desktop/LeftNav.tsx:67 1303 + #: src/view/shell/desktop/LeftNav.tsx:64 1304 1304 msgid "My Profile" 1305 1305 msgstr "" 1306 1306 ··· 1328 1328 #: src/view/screens/ProfileFeed.tsx:451 1329 1329 #: src/view/screens/ProfileList.tsx:212 1330 1330 #: src/view/screens/ProfileList.tsx:244 1331 - #: src/view/shell/desktop/LeftNav.tsx:254 1331 + #: src/view/shell/desktop/LeftNav.tsx:246 1332 1332 msgid "New post" 1333 1333 msgstr "" 1334 1334 1335 - #: src/view/shell/desktop/LeftNav.tsx:264 1335 + #: src/view/shell/desktop/LeftNav.tsx:256 1336 1336 msgid "New Post" 1337 1337 msgstr "" 1338 1338 ··· 1402 1402 #~ msgid "Note: Third-party apps that display Bluesky content may not respect this setting." 1403 1403 #~ msgstr "" 1404 1404 1405 - #: src/view/screens/Notifications.tsx:97 1406 - #: src/view/screens/Notifications.tsx:121 1407 - #: src/view/shell/bottom-bar/BottomBar.tsx:195 1408 - #: src/view/shell/desktop/LeftNav.tsx:363 1409 - #: src/view/shell/Drawer.tsx:424 1410 - #: src/view/shell/Drawer.tsx:425 1405 + #: src/view/screens/Notifications.tsx:108 1406 + #: src/view/screens/Notifications.tsx:132 1407 + #: src/view/shell/bottom-bar/BottomBar.tsx:187 1408 + #: src/view/shell/desktop/LeftNav.tsx:355 1409 + #: src/view/shell/Drawer.tsx:416 1410 + #: src/view/shell/Drawer.tsx:417 1411 1411 msgid "Notifications" 1412 1412 msgstr "" 1413 1413 ··· 1432 1432 msgstr "" 1433 1433 1434 1434 #: src/view/shell/desktop/RightNav.tsx:146 1435 - #: src/view/shell/Drawer.tsx:630 1435 + #: src/view/shell/Drawer.tsx:622 1436 1436 msgid "Opens list of invite codes" 1437 1437 msgstr "" 1438 1438 ··· 1592 1592 msgid "Processing..." 1593 1593 msgstr "" 1594 1594 1595 - #: src/view/shell/bottom-bar/BottomBar.tsx:237 1596 - #: src/view/shell/Drawer.tsx:72 1597 - #: src/view/shell/Drawer.tsx:533 1598 - #: src/view/shell/Drawer.tsx:534 1595 + #: src/view/shell/bottom-bar/BottomBar.tsx:229 1596 + #: src/view/shell/Drawer.tsx:69 1597 + #: src/view/shell/Drawer.tsx:525 1598 + #: src/view/shell/Drawer.tsx:526 1599 1599 msgid "Profile" 1600 1600 msgstr "" 1601 1601 ··· 1813 1813 #: src/view/com/util/forms/SearchInput.tsx:64 1814 1814 #: src/view/screens/Search/Search.tsx:381 1815 1815 #: src/view/screens/Search/Search.tsx:533 1816 - #: src/view/shell/bottom-bar/BottomBar.tsx:146 1817 - #: src/view/shell/desktop/LeftNav.tsx:323 1816 + #: src/view/shell/bottom-bar/BottomBar.tsx:138 1817 + #: src/view/shell/desktop/LeftNav.tsx:315 1818 1818 #: src/view/shell/desktop/Search.tsx:161 1819 1819 #: src/view/shell/desktop/Search.tsx:170 1820 - #: src/view/shell/Drawer.tsx:351 1821 - #: src/view/shell/Drawer.tsx:352 1820 + #: src/view/shell/Drawer.tsx:343 1821 + #: src/view/shell/Drawer.tsx:344 1822 1822 msgid "Search" 1823 1823 msgstr "" 1824 1824 ··· 1870 1870 msgid "Send Email" 1871 1871 msgstr "" 1872 1872 1873 - #: src/view/shell/Drawer.tsx:284 1874 - #: src/view/shell/Drawer.tsx:305 1873 + #: src/view/shell/Drawer.tsx:276 1874 + #: src/view/shell/Drawer.tsx:297 1875 1875 msgid "Send feedback" 1876 1876 msgstr "" 1877 1877 ··· 1904 1904 msgstr "" 1905 1905 1906 1906 #: src/view/screens/Settings.tsx:277 1907 - #: src/view/shell/desktop/LeftNav.tsx:435 1908 - #: src/view/shell/Drawer.tsx:554 1909 - #: src/view/shell/Drawer.tsx:555 1907 + #: src/view/shell/desktop/LeftNav.tsx:427 1908 + #: src/view/shell/Drawer.tsx:546 1909 + #: src/view/shell/Drawer.tsx:547 1910 1910 msgid "Settings" 1911 1911 msgstr "" 1912 1912 ··· 2447 2447 2448 2448 #: src/view/screens/Settings.tsx:402 2449 2449 #: src/view/shell/desktop/RightNav.tsx:127 2450 - #: src/view/shell/Drawer.tsx:644 2450 + #: src/view/shell/Drawer.tsx:636 2451 2451 msgid "Your invite codes are hidden when logged in using an App Password" 2452 2452 msgstr "" 2453 2453
+45 -45
src/locale/locales/hi/messages.po
··· 38 38 msgstr "" 39 39 40 40 #: src/view/screens/Settings.tsx:407 41 - #: src/view/shell/Drawer.tsx:648 41 + #: src/view/shell/Drawer.tsx:640 42 42 msgid "{invitesAvailable} invite code available" 43 43 msgstr "" 44 44 45 45 #: src/view/screens/Settings.tsx:409 46 - #: src/view/shell/Drawer.tsx:650 46 + #: src/view/shell/Drawer.tsx:642 47 47 msgid "{invitesAvailable} invite codes available" 48 48 msgstr "" 49 49 ··· 826 826 msgstr "फ़ीड प्राथमिकता" 827 827 828 828 #: src/view/shell/desktop/RightNav.tsx:64 829 - #: src/view/shell/Drawer.tsx:300 829 + #: src/view/shell/Drawer.tsx:292 830 830 msgid "Feedback" 831 831 msgstr "प्रतिक्रिया" 832 832 833 833 #: src/view/screens/Feeds.tsx:475 834 834 #: src/view/screens/Profile.tsx:164 835 - #: src/view/shell/bottom-bar/BottomBar.tsx:168 836 - #: src/view/shell/desktop/LeftNav.tsx:341 837 - #: src/view/shell/Drawer.tsx:463 838 - #: src/view/shell/Drawer.tsx:464 835 + #: src/view/shell/bottom-bar/BottomBar.tsx:160 836 + #: src/view/shell/desktop/LeftNav.tsx:333 837 + #: src/view/shell/Drawer.tsx:455 838 + #: src/view/shell/Drawer.tsx:456 839 839 msgid "Feeds" 840 840 msgstr "सभी फ़ीड" 841 841 ··· 920 920 #: src/view/com/auth/LoggedOut.tsx:68 921 921 #: src/view/com/auth/LoggedOut.tsx:69 922 922 #: src/view/com/util/moderation/ScreenHider.tsx:105 923 - #: src/view/shell/desktop/LeftNav.tsx:106 923 + #: src/view/shell/desktop/LeftNav.tsx:103 924 924 msgid "Go back" 925 925 msgstr "वापस जाओ" 926 926 ··· 942 942 msgstr "हैंडल" 943 943 944 944 #: src/view/shell/desktop/RightNav.tsx:93 945 - #: src/view/shell/Drawer.tsx:310 945 + #: src/view/shell/Drawer.tsx:302 946 946 msgid "Help" 947 947 msgstr "सहायता" 948 948 ··· 986 986 #~ msgid "Hmmm, we're having trouble finding this feed. It may have been deleted." 987 987 #~ msgstr "" 988 988 989 - #: src/view/shell/bottom-bar/BottomBar.tsx:124 990 - #: src/view/shell/desktop/LeftNav.tsx:305 991 - #: src/view/shell/Drawer.tsx:387 992 - #: src/view/shell/Drawer.tsx:388 989 + #: src/view/shell/bottom-bar/BottomBar.tsx:116 990 + #: src/view/shell/desktop/LeftNav.tsx:297 991 + #: src/view/shell/Drawer.tsx:379 992 + #: src/view/shell/Drawer.tsx:380 993 993 msgid "Home" 994 994 msgstr "होम फीड" 995 995 ··· 1055 1055 msgid "Invite code not accepted. Check that you input it correctly and try again." 1056 1056 msgstr "" 1057 1057 1058 - #: src/view/shell/Drawer.tsx:629 1058 + #: src/view/shell/Drawer.tsx:621 1059 1059 msgid "Invite codes: {invitesAvailable} available" 1060 1060 msgstr "" 1061 1061 ··· 1154 1154 msgstr "सूची का नाम" 1155 1155 1156 1156 #: src/view/screens/Profile.tsx:165 1157 - #: src/view/shell/desktop/LeftNav.tsx:381 1158 - #: src/view/shell/Drawer.tsx:479 1159 - #: src/view/shell/Drawer.tsx:480 1157 + #: src/view/shell/desktop/LeftNav.tsx:373 1158 + #: src/view/shell/Drawer.tsx:471 1159 + #: src/view/shell/Drawer.tsx:472 1160 1160 msgid "Lists" 1161 1161 msgstr "सूची" 1162 1162 ··· 1165 1165 msgid "Load more posts" 1166 1166 msgstr "अधिक पोस्ट लोड करें" 1167 1167 1168 - #: src/view/screens/Notifications.tsx:130 1168 + #: src/view/screens/Notifications.tsx:141 1169 1169 msgid "Load new notifications" 1170 1170 msgstr "नई सूचनाएं लोड करें" 1171 1171 ··· 1216 1216 1217 1217 #: src/view/screens/Moderation.tsx:63 1218 1218 #: src/view/screens/Settings.tsx:563 1219 - #: src/view/shell/desktop/LeftNav.tsx:399 1220 - #: src/view/shell/Drawer.tsx:498 1221 - #: src/view/shell/Drawer.tsx:499 1219 + #: src/view/shell/desktop/LeftNav.tsx:391 1220 + #: src/view/shell/Drawer.tsx:490 1221 + #: src/view/shell/Drawer.tsx:491 1222 1222 msgid "Moderation" 1223 1223 msgstr "मॉडरेशन" 1224 1224 ··· 1292 1292 msgid "My Feeds" 1293 1293 msgstr "मेरी फ़ीड" 1294 1294 1295 - #: src/view/shell/desktop/LeftNav.tsx:67 1295 + #: src/view/shell/desktop/LeftNav.tsx:64 1296 1296 msgid "My Profile" 1297 1297 msgstr "मेरी प्रोफाइल" 1298 1298 ··· 1320 1320 #: src/view/screens/ProfileFeed.tsx:451 1321 1321 #: src/view/screens/ProfileList.tsx:212 1322 1322 #: src/view/screens/ProfileList.tsx:244 1323 - #: src/view/shell/desktop/LeftNav.tsx:254 1323 + #: src/view/shell/desktop/LeftNav.tsx:246 1324 1324 msgid "New post" 1325 1325 msgstr "नई पोस्ट" 1326 1326 1327 - #: src/view/shell/desktop/LeftNav.tsx:264 1327 + #: src/view/shell/desktop/LeftNav.tsx:256 1328 1328 msgid "New Post" 1329 1329 msgstr "नई पोस्ट" 1330 1330 ··· 1394 1394 #~ msgid "Note: Third-party apps that display Bluesky content may not respect this setting." 1395 1395 #~ msgstr "" 1396 1396 1397 - #: src/view/screens/Notifications.tsx:97 1398 - #: src/view/screens/Notifications.tsx:121 1399 - #: src/view/shell/bottom-bar/BottomBar.tsx:195 1400 - #: src/view/shell/desktop/LeftNav.tsx:363 1401 - #: src/view/shell/Drawer.tsx:424 1402 - #: src/view/shell/Drawer.tsx:425 1397 + #: src/view/screens/Notifications.tsx:108 1398 + #: src/view/screens/Notifications.tsx:132 1399 + #: src/view/shell/bottom-bar/BottomBar.tsx:187 1400 + #: src/view/shell/desktop/LeftNav.tsx:355 1401 + #: src/view/shell/Drawer.tsx:416 1402 + #: src/view/shell/Drawer.tsx:417 1403 1403 msgid "Notifications" 1404 1404 msgstr "सूचनाएं" 1405 1405 ··· 1424 1424 msgstr "भाषा सेटिंग्स खोलें" 1425 1425 1426 1426 #: src/view/shell/desktop/RightNav.tsx:146 1427 - #: src/view/shell/Drawer.tsx:630 1427 + #: src/view/shell/Drawer.tsx:622 1428 1428 msgid "Opens list of invite codes" 1429 1429 msgstr "" 1430 1430 ··· 1584 1584 msgid "Processing..." 1585 1585 msgstr "प्रसंस्करण..." 1586 1586 1587 - #: src/view/shell/bottom-bar/BottomBar.tsx:237 1588 - #: src/view/shell/Drawer.tsx:72 1589 - #: src/view/shell/Drawer.tsx:533 1590 - #: src/view/shell/Drawer.tsx:534 1587 + #: src/view/shell/bottom-bar/BottomBar.tsx:229 1588 + #: src/view/shell/Drawer.tsx:69 1589 + #: src/view/shell/Drawer.tsx:525 1590 + #: src/view/shell/Drawer.tsx:526 1591 1591 msgid "Profile" 1592 1592 msgstr "प्रोफ़ाइल" 1593 1593 ··· 1805 1805 #: src/view/com/util/forms/SearchInput.tsx:64 1806 1806 #: src/view/screens/Search/Search.tsx:381 1807 1807 #: src/view/screens/Search/Search.tsx:533 1808 - #: src/view/shell/bottom-bar/BottomBar.tsx:146 1809 - #: src/view/shell/desktop/LeftNav.tsx:323 1808 + #: src/view/shell/bottom-bar/BottomBar.tsx:138 1809 + #: src/view/shell/desktop/LeftNav.tsx:315 1810 1810 #: src/view/shell/desktop/Search.tsx:161 1811 1811 #: src/view/shell/desktop/Search.tsx:170 1812 - #: src/view/shell/Drawer.tsx:351 1813 - #: src/view/shell/Drawer.tsx:352 1812 + #: src/view/shell/Drawer.tsx:343 1813 + #: src/view/shell/Drawer.tsx:344 1814 1814 msgid "Search" 1815 1815 msgstr "खोज" 1816 1816 ··· 1862 1862 msgid "Send Email" 1863 1863 msgstr "ईमेल भेजें" 1864 1864 1865 - #: src/view/shell/Drawer.tsx:284 1866 - #: src/view/shell/Drawer.tsx:305 1865 + #: src/view/shell/Drawer.tsx:276 1866 + #: src/view/shell/Drawer.tsx:297 1867 1867 msgid "Send feedback" 1868 1868 msgstr "प्रतिक्रिया भेजें" 1869 1869 ··· 1896 1896 msgstr "इस सेटिंग को अपने निम्नलिखित फ़ीड में अपने सहेजे गए फ़ीड के नमूने दिखाने के लिए \"हाँ\" पर सेट करें। यह एक प्रयोगात्मक विशेषता है।।" 1897 1897 1898 1898 #: src/view/screens/Settings.tsx:277 1899 - #: src/view/shell/desktop/LeftNav.tsx:435 1900 - #: src/view/shell/Drawer.tsx:554 1901 - #: src/view/shell/Drawer.tsx:555 1899 + #: src/view/shell/desktop/LeftNav.tsx:427 1900 + #: src/view/shell/Drawer.tsx:546 1901 + #: src/view/shell/Drawer.tsx:547 1902 1902 msgid "Settings" 1903 1903 msgstr "सेटिंग्स" 1904 1904 ··· 2439 2439 2440 2440 #: src/view/screens/Settings.tsx:402 2441 2441 #: src/view/shell/desktop/RightNav.tsx:127 2442 - #: src/view/shell/Drawer.tsx:644 2442 + #: src/view/shell/Drawer.tsx:636 2443 2443 msgid "Your invite codes are hidden when logged in using an App Password" 2444 2444 msgstr "" 2445 2445
+40 -11
src/state/queries/notifications/feed.ts
··· 16 16 * 3. Don't call this query's `refetch()` if you're trying to sync latest; call `checkUnread()` instead. 17 17 */ 18 18 19 - import {useEffect} from 'react' 19 + import {useEffect, useRef} from 'react' 20 20 import {AppBskyFeedDefs} from '@atproto/api' 21 21 import { 22 22 useInfiniteQuery, ··· 49 49 const threadMutes = useMutedThreads() 50 50 const unreads = useUnreadNotificationsApi() 51 51 const enabled = opts?.enabled !== false 52 + // state tracked across page fetches 53 + const pageState = useRef({pageNum: 0, hasMarkedRead: false}) 52 54 53 55 const query = useInfiniteQuery< 54 56 FeedPage, ··· 60 62 staleTime: STALE.INFINITY, 61 63 queryKey: RQKEY(), 62 64 async queryFn({pageParam}: {pageParam: RQPageParam}) { 63 - let page = await fetchPage({ 64 - limit: PAGE_SIZE, 65 - cursor: pageParam, 66 - queryClient, 67 - moderationOpts, 68 - threadMutes, 69 - }) 65 + let page 66 + if (!pageParam) { 67 + // for the first page, we check the cached page held by the unread-checker first 68 + page = unreads.getCachedUnreadPage() 69 + // reset the page state 70 + pageState.current = {pageNum: 0, hasMarkedRead: false} 71 + } 72 + if (!page) { 73 + page = await fetchPage({ 74 + limit: PAGE_SIZE, 75 + cursor: pageParam, 76 + queryClient, 77 + moderationOpts, 78 + threadMutes, 79 + }) 80 + } 70 81 71 - // if the first page has an unread, mark all read 72 - if (!pageParam && page.items[0] && !page.items[0].notification.isRead) { 73 - unreads.markAllRead() 82 + // NOTE 83 + // this section checks to see if we need to mark notifs read 84 + // we want to wait until we've seen a read notification because 85 + // of a timing challenge; marking read on the first page would 86 + // cause subsequent pages of unread notifs to incorrectly come 87 + // back as "read". we use page 6 as an abort condition, which means 88 + // after ~180 notifs we give up on tracking unread state correctly 89 + // -prf 90 + if (!pageState.current.hasMarkedRead) { 91 + let hasMarkedRead = false 92 + if ( 93 + pageState.current.pageNum > 5 || 94 + page.items.some(item => item.notification.isRead) 95 + ) { 96 + unreads.markAllRead() 97 + hasMarkedRead = true 98 + } 99 + pageState.current = { 100 + pageNum: pageState.current.pageNum + 1, 101 + hasMarkedRead, 102 + } 74 103 } 75 104 76 105 return page
+4 -1
src/state/queries/notifications/types.ts
··· 28 28 } 29 29 30 30 export interface CachedFeedPage { 31 - sessDid: string // used to invalidate on session changes 31 + /** 32 + * if true, the cached page is recent enough to use as the response 33 + */ 34 + usableInFeed: boolean 32 35 syncedAt: Date 33 36 data: FeedPage | undefined 34 37 }
+8 -9
src/state/queries/notifications/unread.tsx
··· 37 37 }) 38 38 39 39 export function Provider({children}: React.PropsWithChildren<{}>) { 40 - const {hasSession, currentAccount} = useSession() 40 + const {hasSession} = useSession() 41 41 const queryClient = useQueryClient() 42 42 const moderationOpts = useModerationOpts() 43 43 const threadMutes = useMutedThreads() ··· 46 46 47 47 const checkUnreadRef = React.useRef<ApiContext['checkUnread'] | null>(null) 48 48 const cacheRef = React.useRef<CachedFeedPage>({ 49 - sessDid: currentAccount?.did || '', 49 + usableInFeed: false, 50 50 syncedAt: new Date(), 51 51 data: undefined, 52 52 }) ··· 65 65 React.useEffect(() => { 66 66 const listener = ({data}: MessageEvent) => { 67 67 cacheRef.current = { 68 - sessDid: currentAccount?.did || '', 68 + usableInFeed: false, 69 69 syncedAt: new Date(), 70 70 data: undefined, 71 71 } ··· 75 75 return () => { 76 76 broadcast.removeEventListener('message', listener) 77 77 } 78 - }, [setNumUnread, currentAccount]) 78 + }, [setNumUnread]) 79 79 80 80 // create API 81 81 const api = React.useMemo<ApiContext>(() => { ··· 119 119 const lastIndexed = 120 120 page.items[0] && new Date(page.items[0].notification.indexedAt) 121 121 cacheRef.current = { 122 - sessDid: currentAccount?.did || '', 122 + usableInFeed: !!invalidate, // will be used immediately 123 123 data: page, 124 124 syncedAt: !lastIndexed || now > lastIndexed ? now : lastIndexed, 125 125 } ··· 136 136 }, 137 137 138 138 getCachedUnreadPage() { 139 - // return cached page if was for the current user 140 - // (protects against session changes serving data from the past session) 141 - if (cacheRef.current.sessDid === currentAccount?.did) { 139 + // return cached page if it's marked as fresh enough 140 + if (cacheRef.current.usableInFeed) { 142 141 return cacheRef.current.data 143 142 } 144 143 }, 145 144 } 146 - }, [setNumUnread, queryClient, moderationOpts, threadMutes, currentAccount]) 145 + }, [setNumUnread, queryClient, moderationOpts, threadMutes]) 147 146 checkUnreadRef.current = api.checkUnread 148 147 149 148 return (
+1 -2
src/state/queries/notifications/util.ts
··· 119 119 Math.abs(ts2 - ts) < MS_2DAY && 120 120 notif.reason === groupedNotif.notification.reason && 121 121 notif.reasonSubject === groupedNotif.notification.reasonSubject && 122 - notif.author.did !== groupedNotif.notification.author.did && 123 - notif.isRead === groupedNotif.notification.isRead 122 + notif.author.did !== groupedNotif.notification.author.did 124 123 ) { 125 124 groupedNotif.additional = groupedNotif.additional || [] 126 125 groupedNotif.additional.push(notif)
+14 -3
src/view/screens/Notifications.tsx
··· 37 37 const setMinimalShellMode = useSetMinimalShellMode() 38 38 const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll() 39 39 const scrollElRef = React.useRef<FlatList>(null) 40 + const checkLatestRef = React.useRef<() => void | null>() 40 41 const {screen} = useAnalytics() 41 42 const pal = usePalette('default') 42 43 const {isDesktop} = useWebMediaQueries() ··· 63 64 } 64 65 }, [scrollToTop, queryClient, unreadApi, hasNew]) 65 66 67 + const onFocusCheckLatest = React.useCallback(() => { 68 + // on focus, check for latest, but only invalidate if the user 69 + // isnt scrolled down to avoid moving content underneath them 70 + unreadApi.checkUnread({invalidate: !isScrolledDown}) 71 + }, [unreadApi, isScrolledDown]) 72 + checkLatestRef.current = onFocusCheckLatest 73 + 66 74 // on-visible setup 67 75 // = 68 76 useFocusEffect( 69 77 React.useCallback(() => { 70 78 setMinimalShellMode(false) 71 - logger.debug('NotificationsScreen: Updating feed') 79 + logger.debug('NotificationsScreen: Focus') 72 80 screen('Notifications') 73 - return listenSoftReset(onPressLoadLatest) 74 - }, [screen, onPressLoadLatest, setMinimalShellMode]), 81 + checkLatestRef.current?.() 82 + }, [screen, setMinimalShellMode]), 75 83 ) 84 + React.useEffect(() => { 85 + return listenSoftReset(onPressLoadLatest) 86 + }, [onPressLoadLatest]) 76 87 77 88 const ListHeaderComponent = React.useCallback(() => { 78 89 if (isDesktop) {
+1 -9
src/view/shell/Drawer.tsx
··· 14 14 FontAwesomeIcon, 15 15 FontAwesomeIconStyle, 16 16 } from '@fortawesome/react-native-fontawesome' 17 - import {useQueryClient} from '@tanstack/react-query' 18 17 import {s, colors} from 'lib/styles' 19 18 import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants' 20 19 import { ··· 51 50 import {useUnreadNotifications} from '#/state/queries/notifications/unread' 52 51 import {emitSoftReset} from '#/state/events' 53 52 import {useInviteCodesQuery} from '#/state/queries/invites' 54 - import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed' 55 53 import {NavSignupCard} from '#/view/shell/NavSignupCard' 56 - import {truncateAndInvalidate} from '#/state/queries/util' 57 54 58 55 let DrawerProfileCard = ({ 59 56 account, ··· 109 106 let DrawerContent = ({}: {}): React.ReactNode => { 110 107 const theme = useTheme() 111 108 const pal = usePalette('default') 112 - const queryClient = useQueryClient() 113 109 const setDrawerOpen = useSetDrawerOpen() 114 110 const navigation = useNavigation<NavigationProp>() 115 111 const {track} = useAnalytics() ··· 140 136 } else if (tabState === TabState.Inside) { 141 137 navigation.dispatch(StackActions.popToTop()) 142 138 } else { 143 - if (tab === 'Notifications') { 144 - // fetch new notifs on view 145 - truncateAndInvalidate(queryClient, NOTIFS_RQKEY()) 146 - } 147 139 // @ts-ignore must be Home, Search, Notifications, or MyProfile 148 140 navigation.navigate(`${tab}Tab`) 149 141 } 150 142 } 151 143 }, 152 - [track, navigation, setDrawerOpen, currentAccount, queryClient], 144 + [track, navigation, setDrawerOpen, currentAccount], 153 145 ) 154 146 155 147 const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
+1 -9
src/view/shell/bottom-bar/BottomBar.tsx
··· 1 1 import React, {ComponentProps} from 'react' 2 2 import {GestureResponderEvent, TouchableOpacity, View} from 'react-native' 3 3 import Animated from 'react-native-reanimated' 4 - import {useQueryClient} from '@tanstack/react-query' 5 4 import {StackActions} from '@react-navigation/native' 6 5 import {BottomTabBarProps} from '@react-navigation/bottom-tabs' 7 6 import {useSafeAreaInsets} from 'react-native-safe-area-context' ··· 31 30 import {emitSoftReset} from '#/state/events' 32 31 import {useSession} from '#/state/session' 33 32 import {useProfileQuery} from '#/state/queries/profile' 34 - import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed' 35 - import {truncateAndInvalidate} from '#/state/queries/util' 36 33 37 34 type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds' 38 35 ··· 41 38 const {hasSession, currentAccount} = useSession() 42 39 const pal = usePalette('default') 43 40 const {_} = useLingui() 44 - const queryClient = useQueryClient() 45 41 const safeAreaInsets = useSafeAreaInsets() 46 42 const {track} = useAnalytics() 47 43 const {footerHeight} = useShellLayout() ··· 61 57 } else if (tabState === TabState.Inside) { 62 58 navigation.dispatch(StackActions.popToTop()) 63 59 } else { 64 - if (tab === 'Notifications') { 65 - // fetch new notifs on view 66 - truncateAndInvalidate(queryClient, NOTIFS_RQKEY()) 67 - } 68 60 navigation.navigate(`${tab}Tab`) 69 61 } 70 62 }, 71 - [track, navigation, queryClient], 63 + [track, navigation], 72 64 ) 73 65 const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab]) 74 66 const onPressSearch = React.useCallback(
+1 -9
src/view/shell/desktop/LeftNav.tsx
··· 45 45 import {useComposerControls} from '#/state/shell/composer' 46 46 import {useFetchHandle} from '#/state/queries/handle' 47 47 import {emitSoftReset} from '#/state/events' 48 - import {useQueryClient} from '@tanstack/react-query' 49 - import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed' 50 48 import {NavSignupCard} from '#/view/shell/NavSignupCard' 51 - import {truncateAndInvalidate} from '#/state/queries/util' 52 49 53 50 function ProfileCard() { 54 51 const {currentAccount} = useSession() ··· 123 120 } 124 121 function NavItem({count, href, icon, iconFilled, label}: NavItemProps) { 125 122 const pal = usePalette('default') 126 - const queryClient = useQueryClient() 127 123 const {currentAccount} = useSession() 128 124 const {isDesktop, isTablet} = useWebMediaQueries() 129 125 const [pathName] = React.useMemo(() => router.matchPath(href), [href]) ··· 149 145 if (isCurrent) { 150 146 emitSoftReset() 151 147 } else { 152 - if (href === '/notifications') { 153 - // fetch new notifs on view 154 - truncateAndInvalidate(queryClient, NOTIFS_RQKEY()) 155 - } 156 148 onPress() 157 149 } 158 150 }, 159 - [onPress, isCurrent, queryClient, href], 151 + [onPress, isCurrent], 160 152 ) 161 153 162 154 return (