@@ -10,29 +10,35 @@ import Animated, {
1010import { useSafeAreaInsets } from 'react-native-safe-area-context'
1111import { type AppBskyActorDefs , type ModerationDecision } from '@atproto/api'
1212import { utils } from '@bsky.app/alf'
13- import { msg } from '@lingui/macro'
13+ import { msg , Trans } from '@lingui/macro'
1414import { useLingui } from '@lingui/react'
1515import { useNavigation } from '@react-navigation/native'
1616
1717import { useActorStatus } from '#/lib/actor-status'
1818import { BACK_HITSLOP } from '#/lib/constants'
1919import { useHaptics } from '#/lib/haptics'
20+ import { getModerationCauseKey , unique } from '#/lib/moderation'
2021import { type NavigationProp } from '#/lib/routes/types'
2122import { type Shadow } from '#/state/cache/types'
2223import { useLightboxControls } from '#/state/lightbox'
24+ import { usePublicProfileQuery } from '#/state/queries/profile'
2325import { useSession } from '#/state/session'
2426import { LoadingPlaceholder } from '#/view/com/util/LoadingPlaceholder'
2527import { UserAvatar } from '#/view/com/util/UserAvatar'
2628import { UserBanner } from '#/view/com/util/UserBanner'
2729import { atoms as a , platform , useTheme } from '#/alf'
30+ import { colors } from '#/components/Admonition'
2831import { Button } from '#/components/Button'
2932import { useDialogControl } from '#/components/Dialog'
3033import { ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon } from '#/components/icons/Arrow'
34+ import { ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateIcon } from '#/components/icons/ArrowRotate'
3135import { EditLiveDialog } from '#/components/live/EditLiveDialog'
3236import { LiveIndicator } from '#/components/live/LiveIndicator'
3337import { LiveStatusDialog } from '#/components/live/LiveStatusDialog'
3438import { LabelsOnMe } from '#/components/moderation/LabelsOnMe'
35- import { ProfileHeaderAlerts } from '#/components/moderation/ProfileHeaderAlerts'
39+ import * as Pills from '#/components/Pills'
40+ import * as Prompt from '#/components/Prompt'
41+ import { Text } from '#/components/Typography'
3642import { useAnalytics } from '#/analytics'
3743import { IS_IOS } from '#/env'
3844import { GrowableAvatar } from './GrowableAvatar'
@@ -243,15 +249,7 @@ let ProfileHeaderShell = ({
243249 ] }
244250 />
245251 ) : (
246- < ProfileHeaderAlerts
247- moderation = { moderation }
248- style = { [
249- a . px_lg ,
250- a . pt_xs ,
251- a . pb_sm ,
252- IS_IOS ? a . pointer_events_auto : { pointerEvents : 'box-none' } ,
253- ] }
254- />
252+ < ProfileHeaderPills profile = { profile } moderation = { moderation } />
255253 ) ) }
256254
257255 < GrowableAvatar style = { [ a . absolute , { top : 104 , left : 10 } ] } >
@@ -310,3 +308,125 @@ let ProfileHeaderShell = ({
310308
311309ProfileHeaderShell = memo ( ProfileHeaderShell )
312310export { ProfileHeaderShell }
311+
312+ function ProfileHeaderPills ( {
313+ profile,
314+ moderation,
315+ } : {
316+ profile : Shadow < AppBskyActorDefs . ProfileViewDetailed >
317+ moderation : ModerationDecision
318+ } ) {
319+ const modui = moderation . ui ( 'profileView' )
320+ const { data : publicProfile } = usePublicProfileQuery ( { did : profile . did } )
321+
322+ const isPartiallyBackfilled = useMemo ( ( ) => {
323+ if ( ! publicProfile ) return false
324+ const THRESHOLD_PCT = 0.05
325+ const THRESHOLD_ABS = 10
326+ const isLower = ( local : number , canonical : number ) => {
327+ const diff = canonical - local
328+ return diff > THRESHOLD_ABS && diff > canonical * THRESHOLD_PCT
329+ }
330+ return (
331+ isLower ( profile . followersCount ?? 0 , publicProfile . followersCount ?? 0 ) ||
332+ isLower ( profile . followsCount ?? 0 , publicProfile . followsCount ?? 0 ) ||
333+ isLower ( profile . postsCount ?? 0 , publicProfile . postsCount ?? 0 )
334+ )
335+ } , [ profile , publicProfile ] )
336+
337+ const hasAlerts = modui . alert || modui . inform
338+ if ( ! isPartiallyBackfilled && ! hasAlerts ) return null
339+
340+ return (
341+ < Pills . Row
342+ size = "lg"
343+ style = { [
344+ a . px_lg ,
345+ a . pt_xs ,
346+ a . pb_sm ,
347+ IS_IOS ? a . pointer_events_auto : { pointerEvents : 'box-none' } ,
348+ ] } >
349+ { isPartiallyBackfilled && < BackfillPill /> }
350+ { modui . alerts . filter ( unique ) . map ( cause => (
351+ < Pills . Label
352+ size = "lg"
353+ key = { getModerationCauseKey ( cause ) }
354+ cause = { cause }
355+ />
356+ ) ) }
357+ { modui . informs . filter ( unique ) . map ( cause => (
358+ < Pills . Label
359+ size = "lg"
360+ key = { getModerationCauseKey ( cause ) }
361+ cause = { cause }
362+ />
363+ ) ) }
364+ </ Pills . Row >
365+ )
366+ }
367+
368+ function BackfillPill ( ) {
369+ const t = useTheme ( )
370+ const { _} = useLingui ( )
371+ const control = Prompt . usePromptControl ( )
372+
373+ return (
374+ < >
375+ < Button
376+ label = { _ ( msg `Backfill in progress` ) }
377+ onPress = { e => {
378+ e . preventDefault ( )
379+ e . stopPropagation ( )
380+ control . open ( )
381+ } } >
382+ { ( { hovered, pressed} ) => (
383+ < View
384+ style = { [
385+ a . flex_row ,
386+ a . align_center ,
387+ a . rounded_full ,
388+ t . atoms . bg_contrast_25 ,
389+ ( hovered || pressed ) && t . atoms . bg_contrast_50 ,
390+ {
391+ gap : 5 ,
392+ paddingHorizontal : 5 ,
393+ paddingVertical : 5 ,
394+ } ,
395+ ] } >
396+ < ArrowRotateIcon width = { 16 } fill = { colors . warning } />
397+ < Text
398+ style = { [
399+ a . text_sm ,
400+ a . font_semi_bold ,
401+ a . leading_tight ,
402+ t . atoms . text_contrast_medium ,
403+ { paddingRight : 3 } ,
404+ ] } >
405+ < Trans > Backfill in progress</ Trans >
406+ </ Text >
407+ </ View >
408+ ) }
409+ </ Button >
410+
411+ < Prompt . Outer control = { control } >
412+ < Prompt . Content >
413+ < Prompt . TitleText >
414+ < Trans > Backfill in progress</ Trans >
415+ </ Prompt . TitleText >
416+ < Prompt . DescriptionText >
417+ < Trans >
418+ Blacksky is still indexing this account's data from the AT
419+ Protocol network. The follower, following, and post counts shown
420+ may be lower than the actual totals. Relationship indicators (like
421+ whether this account follows you) may also be incomplete until
422+ backfill is finished.
423+ </ Trans >
424+ </ Prompt . DescriptionText >
425+ </ Prompt . Content >
426+ < Prompt . Actions >
427+ < Prompt . Action cta = { _ ( msg `Okay` ) } onPress = { ( ) => { } } />
428+ </ Prompt . Actions >
429+ </ Prompt . Outer >
430+ </ >
431+ )
432+ }
0 commit comments