diff --git a/blue_modules/storage-context.js b/blue_modules/storage-context.js index 3195c0fbc..1556e19a1 100644 --- a/blue_modules/storage-context.js +++ b/blue_modules/storage-context.js @@ -1,6 +1,7 @@ /* eslint-disable react/prop-types */ import { useAsyncStorage } from '@react-native-async-storage/async-storage'; import React, { createContext, useEffect, useState } from 'react'; +import { LayoutAnimation } from 'react-native'; import { AppStorage } from '../class'; import { FiatUnit } from '../models/fiatUnit'; const BlueApp = require('../BlueApp'); @@ -22,6 +23,8 @@ export const BlueStorageProvider = ({ children }) => { const getLanguageAsyncStorage = useAsyncStorage(AppStorage.LANG).getItem; const [newWalletAdded, setNewWalletAdded] = useState(false); const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false); + const [isDrawerListBlurred, _setIsDrawerListBlurred] = useState(false); + const setIsHandOffUseEnabledAsyncStorage = value => { setIsHandOffUseEnabled(value); return BlueApp.setItem(AppStorage.HANDOFF_STORAGE_KEY, value === true ? '1' : ''); @@ -50,6 +53,11 @@ export const BlueStorageProvider = ({ children }) => { })(); }, []); + const setIsDrawerListBlurred = value => { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + _setIsDrawerListBlurred(value); + }; + const getPreferredCurrency = async () => { const item = await getPreferredCurrencyAsyncStorage(); _setPreferredFiatCurrency(item); @@ -220,6 +228,8 @@ export const BlueStorageProvider = ({ children }) => { setIsHandOffUseEnabledAsyncStorage, walletTransactionUpdateStatus, setWalletTransactionUpdateStatus, + isDrawerListBlurred, + setIsDrawerListBlurred, }} > {children} diff --git a/loc/en.json b/loc/en.json index 2f9c529db..727b323e8 100644 --- a/loc/en.json +++ b/loc/en.json @@ -16,7 +16,9 @@ "seed": "Seed", "wallet_key": "Wallet key", "invalid_animated_qr_code_fragment" : "Invalid animated QRCode fragment. Please try again.", - "file_saved": "File ({filePath}) has been saved in your Downloads folder." + "file_saved": "File ({filePath}) has been saved in your Downloads folder.", + "discard_changes": "Discard changes?", + "discard_changes_detail": "You have unsaved changes. Are you sure to discard them and leave the screen?" }, "azteco": { "codeIs": "Your voucher code is", diff --git a/screen/wallets/addMultisigStep2.js b/screen/wallets/addMultisigStep2.js index 73b8e2f3b..8d7b2983b 100644 --- a/screen/wallets/addMultisigStep2.js +++ b/screen/wallets/addMultisigStep2.js @@ -1,5 +1,5 @@ /* global alert */ -import React, { useContext, useRef, useState } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import { ActivityIndicator, FlatList, @@ -12,6 +12,7 @@ import { Text, TouchableOpacity, View, + Alert, } from 'react-native'; import { Icon } from 'react-native-elements'; import { useNavigation, useRoute, useTheme } from '@react-navigation/native'; @@ -67,6 +68,7 @@ const WalletsAddMultisigStep2 = () => { const [importText, setImportText] = useState(''); const tooltip = useRef(); const data = useRef(new Array(n)); + const hasUnsavedChanges = Boolean(cosigners.length > 0 && cosigners.length !== n); const handleOnHelpPress = () => { navigation.navigate('WalletsAddMultisigHelp'); @@ -143,6 +145,28 @@ const WalletsAddMultisigStep2 = () => { setTimeout(_onCreate, 100); }; + useEffect(() => { + navigation.addListener('beforeRemove', e => { + if (e.data.action.type === 'POP' && hasUnsavedChanges) { + e.preventDefault(); + + // Prompt the user before leaving the screen + + Alert.alert(loc._.discard_changes, loc._.discard_changes_detail, [ + { text: loc._.cancel, style: 'cancel', onPress: () => {} }, + { + text: loc._.ok, + style: 'destructive', + // If the user confirmed, then we dispatch the action we blocked earlier + // This will continue the action that had triggered the removal of the screen + onPress: () => navigation.dispatch(e.data.action), + }, + ]); + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [navigation, hasUnsavedChanges, cosigners]); + const _onCreate = async () => { const w = new MultisigHDWallet(); w.setM(m); @@ -175,8 +199,7 @@ const WalletsAddMultisigStep2 = () => { setNewWalletAdded(true); A(A.ENUM.CREATED_WALLET); ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false }); - - navigation.dangerouslyGetParent().pop(); + navigation.dangerouslyGetParent().goBack(); }; const generateNewKey = () => { diff --git a/screen/wallets/drawerList.js b/screen/wallets/drawerList.js index c10883086..dd30c59ec 100644 --- a/screen/wallets/drawerList.js +++ b/screen/wallets/drawerList.js @@ -12,11 +12,14 @@ import { PlaceholderWallet } from '../../class'; import WalletImport from '../../class/wallet-import'; import loc from '../../loc'; import { BlueStorageContext } from '../../blue_modules/storage-context'; +import { BlurView } from '@react-native-community/blur'; const DrawerList = props => { console.log('drawerList rendering...'); const walletsCarousel = useRef(); - const { wallets, selectedWallet, pendingWallets, newWalletAdded, setNewWalletAdded } = useContext(BlueStorageContext); + const { wallets, selectedWallet, pendingWallets, newWalletAdded, setNewWalletAdded, isDrawerListBlurred } = useContext( + BlueStorageContext, + ); const [carouselData, setCarouselData] = useState([]); const height = useWindowDimensions().height; const { colors } = useTheme(); @@ -124,6 +127,9 @@ const DrawerList = props => { {renderWalletsCarousel()} + {isDrawerListBlurred && ( + + )} ); }; @@ -144,6 +150,13 @@ const styles = StyleSheet.create({ paddingLeft: 32, paddingVertical: 10, }, + absolute: { + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + right: 0, + }, }); DrawerList.propTypes = { diff --git a/screen/wallets/viewEditMultisigCosigners.js b/screen/wallets/viewEditMultisigCosigners.js index b9b026ab7..808d80cb7 100644 --- a/screen/wallets/viewEditMultisigCosigners.js +++ b/screen/wallets/viewEditMultisigCosigners.js @@ -1,5 +1,5 @@ /* global alert */ -import React, { useContext, useRef, useState, useCallback } from 'react'; +import React, { useContext, useRef, useState, useCallback, useEffect } from 'react'; import { ActivityIndicator, Alert, @@ -46,8 +46,8 @@ const fs = require('../../blue_modules/fs'); const ViewEditMultisigCosigners = () => { const hasLoaded = useRef(false); const { colors } = useTheme(); - const { wallets, setWalletsWithNewOrder } = useContext(BlueStorageContext); - const { navigate, goBack } = useNavigation(); + const { wallets, setWalletsWithNewOrder, setIsDrawerListBlurred } = useContext(BlueStorageContext); + const { navigate, dispatch, goBack, addListener } = useNavigation(); const route = useRoute(); const { walletId } = route.params; const w = useRef(wallets.find(wallet => wallet.getID() === walletId)); @@ -131,6 +131,34 @@ const ViewEditMultisigCosigners = () => { }, }); + useEffect(() => { + addListener('beforeRemove', e => { + if (!isSaveButtonDisabled) { + e.preventDefault(); + + // Prompt the user before leaving the screen + + Alert.alert(loc._.discard_changes, loc._.discard_changes_detail, [ + { text: loc._.cancel, style: 'cancel', onPress: () => {} }, + { + text: loc._.ok, + style: 'destructive', + // If the user confirmed, then we dispatch the action we blocked earlier + // This will continue the action that had triggered the removal of the screen + onPress: () => dispatch(e.data.action), + }, + ]); + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isSaveButtonDisabled]); + + useEffect(() => { + setIsDrawerListBlurred(true); + return () => setIsDrawerListBlurred(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const exportCosigner = () => { setIsShareModalVisible(false); setTimeout(() => fs.writeFileAndExport(exportFilename, exportString), 1000);