diff options
Diffstat (limited to 'src/view/screens')
-rw-r--r-- | src/view/screens/Feeds.tsx | 1 | ||||
-rw-r--r-- | src/view/screens/Notifications.tsx | 22 | ||||
-rw-r--r-- | src/view/screens/Profile.tsx | 2 | ||||
-rw-r--r-- | src/view/screens/ProfileFeed.tsx | 2 | ||||
-rw-r--r-- | src/view/screens/ProfileList.tsx | 4 | ||||
-rw-r--r-- | src/view/screens/SavedFeeds.tsx | 133 | ||||
-rw-r--r-- | src/view/screens/Settings.tsx | 18 |
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') |