Address PR review feedback for invite friends
- Remove commented-out Dialog.Handle in InviteFriendsDialog - Defer NUX -> invite handoff with requestAnimationFrame instead of setTimeout - Un-nest the dismiss Pressable in FollowersPromoBanner (avoids Android touch conflicts) - Drop redundant toLowerCase in getInviteShareUrl (handles are API-normalized, matches makeProfileLink) - Request write-only media library permission when saving the QR code - Migrate InviteFriendsAnnouncement to the l macro
This commit is contained in:
@@ -13,7 +13,6 @@ export function InviteFriendsDialog({
|
||||
control={control}
|
||||
onClose={onClose}
|
||||
nativeOptions={{preventExpansion: true}}>
|
||||
{/* <Dialog.Handle /> */}
|
||||
<InviteFriendsDialogInner control={control} />
|
||||
</Dialog.Outer>
|
||||
)
|
||||
|
||||
@@ -2,8 +2,7 @@ import {Suspense, useRef, useState} from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import type ViewShot from 'react-native-view-shot'
|
||||
import {setStringAsync} from 'expo-clipboard'
|
||||
import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
|
||||
import {createAssetAsync} from 'expo-media-library'
|
||||
import {createAssetAsync, requestPermissionsAsync} from 'expo-media-library'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
@@ -78,7 +77,9 @@ export function InviteFriendsDialogInner({
|
||||
return
|
||||
}
|
||||
|
||||
const permission = await requestMediaLibraryPermissionsAsync()
|
||||
// Write-only permission - saving the QR image does not require read access
|
||||
// to the user's photo library.
|
||||
const permission = await requestPermissionsAsync(true)
|
||||
if (!permission.granted) {
|
||||
Toast.show(
|
||||
l`You must grant access to your photo library to save the QR code`,
|
||||
|
||||
@@ -17,39 +17,47 @@ export function FollowersPromoBanner({
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View style={[a.px_lg, a.pt_md]}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={l`Find and invite friends`}
|
||||
accessibilityHint={l`Opens the find and invite friends settings`}
|
||||
onPress={onPress}
|
||||
style={({pressed}) => [
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.w_full,
|
||||
{
|
||||
height: 80,
|
||||
borderRadius: 16,
|
||||
backgroundColor: t.palette.primary_50,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 16,
|
||||
opacity: pressed ? 0.85 : 1,
|
||||
},
|
||||
]}>
|
||||
<Image
|
||||
accessibilityIgnoresInvertColors
|
||||
source={require('../../../../assets/images/invite_friends_promo_banner.webp')}
|
||||
style={{width: 126, height: 64, marginRight: 16}}
|
||||
contentFit="contain"
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
{color: t.palette.primary_700, lineHeight: 19.5},
|
||||
{/*
|
||||
* The dismiss button is a sibling of (not nested inside) the banner
|
||||
* Pressable - nesting Pressables causes touch-handling conflicts on
|
||||
* Android. This relative wrapper matches the banner bounds so the dismiss
|
||||
* button can be positioned absolutely against the card's top-right corner.
|
||||
*/}
|
||||
<View style={[a.relative]}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={l`Find and invite friends`}
|
||||
accessibilityHint={l`Opens the find and invite friends settings`}
|
||||
onPress={onPress}
|
||||
style={({pressed}) => [
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.w_full,
|
||||
{
|
||||
height: 80,
|
||||
borderRadius: 16,
|
||||
backgroundColor: t.palette.primary_50,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 16,
|
||||
opacity: pressed ? 0.85 : 1,
|
||||
},
|
||||
]}>
|
||||
{l`Import contacts or invite your friends`}
|
||||
</Text>
|
||||
<Image
|
||||
accessibilityIgnoresInvertColors
|
||||
source={require('../../../../assets/images/invite_friends_promo_banner.webp')}
|
||||
style={{width: 126, height: 64, marginRight: 16}}
|
||||
contentFit="contain"
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
{color: t.palette.primary_700, lineHeight: 19.5},
|
||||
]}>
|
||||
{l`Import contacts or invite your friends`}
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={l`Dismiss`}
|
||||
@@ -67,7 +75,7 @@ export function FollowersPromoBanner({
|
||||
]}>
|
||||
<TimesIcon width={12} height={12} fill={t.palette.contrast_500} />
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ describe('invite URLs', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('lowercases the handle', () => {
|
||||
it('passes the handle through verbatim (handles are API-normalized, like makeProfileLink)', () => {
|
||||
expect(getInviteShareUrl('Alice.Bsky.Social')).toBe(
|
||||
'https://bsky.app/profile/alice.bsky.social',
|
||||
'https://bsky.app/profile/Alice.Bsky.Social',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -45,9 +45,9 @@ describe('invite URLs', () => {
|
||||
expect(getInviteDisplayUrl('alice')).toBe('bsky.app/profile/alice')
|
||||
})
|
||||
|
||||
it('lowercases the handle', () => {
|
||||
it('passes the handle through verbatim (handles are API-normalized, like makeProfileLink)', () => {
|
||||
expect(getInviteDisplayUrl('Danielle.bsky.team')).toBe(
|
||||
'bsky.app/profile/danielle.bsky.team',
|
||||
'bsky.app/profile/Danielle.bsky.team',
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ function stripLeadingAt(handle: string): string {
|
||||
|
||||
/** Canonical URL - used for QR payload, Share, and Copy. Empty handle -> empty string. */
|
||||
export function getInviteShareUrl(handle: string): string {
|
||||
const bare = stripLeadingAt(handle).toLowerCase()
|
||||
const bare = stripLeadingAt(handle)
|
||||
if (!bare) return ''
|
||||
return `https://bsky.app/profile/${bare}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user