From 8c4cb4b9325a9f66c496fda0b9d6252516989597 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Fri, 20 Oct 2023 14:59:49 -0400 Subject: [PATCH] FIX: Permission issues on newer Android APIs --- android/app/src/main/AndroidManifest.xml | 2 + blue_modules/notifications.js | 123 +++++++++--------- .../xcschemes/xcschememanagement.plist | 2 +- ios/Podfile | 2 +- ios/Podfile.lock | 6 +- screen/send/create.js | 14 +- screen/wallets/details.js | 13 +- 7 files changed, 79 insertions(+), 83 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index feb5e9c0b..fe62704a1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -8,6 +8,8 @@ + + { + if (status === 'granted') { + PushNotification.configure({ + // (optional) Called when Token is generated (iOS and Android) + onRegister: async function (token) { + console.log('TOKEN:', token); + alreadyConfigured = true; + await _setPushToken(token); + resolve(true); + }, - // (required) Called when a remote is received or opened, or local notification is opened - onNotification: async function (notification) { - // since we do not know whether we: - // 1) received notification while app is in background (and storage is not decrypted so wallets are not loaded) - // 2) opening this notification right now but storage is still unencrypted - // 3) any of the above but the storage is decrypted, and app wallets are loaded - // - // ...we save notification in internal notifications queue thats gona be processed later (on unsuspend with decrypted storage) + // (required) Called when a remote is received or opened, or local notification is opened + onNotification: async function (notification) { + // since we do not know whether we: + // 1) received notification while app is in background (and storage is not decrypted so wallets are not loaded) + // 2) opening this notification right now but storage is still unencrypted + // 3) any of the above but the storage is decrypted, and app wallets are loaded + // + // ...we save notification in internal notifications queue thats gona be processed later (on unsuspend with decrypted storage) - const payload = Object.assign({}, notification, notification.data); - if (notification.data && notification.data.data) Object.assign(payload, notification.data.data); - delete payload.data; - // ^^^ weird, but sometimes payload data is not in `data` but in root level - console.log('got push notification', payload); + const payload = Object.assign({}, notification, notification.data); + if (notification.data && notification.data.data) Object.assign(payload, notification.data.data); + delete payload.data; + // ^^^ weird, but sometimes payload data is not in `data` but in root level + console.log('got push notification', payload); - await Notifications.addNotification(payload); + await Notifications.addNotification(payload); - // (required) Called when a remote is received or opened, or local notification is opened - notification.finish(PushNotificationIOS.FetchResult.NoData); + // (required) Called when a remote is received or opened, or local notification is opened + notification.finish(PushNotificationIOS.FetchResult.NoData); - // if user is staring at the app when he receives the notification we process it instantly - // so app refetches related wallet - if (payload.foreground) props.onProcessNotifications(); - }, + // if user is staring at the app when he receives the notification we process it instantly + // so app refetches related wallet + if (payload.foreground) props.onProcessNotifications(); + }, - // (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android) - onAction: function (notification) { - console.log('ACTION:', notification.action); - console.log('NOTIFICATION:', notification); + // (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android) + onAction: function (notification) { + console.log('ACTION:', notification.action); + console.log('NOTIFICATION:', notification); - // process the action - }, + // process the action + }, - // (optional) Called when the user fails to register for remote notifications. Typically occurs when APNS is having issues, or the device is a simulator. (iOS) - onRegistrationError: function (err) { - console.error(err.message, err); - resolve(false); - }, + // (optional) Called when the user fails to register for remote notifications. Typically occurs when APNS is having issues, or the device is a simulator. (iOS) + onRegistrationError: function (err) { + console.error(err.message, err); + resolve(false); + }, - // IOS ONLY (optional): default: all - Permissions to register. - permissions: { - alert: true, - badge: true, - sound: true, - }, + // IOS ONLY (optional): default: all - Permissions to register. + permissions: { + alert: true, + badge: true, + sound: true, + }, - // Should the initial notification be popped automatically - // default: true - popInitialNotification: true, + // Should the initial notification be popped automatically + // default: true + popInitialNotification: true, - /** - * (optional) default: true - * - Specified if permissions (ios) and token (android and ios) will requested or not, - * - if not, you must call PushNotificationsHandler.requestPermissions() later - * - if you are not using remote notification or do not have Firebase installed, use this: - * requestPermissions: Platform.OS === 'ios' - */ - requestPermissions: true, + /** + * (optional) default: true + * - Specified if permissions (ios) and token (android and ios) will requested or not, + * - if not, you must call PushNotificationsHandler.requestPermissions() later + * - if you are not using remote notification or do not have Firebase installed, use this: + * requestPermissions: Platform.OS === 'ios' + */ + requestPermissions: true, + }); + } }); }); + // … }; Notifications.cleanUserOptOutFlag = async function () { @@ -227,8 +233,7 @@ function Notifications(props) { if (!pushToken || !pushToken.token || !pushToken.os) return; const api = new Frisbee({ baseURI }); - - return await api.post( + const postCall = await api.post( '/unsubscribe', Object.assign({}, _getHeaders(), { body: { @@ -240,6 +245,8 @@ function Notifications(props) { }, }), ); + Notifications.abandonPermissions(); + return postCall; }; Notifications.isNotificationsEnabled = async function () { diff --git a/ios/BlueWallet.xcodeproj/xcuserdata/marcosrodriguez.xcuserdatad/xcschemes/xcschememanagement.plist b/ios/BlueWallet.xcodeproj/xcuserdata/marcosrodriguez.xcuserdatad/xcschemes/xcschememanagement.plist index f35b236dc..b581aa931 100644 --- a/ios/BlueWallet.xcodeproj/xcuserdata/marcosrodriguez.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ios/BlueWallet.xcodeproj/xcuserdata/marcosrodriguez.xcuserdatad/xcschemes/xcschememanagement.plist @@ -47,7 +47,7 @@ WidgetsExtension.xcscheme_^#shared#^_ orderHint - 144 + 99 SuppressBuildableAutocreation diff --git a/ios/Podfile b/ios/Podfile index 86affba85..d3632bc56 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -16,7 +16,7 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ workspace 'BlueWallet' platform :ios, '13.0' prepare_react_native_project! -setup_permissions(['Camera']) +setup_permissions(['Camera', 'Notifications']) # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded # diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a35833167..d7a5be25c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -780,7 +780,7 @@ SPEC CHECKSUMS: RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa RNKeychain: a65256b6ca6ba6976132cc4124b238a5b13b3d9c RNLocalize: dbea38dcb344bf80ff18a1757b1becf11f70cae4 - RNPermissions: eae8b97d8ab0587f082966ee608f47c97b2a349b + RNPermissions: e9e703e08dfe50cf4d2ca45852eb45c4ac98f7b7 RNPrivacySnapshot: 71919dde3c6a29dd332115409c2aec564afee8f4 RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93 RNRate: ef3bcff84f39bb1d1e41c5593d3eea4aab2bd73a @@ -793,6 +793,6 @@ SPEC CHECKSUMS: RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236 Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 -PODFILE CHECKSUM: 23bb5c319ccbedd7e109c44fe4273afce6efb48a +PODFILE CHECKSUM: 27db07925dc3a89e9ecced7e377a50dba9deb58a -COCOAPODS: 1.11.3 +COCOAPODS: 1.13.0 diff --git a/screen/send/create.js b/screen/send/create.js index 9514b0aac..5175c8501 100644 --- a/screen/send/create.js +++ b/screen/send/create.js @@ -1,6 +1,6 @@ import React, { useCallback, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { TextInput, FlatList, Linking, TouchableOpacity, StyleSheet, Text, View, Platform, PermissionsAndroid, Alert } from 'react-native'; +import { TextInput, FlatList, Linking, TouchableOpacity, StyleSheet, Text, View, Platform, Alert } from 'react-native'; import Clipboard from '@react-native-clipboard/clipboard'; import { Icon } from 'react-native-elements'; import Share from 'react-native-share'; @@ -15,6 +15,7 @@ import { DynamicQRCode } from '../../components/DynamicQRCode'; import { isDesktop } from '../../blue_modules/environment'; import { useNavigation, useRoute, useTheme } from '@react-navigation/native'; import alert from '../../components/Alert'; +import { PERMISSIONS, RESULTS, request } from 'react-native-permissions'; const bitcoin = require('bitcoinjs-lib'); const currency = require('../../blue_modules/currency'); @@ -68,15 +69,8 @@ const SendCreate = () => { RNFS.unlink(filePath); }); } else if (Platform.OS === 'android') { - const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, { - title: loc.send.permission_storage_title, - message: loc.send.permission_storage_message, - buttonNeutral: loc.send.permission_storage_later, - buttonNegative: loc._.cancel, - buttonPositive: loc._.ok, - }); - - if (granted === PermissionsAndroid.RESULTS.GRANTED || Platform.Version >= 33) { + const granted = await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE); + if (granted === RESULTS.GRANTED) { console.log('Storage Permission: Granted'); const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`; try { diff --git a/screen/wallets/details.js b/screen/wallets/details.js index 3ef9df003..2acc7f3c1 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -14,7 +14,6 @@ import { StyleSheet, StatusBar, ScrollView, - PermissionsAndroid, InteractionManager, ActivityIndicator, I18nManager, @@ -45,6 +44,7 @@ import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electr import alert from '../../components/Alert'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import { writeFileAndExport } from '../../blue_modules/fs'; +import { PERMISSIONS, RESULTS, request } from 'react-native-permissions'; const prompt = require('../../helpers/prompt'); @@ -364,15 +364,8 @@ const WalletDetails = () => { RNFS.unlink(filePath); }); } else if (Platform.OS === 'android') { - const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, { - title: loc.send.permission_storage_title, - message: loc.send.permission_storage_message, - buttonNeutral: loc.send.permission_storage_later, - buttonNegative: loc._.cancel, - buttonPositive: loc._.ok, - }); - - if (granted === PermissionsAndroid.RESULTS.GRANTED || Platform.Version >= 33) { + const granted = await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE); + if (granted === RESULTS.GRANTED) { console.log('Storage Permission: Granted'); const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`; try {