about summary refs log tree commit diff
path: root/src/view/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com')
-rw-r--r--src/view/com/auth/login/ChooseAccountForm.tsx53
-rw-r--r--src/view/com/feeds/FeedPage.tsx4
-rw-r--r--src/view/com/feeds/FeedSourceCard.tsx11
-rw-r--r--src/view/com/notifications/Feed.tsx31
-rw-r--r--src/view/com/pager/FeedsTabBar.web.tsx3
-rw-r--r--src/view/com/pager/FeedsTabBarMobile.tsx2
-rw-r--r--src/view/com/posts/Feed.tsx8
-rw-r--r--src/view/com/util/Link.tsx41
-rw-r--r--src/view/com/util/LoadingPlaceholder.tsx28
9 files changed, 121 insertions, 60 deletions
diff --git a/src/view/com/auth/login/ChooseAccountForm.tsx b/src/view/com/auth/login/ChooseAccountForm.tsx
index 8c94ef2da..73ddfc9d6 100644
--- a/src/view/com/auth/login/ChooseAccountForm.tsx
+++ b/src/view/com/auth/login/ChooseAccountForm.tsx
@@ -1,23 +1,30 @@
 import React from 'react'
 import {ScrollView, TouchableOpacity, View} from 'react-native'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {Text} from '../../util/text/Text'
 import {UserAvatar} from '../../util/UserAvatar'
-import {s} from 'lib/styles'
+import {s, colors} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
 import {Trans, msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {styles} from './styles'
 import {useSession, useSessionApi, SessionAccount} from '#/state/session'
 import {useProfileQuery} from '#/state/queries/profile'
+import {useLoggedOutViewControls} from '#/state/shell/logged-out'
+import * as Toast from '#/view/com/util/Toast'
 
 function AccountItem({
   account,
   onSelect,
+  isCurrentAccount,
 }: {
   account: SessionAccount
   onSelect: (account: SessionAccount) => void
+  isCurrentAccount: boolean
 }) {
   const pal = usePalette('default')
   const {_} = useLingui()
@@ -48,11 +55,19 @@ function AccountItem({
             {account.handle}
           </Text>
         </Text>
-        <FontAwesomeIcon
-          icon="angle-right"
-          size={16}
-          style={[pal.text, s.mr10]}
-        />
+        {isCurrentAccount ? (
+          <FontAwesomeIcon
+            icon="check"
+            size={16}
+            style={[{color: colors.green3} as FontAwesomeIconStyle, s.mr10]}
+          />
+        ) : (
+          <FontAwesomeIcon
+            icon="angle-right"
+            size={16}
+            style={[pal.text, s.mr10]}
+          />
+        )}
       </View>
     </TouchableOpacity>
   )
@@ -67,8 +82,9 @@ export const ChooseAccountForm = ({
   const {track, screen} = useAnalytics()
   const pal = usePalette('default')
   const {_} = useLingui()
-  const {accounts} = useSession()
+  const {accounts, currentAccount} = useSession()
   const {initSession} = useSessionApi()
+  const {setShowLoggedOut} = useLoggedOutViewControls()
 
   React.useEffect(() => {
     screen('Choose Account')
@@ -77,13 +93,21 @@ export const ChooseAccountForm = ({
   const onSelect = React.useCallback(
     async (account: SessionAccount) => {
       if (account.accessJwt) {
-        await initSession(account)
-        track('Sign In', {resumedSession: true})
+        if (account.did === currentAccount?.did) {
+          setShowLoggedOut(false)
+          Toast.show(`Already signed in as @${account.handle}`)
+        } else {
+          await initSession(account)
+          track('Sign In', {resumedSession: true})
+          setTimeout(() => {
+            Toast.show(`Signed in as @${account.handle}`)
+          }, 100)
+        }
       } else {
         onSelectAccount(account)
       }
     },
-    [track, initSession, onSelectAccount],
+    [currentAccount, track, initSession, onSelectAccount, setShowLoggedOut],
   )
 
   return (
@@ -94,7 +118,12 @@ export const ChooseAccountForm = ({
         <Trans>Sign in as...</Trans>
       </Text>
       {accounts.map(account => (
-        <AccountItem key={account.did} account={account} onSelect={onSelect} />
+        <AccountItem
+          key={account.did}
+          account={account}
+          onSelect={onSelect}
+          isCurrentAccount={account.did === currentAccount?.did}
+        />
       ))}
       <TouchableOpacity
         testID="chooseNewAccountBtn"
diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx
index 885cd2a15..1a32d29c8 100644
--- a/src/view/com/feeds/FeedPage.tsx
+++ b/src/view/com/feeds/FeedPage.tsx
@@ -62,7 +62,7 @@ export function FeedPage({
   const onSoftReset = React.useCallback(() => {
     if (isPageFocused) {
       scrollToTop()
-      queryClient.invalidateQueries({queryKey: FEED_RQKEY(feed)})
+      queryClient.resetQueries({queryKey: FEED_RQKEY(feed)})
       setHasNew(false)
     }
   }, [isPageFocused, scrollToTop, queryClient, feed, setHasNew])
@@ -83,7 +83,7 @@ export function FeedPage({
 
   const onPressLoadLatest = React.useCallback(() => {
     scrollToTop()
-    queryClient.invalidateQueries({queryKey: FEED_RQKEY(feed)})
+    queryClient.resetQueries({queryKey: FEED_RQKEY(feed)})
     setHasNew(false)
   }, [scrollToTop, feed, queryClient, setHasNew])
 
diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx
index 64871ca6d..1f2af069b 100644
--- a/src/view/com/feeds/FeedSourceCard.tsx
+++ b/src/view/com/feeds/FeedSourceCard.tsx
@@ -24,6 +24,7 @@ import {
   useRemoveFeedMutation,
 } from '#/state/queries/preferences'
 import {useFeedSourceInfoQuery, FeedSourceInfo} from '#/state/queries/feed'
+import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
 
 export function FeedSourceCard({
   feedUri,
@@ -31,6 +32,7 @@ export function FeedSourceCard({
   showSaveBtn = false,
   showDescription = false,
   showLikes = false,
+  LoadingComponent,
   pinOnSave = false,
 }: {
   feedUri: string
@@ -38,12 +40,19 @@ export function FeedSourceCard({
   showSaveBtn?: boolean
   showDescription?: boolean
   showLikes?: boolean
+  LoadingComponent?: JSX.Element
   pinOnSave?: boolean
 }) {
   const {data: preferences} = usePreferencesQuery()
   const {data: feed} = useFeedSourceInfoQuery({uri: feedUri})
 
-  if (!feed || !preferences) return null
+  if (!feed || !preferences) {
+    return LoadingComponent ? (
+      LoadingComponent
+    ) : (
+      <FeedLoadingPlaceholder style={{flex: 1}} />
+    )
+  }
 
   return (
     <FeedSourceCardLoaded
diff --git a/src/view/com/notifications/Feed.tsx b/src/view/com/notifications/Feed.tsx
index ba88f78c0..c496d5f7c 100644
--- a/src/view/com/notifications/Feed.tsx
+++ b/src/view/com/notifications/Feed.tsx
@@ -35,15 +35,13 @@ export function Feed({
   const [isPTRing, setIsPTRing] = React.useState(false)
 
   const moderationOpts = useModerationOpts()
-  const {markAllRead} = useUnreadNotificationsApi()
+  const {markAllRead, checkUnread} = useUnreadNotificationsApi()
   const {
     data,
-    isLoading,
     isFetching,
     isFetched,
     isError,
     error,
-    refetch,
     hasNextPage,
     isFetchingNextPage,
     fetchNextPage,
@@ -52,13 +50,11 @@ export function Feed({
   const firstItem = data?.pages[0]?.items[0]
 
   // mark all read on fresh data
+  // (this will fire each time firstItem changes)
   React.useEffect(() => {
-    let cleanup
     if (firstItem) {
-      const to = setTimeout(() => markAllRead(), 250)
-      cleanup = () => clearTimeout(to)
+      markAllRead()
     }
-    return cleanup
   }, [firstItem, markAllRead])
 
   const items = React.useMemo(() => {
@@ -83,7 +79,7 @@ export function Feed({
   const onRefresh = React.useCallback(async () => {
     try {
       setIsPTRing(true)
-      await refetch()
+      await checkUnread({invalidate: true})
     } catch (err) {
       logger.error('Failed to refresh notifications feed', {
         error: err,
@@ -91,7 +87,7 @@ export function Feed({
     } finally {
       setIsPTRing(false)
     }
-  }, [refetch, setIsPTRing])
+  }, [checkUnread, setIsPTRing])
 
   const onEndReached = React.useCallback(async () => {
     if (isFetching || !hasNextPage || isError) return
@@ -136,21 +132,6 @@ export function Feed({
     [onPressRetryLoadMore, moderationOpts],
   )
 
-  const showHeaderSpinner = !isPTRing && isFetching && !isLoading
-  const FeedHeader = React.useCallback(
-    () => (
-      <View>
-        {ListHeaderComponent ? <ListHeaderComponent /> : null}
-        {showHeaderSpinner ? (
-          <View style={{padding: 10}}>
-            <ActivityIndicator />
-          </View>
-        ) : null}
-      </View>
-    ),
-    [ListHeaderComponent, showHeaderSpinner],
-  )
-
   const FeedFooter = React.useCallback(
     () =>
       isFetchingNextPage ? (
@@ -180,7 +161,7 @@ export function Feed({
         data={items}
         keyExtractor={item => item._reactKey}
         renderItem={renderItem}
-        ListHeaderComponent={FeedHeader}
+        ListHeaderComponent={ListHeaderComponent}
         ListFooterComponent={FeedFooter}
         refreshControl={
           <RefreshControl
diff --git a/src/view/com/pager/FeedsTabBar.web.tsx b/src/view/com/pager/FeedsTabBar.web.tsx
index a39499b24..fdb4df171 100644
--- a/src/view/com/pager/FeedsTabBar.web.tsx
+++ b/src/view/com/pager/FeedsTabBar.web.tsx
@@ -81,9 +81,10 @@ function FeedsTabBarTablet(
 ) {
   const feeds = usePinnedFeedsInfos()
   const pal = usePalette('default')
+  const {hasSession} = useSession()
   const {headerMinimalShellTransform} = useMinimalShellMode()
   const {headerHeight} = useShellLayout()
-  const items = feeds.map(f => f.displayName)
+  const items = hasSession ? feeds.map(f => f.displayName) : []
 
   return (
     // @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx
index 2983a4575..735aa1bac 100644
--- a/src/view/com/pager/FeedsTabBarMobile.tsx
+++ b/src/view/com/pager/FeedsTabBarMobile.tsx
@@ -30,7 +30,7 @@ export function FeedsTabBar(
   const brandBlue = useColorSchemeStyle(s.brandBlue, s.blue3)
   const {headerHeight} = useShellLayout()
   const {headerMinimalShellTransform} = useMinimalShellMode()
-  const items = feeds.map(f => f.displayName)
+  const items = hasSession ? feeds.map(f => f.displayName) : []
 
   const onPressAvi = React.useCallback(() => {
     setDrawerOpen(true)
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index fc6d77696..393c1bc91 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -23,6 +23,7 @@ import {
   FeedDescriptor,
   FeedParams,
   usePostFeedQuery,
+  pollLatest,
 } from '#/state/queries/post-feed'
 import {useModerationOpts} from '#/state/queries/preferences'
 
@@ -84,22 +85,21 @@ let Feed = ({
     hasNextPage,
     isFetchingNextPage,
     fetchNextPage,
-    pollLatest,
   } = usePostFeedQuery(feed, feedParams, opts)
   const isEmpty = !isFetching && !data?.pages[0]?.slices.length
 
   const checkForNew = React.useCallback(async () => {
-    if (!isFetched || isFetching || !onHasNew) {
+    if (!data?.pages[0] || isFetching || !onHasNew) {
       return
     }
     try {
-      if (await pollLatest()) {
+      if (await pollLatest(data.pages[0])) {
         onHasNew(true)
       }
     } catch (e) {
       logger.error('Poll latest failed', {feed, error: String(e)})
     }
-  }, [feed, isFetched, isFetching, pollLatest, onHasNew])
+  }, [feed, data, isFetching, onHasNew])
 
   React.useEffect(() => {
     // we store the interval handler in a ref to avoid needless
diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx
index 074ab2329..dcbec7cb4 100644
--- a/src/view/com/util/Link.tsx
+++ b/src/view/com/util/Link.tsx
@@ -46,6 +46,7 @@ interface Props extends ComponentProps<typeof TouchableOpacity> {
   noFeedback?: boolean
   asAnchor?: boolean
   anchorNoUnderline?: boolean
+  navigationAction?: 'push' | 'replace' | 'navigate'
 }
 
 export const Link = memo(function Link({
@@ -58,6 +59,7 @@ export const Link = memo(function Link({
   asAnchor,
   accessible,
   anchorNoUnderline,
+  navigationAction,
   ...props
 }: Props) {
   const {closeModal} = useModalControls()
@@ -67,10 +69,16 @@ export const Link = memo(function Link({
   const onPress = React.useCallback(
     (e?: Event) => {
       if (typeof href === 'string') {
-        return onPressInner(closeModal, navigation, sanitizeUrl(href), e)
+        return onPressInner(
+          closeModal,
+          navigation,
+          sanitizeUrl(href),
+          navigationAction,
+          e,
+        )
       }
     },
-    [closeModal, navigation, href],
+    [closeModal, navigation, navigationAction, href],
   )
 
   if (noFeedback) {
@@ -146,6 +154,7 @@ export const TextLink = memo(function TextLink({
   title,
   onPress,
   warnOnMismatchingLabel,
+  navigationAction,
   ...orgProps
 }: {
   testID?: string
@@ -158,6 +167,7 @@ export const TextLink = memo(function TextLink({
   dataSet?: any
   title?: string
   warnOnMismatchingLabel?: boolean
+  navigationAction?: 'push' | 'replace' | 'navigate'
 } & TextProps) {
   const {...props} = useLinkProps({to: sanitizeUrl(href)})
   const navigation = useNavigation<NavigationProp>()
@@ -185,7 +195,13 @@ export const TextLink = memo(function TextLink({
         // @ts-ignore function signature differs by platform -prf
         return onPress()
       }
-      return onPressInner(closeModal, navigation, sanitizeUrl(href), e)
+      return onPressInner(
+        closeModal,
+        navigation,
+        sanitizeUrl(href),
+        navigationAction,
+        e,
+      )
     },
     [
       onPress,
@@ -195,6 +211,7 @@ export const TextLink = memo(function TextLink({
       href,
       text,
       warnOnMismatchingLabel,
+      navigationAction,
     ],
   )
   const hrefAttrs = useMemo(() => {
@@ -241,6 +258,7 @@ interface TextLinkOnWebOnlyProps extends TextProps {
   accessibilityLabel?: string
   accessibilityHint?: string
   title?: string
+  navigationAction?: 'push' | 'replace' | 'navigate'
 }
 export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({
   testID,
@@ -250,6 +268,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({
   text,
   numberOfLines,
   lineHeight,
+  navigationAction,
   ...props
 }: TextLinkOnWebOnlyProps) {
   if (isWeb) {
@@ -263,6 +282,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({
         numberOfLines={numberOfLines}
         lineHeight={lineHeight}
         title={props.title}
+        navigationAction={navigationAction}
         {...props}
       />
     )
@@ -296,6 +316,7 @@ function onPressInner(
   closeModal = () => {},
   navigation: NavigationProp,
   href: string,
+  navigationAction: 'push' | 'replace' | 'navigate' = 'push',
   e?: Event,
 ) {
   let shouldHandle = false
@@ -328,8 +349,18 @@ function onPressInner(
     } else {
       closeModal() // close any active modals
 
-      // @ts-ignore we're not able to type check on this one -prf
-      navigation.dispatch(StackActions.push(...router.matchPath(href)))
+      if (navigationAction === 'push') {
+        // @ts-ignore we're not able to type check on this one -prf
+        navigation.dispatch(StackActions.push(...router.matchPath(href)))
+      } else if (navigationAction === 'replace') {
+        // @ts-ignore we're not able to type check on this one -prf
+        navigation.dispatch(StackActions.replace(...router.matchPath(href)))
+      } else if (navigationAction === 'navigate') {
+        // @ts-ignore we're not able to type check on this one -prf
+        navigation.navigate(...router.matchPath(href))
+      } else {
+        throw Error('Unsupported navigator action.')
+      }
     }
   }
 }
diff --git a/src/view/com/util/LoadingPlaceholder.tsx b/src/view/com/util/LoadingPlaceholder.tsx
index 461cbcbe5..74e36ff7b 100644
--- a/src/view/com/util/LoadingPlaceholder.tsx
+++ b/src/view/com/util/LoadingPlaceholder.tsx
@@ -171,14 +171,22 @@ export function ProfileCardFeedLoadingPlaceholder() {
 
 export function FeedLoadingPlaceholder({
   style,
+  showLowerPlaceholder = true,
+  showTopBorder = true,
 }: {
   style?: StyleProp<ViewStyle>
+  showTopBorder?: boolean
+  showLowerPlaceholder?: boolean
 }) {
   const pal = usePalette('default')
   return (
     <View
       style={[
-        {paddingHorizontal: 12, paddingVertical: 18, borderTopWidth: 1},
+        {
+          paddingHorizontal: 12,
+          paddingVertical: 18,
+          borderTopWidth: showTopBorder ? 1 : 0,
+        },
         pal.border,
         style,
       ]}>
@@ -193,14 +201,16 @@ export function FeedLoadingPlaceholder({
           <LoadingPlaceholder width={120} height={8} />
         </View>
       </View>
-      <View style={{paddingHorizontal: 5}}>
-        <LoadingPlaceholder
-          width={260}
-          height={8}
-          style={{marginVertical: 12}}
-        />
-        <LoadingPlaceholder width={120} height={8} />
-      </View>
+      {showLowerPlaceholder && (
+        <View style={{paddingHorizontal: 5}}>
+          <LoadingPlaceholder
+            width={260}
+            height={8}
+            style={{marginVertical: 12}}
+          />
+          <LoadingPlaceholder width={120} height={8} />
+        </View>
+      )}
     </View>
   )
 }