Skip to content

Commit 4cebd26

Browse files
committed
fix(ui): android hardware back press for bottom-sheets
1 parent 57fa923 commit 4cebd26

File tree

16 files changed

+108
-24
lines changed

16 files changed

+108
-24
lines changed

src/hooks/bottomSheet.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
import { useEffect, useMemo, useRef } from 'react';
1+
import { useCallback, useEffect, useMemo, useRef } from 'react';
22
import { BackHandler, NativeEventSubscription } from 'react-native';
3+
import { useFocusEffect, useNavigation } from '@react-navigation/native';
34
import {
45
useSafeAreaFrame,
56
useSafeAreaInsets,
67
} from 'react-native-safe-area-context';
78

89
import { useAppDispatch, useAppSelector } from './redux';
9-
import { closeSheet } from '../store/slices/ui';
10-
import { viewControllerIsOpenSelector } from '../store/reselect/ui';
10+
import { objectKeys } from '../utils/objectKeys';
1111
import { TViewController } from '../store/types/ui';
12+
import { closeAllSheets, closeSheet } from '../store/slices/ui';
13+
import {
14+
viewControllerIsOpenSelector,
15+
viewControllersSelector,
16+
} from '../store/reselect/ui';
1217

1318
export const useSnapPoints = (
1419
size: 'small' | 'medium' | 'large' | 'calendar',
@@ -44,6 +49,10 @@ export const useSnapPoints = (
4449
return snapPoints;
4550
};
4651

52+
/**
53+
* Hook to handle hardware back press (Android) when bottom sheet is open
54+
* for simple one-sheet screens
55+
*/
4756
export const useBottomSheetBackPress = (
4857
viewController: TViewController,
4958
): void => {
@@ -75,3 +84,47 @@ export const useBottomSheetBackPress = (
7584
};
7685
}, [isBottomSheetOpen, viewController, dispatch]);
7786
};
87+
88+
/**
89+
* Hook to handle hardware back press (Android) when bottom sheet is open
90+
* for screens that are part of a navigator nested in a bottom sheet
91+
*/
92+
export const useBottomSheetScreenBackPress = (): void => {
93+
const dispatch = useAppDispatch();
94+
const navigation = useNavigation();
95+
const viewControllers = useAppSelector(viewControllersSelector);
96+
97+
const isBottomSheetOpen = useMemo(() => {
98+
const viewControllerKeys = objectKeys(viewControllers);
99+
return viewControllerKeys.some((view) => viewControllers[view].isOpen);
100+
}, [viewControllers]);
101+
102+
const backHandlerSubscriptionRef = useRef<NativeEventSubscription | null>(
103+
null,
104+
);
105+
106+
useFocusEffect(
107+
useCallback(() => {
108+
if (!isBottomSheetOpen) {
109+
return;
110+
}
111+
112+
backHandlerSubscriptionRef.current = BackHandler.addEventListener(
113+
'hardwareBackPress',
114+
() => {
115+
if (navigation.canGoBack()) {
116+
navigation.goBack();
117+
} else {
118+
dispatch(closeAllSheets());
119+
}
120+
return true;
121+
},
122+
);
123+
124+
return (): void => {
125+
backHandlerSubscriptionRef.current?.remove();
126+
backHandlerSubscriptionRef.current = null;
127+
};
128+
}, [dispatch, isBottomSheetOpen, navigation]),
129+
);
130+
};

src/navigation/bottom-sheet/LNURLWithdrawNavigation.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import { NavigationContainer } from '../../styles/components';
1111
import BottomSheetWrapper from '../../components/BottomSheetWrapper';
1212
import Amount from '../../screens/Wallets/LNURLWithdraw/Amount';
1313
import Confirm from '../../screens/Wallets/LNURLWithdraw/Confirm';
14-
import { useSnapPoints } from '../../hooks/bottomSheet';
14+
import {
15+
useBottomSheetBackPress,
16+
useSnapPoints,
17+
} from '../../hooks/bottomSheet';
1518
import { useAppSelector } from '../../hooks/redux';
1619
import { viewControllerSelector } from '../../store/reselect/ui';
1720
import { __E2E__ } from '../../constants/env';
@@ -37,6 +40,8 @@ const LNURLWithdrawNavigation = (): ReactElement => {
3740
return viewControllerSelector(state, 'lnurlWithdraw');
3841
});
3942

43+
useBottomSheetBackPress('lnurlWithdraw');
44+
4045
if (!wParams) {
4146
return <></>;
4247
}

src/navigation/bottom-sheet/PubkyAuth.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import SafeAreaInset from '../../components/SafeAreaInset';
1414
import Button from '../../components/buttons/Button';
1515
import BottomSheetNavigationHeader from '../../components/BottomSheetNavigationHeader';
1616
import { useAppSelector } from '../../hooks/redux';
17-
import { useSnapPoints } from '../../hooks/bottomSheet';
17+
import {
18+
useBottomSheetBackPress,
19+
useSnapPoints,
20+
} from '../../hooks/bottomSheet';
1821
import { viewControllerSelector } from '../../store/reselect/ui.ts';
1922
import { auth, parseAuthUrl } from '@synonymdev/react-native-pubky';
2023
import { getPubkySecretKey } from '../../utils/pubky';
@@ -89,6 +92,8 @@ const PubkyAuth = (): ReactElement => {
8992
const [authorizing, setAuthorizing] = React.useState(false);
9093
const [authSuccess, setAuthSuccess] = React.useState(false);
9194

95+
useBottomSheetBackPress('pubkyAuth');
96+
9297
useEffect(() => {
9398
const fetchParsed = async (): Promise<void> => {
9499
const res = await parseAuthUrl(url);

src/screens/Settings/Backup/ConfirmPassphrase.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import SafeAreaInset from '../../../components/SafeAreaInset';
99
import GradientView from '../../../components/GradientView';
1010
import Button from '../../../components/buttons/Button';
1111
import { capitalize } from '../../../utils/helpers';
12-
import { useBottomSheetBackPress } from '../../../hooks/bottomSheet';
1312
import type { BackupScreenProps } from '../../../navigation/types';
1413

1514
const ConfirmPassphrase = ({
@@ -20,8 +19,6 @@ const ConfirmPassphrase = ({
2019
const { bip39Passphrase: origPass } = route.params;
2120
const [bip39Passphrase, setPassphrase] = useState<string>('');
2221

23-
useBottomSheetBackPress('backupNavigation');
24-
2522
return (
2623
<GradientView style={styles.gradient}>
2724
<BottomSheetNavigationHeader title={t('pass_confirm')} />

src/screens/Settings/Backup/ShowPassphrase.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigati
99
import SafeAreaInset from '../../../components/SafeAreaInset';
1010
import GradientView from '../../../components/GradientView';
1111
import Button from '../../../components/buttons/Button';
12-
import { useBottomSheetBackPress } from '../../../hooks/bottomSheet';
1312
import type { BackupScreenProps } from '../../../navigation/types';
1413

1514
const ShowPassphrase = ({
@@ -19,8 +18,6 @@ const ShowPassphrase = ({
1918
const { t } = useTranslation('security');
2019
const { bip39Passphrase, seed } = route.params;
2120

22-
useBottomSheetBackPress('backupNavigation');
23-
2421
return (
2522
<GradientView style={styles.gradient}>
2623
<BottomSheetNavigationHeader title={t('pass_your')} />

src/screens/Settings/PIN/AskForBiometrics.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import GradientView from '../../../components/GradientView';
2424
import Button from '../../../components/buttons/Button';
2525
import { IsSensorAvailableResult } from '../../../components/Biometrics';
2626
import { useAppDispatch } from '../../../hooks/redux';
27+
import { useBottomSheetScreenBackPress } from '../../../hooks/bottomSheet';
2728
import rnBiometrics from '../../../utils/biometrics';
2829
import { showToast } from '../../../utils/notifications';
2930
import { updateSettings } from '../../../store/slices/settings';
@@ -39,6 +40,8 @@ const AskForBiometrics = ({
3940
const [biometryData, setBiometricData] = useState<IsSensorAvailableResult>();
4041
const [shouldEnableBiometrics, setShouldEnableBiometrics] = useState(false);
4142

43+
useBottomSheetScreenBackPress();
44+
4245
useEffect(() => {
4346
(async (): Promise<void> => {
4447
const data = await rnBiometrics.isSensorAvailable();

src/screens/Settings/PIN/ChoosePIN.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@ import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigati
1414
import GradientView from '../../../components/GradientView';
1515
import NumberPad from '../../../components/NumberPad';
1616
import useColors from '../../../hooks/colors';
17+
import { useAppDispatch } from '../../../hooks/redux';
1718
import { vibrate } from '../../../utils/helpers';
18-
import { useBottomSheetBackPress } from '../../../hooks/bottomSheet';
1919
import { addPin } from '../../../utils/settings';
2020
import { hideTodo } from '../../../store/slices/todos';
2121
import { pinTodo } from '../../../store/shapes/todos';
2222
import type { PinScreenProps } from '../../../navigation/types';
23-
import { useAppDispatch } from '../../../hooks/redux';
2423

2524
const ChoosePIN = ({
2625
navigation,
@@ -49,8 +48,6 @@ const ChoosePIN = ({
4948
// reset pin on back
5049
useFocusEffect(useCallback(() => setPin(''), []));
5150

52-
useBottomSheetBackPress('PINNavigation');
53-
5451
useEffect(() => {
5552
const timer = setTimeout(async () => {
5653
if (pin.length !== 4) {

src/screens/Settings/PIN/Result.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import SafeAreaInset from '../../../components/SafeAreaInset';
99
import GradientView from '../../../components/GradientView';
1010
import Button from '../../../components/buttons/Button';
1111
import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
12+
import { useBottomSheetScreenBackPress } from '../../../hooks/bottomSheet';
1213
import { closeSheet } from '../../../store/slices/ui';
1314
import { updateSettings } from '../../../store/slices/settings';
1415
import { pinForPaymentsSelector } from '../../../store/reselect/settings';
@@ -22,6 +23,8 @@ const Result = ({ route }: PinScreenProps<'Result'>): ReactElement => {
2223
const dispatch = useAppDispatch();
2324
const pinForPayments = useAppSelector(pinForPaymentsSelector);
2425

26+
useBottomSheetScreenBackPress();
27+
2528
const biometricsName = useMemo(
2629
() =>
2730
type === 'TouchID'

src/screens/Wallets/LNURLPay/Amount.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ import {
3131
} from '../../../store/reselect/settings';
3232
import { useAppSelector } from '../../../hooks/redux';
3333
import { useBalance, useSwitchUnit } from '../../../hooks/wallet';
34+
import { useBottomSheetScreenBackPress } from '../../../hooks/bottomSheet';
3435
import { getNumberPadText } from '../../../utils/numberpad';
3536
import { convertToSats } from '../../../utils/conversion';
36-
import type { SendScreenProps } from '../../../navigation/types';
3737
import { showToast } from '../../../utils/notifications';
38+
import type { SendScreenProps } from '../../../navigation/types';
3839

3940
const LNURLAmount = ({
4041
navigation,
@@ -52,6 +53,8 @@ const LNURLAmount = ({
5253
const [text, setText] = useState('');
5354
const [error, setError] = useState(false);
5455

56+
useBottomSheetScreenBackPress();
57+
5558
const amount = useMemo((): number => {
5659
return convertToSats(text, conversionUnit);
5760
}, [text, conversionUnit]);

src/screens/Wallets/LNURLPay/Confirm.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import { useTranslation } from 'react-i18next';
99
import { StyleSheet, TouchableOpacity, View } from 'react-native';
1010
import { FadeIn, FadeOut } from 'react-native-reanimated';
1111

12+
import { BodySSB, Caption13Up } from '../../../styles/text';
13+
import { Checkmark, LightningHollow } from '../../../styles/icons';
14+
import { AnimatedView, BottomSheetTextInput } from '../../../styles/components';
1215
import AmountToggle from '../../../components/AmountToggle';
1316
import Biometrics from '../../../components/Biometrics';
1417
import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigationHeader';
@@ -19,6 +22,7 @@ import SwipeToConfirm from '../../../components/SwipeToConfirm';
1922
import useColors from '../../../hooks/colors';
2023
import useKeyboard, { Keyboard } from '../../../hooks/keyboard';
2124
import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
25+
import { useBottomSheetScreenBackPress } from '../../../hooks/bottomSheet';
2226
import type { SendScreenProps } from '../../../navigation/types';
2327
import {
2428
pinForPaymentsSelector,
@@ -28,9 +32,6 @@ import {
2832
import { addPendingPayment } from '../../../store/slices/lightning';
2933
import { updateMetaTxComment } from '../../../store/slices/metadata';
3034
import { EActivityType } from '../../../store/types/activity';
31-
import { AnimatedView, BottomSheetTextInput } from '../../../styles/components';
32-
import { Checkmark, LightningHollow } from '../../../styles/icons';
33-
import { BodySSB, Caption13Up } from '../../../styles/text';
3435
import { FeeText } from '../../../utils/fees';
3536
import {
3637
decodeLightningInvoice,
@@ -78,6 +79,8 @@ const LNURLConfirm = ({
7879
const [showBiotmetrics, setShowBiometrics] = useState(false);
7980
const [comment, setComment] = useState('');
8081

82+
useBottomSheetScreenBackPress();
83+
8184
const onError = useCallback(
8285
(errorMessage: string) => {
8386
navigation.navigate('Error', { errorMessage });

0 commit comments

Comments
 (0)