Add profile QR code for sharing and inviting friends (#10659)

This commit is contained in:
Spence Pope
2026-06-10 14:14:45 -04:00
committed by GitHub
parent adb39b4442
commit c6f9803676
32 changed files with 1853 additions and 55 deletions
+80 -48
View File
@@ -5,14 +5,21 @@ import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {type NavigationProp} from '#/lib/routes/types'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useSession} from '#/state/session'
import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from '#/components/contacts/country-allowlist'
import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env'
import {
FollowersPromoBanner,
useFollowersPromoDismissed,
} from '#/features/inviteFriends'
import {List} from '../util/List'
import {ProfileCardWithFollowBtn} from './ProfileCard'
@@ -43,7 +50,7 @@ function keyExtractor(item: ActorDefs.ProfileViewBasic) {
export function ProfileFollowers({name}: {name: string}) {
const {_} = useLingui()
const ax = useAnalytics()
const navigation = useNavigation()
const navigation = useNavigation<NavigationProp>()
const initialNumToRender = useInitialNumToRender()
const {currentAccount} = useSession()
@@ -158,55 +165,80 @@ export function ProfileFollowers({name}: {name: string}) {
[ax, followers, resolvedDid],
)
if (followers.length < 1) {
return (
<ListMaybePlaceholder
isLoading={isDidLoading || isFollowersLoading}
isError={isError}
emptyType="results"
emptyMessage={
isMe
? _(msg`No followers yet`)
: _(msg`This user doesn't have any followers.`)
}
errorMessage={cleanError(resolveError || error)}
onRetry={isError ? refetch : undefined}
sideBorders={false}
useEmptyState={true}
emptyStateIcon={PeopleRemoveIcon}
emptyStateButton={{
label: _(msg`Go back`),
text: _(msg`Go back`),
color: 'secondary',
size: 'small',
onPress: () => navigation.goBack(),
}}
/>
)
}
const [followersPromoDismissed, setFollowersPromoDismissed] =
useFollowersPromoDismissed()
const findContactsEnabled =
useIsFindContactsFeatureEnabledBasedOnGeolocation()
// The banner deep-links into the Find and Invite Friends settings screen, so
// mirror that screen's availability gates: native-only, allowed in the user's
// region (geolocation allowlist), and not disabled by the feature flag. This
// avoids promoting contact import where the settings entry itself is hidden.
const showFollowersPromo =
IS_NATIVE &&
isMe &&
findContactsEnabled &&
!ax.features.enabled(ax.features.ImportContactsSettingsDisable) &&
!followersPromoDismissed &&
followers.length < 1 &&
!isDidLoading &&
!isFollowersLoading &&
!isError
return (
<List
data={followers}
renderItem={renderItemWithContext}
keyExtractor={keyExtractor}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={4}
onItemSeen={onItemSeen}
ListFooterComponent={
<ListFooter
isFetchingNextPage={isFetchingNextPage}
error={cleanError(error)}
onRetry={fetchNextPage}
<>
{showFollowersPromo && (
<FollowersPromoBanner
onPress={() => navigation.navigate('FindContactsSettings')}
onDismiss={() => setFollowersPromoDismissed(true)}
/>
}
// @ts-ignore our .web version only -prf
desktopFixedHeight
initialNumToRender={initialNumToRender}
windowSize={11}
sideBorders={false}
/>
)}
{followers.length < 1 ? (
<ListMaybePlaceholder
isLoading={isDidLoading || isFollowersLoading}
isError={isError}
emptyType="results"
emptyMessage={
isMe
? _(msg`No followers yet`)
: _(msg`This user doesn't have any followers.`)
}
errorMessage={cleanError(resolveError || error)}
onRetry={isError ? refetch : undefined}
sideBorders={false}
useEmptyState={true}
emptyStateIcon={PeopleRemoveIcon}
emptyStateButton={{
label: _(msg`Go back`),
text: _(msg`Go back`),
color: 'secondary',
size: 'small',
onPress: () => navigation.goBack(),
}}
/>
) : (
<List
data={followers}
renderItem={renderItemWithContext}
keyExtractor={keyExtractor}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={4}
onItemSeen={onItemSeen}
ListFooterComponent={
<ListFooter
isFetchingNextPage={isFetchingNextPage}
error={cleanError(error)}
onRetry={fetchNextPage}
/>
}
// @ts-ignore our .web version only -prf
desktopFixedHeight
initialNumToRender={initialNumToRender}
windowSize={11}
sideBorders={false}
/>
)}
</>
)
}