about summary refs log tree commit diff
path: root/src/view/screens
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/screens')
-rw-r--r--src/view/screens/Feeds.tsx1
-rw-r--r--src/view/screens/Notifications.tsx22
-rw-r--r--src/view/screens/Profile.tsx2
-rw-r--r--src/view/screens/ProfileFeed.tsx2
-rw-r--r--src/view/screens/ProfileList.tsx4
-rw-r--r--src/view/screens/SavedFeeds.tsx133
-rw-r--r--src/view/screens/Settings.tsx18
7 files changed, 115 insertions, 67 deletions
diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx
index ced8592c5..f319fbc39 100644
--- a/src/view/screens/Feeds.tsx
+++ b/src/view/screens/Feeds.tsx
@@ -437,6 +437,7 @@ export function FeedsScreen(_props: Props) {
             showSaveBtn={hasSession}
             showDescription
             showLikes
+            pinOnSave
           />
         )
       } else if (item.type === 'popularFeedsNoResults') {
diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx
index 8516d1667..0f442038b 100644
--- a/src/view/screens/Notifications.tsx
+++ b/src/view/screens/Notifications.tsx
@@ -19,7 +19,10 @@ import {logger} from '#/logger'
 import {useSetMinimalShellMode} from '#/state/shell'
 import {Trans, msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
-import {useUnreadNotifications} from '#/state/queries/notifications/unread'
+import {
+  useUnreadNotifications,
+  useUnreadNotificationsApi,
+} from '#/state/queries/notifications/unread'
 import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
 import {listenSoftReset, emitSoftReset} from '#/state/events'
 
@@ -35,8 +38,9 @@ export function NotificationsScreen({}: Props) {
   const {screen} = useAnalytics()
   const pal = usePalette('default')
   const {isDesktop} = useWebMediaQueries()
-  const unreadNotifs = useUnreadNotifications()
   const queryClient = useQueryClient()
+  const unreadNotifs = useUnreadNotifications()
+  const unreadApi = useUnreadNotificationsApi()
   const hasNew = !!unreadNotifs
 
   // event handlers
@@ -48,10 +52,16 @@ export function NotificationsScreen({}: Props) {
 
   const onPressLoadLatest = React.useCallback(() => {
     scrollToTop()
-    queryClient.invalidateQueries({
-      queryKey: NOTIFS_RQKEY(),
-    })
-  }, [scrollToTop, queryClient])
+    if (hasNew) {
+      // render what we have now
+      queryClient.resetQueries({
+        queryKey: NOTIFS_RQKEY(),
+      })
+    } else {
+      // check with the server
+      unreadApi.checkUnread({invalidate: true})
+    }
+  }, [scrollToTop, queryClient, unreadApi, hasNew])
 
   // on-visible setup
   // =
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 35efe3a0c..3e9a59929 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -404,7 +404,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
 
     const onScrollToTop = React.useCallback(() => {
       scrollElRef.current?.scrollToOffset({offset: -headerHeight})
-      queryClient.invalidateQueries({queryKey: FEED_RQKEY(feed)})
+      queryClient.resetQueries({queryKey: FEED_RQKEY(feed)})
       setHasNew(false)
     }, [scrollElRef, headerHeight, queryClient, feed, setHasNew])
     React.useImperativeHandle(ref, () => ({
diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx
index 1471db9c6..e38543e6b 100644
--- a/src/view/screens/ProfileFeed.tsx
+++ b/src/view/screens/ProfileFeed.tsx
@@ -502,7 +502,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
 
     const onScrollToTop = useCallback(() => {
       scrollElRef.current?.scrollToOffset({offset: -headerHeight})
-      queryClient.invalidateQueries({queryKey: FEED_RQKEY(feed)})
+      queryClient.resetQueries({queryKey: FEED_RQKEY(feed)})
       setHasNew(false)
     }, [scrollElRef, headerHeight, queryClient, feed, setHasNew])
 
diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx
index cc6d85e6f..9be499561 100644
--- a/src/view/screens/ProfileList.tsx
+++ b/src/view/screens/ProfileList.tsx
@@ -127,7 +127,7 @@ function ProfileListScreenLoaded({
       list,
       onChange() {
         if (isCurateList) {
-          queryClient.invalidateQueries({
+          queryClient.resetQueries({
             // TODO(eric) should construct these strings with a fn too
             queryKey: FEED_RQKEY(`list|${list.uri}`),
           })
@@ -530,7 +530,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
 
     const onScrollToTop = useCallback(() => {
       scrollElRef.current?.scrollToOffset({offset: -headerHeight})
-      queryClient.invalidateQueries({queryKey: FEED_RQKEY(feed)})
+      queryClient.resetQueries({queryKey: FEED_RQKEY(feed)})
       setHasNew(false)
     }, [scrollElRef, headerHeight, queryClient, feed, setHasNew])
     React.useImperativeHandle(ref, () => ({
diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx
index ce668877b..858a58a3c 100644
--- a/src/view/screens/SavedFeeds.tsx
+++ b/src/view/screens/SavedFeeds.tsx
@@ -1,14 +1,7 @@
 import React from 'react'
-import {
-  StyleSheet,
-  View,
-  ActivityIndicator,
-  Pressable,
-  TouchableOpacity,
-} from 'react-native'
+import {StyleSheet, View, ActivityIndicator, Pressable} from 'react-native'
 import {useFocusEffect} from '@react-navigation/native'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
-import {useQueryClient} from '@tanstack/react-query'
 import {track} from '#/lib/analytics/analytics'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {usePalette} from 'lib/hooks/usePalette'
@@ -32,9 +25,8 @@ import {
   usePinFeedMutation,
   useUnpinFeedMutation,
   useSetSaveFeedsMutation,
-  preferencesQueryKey,
-  UsePreferencesQueryResponse,
 } from '#/state/queries/preferences'
+import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
 
 const HITSLOP_TOP = {
   top: 20,
@@ -57,6 +49,24 @@ export function SavedFeeds({}: Props) {
   const {screen} = useAnalytics()
   const setMinimalShellMode = useSetMinimalShellMode()
   const {data: preferences} = usePreferencesQuery()
+  const {
+    mutateAsync: setSavedFeeds,
+    variables: optimisticSavedFeedsResponse,
+    reset: resetSaveFeedsMutationState,
+    error: setSavedFeedsError,
+  } = useSetSaveFeedsMutation()
+
+  /*
+   * Use optimistic data if exists and no error, otherwise fallback to remote
+   * data
+   */
+  const currentFeeds =
+    optimisticSavedFeedsResponse && !setSavedFeedsError
+      ? optimisticSavedFeedsResponse
+      : preferences?.feeds || {saved: [], pinned: []}
+  const unpinned = currentFeeds.saved.filter(f => {
+    return !currentFeeds.pinned?.includes(f)
+  })
 
   useFocusEffect(
     React.useCallback(() => {
@@ -80,7 +90,7 @@ export function SavedFeeds({}: Props) {
           </Text>
         </View>
         {preferences?.feeds ? (
-          !preferences.feeds.pinned.length ? (
+          !currentFeeds.pinned.length ? (
             <View
               style={[
                 pal.border,
@@ -93,8 +103,15 @@ export function SavedFeeds({}: Props) {
               </Text>
             </View>
           ) : (
-            preferences?.feeds?.pinned?.map(uri => (
-              <ListItem key={uri} feedUri={uri} isPinned />
+            currentFeeds.pinned.map(uri => (
+              <ListItem
+                key={uri}
+                feedUri={uri}
+                isPinned
+                setSavedFeeds={setSavedFeeds}
+                resetSaveFeedsMutationState={resetSaveFeedsMutationState}
+                currentFeeds={currentFeeds}
+              />
             ))
           )
         ) : (
@@ -106,7 +123,7 @@ export function SavedFeeds({}: Props) {
           </Text>
         </View>
         {preferences?.feeds ? (
-          !preferences.feeds.unpinned.length ? (
+          !unpinned.length ? (
             <View
               style={[
                 pal.border,
@@ -119,8 +136,15 @@ export function SavedFeeds({}: Props) {
               </Text>
             </View>
           ) : (
-            preferences.feeds.unpinned.map(uri => (
-              <ListItem key={uri} feedUri={uri} isPinned={false} />
+            unpinned.map(uri => (
+              <ListItem
+                key={uri}
+                feedUri={uri}
+                isPinned={false}
+                setSavedFeeds={setSavedFeeds}
+                resetSaveFeedsMutationState={resetSaveFeedsMutationState}
+                currentFeeds={currentFeeds}
+              />
             ))
           )
         ) : (
@@ -151,22 +175,30 @@ export function SavedFeeds({}: Props) {
 function ListItem({
   feedUri,
   isPinned,
+  currentFeeds,
+  setSavedFeeds,
+  resetSaveFeedsMutationState,
 }: {
   feedUri: string // uri
   isPinned: boolean
+  currentFeeds: {saved: string[]; pinned: string[]}
+  setSavedFeeds: ReturnType<typeof useSetSaveFeedsMutation>['mutateAsync']
+  resetSaveFeedsMutationState: ReturnType<
+    typeof useSetSaveFeedsMutation
+  >['reset']
 }) {
   const pal = usePalette('default')
-  const queryClient = useQueryClient()
   const {isPending: isPinPending, mutateAsync: pinFeed} = usePinFeedMutation()
   const {isPending: isUnpinPending, mutateAsync: unpinFeed} =
     useUnpinFeedMutation()
-  const {isPending: isMovePending, mutateAsync: setSavedFeeds} =
-    useSetSaveFeedsMutation()
+  const isPending = isPinPending || isUnpinPending
 
   const onTogglePinned = React.useCallback(async () => {
     Haptics.default()
 
     try {
+      resetSaveFeedsMutationState()
+
       if (isPinned) {
         await unpinFeed({uri: feedUri})
       } else {
@@ -176,24 +208,20 @@ function ListItem({
       Toast.show('There was an issue contacting the server')
       logger.error('Failed to toggle pinned feed', {error: e})
     }
-  }, [feedUri, isPinned, pinFeed, unpinFeed])
+  }, [feedUri, isPinned, pinFeed, unpinFeed, resetSaveFeedsMutationState])
 
   const onPressUp = React.useCallback(async () => {
     if (!isPinned) return
 
-    const feeds =
-      queryClient.getQueryData<UsePreferencesQueryResponse>(
-        preferencesQueryKey,
-      )?.feeds
     // create new array, do not mutate
-    const pinned = feeds?.pinned ? [...feeds.pinned] : []
+    const pinned = [...currentFeeds.pinned]
     const index = pinned.indexOf(feedUri)
 
     if (index === -1 || index === 0) return
     ;[pinned[index], pinned[index - 1]] = [pinned[index - 1], pinned[index]]
 
     try {
-      await setSavedFeeds({saved: feeds?.saved ?? [], pinned})
+      await setSavedFeeds({saved: currentFeeds.saved, pinned})
       track('CustomFeed:Reorder', {
         uri: feedUri,
         index: pinned.indexOf(feedUri),
@@ -202,24 +230,19 @@ function ListItem({
       Toast.show('There was an issue contacting the server')
       logger.error('Failed to set pinned feed order', {error: e})
     }
-  }, [feedUri, isPinned, queryClient, setSavedFeeds])
+  }, [feedUri, isPinned, setSavedFeeds, currentFeeds])
 
   const onPressDown = React.useCallback(async () => {
     if (!isPinned) return
 
-    const feeds =
-      queryClient.getQueryData<UsePreferencesQueryResponse>(
-        preferencesQueryKey,
-      )?.feeds
-    // create new array, do not mutate
-    const pinned = feeds?.pinned ? [...feeds.pinned] : []
+    const pinned = [...currentFeeds.pinned]
     const index = pinned.indexOf(feedUri)
 
     if (index === -1 || index >= pinned.length - 1) return
     ;[pinned[index], pinned[index + 1]] = [pinned[index + 1], pinned[index]]
 
     try {
-      await setSavedFeeds({saved: feeds?.saved ?? [], pinned})
+      await setSavedFeeds({saved: currentFeeds.saved, pinned})
       track('CustomFeed:Reorder', {
         uri: feedUri,
         index: pinned.indexOf(feedUri),
@@ -228,7 +251,7 @@ function ListItem({
       Toast.show('There was an issue contacting the server')
       logger.error('Failed to set pinned feed order', {error: e})
     }
-  }, [feedUri, isPinned, queryClient, setSavedFeeds])
+  }, [feedUri, isPinned, setSavedFeeds, currentFeeds])
 
   return (
     <Pressable
@@ -236,24 +259,30 @@ function ListItem({
       style={[styles.itemContainer, pal.border]}>
       {isPinned ? (
         <View style={styles.webArrowButtonsContainer}>
-          <TouchableOpacity
-            disabled={isMovePending}
+          <Pressable
+            disabled={isPending}
             accessibilityRole="button"
             onPress={onPressUp}
-            hitSlop={HITSLOP_TOP}>
+            hitSlop={HITSLOP_TOP}
+            style={state => ({
+              opacity: state.hovered || state.focused || isPending ? 0.5 : 1,
+            })}>
             <FontAwesomeIcon
               icon="arrow-up"
               size={12}
               style={[pal.text, styles.webArrowUpButton]}
             />
-          </TouchableOpacity>
-          <TouchableOpacity
-            disabled={isMovePending}
+          </Pressable>
+          <Pressable
+            disabled={isPending}
             accessibilityRole="button"
             onPress={onPressDown}
-            hitSlop={HITSLOP_BOTTOM}>
+            hitSlop={HITSLOP_BOTTOM}
+            style={state => ({
+              opacity: state.hovered || state.focused || isPending ? 0.5 : 1,
+            })}>
             <FontAwesomeIcon icon="arrow-down" size={12} style={[pal.text]} />
-          </TouchableOpacity>
+          </Pressable>
         </View>
       ) : null}
       <FeedSourceCard
@@ -261,18 +290,28 @@ function ListItem({
         feedUri={feedUri}
         style={styles.noBorder}
         showSaveBtn
+        LoadingComponent={
+          <FeedLoadingPlaceholder
+            style={{flex: 1}}
+            showLowerPlaceholder={false}
+            showTopBorder={false}
+          />
+        }
       />
-      <TouchableOpacity
-        disabled={isPinPending || isUnpinPending}
+      <Pressable
+        disabled={isPending}
         accessibilityRole="button"
         hitSlop={10}
-        onPress={onTogglePinned}>
+        onPress={onTogglePinned}
+        style={state => ({
+          opacity: state.hovered || state.focused || isPending ? 0.5 : 1,
+        })}>
         <FontAwesomeIcon
           icon="thumb-tack"
           size={20}
           color={isPinned ? colors.blue3 : pal.colors.icon}
         />
-      </TouchableOpacity>
+      </Pressable>
     </Pressable>
   )
 }
diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx
index 88cc2d532..579a04b01 100644
--- a/src/view/screens/Settings.tsx
+++ b/src/view/screens/Settings.tsx
@@ -10,11 +10,7 @@ import {
   View,
   ViewStyle,
 } from 'react-native'
-import {
-  useFocusEffect,
-  useNavigation,
-  StackActions,
-} from '@react-navigation/native'
+import {useFocusEffect, useNavigation} from '@react-navigation/native'
 import {
   FontAwesomeIcon,
   FontAwesomeIconStyle,
@@ -74,6 +70,8 @@ import {STATUS_PAGE_URL} from 'lib/constants'
 import {Trans, msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useQueryClient} from '@tanstack/react-query'
+import {useLoggedOutViewControls} from '#/state/shell/logged-out'
+import {useCloseAllActiveElements} from '#/state/util'
 
 function SettingsAccountCard({account}: {account: SessionAccount}) {
   const pal = usePalette('default')
@@ -155,13 +153,14 @@ export function SettingsScreen({}: Props) {
   const {screen, track} = useAnalytics()
   const {openModal} = useModalControls()
   const {isSwitchingAccounts, accounts, currentAccount} = useSession()
-  const {clearCurrentAccount} = useSessionApi()
   const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting(
     getAgent(),
   )
   const {mutate: clearPreferences} = useClearPreferencesMutation()
   const {data: invites} = useInviteCodesQuery()
   const invitesAvailable = invites?.available?.length ?? 0
+  const {setShowLoggedOut} = useLoggedOutViewControls()
+  const closeAllActiveElements = useCloseAllActiveElements()
 
   const primaryBg = useCustomPalette<ViewStyle>({
     light: {backgroundColor: colors.blue0},
@@ -190,10 +189,9 @@ export function SettingsScreen({}: Props) {
 
   const onPressAddAccount = React.useCallback(() => {
     track('Settings:AddAccountButtonClicked')
-    navigation.navigate('HomeTab')
-    navigation.dispatch(StackActions.popToTop())
-    clearCurrentAccount()
-  }, [track, navigation, clearCurrentAccount])
+    setShowLoggedOut(true)
+    closeAllActiveElements()
+  }, [track, setShowLoggedOut, closeAllActiveElements])
 
   const onPressChangeHandle = React.useCallback(() => {
     track('Settings:ChangeHandleButtonClicked')