BlueWallet/components/BottomModal.tsx

251 lines
7.0 KiB
TypeScript
Raw Normal View History

2024-08-24 17:53:33 +02:00
import React, { forwardRef, useImperativeHandle, useRef, ReactElement, ComponentType, ReactNode } from 'react';
2024-07-02 16:41:17 +02:00
import { SheetSize, SizeInfo, TrueSheet, TrueSheetProps } from '@lodev09/react-native-true-sheet';
2024-08-24 01:53:44 +02:00
import { Keyboard, StyleSheet, View, TouchableOpacity, Platform, GestureResponderEvent, Text } from 'react-native';
2024-08-23 19:40:27 +02:00
import Ionicons from 'react-native-vector-icons/Ionicons';
import SaveFileButton from './SaveFileButton';
2024-08-24 01:53:44 +02:00
import { useTheme } from './themes';
import { Image } from '@rneui/base';
2024-05-20 11:54:13 +02:00
2024-06-30 19:17:55 +02:00
interface BottomModalProps extends TrueSheetProps {
2024-02-10 05:35:24 +01:00
children?: React.ReactNode;
2024-06-30 19:17:55 +02:00
onClose?: () => void;
2024-08-24 01:53:44 +02:00
onCloseModalPressed?: () => Promise<void>;
2024-06-30 19:17:55 +02:00
isGrabberVisible?: boolean;
2024-07-01 00:27:37 +02:00
sizes?: SheetSize[] | undefined;
2024-08-02 19:08:14 +02:00
footer?: ReactElement | ComponentType<any> | null;
2024-07-01 18:58:19 +02:00
footerDefaultMargins?: boolean | number;
2024-07-02 16:41:17 +02:00
onPresent?: () => void;
onSizeChange?: (size: SizeInfo) => void;
2024-07-06 22:58:42 +02:00
showCloseButton?: boolean;
2024-08-23 19:40:27 +02:00
shareContent?: BottomModalShareContent;
shareButtonOnPress?: (event: GestureResponderEvent) => void;
header?: ReactElement | ComponentType<any> | null;
2024-08-24 01:53:44 +02:00
headerTitle?: string;
headerSubtitle?: string;
2024-02-10 05:35:24 +01:00
}
2024-08-23 19:40:27 +02:00
type BottomModalShareContent = {
fileName: string;
fileContent: string;
};
2024-06-30 19:17:55 +02:00
export interface BottomModalHandle {
present: () => Promise<void>;
dismiss: () => Promise<void>;
}
const BottomModal = forwardRef<BottomModalHandle, BottomModalProps>(
2024-07-01 18:58:19 +02:00
(
2024-07-06 22:58:42 +02:00
{
onClose,
2024-08-24 01:53:44 +02:00
onCloseModalPressed,
2024-07-06 22:58:42 +02:00
onPresent,
onSizeChange,
showCloseButton = true,
isGrabberVisible = true,
2024-08-23 19:40:27 +02:00
shareContent,
shareButtonOnPress,
2024-07-06 22:58:42 +02:00
sizes = ['auto'],
footer,
footerDefaultMargins,
2024-08-23 19:40:27 +02:00
header,
2024-08-24 01:53:44 +02:00
headerTitle,
headerSubtitle,
2024-07-06 22:58:42 +02:00
children,
...props
},
2024-07-01 18:58:19 +02:00
ref,
) => {
2024-06-30 19:17:55 +02:00
const trueSheetRef = useRef<TrueSheet>(null);
2024-08-24 01:53:44 +02:00
const { colors } = useTheme();
const stylesHook = StyleSheet.create({
barButton: {
backgroundColor: colors.lightButton,
},
headerTitle: {
color: colors.foregroundColor,
},
});
2024-02-10 05:35:24 +01:00
2024-06-30 19:17:55 +02:00
useImperativeHandle(ref, () => ({
present: async () => {
2024-07-04 21:55:58 +02:00
Keyboard.dismiss();
2024-06-30 19:17:55 +02:00
if (trueSheetRef.current?.present) {
await trueSheetRef.current.present();
} else {
return Promise.resolve();
}
},
dismiss: async () => {
2024-07-04 21:55:58 +02:00
Keyboard.dismiss();
2024-06-30 19:17:55 +02:00
if (trueSheetRef.current?.dismiss) {
await trueSheetRef.current.dismiss();
} else {
return Promise.resolve();
}
},
}));
2024-08-24 01:53:44 +02:00
const dismiss = async () => {
2024-08-24 20:08:00 +02:00
try {
await onCloseModalPressed?.();
await trueSheetRef.current?.dismiss();
} catch (error) {
console.error('Error during dismiss:', error);
}
2024-07-06 22:58:42 +02:00
};
2024-08-23 19:40:27 +02:00
const renderTopRightButton = () => {
const buttons = [];
if (shareContent || shareButtonOnPress) {
if (shareContent) {
buttons.push(
<SaveFileButton
2024-08-24 01:53:44 +02:00
style={[styles.topRightButton, stylesHook.barButton]}
2024-08-23 19:40:27 +02:00
fileContent={shareContent.fileContent}
fileName={shareContent.fileName}
testID="ModalShareButton"
2024-08-24 01:53:44 +02:00
key="ModalShareButton"
2024-08-23 19:40:27 +02:00
>
2024-08-24 01:53:44 +02:00
<Ionicons name="share" size={20} color={colors.buttonTextColor} />
2024-08-23 19:40:27 +02:00
</SaveFileButton>,
);
} else if (shareButtonOnPress) {
buttons.push(
2024-08-24 01:53:44 +02:00
<TouchableOpacity
testID="ModalShareButton"
key="ModalShareButton"
style={[styles.topRightButton, stylesHook.barButton]}
onPress={shareButtonOnPress}
>
<Ionicons name="share" size={20} color={colors.buttonTextColor} />
2024-08-23 19:40:27 +02:00
</TouchableOpacity>,
);
}
}
if (showCloseButton) {
buttons.push(
2024-08-24 01:53:44 +02:00
<TouchableOpacity
style={[styles.topRightButton, stylesHook.barButton]}
onPress={dismiss}
key="ModalDoneButton"
testID="ModalDoneButton"
>
{Platform.OS === 'ios' ? (
<Ionicons name="close" size={20} color={colors.buttonTextColor} />
) : (
<Image source={require('../img/close.png')} style={styles.closeButton} />
)}
2024-08-23 19:40:27 +02:00
</TouchableOpacity>,
);
}
return <View style={styles.topRightButtonContainer}>{buttons}</View>;
};
const renderHeader = () => {
2024-08-24 01:53:44 +02:00
if (headerTitle || headerSubtitle) {
return (
<View style={styles.headerContainer}>
<View style={styles.headerContent}>
{headerTitle && <Text style={[styles.headerTitle, stylesHook.headerTitle]}>{headerTitle}</Text>}
{headerSubtitle && <Text style={[styles.headerSubtitle, stylesHook.headerTitle]}>{headerSubtitle}</Text>}
</View>
{renderTopRightButton()}
</View>
);
}
2024-08-23 19:40:27 +02:00
return (
<View style={styles.headerContainer}>
<View style={styles.headerContent}>{typeof header === 'function' ? <header /> : header}</View>
{renderTopRightButton()}
</View>
);
};
2024-07-06 22:58:42 +02:00
2024-08-23 19:40:27 +02:00
const renderFooter = (): ReactElement | undefined => {
2024-07-06 22:58:42 +02:00
// Footer is not working correctly on Android yet.
if (!footer) return undefined;
2024-07-01 18:58:19 +02:00
if (React.isValidElement(footer)) {
2024-07-06 22:58:42 +02:00
return footerDefaultMargins ? <View style={styles.footerContainer}>{footer}</View> : footer;
} else if (typeof footer === 'function') {
2024-08-24 19:35:13 +02:00
const ModalFooterComponent = footer as ComponentType<any>;
return <ModalFooterComponent />;
2024-07-01 18:58:19 +02:00
}
2024-07-06 22:58:42 +02:00
return undefined;
};
2024-07-01 18:58:19 +02:00
2024-08-24 17:53:33 +02:00
const FooterComponent = Platform.OS !== 'android' && renderFooter();
2024-07-06 23:55:03 +02:00
2024-06-30 19:17:55 +02:00
return (
<TrueSheet
ref={trueSheetRef}
2024-07-01 00:27:37 +02:00
sizes={sizes}
2024-06-30 19:17:55 +02:00
onDismiss={onClose}
onPresent={onPresent}
onSizeChange={onSizeChange}
grabber={isGrabberVisible}
2024-08-24 17:53:33 +02:00
FooterComponent={FooterComponent as ReactElement}
2024-06-30 19:17:55 +02:00
{...props}
>
2024-08-23 19:40:27 +02:00
<View style={styles.childrenContainer}>{children}</View>
2024-08-24 17:53:33 +02:00
{Platform.OS === 'android' && (renderFooter() as ReactNode)}
2024-08-24 19:35:13 +02:00
{renderHeader()}
2024-06-30 19:17:55 +02:00
</TrueSheet>
);
},
);
2020-11-17 09:43:38 +01:00
export default BottomModal;
2024-08-23 19:40:27 +02:00
const styles = StyleSheet.create({
footerContainer: {
alignItems: 'center',
justifyContent: 'center',
},
headerContainer: {
2024-08-24 19:35:13 +02:00
position: 'absolute',
2024-08-23 19:40:27 +02:00
flexDirection: 'row',
alignItems: 'center',
2024-08-24 01:53:44 +02:00
paddingVertical: 8,
2024-08-23 19:40:27 +02:00
minHeight: 22,
2024-08-24 19:35:13 +02:00
right: 16,
top: 16,
2024-08-23 19:40:27 +02:00
},
headerContent: {
flex: 1,
justifyContent: 'center',
minHeight: 22,
},
2024-08-24 01:53:44 +02:00
headerTitle: {
fontSize: 18,
fontWeight: 'bold',
},
closeButton: {
width: 10,
height: 10,
},
headerSubtitle: {
fontSize: 14,
},
2024-08-23 19:40:27 +02:00
topRightButton: {
justifyContent: 'center',
alignItems: 'center',
width: 30,
height: 30,
borderRadius: 15,
marginLeft: 22,
},
topRightButtonContainer: {
flexDirection: 'row',
alignItems: 'center',
},
childrenContainer: {
2024-08-24 19:35:13 +02:00
paddingTop: 66,
paddingHorizontal: 16,
2024-08-24 01:53:44 +02:00
width: '100%',
2024-08-23 19:40:27 +02:00
},
});