Merge remote-tracking branch 'origin/main' into 3p-moderators

* origin/main: (33 commits)
  Improved server selector during account creation and signin (#2840)
  remove line height from text input for composer on ios (#2844)
  Add follow button to highlighted post (#2828)
  Cleaning up old codes pt_BR (#2809)
  Fix wrong translations in zh-CN localization  (#2812)
  Adjust Japanese translation (#2814)
  Update ja messages.po (#2821)
  change follow to follow all when !== 20 (#2831)
  ios adult content link fix 🤦 (#2845)
  improves build.md completeness (#2835)
  increase stroke width for active hashtag icon (#2829)
  Update `blueskyweb.xyz` links to `bsky.social` (#2830)
  eas nightlies (#2826)
  Always show post dropdown button at the bottom of the post, add share button to highlighted post (#2646)
  Measure header layout reliably (#2817)
  Round line height (#2824)
  Design system tweaks (#2822)
  Fix layout calculations (#2816)
  Fix flashes and jumps when opening profile (#2815)
  Show text cursor on web bio (#2813)
  ...
This commit is contained in:
Eric Bailey
2024-02-12 15:43:33 -06:00
90 changed files with 1781 additions and 1707 deletions
+52 -91
View File
@@ -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'
@@ -43,79 +44,57 @@ 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'
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: Shadow<AppBskyActorDefs.ProfileViewDetailed> | null
moderation: ProfileModeration | null
hideBackButton?: boolean
isProfilePreview?: boolean
}
export function ProfileHeader({
profile,
moderation,
hideBackButton = false,
isProfilePreview,
}: Props) {
let ProfileHeaderLoading = (_props: {}): React.ReactNode => {
const pal = usePalette('default')
// loading
// =
if (!profile || !moderation) {
return (
<View style={pal.view}>
<LoadingPlaceholder width="100%" height={153} />
<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}
moderation={moderation}
hideBackButton={hideBackButton}
isProfilePreview={isProfilePreview}
/>
</View>
)
}
ProfileHeaderLoading = memo(ProfileHeaderLoading)
export {ProfileHeaderLoading}
interface LoadedProps {
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
moderation: ProfileModeration
interface Props {
profile: AppBskyActorDefs.ProfileViewDetailed
descriptionRT: RichTextAPI | null
moderationOpts: ModerationOpts
hideBackButton?: boolean
isProfilePreview?: boolean
isPlaceholderProfile?: boolean
}
let ProfileHeaderLoaded = ({
profile,
moderation,
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')
const palInverted = usePalette('inverted')
const {currentAccount, hasSession} = useSession()
@@ -132,37 +111,10 @@ let ProfileHeaderLoaded = ({
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
const queryClient = useQueryClient()
/*
* 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,
const moderation = useMemo(
() => moderateProfile(profile, moderationOpts),
[profile, moderationOpts],
)
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({
@@ -443,9 +395,17 @@ let ProfileHeaderLoaded = ({
const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower')
return (
<View style={pal.view} pointerEvents="box-none">
<View style={[pal.view]} pointerEvents="box-none">
<View pointerEvents="none">
<UserBanner banner={profile.banner} moderation={moderation.avatar} />
{isPlaceholderProfile ? (
<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">
@@ -479,7 +439,7 @@ let ProfileHeaderLoaded = ({
)
) : !profile.viewer?.blockedBy ? (
<>
{!isProfilePreview && hasSession && (
{hasSession && (
<TouchableOpacity
testID="suggestedFollowsBtn"
onPress={() => setShowSuggestedFollows(!showSuggestedFollows)}
@@ -598,7 +558,7 @@ let ProfileHeaderLoaded = ({
{invalidHandle ? _(msg`⚠Invalid Handle`) : `@${profile.handle}`}
</ThemedText>
</View>
{!blockHide && (
{!isPlaceholderProfile && !blockHide && (
<>
<View style={styles.metricsLine} pointerEvents="box-none">
<Link
@@ -649,7 +609,7 @@ let ProfileHeaderLoaded = ({
</Text>
</View>
{descriptionRT && !moderation.profile.blur ? (
<View pointerEvents={isNative ? 'auto' : 'none'}>
<View pointerEvents="auto">
<RichText
testID="profileHeaderDescription"
style={[styles.description, pal.text]}
@@ -667,7 +627,7 @@ let ProfileHeaderLoaded = ({
<ProfileHeaderModCard />
</View>
{!isProfilePreview && showSuggestedFollows && (
{showSuggestedFollows && (
<ProfileHeaderSuggestedFollows
actorDid={profile.did}
requestDismiss={() => {
@@ -714,7 +674,8 @@ let ProfileHeaderLoaded = ({
</View>
)
}
ProfileHeaderLoaded = memo(ProfileHeaderLoaded)
ProfileHeader = memo(ProfileHeader)
export {ProfileHeader}
const styles = StyleSheet.create({
banner: {