Invite friends: wire followers banner to find-friends settings, polish
- Add promo banner illustration (assets/images/invite_friends_promo_banner.webp) and replace the empty placeholder box in FollowersPromoBanner. - Redirect the followers empty-state banner to the Find and Invite Friends settings screen instead of opening the share sheet, gated to mirror that screen's availability (native, geolocation allowlist, feature flag) so we don't promote contact import where the settings entry is hidden. - Drop the now-dead invite dialog mount/control from ProfileFollowers and update the banner a11y copy to match the new destination. - Scanner: collapse the unused error-message state to a boolean (the overlay renders its own localized copy).
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -25,7 +25,7 @@ export function InviteScannerScreen() {
|
|||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const [permission, requestPermission] = useCameraPermissions()
|
const [permission, requestPermission] = useCameraPermissions()
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [showError, setShowError] = useState(false)
|
||||||
const [scannerEnabled, setScannerEnabled] = useState(true)
|
const [scannerEnabled, setScannerEnabled] = useState(true)
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
@@ -44,13 +44,7 @@ export function InviteScannerScreen() {
|
|||||||
)
|
)
|
||||||
if (!profileMatch) {
|
if (!profileMatch) {
|
||||||
setScannerEnabled(false)
|
setScannerEnabled(false)
|
||||||
setError(
|
setShowError(true)
|
||||||
l({
|
|
||||||
message: 'Profile not found',
|
|
||||||
comment:
|
|
||||||
'Error shown when a scanned QR code is not a valid Bluesky profile URL',
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,11 +52,11 @@ export function InviteScannerScreen() {
|
|||||||
const handle = profileMatch[1]
|
const handle = profileMatch[1]
|
||||||
navigation.replace('Profile', {name: handle})
|
navigation.replace('Profile', {name: handle})
|
||||||
},
|
},
|
||||||
[scannerEnabled, navigation, l],
|
[scannerEnabled, navigation],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onRetry = useCallback(() => {
|
const onRetry = useCallback(() => {
|
||||||
setError(null)
|
setShowError(false)
|
||||||
setScannerEnabled(true)
|
setScannerEnabled(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -165,7 +159,7 @@ export function InviteScannerScreen() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Error overlay shown when scanned QR isn't a valid profile URL */}
|
{/* Error overlay shown when scanned QR isn't a valid profile URL */}
|
||||||
{error && (
|
{showError && (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {Pressable, View} from 'react-native'
|
import {Pressable, View} from 'react-native'
|
||||||
|
import {Image} from 'expo-image'
|
||||||
import {useLingui} from '@lingui/react/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
@@ -18,8 +19,8 @@ export function FollowersPromoBanner({
|
|||||||
<View style={[a.px_lg, a.pt_md]}>
|
<View style={[a.px_lg, a.pt_md]}>
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={l`Invite friends`}
|
accessibilityLabel={l`Find and invite friends`}
|
||||||
accessibilityHint={l`Opens the invite friends sheet to share your profile`}
|
accessibilityHint={l`Opens the find and invite friends settings`}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
style={({pressed}) => [
|
style={({pressed}) => [
|
||||||
a.flex_row,
|
a.flex_row,
|
||||||
@@ -34,14 +35,11 @@ export function FollowersPromoBanner({
|
|||||||
opacity: pressed ? 0.85 : 1,
|
opacity: pressed ? 0.85 : 1,
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
<View
|
<Image
|
||||||
style={{
|
accessibilityIgnoresInvertColors
|
||||||
width: 126,
|
source={require('../../../../assets/images/invite_friends_promo_banner.webp')}
|
||||||
height: 64,
|
style={{width: 126, height: 64, marginRight: 16}}
|
||||||
marginRight: 16,
|
contentFit="contain"
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||||
|
import {type NavigationProp} from '#/lib/routes/types'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
|
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
|
||||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from '#/components/contacts/country-allowlist'
|
||||||
import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2'
|
import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2'
|
||||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {IS_NATIVE} from '#/env'
|
import {IS_NATIVE} from '#/env'
|
||||||
import {
|
import {
|
||||||
FollowersPromoBanner,
|
FollowersPromoBanner,
|
||||||
InviteFriendsDialog,
|
|
||||||
useFollowersPromoDismissed,
|
useFollowersPromoDismissed,
|
||||||
} from '#/features/inviteFriends'
|
} from '#/features/inviteFriends'
|
||||||
import {List} from '../util/List'
|
import {List} from '../util/List'
|
||||||
@@ -50,7 +50,7 @@ function keyExtractor(item: ActorDefs.ProfileViewBasic) {
|
|||||||
export function ProfileFollowers({name}: {name: string}) {
|
export function ProfileFollowers({name}: {name: string}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const initialNumToRender = useInitialNumToRender()
|
const initialNumToRender = useInitialNumToRender()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
@@ -165,14 +165,18 @@ export function ProfileFollowers({name}: {name: string}) {
|
|||||||
[ax, followers, resolvedDid],
|
[ax, followers, resolvedDid],
|
||||||
)
|
)
|
||||||
|
|
||||||
const inviteFriendsControl = useDialogControl()
|
|
||||||
const [followersPromoDismissed, setFollowersPromoDismissed] =
|
const [followersPromoDismissed, setFollowersPromoDismissed] =
|
||||||
useFollowersPromoDismissed()
|
useFollowersPromoDismissed()
|
||||||
// Native-only: the invite-friends sheet is a no-op on web, so the banner
|
const findContactsEnabled = useIsFindContactsFeatureEnabledBasedOnGeolocation()
|
||||||
// would open nothing. Gate it to avoid a dead promo on web.
|
// 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 =
|
const showFollowersPromo =
|
||||||
IS_NATIVE &&
|
IS_NATIVE &&
|
||||||
isMe &&
|
isMe &&
|
||||||
|
findContactsEnabled &&
|
||||||
|
!ax.features.enabled(ax.features.ImportContactsSettingsDisable) &&
|
||||||
!followersPromoDismissed &&
|
!followersPromoDismissed &&
|
||||||
followers.length < 1 &&
|
followers.length < 1 &&
|
||||||
!isDidLoading &&
|
!isDidLoading &&
|
||||||
@@ -184,7 +188,7 @@ export function ProfileFollowers({name}: {name: string}) {
|
|||||||
<>
|
<>
|
||||||
{showFollowersPromo && (
|
{showFollowersPromo && (
|
||||||
<FollowersPromoBanner
|
<FollowersPromoBanner
|
||||||
onPress={() => inviteFriendsControl.open()}
|
onPress={() => navigation.navigate('FindContactsSettings')}
|
||||||
onDismiss={() => setFollowersPromoDismissed(true)}
|
onDismiss={() => setFollowersPromoDismissed(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -210,9 +214,6 @@ export function ProfileFollowers({name}: {name: string}) {
|
|||||||
onPress: () => navigation.goBack(),
|
onPress: () => navigation.goBack(),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{showFollowersPromo && (
|
|
||||||
<InviteFriendsDialog control={inviteFriendsControl} />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user