Precache basic profile from posts for instant future navigations (#2795)
* skeleton for caching * modify some existing logic * refactor uri resolution query * add precache feed posts * adjustments * remove prefetch on hover (maybe revert, just example) * fix * change arg name to match what we want * optional infinite stale time * use `ProfileViewDetailed` * Revert "remove prefetch on hover (maybe revert, just example)" This reverts commit 08609deb0defa7cea040438bc37dd3488ddc56f4. * add warning comment back for stale time * remove comment * store profile with both the handle and did for query key * remove extra block from revert * clarify argument name * remove QT cache * structure queries the same (put `enabled` at bottom) * use both `ProfileViewDetailed` and `ProfileView` for the query return type * placeholder profile header * remove logs * remove a few other things we don't need * add placeholder * refactor * refactor * we don't need this height adjustment now * use gray banner while loading * set background color of image to the loading placeholder color * reorg imports * add border to header on loading * Fix style * Rm radius * oops * Undo edit * Back out type changes * Tighten some types and moderate shadow * Move precaching fns to profile where the cache is * Rename functions to match what they do now * Remove anys --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
This commit is contained in:
parent
d9b62955b5
commit
de28626001
8 changed files with 170 additions and 85 deletions
|
@ -1,4 +1,4 @@
|
|||
import React, {memo} from 'react'
|
||||
import React, {memo, useMemo} from 'react'
|
||||
import {
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
|
@ -10,7 +10,8 @@ import {useNavigation} from '@react-navigation/native'
|
|||
import {useQueryClient} from '@tanstack/react-query'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
ProfileModeration,
|
||||
ModerationOpts,
|
||||
moderateProfile,
|
||||
RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
|
@ -42,12 +43,11 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {BACK_HITSLOP} from 'lib/constants'
|
||||
import {isInvalidHandle} from 'lib/strings/handles'
|
||||
import {isInvalidHandle, sanitizeHandle} from 'lib/strings/handles'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {pluralize} from 'lib/strings/helpers'
|
||||
import {toShareUrl} from 'lib/strings/url-helpers'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {shareUrl} from 'lib/sharing'
|
||||
import {s, colors} from 'lib/styles'
|
||||
import {logger} from '#/logger'
|
||||
|
@ -55,17 +55,19 @@ import {useSession, getAgent} 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: Shadow<AppBskyActorDefs.ProfileViewDetailed> | null
|
||||
moderation: ProfileModeration | null
|
||||
profile: AppBskyActorDefs.ProfileView | null
|
||||
placeholderData?: AppBskyActorDefs.ProfileView | null
|
||||
moderationOpts: ModerationOpts | null
|
||||
hideBackButton?: boolean
|
||||
isProfilePreview?: boolean
|
||||
}
|
||||
|
||||
export function ProfileHeader({
|
||||
profile,
|
||||
moderation,
|
||||
moderationOpts,
|
||||
hideBackButton = false,
|
||||
isProfilePreview,
|
||||
}: Props) {
|
||||
|
@ -73,10 +75,14 @@ export function ProfileHeader({
|
|||
|
||||
// loading
|
||||
// =
|
||||
if (!profile || !moderation) {
|
||||
if (!profile || !moderationOpts) {
|
||||
return (
|
||||
<View style={pal.view}>
|
||||
<LoadingPlaceholder width="100%" height={153} />
|
||||
<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} />
|
||||
|
@ -95,7 +101,7 @@ export function ProfileHeader({
|
|||
return (
|
||||
<ProfileHeaderLoaded
|
||||
profile={profile}
|
||||
moderation={moderation}
|
||||
moderationOpts={moderationOpts}
|
||||
hideBackButton={hideBackButton}
|
||||
isProfilePreview={isProfilePreview}
|
||||
/>
|
||||
|
@ -103,18 +109,20 @@ export function ProfileHeader({
|
|||
}
|
||||
|
||||
interface LoadedProps {
|
||||
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
|
||||
moderation: ProfileModeration
|
||||
profile: AppBskyActorDefs.ProfileViewDetailed
|
||||
moderationOpts: ModerationOpts
|
||||
hideBackButton?: boolean
|
||||
isProfilePreview?: boolean
|
||||
}
|
||||
|
||||
let ProfileHeaderLoaded = ({
|
||||
profile,
|
||||
moderation,
|
||||
profile: profileUnshadowed,
|
||||
moderationOpts,
|
||||
hideBackButton = false,
|
||||
isProfilePreview,
|
||||
}: LoadedProps): React.ReactNode => {
|
||||
const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> =
|
||||
useProfileShadow(profileUnshadowed)
|
||||
const pal = usePalette('default')
|
||||
const palInverted = usePalette('inverted')
|
||||
const {currentAccount, hasSession} = useSession()
|
||||
|
@ -131,6 +139,10 @@ let ProfileHeaderLoaded = ({
|
|||
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
|
||||
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
|
||||
const queryClient = useQueryClient()
|
||||
const moderation = useMemo(
|
||||
() => moderateProfile(profile, moderationOpts),
|
||||
[profile, moderationOpts],
|
||||
)
|
||||
|
||||
/*
|
||||
* BEGIN handle bio facet resolution
|
||||
|
@ -442,9 +454,22 @@ let ProfileHeaderLoaded = ({
|
|||
const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower')
|
||||
|
||||
return (
|
||||
<View style={pal.view} pointerEvents="box-none">
|
||||
<View
|
||||
style={[
|
||||
pal.view,
|
||||
isProfilePreview && isDesktop && styles.loadingBorderStyle,
|
||||
]}
|
||||
pointerEvents="box-none">
|
||||
<View pointerEvents="none">
|
||||
<UserBanner banner={profile.banner} moderation={moderation.avatar} />
|
||||
{isProfilePreview ? (
|
||||
<LoadingPlaceholder
|
||||
width="100%"
|
||||
height={150}
|
||||
style={{borderRadius: 0}}
|
||||
/>
|
||||
) : (
|
||||
<UserBanner banner={profile.banner} moderation={moderation.avatar} />
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.content} pointerEvents="box-none">
|
||||
<View style={[styles.buttonsLine]} pointerEvents="box-none">
|
||||
|
@ -478,7 +503,7 @@ let ProfileHeaderLoaded = ({
|
|||
)
|
||||
) : !profile.viewer?.blockedBy ? (
|
||||
<>
|
||||
{!isProfilePreview && hasSession && (
|
||||
{hasSession && (
|
||||
<TouchableOpacity
|
||||
testID="suggestedFollowsBtn"
|
||||
onPress={() => setShowSuggestedFollows(!showSuggestedFollows)}
|
||||
|
@ -597,7 +622,7 @@ let ProfileHeaderLoaded = ({
|
|||
{invalidHandle ? _(msg`⚠Invalid Handle`) : `@${profile.handle}`}
|
||||
</ThemedText>
|
||||
</View>
|
||||
{!blockHide && (
|
||||
{!isProfilePreview && !blockHide && (
|
||||
<>
|
||||
<View style={styles.metricsLine} pointerEvents="box-none">
|
||||
<Link
|
||||
|
@ -665,7 +690,7 @@ let ProfileHeaderLoaded = ({
|
|||
)}
|
||||
</View>
|
||||
|
||||
{!isProfilePreview && showSuggestedFollows && (
|
||||
{showSuggestedFollows && (
|
||||
<ProfileHeaderSuggestedFollows
|
||||
actorDid={profile.did}
|
||||
requestDismiss={() => {
|
||||
|
@ -820,4 +845,9 @@ const styles = StyleSheet.create({
|
|||
|
||||
br40: {borderRadius: 40},
|
||||
br50: {borderRadius: 50},
|
||||
|
||||
loadingBorderStyle: {
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -3,7 +3,10 @@ import {StyleSheet, View} from 'react-native'
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {ModerationUI} from '@atproto/api'
|
||||
import {Image} from 'expo-image'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {colors} from 'lib/styles'
|
||||
import {useTheme} from 'lib/ThemeContext'
|
||||
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
|
||||
import {
|
||||
usePhotoLibraryPermission,
|
||||
|
@ -13,8 +16,6 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||
import {isWeb, isAndroid} from 'platform/detection'
|
||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||
import {NativeDropdown, DropdownItem} from './forms/NativeDropdown'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg} from '@lingui/macro'
|
||||
|
||||
export function UserBanner({
|
||||
banner,
|
||||
|
@ -26,6 +27,7 @@ export function UserBanner({
|
|||
onSelectNewBanner?: (img: RNImage | null) => void
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const theme = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {requestCameraAccessIfNeeded} = useCameraPermission()
|
||||
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
||||
|
@ -142,7 +144,10 @@ export function UserBanner({
|
|||
!((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
|
||||
<Image
|
||||
testID="userBannerImage"
|
||||
style={styles.bannerImage}
|
||||
style={[
|
||||
styles.bannerImage,
|
||||
{backgroundColor: theme.palette.default.backgroundLight},
|
||||
]}
|
||||
resizeMode="cover"
|
||||
source={{uri: banner}}
|
||||
blurRadius={moderation?.blur ? 100 : 0}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue