import React, { useState } from 'react' import { useAsync } from 'react-async-hook' import { useTranslation } from 'react-i18next' import { Platform } from 'react-native' import { RESULTS as PERMISSION_RESULTS, PermissionStatus, check as checkPermission, request as requestPermission, } from 'react-native-permissions' import AppAnalytics from 'src/analytics/AppAnalytics' import { JumpstartEvents, SendEvents } from 'src/analytics/Events' import { phoneNumberVerifiedSelector } from 'src/app/selectors' import Dialog from 'src/components/Dialog' import SelectRecipientButton from 'src/components/SelectRecipientButton' import MagicWand from 'src/icons/MagicWand' import QRCode from 'src/icons/QRCode' import Social from 'src/icons/Social' import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import { useSelector } from 'src/redux/hooks' import { getFeatureGate } from 'src/statsig' import { StatsigFeatureGates } from 'src/statsig/types' import Colors from 'src/styles/colors' import Logger from 'src/utils/Logger' import { CONTACTS_PERMISSION } from 'src/utils/contacts' import { navigateToPhoneSettings } from 'src/utils/linking' type Props = { onContactsPermissionGranted: () => void defaultTokenIdOverride?: string } export default function SelectRecipientButtons({ onContactsPermissionGranted, defaultTokenIdOverride, }: Props) { const { t } = useTranslation() const phoneNumberVerified = useSelector(phoneNumberVerifiedSelector) const jumpstartSendEnabled = getFeatureGate(StatsigFeatureGates.SHOW_JUMPSTART_SEND) const [contactsPermissionStatus, setContactsPermissionStatus] = useState< PermissionStatus | undefined >(undefined) const [showConnectPhoneNumberModal, setShowConnectPhoneNumberModal] = useState(false) const [showEnableContactsModal, setShowEnableContactsModal] = useState(false) const [navigateToPhoneVerification, setNavigateToPhoneVerification] = useState(false) useAsync(() => checkPermission(CONTACTS_PERMISSION), [], { onSuccess: (permissionStatus) => { setContactsPermissionStatus(permissionStatus) }, }) const onPressContacts = async () => { // always fetch permission here as it can change outside the app through // native settings, but this screen still remains in view (happens for // Android for when permission changes from denied -> granted, everything else causes an app reload) const currentPermission = await checkPermission(CONTACTS_PERMISSION) setContactsPermissionStatus(currentPermission) AppAnalytics.track(SendEvents.send_select_recipient_contacts, { contactsPermissionStatus: currentPermission, phoneNumberVerified, }) if (!phoneNumberVerified) { setShowConnectPhoneNumberModal(true) return } // Based on https://github.com/zoontek/react-native-permissions#understanding-permission-flow switch (currentPermission) { case PERMISSION_RESULTS.BLOCKED: // permission not requestable setShowEnableContactsModal(true) break case PERMISSION_RESULTS.GRANTED: // permission already granted onContactsPermissionGranted() break case PERMISSION_RESULTS.DENIED: // permission is requestable AppAnalytics.track(SendEvents.request_contacts_permission_started) const newPermission = await requestPermission(CONTACTS_PERMISSION, { // rationale for Android, shows up the 2nd time a permission is requested. title: t('accessContacts.disclosure.title'), message: t('accessContacts.disclosure.body'), buttonPositive: t('continue'), buttonNegative: t('notNow') ?? undefined, }) setContactsPermissionStatus(newPermission) AppAnalytics.track(SendEvents.request_contacts_permission_completed, { permissionStatus: newPermission, }) if (newPermission === PERMISSION_RESULTS.GRANTED) { // permission granted onContactsPermissionGranted() } else if (newPermission === PERMISSION_RESULTS.BLOCKED && Platform.OS === 'android') { // we only know if permissions are requestable or not on Android after // doing a request. So if we get back blocked from the request, its // possible a native modal was never shown, so show our custom modal. setShowEnableContactsModal(true) } break case PERMISSION_RESULTS.UNAVAILABLE: case PERMISSION_RESULTS.LIMITED: default: // this should never happen const unexpectedPermission: 'unavailable' | 'limited' = currentPermission Logger.error( 'SelectRecipientButtons/onPressContacts', `Unexpected contacts permission status: ${unexpectedPermission}` ) break } } const onPressQR = () => { AppAnalytics.track(SendEvents.send_select_recipient_scan_qr) navigate(Screens.QRNavigator, { screen: Screens.QRScanner, params: { defaultTokenIdOverride } }) } const onPressConnectPhoneNumber = () => { AppAnalytics.track(SendEvents.send_phone_number_modal_connect) setShowConnectPhoneNumberModal(false) // navigating directly here causes a screen freeze since the modal is fully // not dismissed. A state is set, which is then checked on the modal hide // handler to navigate setNavigateToPhoneVerification(true) } const onDismissConnectPhoneNumberModal = () => { AppAnalytics.track(SendEvents.send_phone_number_modal_dismiss) setShowConnectPhoneNumberModal(false) } const onHideConnectPhoneNumberModal = () => { if (navigateToPhoneVerification) { // reset state so the next time the modal is dismissed, this doesn't end // up navigating setNavigateToPhoneVerification(false) navigate(Screens.VerificationStartScreen, { hasOnboarded: true }) } } const onPressSettings = () => { AppAnalytics.track(SendEvents.send_contacts_modal_settings) setShowEnableContactsModal(false) navigateToPhoneSettings() } const onDismissEnableContactsModal = () => { AppAnalytics.track(SendEvents.send_contacts_modal_dismiss) setShowEnableContactsModal(false) } const onPressJumpstart = () => { AppAnalytics.track(JumpstartEvents.send_select_recipient_jumpstart) navigate(Screens.JumpstartEnterAmount) } return ( <> {jumpstartSendEnabled && ( } gradientBackground /> )} } /> } showCheckmark={ phoneNumberVerified && contactsPermissionStatus === PERMISSION_RESULTS.GRANTED } /> {t('sendSelectRecipient.connectPhoneNumberModal.description')} {t('sendSelectRecipient.enableContactsModal.description')} ) }