expo-react-native-toastify
Version:
expo-react-native-toastify allows you to add notifications to your expo react-native app (ios, android, web) with ease.
355 lines (326 loc) • 9.44 kB
JavaScript
import Icon from "react-native-vector-icons/Ionicons";
import Modal from "react-native-modal";
import React, { Component } from "react";
import { RFPercentage } from "react-native-responsive-fontsize";
import {
View,
Text,
Animated,
Dimensions,
TouchableOpacity,
Platform,
} from "react-native";
import defaultProps from "../utils/defaultProps";
import { Colors } from "../config/theme";
import styles from "./styles";
const { height } = Dimensions.get("window");
class ToastManager extends Component {
constructor(props) {
super(props);
ToastManager.__singletonRef = this;
}
state = {
isShow: false,
content: "",
opacityValue: new Animated.Value(1),
barWidth: new Animated.Value(RFPercentage(32)),
barColor: Colors.default,
icon: this.props.icon || "checkmark-circle",
position: this.props.position,
theme: 'light',
width: this.props.width,
modalWidth: this.props.modalWidth,
modalMaxWidth: this.props.modalMaxWidth,
height: this.props.height,
zIndex: this.props.zIndex,
animationStyle: {
upInUpOut: {
animationIn: "slideInDown",
animationOut: "slideOutUp",
},
rightInOut: {
animationIn: "slideInRight",
animationOut: "slideOutRight",
},
zoomInOut: {
animationIn: "zoomInDown",
animationOut: "zoomOutUp",
},
},
};
static info = (content, props = {}) => {
if (!ToastManager.__singletonRef) return;
const theme = props.theme || 'light';
ToastManager.__singletonRef.show(
content,
Colors[theme].progressBar.info,
"information-circle",
props.position,
props.duration,
{ theme }
);
};
static success = (content, props = {}) => {
if (!ToastManager.__singletonRef) return;
const theme = props.theme || 'light';
ToastManager.__singletonRef.show(
content,
Colors[theme].progressBar.success,
"checkmark-circle",
props.position,
props.duration,
{ theme }
);
};
static warn = (content, props = {}) => {
if (!ToastManager.__singletonRef) return;
const theme = props.theme || 'light';
ToastManager.__singletonRef.show(
content,
Colors[theme].progressBar.warn,
"warning",
props.position,
props.duration,
{ theme }
);
};
static error = (content, props = {}) => {
if (!ToastManager.__singletonRef) return;
const theme = props.theme || 'light';
ToastManager.__singletonRef.show(
content,
Colors[theme].progressBar.error,
"alert-circle",
props.position,
props.duration,
{ theme }
);
};
static custom = (content, props = {}) => {
if (!ToastManager.__singletonRef) return;
const theme = props.theme || 'light';
ToastManager.__singletonRef.show(
content,
props.barColor || Colors[theme].progressBar.default,
props.icon,
props.position,
props.duration,
{
theme,
style: {
backgroundColor: props.style?.backgroundColor || Colors[theme].background,
...props.style,
},
textStyle: props.textStyle,
width: props.width,
height: props.height,
}
);
};
show = (content = "", barColor = Colors.default, icon, position, duration = this.props.duration, customProps = {}) => {
const isComponent = typeof content !== 'string';
const width = customProps.width || (isComponent ? 350 : 300);
const modalWidth = customProps.modalWidth || (isComponent ? 400 : 350);
this.state.barWidth.setValue(width);
this.setState({
isShow: true,
duration,
content,
barColor,
icon,
width,
modalWidth,
height: customProps.height || (isComponent ? 80 : 68),
...customProps
});
if (position) this.setState({ position });
this.isShow = true;
if (duration !== 0) {
this.close(duration);
this.startProgressBar(duration);
}
};
startProgressBar = (duration) => {
Animated.timing(this.state.barWidth, {
toValue: 0,
duration: duration,
useNativeDriver: false,
}).start();
};
close = (duration) => {
if (!this.isShow && !this.state.isShow) return;
this.resetAll();
this.timer = setTimeout(() => {
this.setState({ isShow: false });
}, duration || this.state.duration);
};
position = () => {
const { position } = this.state;
if (position === "top") return this.props.positionValue;
if (position === "center") return height / 2 - RFPercentage(9);
return height - this.props.positionValue - RFPercentage(10);
};
handleBar = () => {
// Remove this since we don't want to restart animation on every render
// Animated.timing(this.state.barWidth, {
// toValue: 0,
// duration: this.state.duration,
// useNativeDriver: false,
// }).start();
};
pause = () => {
if (this.state.duration === 0) return; // Don't pause if it's a static toast
clearTimeout(this.timer); // Clear the close timer
Animated.timing(this.state.barWidth).stop(); // Stop the progress bar animation
this.setState({
oldDuration: this.state.duration,
pausedWidth: this.state.barWidth.__getValue() // Store current width
});
};
resume = () => {
if (this.state.duration === 0) return; // Don't resume if it's a static toast
const remainingTime = (this.state.pausedWidth / this.state.width) * this.state.oldDuration;
// Restart the close timer
this.close(remainingTime);
// Resume the progress bar animation from current position
Animated.timing(this.state.barWidth, {
toValue: 0,
duration: remainingTime,
useNativeDriver: false,
}).start();
};
hideToast = () => {
this.resetAll();
this.setState({ isShow: false });
this.isShow = false;
if (!this.isShow && !this.state.isShow) return;
};
resetAll = () => {
clearTimeout(this.timer);
};
render() {
const {
animationIn,
animationStyle,
animationOut,
backdropTransitionOutTiming,
backdropTransitionInTiming,
animationInTiming,
animationOutTiming,
backdropColor,
backdropOpacity,
hasBackdrop,
} = this.props;
const {
isShow,
animationStyle: stateAnimationStyle,
barColor,
icon,
content,
barWidth,
theme,
width,
modalWidth,
modalMaxWidth,
height,
zIndex,
} = this.state;
// Get mouse event props only for web platform
const mouseEventProps = Platform.OS === 'web' ? {
onMouseEnter: this.pause,
onMouseLeave: this.resume,
} : {};
return (
<Modal
animationIn={
animationIn || stateAnimationStyle[animationStyle].animationIn
}
animationOut={
animationOut || stateAnimationStyle[animationStyle].animationOut
}
backdropTransitionOutTiming={backdropTransitionOutTiming}
backdropTransitionInTiming={backdropTransitionInTiming}
animationInTiming={animationInTiming}
animationOutTiming={animationOutTiming}
onTouchStart={this.pause}
onTouchEnd={this.resume}
{...mouseEventProps}
swipeDirection={["up", "down", "left", "right"]}
onSwipeComplete={this.hideToast}
onModalHide={this.resetAll}
isVisible={isShow}
coverScreen={false}
backdropColor={backdropColor}
backdropOpacity={backdropOpacity}
hasBackdrop={hasBackdrop}
style={[
styles.modalContainer,
{
zIndex: zIndex || 20000,
width: typeof content === 'string' ? 300 : (modalWidth || 400),
alignSelf: 'center'
}
]}
>
<View
style={[
styles.mainContainer,
{
width: width || 350,
height: height || 80,
backgroundColor: Colors[theme].back,
top: this.position(),
},
]}
>
<TouchableOpacity
onPress={this.hideToast}
activeOpacity={0.9}
style={styles.hideButton}
>
<Icon
name="close-outline"
size={22}
color={Colors[theme].text}
/>
</TouchableOpacity>
<View style={styles.content}>
{typeof content === 'string' ? (
<>
{icon && (
<Icon
name={icon}
size={24}
color={barColor}
style={styles.iconWrapper}
/>
)}
<Text style={[styles.textStyle, { color: Colors[theme].text }]}>
{content}
</Text>
</>
) : (
content
)}
</View>
<View style={styles.progressBarContainer}>
<Animated.View
style={[
styles.progressBar,
{ width: barWidth, backgroundColor: barColor },
]}
/>
</View>
</View>
</Modal>
);
}
}
ToastManager.defaultProps = {
...defaultProps,
zIndex: 20000,
width: 300,
modalWidth: 300,
height: 80,
};
export default ToastManager;