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/pager/PagerWithHeader.tsx22
-rw-r--r--src/view/com/pager/PagerWithHeader.web.tsx34
-rw-r--r--src/view/com/profile/ProfileHeader.tsx123
-rw-r--r--src/view/com/util/UserAvatar.tsx5
4 files changed, 71 insertions, 113 deletions
diff --git a/src/view/com/pager/PagerWithHeader.tsx b/src/view/com/pager/PagerWithHeader.tsx
index 7e9ed24db..31abc1ab7 100644
--- a/src/view/com/pager/PagerWithHeader.tsx
+++ b/src/view/com/pager/PagerWithHeader.tsx
@@ -61,25 +61,21 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
     const headerHeight = headerOnlyHeight + tabBarHeight
 
     // capture the header bar sizing
-    const onTabBarLayout = React.useCallback(
-      (evt: LayoutChangeEvent) => {
-        const height = evt.nativeEvent.layout.height
-        if (height > 0) {
-          // The rounding is necessary to prevent jumps on iOS
-          setTabBarHeight(Math.round(height))
-        }
-      },
-      [setTabBarHeight],
-    )
-    const onHeaderOnlyLayout = React.useCallback(
+    const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => {
+      const height = evt.nativeEvent.layout.height
+      if (height > 0) {
+        // The rounding is necessary to prevent jumps on iOS
+        setTabBarHeight(Math.round(height))
+      }
+    })
+    const onHeaderOnlyLayout = useNonReactiveCallback(
       (evt: LayoutChangeEvent) => {
         const height = evt.nativeEvent.layout.height
-        if (height > 0) {
+        if (height > 0 && isHeaderReady) {
           // The rounding is necessary to prevent jumps on iOS
           setHeaderOnlyHeight(Math.round(height))
         }
       },
-      [setHeaderOnlyHeight],
     )
 
     const renderTabBar = React.useCallback(
diff --git a/src/view/com/pager/PagerWithHeader.web.tsx b/src/view/com/pager/PagerWithHeader.web.tsx
index 4f959d548..9c63c149f 100644
--- a/src/view/com/pager/PagerWithHeader.web.tsx
+++ b/src/view/com/pager/PagerWithHeader.web.tsx
@@ -31,6 +31,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
       children,
       testID,
       items,
+      isHeaderReady,
       renderHeader,
       initialPage,
       onPageSelected,
@@ -46,6 +47,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
           <PagerTabBar
             items={items}
             renderHeader={renderHeader}
+            isHeaderReady={isHeaderReady}
             currentPage={currentPage}
             onCurrentPageSelected={onCurrentPageSelected}
             onSelect={props.onSelect}
@@ -54,7 +56,14 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
           />
         )
       },
-      [items, renderHeader, currentPage, onCurrentPageSelected, testID],
+      [
+        items,
+        isHeaderReady,
+        renderHeader,
+        currentPage,
+        onCurrentPageSelected,
+        testID,
+      ],
     )
 
     const onPageSelectedInner = React.useCallback(
@@ -80,8 +89,14 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
         {toArray(children)
           .filter(Boolean)
           .map((child, i) => {
+            const isReady = isHeaderReady
             return (
-              <View key={i} collapsable={false}>
+              <View
+                key={i}
+                collapsable={false}
+                style={{
+                  display: isReady ? undefined : 'none',
+                }}>
                 <PagerItem isFocused={i === currentPage} renderTab={child} />
               </View>
             )
@@ -94,6 +109,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
 let PagerTabBar = ({
   currentPage,
   items,
+  isHeaderReady,
   testID,
   renderHeader,
   onCurrentPageSelected,
@@ -104,6 +120,7 @@ let PagerTabBar = ({
   items: string[]
   testID?: string
   renderHeader?: () => JSX.Element
+  isHeaderReady: boolean
   onCurrentPageSelected?: (index: number) => void
   onSelect?: (index: number) => void
   tabBarAnchor?: JSX.Element | null | undefined
@@ -112,7 +129,12 @@ let PagerTabBar = ({
   const {isMobile} = useWebMediaQueries()
   return (
     <>
-      <View style={[!isMobile && styles.headerContainerDesktop, pal.border]}>
+      <View
+        style={[
+          !isMobile && styles.headerContainerDesktop,
+          pal.border,
+          !isHeaderReady && styles.loadingHeader,
+        ]}>
         {renderHeader?.()}
       </View>
       {tabBarAnchor}
@@ -123,6 +145,9 @@ let PagerTabBar = ({
             ? styles.tabBarContainerMobile
             : styles.tabBarContainerDesktop,
           pal.border,
+          {
+            display: isHeaderReady ? undefined : 'none',
+          },
         ]}>
         <TabBar
           testID={testID}
@@ -183,6 +208,9 @@ const styles = StyleSheet.create({
     paddingLeft: 14,
     paddingRight: 14,
   },
+  loadingHeader: {
+    borderColor: 'transparent',
+  },
 })
 
 function toArray<T>(v: T | T[]): T[] {
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index 2e80ca808..8fd50fad6 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -51,76 +51,47 @@ import {sanitizeDisplayName} from 'lib/strings/display-names'
 import {shareUrl} from 'lib/sharing'
 import {s, colors} from 'lib/styles'
 import {logger} from '#/logger'
-import {useSession, getAgent} from '#/state/session'
+import {useSession} from '#/state/session'
 import {Shadow} from '#/state/cache/types'
 import {useRequireAuth} from '#/state/session'
 import {LabelInfo} from '../util/moderation/LabelInfo'
 import {useProfileShadow} from 'state/cache/profile-shadow'
 
-interface Props {
-  profile: AppBskyActorDefs.ProfileView | null
-  placeholderData?: AppBskyActorDefs.ProfileView | null
-  moderationOpts: ModerationOpts | null
-  hideBackButton?: boolean
-  isProfilePreview?: boolean
-}
-
-export function ProfileHeader({
-  profile,
-  moderationOpts,
-  hideBackButton = false,
-  isProfilePreview,
-}: Props) {
+let ProfileHeaderLoading = (_props: {}): React.ReactNode => {
   const pal = usePalette('default')
-
-  // loading
-  // =
-  if (!profile || !moderationOpts) {
-    return (
-      <View style={pal.view}>
-        <LoadingPlaceholder
-          width="100%"
-          height={150}
-          style={{borderRadius: 0}}
-        />
-        <View
-          style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}>
-          <LoadingPlaceholder width={80} height={80} style={styles.br40} />
-        </View>
-        <View style={styles.content}>
-          <View style={[styles.buttonsLine]}>
-            <LoadingPlaceholder width={167} height={31} style={styles.br50} />
-          </View>
+  return (
+    <View style={pal.view}>
+      <LoadingPlaceholder width="100%" height={150} style={{borderRadius: 0}} />
+      <View
+        style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}>
+        <LoadingPlaceholder width={80} height={80} style={styles.br40} />
+      </View>
+      <View style={styles.content}>
+        <View style={[styles.buttonsLine]}>
+          <LoadingPlaceholder width={167} height={31} style={styles.br50} />
         </View>
       </View>
-    )
-  }
-
-  // loaded
-  // =
-  return (
-    <ProfileHeaderLoaded
-      profile={profile}
-      moderationOpts={moderationOpts}
-      hideBackButton={hideBackButton}
-      isProfilePreview={isProfilePreview}
-    />
+    </View>
   )
 }
+ProfileHeaderLoading = memo(ProfileHeaderLoading)
+export {ProfileHeaderLoading}
 
-interface LoadedProps {
+interface Props {
   profile: AppBskyActorDefs.ProfileViewDetailed
+  descriptionRT: RichTextAPI | null
   moderationOpts: ModerationOpts
   hideBackButton?: boolean
-  isProfilePreview?: boolean
+  isPlaceholderProfile?: boolean
 }
 
-let ProfileHeaderLoaded = ({
+let ProfileHeader = ({
   profile: profileUnshadowed,
+  descriptionRT,
   moderationOpts,
   hideBackButton = false,
-  isProfilePreview,
-}: LoadedProps): React.ReactNode => {
+  isPlaceholderProfile,
+}: Props): React.ReactNode => {
   const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> =
     useProfileShadow(profileUnshadowed)
   const pal = usePalette('default')
@@ -144,37 +115,6 @@ let ProfileHeaderLoaded = ({
     [profile, moderationOpts],
   )
 
-  /*
-   * BEGIN handle bio facet resolution
-   */
-  // should be undefined on first render to trigger a resolution
-  const prevProfileDescription = React.useRef<string | undefined>()
-  const [descriptionRT, setDescriptionRT] = React.useState<
-    RichTextAPI | undefined
-  >(
-    profile.description
-      ? new RichTextAPI({text: profile.description})
-      : undefined,
-  )
-  React.useEffect(() => {
-    async function resolveRTFacets() {
-      // new each time
-      const rt = new RichTextAPI({text: profile.description || ''})
-      await rt.detectFacets(getAgent())
-      // replace existing RT instance
-      setDescriptionRT(rt)
-    }
-
-    if (profile.description !== prevProfileDescription.current) {
-      // update prev immediately
-      prevProfileDescription.current = profile.description
-      resolveRTFacets()
-    }
-  }, [profile.description, setDescriptionRT])
-  /*
-   * END handle bio facet resolution
-   */
-
   const invalidateProfileQuery = React.useCallback(() => {
     queryClient.invalidateQueries({
       queryKey: profileQueryKey(profile.did),
@@ -454,14 +394,9 @@ let ProfileHeaderLoaded = ({
   const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower')
 
   return (
-    <View
-      style={[
-        pal.view,
-        isProfilePreview && isDesktop && styles.loadingBorderStyle,
-      ]}
-      pointerEvents="box-none">
+    <View style={[pal.view]} pointerEvents="box-none">
       <View pointerEvents="none">
-        {isProfilePreview ? (
+        {isPlaceholderProfile ? (
           <LoadingPlaceholder
             width="100%"
             height={150}
@@ -622,7 +557,7 @@ let ProfileHeaderLoaded = ({
             {invalidHandle ? _(msg`⚠Invalid Handle`) : `@${profile.handle}`}
           </ThemedText>
         </View>
-        {!isProfilePreview && !blockHide && (
+        {!isPlaceholderProfile && !blockHide && (
           <>
             <View style={styles.metricsLine} pointerEvents="box-none">
               <Link
@@ -737,7 +672,8 @@ let ProfileHeaderLoaded = ({
     </View>
   )
 }
-ProfileHeaderLoaded = memo(ProfileHeaderLoaded)
+ProfileHeader = memo(ProfileHeader)
+export {ProfileHeader}
 
 const styles = StyleSheet.create({
   banner: {
@@ -845,9 +781,4 @@ const styles = StyleSheet.create({
 
   br40: {borderRadius: 40},
   br50: {borderRadius: 50},
-
-  loadingBorderStyle: {
-    borderLeftWidth: 1,
-    borderRightWidth: 1,
-  },
 })
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index 00ff7e1ec..f673db1ee 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -123,6 +123,7 @@ let UserAvatar = ({
   usePlainRNImage = false,
 }: UserAvatarProps): React.ReactNode => {
   const pal = usePalette('default')
+  const backgroundColor = pal.colors.backgroundLight
 
   const aviStyle = useMemo(() => {
     if (type === 'algo' || type === 'list') {
@@ -130,14 +131,16 @@ let UserAvatar = ({
         width: size,
         height: size,
         borderRadius: size > 32 ? 8 : 3,
+        backgroundColor,
       }
     }
     return {
       width: size,
       height: size,
       borderRadius: Math.floor(size / 2),
+      backgroundColor,
     }
-  }, [type, size])
+  }, [type, size, backgroundColor])
 
   const alert = useMemo(() => {
     if (!moderation?.alert) {