From 85bb80e45b0a466358e52ace65e9adb7a36ef4db Mon Sep 17 00:00:00 2001 From: Dumitru Gotca Date: Tue, 27 Feb 2024 13:34:16 +0200 Subject: [PATCH 1/4] Display multiple toasts --- .gitignore | 4 +- components/ToastManager.tsx | 482 +++++++++++++++++++++--------------- components/styles.ts | 5 +- package.json | 2 +- utils/default.ts | 35 +++ utils/defaultProps.ts | 23 -- utils/generateUUID.ts | 2 + utils/interfaces.ts | 76 +++--- 8 files changed, 374 insertions(+), 255 deletions(-) create mode 100644 utils/default.ts delete mode 100644 utils/defaultProps.ts create mode 100644 utils/generateUUID.ts diff --git a/.gitignore b/.gitignore index 2ea1a85..e11bbcd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ build dist # Dependency directory -node_modules \ No newline at end of file +node_modules + +.idea \ No newline at end of file diff --git a/components/ToastManager.tsx b/components/ToastManager.tsx index 0e1ba46..cde7892 100644 --- a/components/ToastManager.tsx +++ b/components/ToastManager.tsx @@ -1,213 +1,299 @@ -import Icon from 'react-native-vector-icons/Ionicons' +import React, {Component} from 'react' +import {Animated, EmitterSubscription, Keyboard, KeyboardEvent, Text, TouchableOpacity, View,} from 'react-native' import Modal from 'react-native-modal' -import React, { Component } from 'react' -import { RFPercentage } from 'react-native-responsive-fontsize' -import { View, Text, Animated, Dimensions, TouchableOpacity } from 'react-native' +import Icon from 'react-native-vector-icons/Ionicons' +import {Colors} from '../config/theme' -import defaultProps from '../utils/defaultProps' -import { Colors } from '../config/theme' +import {defaultProps, defaultToastData} from '../utils/default' +import generateUUID from "../utils/generateUUID"; +import { + AnimationStyleProps, + NotificationArgumentsType, + ToastManagerProps, + ToastManagerState, + ToastType +} from '../utils/interfaces' import styles from './styles' -import { ToastManagerProps, ToastManagerState } from '../utils/interfaces' -const { height } = Dimensions.get('window') -class ToastManager extends Component { - private timer: NodeJS.Timeout - private isShow: boolean - static defaultProps = defaultProps - static __singletonRef: ToastManager | null - - constructor(props: ToastManagerProps) { - super(props) - ToastManager.__singletonRef = this - this.timer = setTimeout(() => {}, 0) // Initialize timer with a dummy value - this.isShow = false - } - - state: any = { - isShow: false, - text: '', - opacityValue: new Animated.Value(1), - barWidth: new Animated.Value(RFPercentage(32)), - barColor: Colors.default, - icon: 'checkmark-circle', - position: this.props.position, - animationStyle: { - upInUpOut: { +const animationStyleOptions: AnimationStyleProps = { + upInUpOut: { animationIn: 'slideInDown', animationOut: 'slideOutUp', - }, - rightInOut: { + }, + rightInOut: { animationIn: 'slideInRight', animationOut: 'slideOutRight', - }, - zoomInOut: { + }, + zoomInOut: { animationIn: 'zoomInDown', animationOut: 'zoomOutUp', - }, }, - } - - static info = (text: string, position: string) => { - ToastManager.__singletonRef?.show(text, Colors.info, 'ios-information-circle', position) - } - - static success = (text: string, position?: string) => { - ToastManager.__singletonRef?.show(text, Colors.success, 'checkmark-circle', position) - } - - static warn = (text: string, position: string) => { - ToastManager.__singletonRef?.show(text, Colors.warn, 'warning', position) - } - - static error = (text: string, position: string) => { - ToastManager.__singletonRef?.show(text, Colors.error, 'alert-circle', position) - } - - show = (text = '', barColor = Colors.default, icon: string, position?: string) => { - const { duration } = this.props - this.state.barWidth.setValue(this.props.width) - this.setState({ - isShow: true, - duration, - text, - barColor, - icon, - }) - if (position) this.setState({ position }) - this.isShow = true - if (duration !== this.props.end) this.close(duration) - } - - close = (duration: number) => { - 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 = () => { - Animated.timing(this.state.barWidth, { - toValue: 0, - duration: this.state.duration, - useNativeDriver: false, - }).start() - } - - pause = () => { - this.setState({ oldDuration: this.state.duration, duration: 10000 }) - Animated.timing(this.state.barWidth, { - toValue: 0, - duration: this.state.duration, - useNativeDriver: false, - }).stop() - } - - resume = () => { - this.setState({ duration: this.state.oldDuration, oldDuration: 0 }) - Animated.timing(this.state.barWidth, { - toValue: 0, - duration: this.state.duration, - 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() { - this.handleBar() - const { - animationIn, - animationStyle, - animationOut, - backdropTransitionOutTiming, - backdropTransitionInTiming, - animationInTiming, - animationOutTiming, - backdropColor, - backdropOpacity, - hasBackdrop, - width, - height, - style, - textStyle, - theme, - } = this.props - - const { - isShow, - animationStyle: stateAnimationStyle, - barColor, - icon, - text, - barWidth, - } = this.state - - return ( - - - - - - - - - {text} - - - - - - - - ) - } + slide: { + animationIn: 'slideInUp', + animationOut: 'slideOutDown', + }, +} + +class ToastManager extends Component { + static defaultProps = defaultProps + static __singletonRef: ToastManager | null + + constructor(props: ToastManagerProps) { + super(props) + ToastManager.__singletonRef = this + + this.state = { + toasts: [] as ToastType[], + keyboardHeight: 0, + } + } + + static info = (toastData: Partial) => { + ToastManager.__singletonRef?.show?.({ + ...defaultToastData, + ...toastData, + barColor: Colors.info, + icon: 'information-circle', + } as ToastType) + } + + static success = (toastData: Partial) => { + ToastManager.__singletonRef?.show?.({ + ...defaultToastData, + ...toastData, + barColor: Colors.success, + icon: 'checkmark-circle', + } as ToastType) + } + + static warn = (toastData: Partial) => { + ToastManager.__singletonRef?.show?.({ + ...defaultToastData, + ...toastData, + barColor: Colors.warn, + icon: 'warning', + } as ToastType) + } + + static error = (toastData: Partial) => { + ToastManager.__singletonRef?.show?.({ + ...defaultToastData, + ...toastData, + barColor: Colors.error, + icon: 'alert-circle', + } as ToastType) + } + + show = ({text, barColor, icon, duration, id, position, width}: ToastType) => { + const toastId = id || generateUUID() + const {toasts} = this.state + + let newToasts: ToastType[] = [] + + const newToast: Omit = { + text, + duration, + barColor, + position, + icon: icon, + barWidthAnimation: new Animated.Value(width), + width, + } + + const oldToast = toasts.find((toast) => toast.id === id) + + if (oldToast) { + newToasts = toasts.map((toast) => + toast.id === id + ? { + ...toast, + ...newToast, + } + : toast, + ) + } else { + newToasts = [ + ...toasts, + { + ...newToast, + id: toastId, + }, + ] + } + this.setState({toasts: newToasts}, () => this.handleBar()) + } + + keyboardDidShowListener: EmitterSubscription | null = null + + keyboardDidHideListener: EmitterSubscription | null = null + + keyboardDidShow = (e: KeyboardEvent) => { + this.setState({ + keyboardHeight: e.endCoordinates.height, + }) + } + + keyboardDidHide = () => { + this.setState({ + keyboardHeight: 0, + }) + } + + UNSAFE_componentWillMount = () => { + this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow) + this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide) + } + + componentWillUnmount = () => { + this.keyboardDidShowListener?.remove() + this.keyboardDidHideListener?.remove() + } + + handleBar = () => { + const {toasts} = this.state + Animated.parallel( + toasts.map((toast) => { + const duration = + // @ts-ignore + (toast.barWidthAnimation._value * toast.duration) / toast.width + + const animation = Animated.timing(toast.barWidthAnimation, { + toValue: 0, + duration, + useNativeDriver: false, + }) + + // @ts-ignore + animation.start(() => toast.barWidthAnimation._value === 0 && this.hideToast(toast.id)) + return animation + }), + ) + } + + pause = (): void => { + const {toasts} = this.state + toasts.forEach((toast) => { + return toast.barWidthAnimation.stopAnimation() + }) + } + + hideToast = (toastId: string): void => { + const {toasts} = this.state + const filtredToasts = toasts.filter((toast) => toast.id !== toastId) + if (filtredToasts.length !== toasts.length) { + this.setState({ + toasts: filtredToasts, + }) + //@ts-ignore + this.state.toasts = filtredToasts + } + } + + + renderToast = ({ + icon, + id, + barWidthAnimation, + barColor, + text, + key, + positionOffset, + height, + width, + }: NotificationArgumentsType) => { + const { + animationIn, + animationStyle, + animationOut, + backdropTransitionOutTiming, + backdropTransitionInTiming, + animationInTiming, + animationOutTiming, + backdropColor, + backdropOpacity, + hasBackdrop, + position, + style, + textStyle, + theme, + } = this.props + return ( + this.hideToast(id)} + onModalHide={() => this.hideToast(id)} + isVisible + coverScreen={false} + backdropColor={backdropColor} + backdropOpacity={backdropOpacity} + hasBackdrop={hasBackdrop} + style={styles.modalContainer} + hideModalContentWhileAnimating + > + { + const layout = event.nativeEvent.layout + const toast = this.state.toasts.find((toast) => toast.id === id) + if (toast) toast.height = layout.height + }} + > + this.hideToast(id)} + activeOpacity={0.9} + style={styles.hideButton} + > + + + + + + {text} + + + + + + + + ) + } + + render() { + const {toasts, keyboardHeight} = this.state + return toasts.map((toast, index) => { + const positionOffset: number = toasts.reduce((reduceBottom, reduceToast, reduceIndex) => { + if (reduceIndex >= index) return reduceBottom + return reduceBottom + (reduceToast.height! || 200) + 10 + }, 0) + + return this.renderToast({ + ...toast, + key: toast.id, + positionOffset: this.props.position === "top" ? this.props.positionValue + positionOffset : positionOffset + this.props.positionValue + keyboardHeight, + }) + }) + } } ToastManager.defaultProps = defaultProps diff --git a/components/styles.ts b/components/styles.ts index 920748b..6edbd55 100644 --- a/components/styles.ts +++ b/components/styles.ts @@ -12,8 +12,8 @@ const styles = StyleSheet.create({ mainContainer: { borderRadius: 6, position: 'absolute', - flexDirection: 'column', - alignItems: 'flex-start', + flexDirection: 'row', + alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { @@ -29,6 +29,7 @@ const styles = StyleSheet.create({ position: 'absolute', top: RFPercentage(0.5), right: RFPercentage(0.5), + zIndex:1 }, textStyle: { diff --git a/package.json b/package.json index 8caaf1d..886597b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "publishConfig": { "registry": "https://npm.pkg.github.com/@zahidalidev" }, - "version": "5.0.0", + "version": "5.0.1", "description": "🎉 toastify-react-native allows you to add notifications to your react-native app (ios, android) with ease. No more nonsense!", "main": "index.ts", "scripts": { diff --git a/utils/default.ts b/utils/default.ts new file mode 100644 index 0000000..a22261b --- /dev/null +++ b/utils/default.ts @@ -0,0 +1,35 @@ +import {Animated,} from 'react-native' +import {RFPercentage} from 'react-native-responsive-fontsize' +import {ToastType} from "./interfaces"; +import {Colors} from '../config/theme' + + +export const defaultProps = { + theme: 'light', + style: {}, + textStyle: {}, + position: 'top', + positionValue: 50, + animationInTiming: 300, + animationOutTiming: 300, + backdropTransitionInTiming: 300, + backdropTransitionOutTiming: 300, + animationIn: '', + animationOut: '', + animationStyle: 'slide', + hasBackdrop: false, + backdropColor: 'black', + backdropOpacity: 0.2, +} + + +export const defaultToastData: Partial = { + position: 'top', + duration: 3000, + text: '', + barColor: Colors.default, + icon: 'checkmark-circle', + barWidthAnimation: new Animated.Value(RFPercentage(32)), + width: RFPercentage(32), + height: RFPercentage(8.5), +} \ No newline at end of file diff --git a/utils/defaultProps.ts b/utils/defaultProps.ts deleted file mode 100644 index a7a3f0d..0000000 --- a/utils/defaultProps.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { RFPercentage } from 'react-native-responsive-fontsize' - -export default { - theme: 'light', - width: RFPercentage(32), - height: RFPercentage(8.5), - style: {}, - textStyle: {}, - position: 'top', - positionValue: 50, - end: 0, - duration: 3000, - animationInTiming: 300, - animationOutTiming: 300, - backdropTransitionInTiming: 300, - backdropTransitionOutTiming: 300, - animationIn: '', - animationOut: '', - animationStyle: 'upInUpOut', - hasBackdrop: false, - backdropColor: 'black', - backdropOpacity: 0.2, -} diff --git a/utils/generateUUID.ts b/utils/generateUUID.ts new file mode 100644 index 0000000..e937060 --- /dev/null +++ b/utils/generateUUID.ts @@ -0,0 +1,2 @@ +const generateUUID = () => "id" + Math.random().toString(16).slice(2) +export default generateUUID \ No newline at end of file diff --git a/utils/interfaces.ts b/utils/interfaces.ts index 3b83236..ded6006 100644 --- a/utils/interfaces.ts +++ b/utils/interfaces.ts @@ -1,36 +1,52 @@ -type AnimationStyle = any +import {Animated} from "react-native"; +import {ModalProps} from "react-native-modal/dist/modal"; + +type AnimationStyle = "upInUpOut" | "rightInOut" | "zoomInOut" + +type Position = "top" | 'bottom' export interface ToastManagerProps { - positionValue: number - width: number - duration: number - end: number - animationIn?: any - animationOut?: any - backdropTransitionOutTiming: number - backdropTransitionInTiming: number - animationInTiming: number - animationOutTiming: number - backdropColor: string - backdropOpacity: number - hasBackdrop: boolean - height: number - style: any - textStyle: any - theme: any - animationStyle?: AnimationStyle - position?: any + positionValue: number + position?: Position + animationIn?: ModalProps['animationIn'] + animationOut?: ModalProps['animationOut'] + backdropTransitionOutTiming: number + backdropTransitionInTiming: number + animationInTiming: number + animationOutTiming: number + backdropColor: string + backdropOpacity: number + hasBackdrop: boolean + style: any + textStyle: any + theme: any + animationStyle?: AnimationStyle } export interface ToastManagerState { - isShow: boolean - text: string - opacityValue: any - barWidth: any - barColor: string - icon: string - position: string - duration: number - oldDuration: number - animationStyle: Record + toasts: ToastType[]; + keyboardHeight: number; } + +export interface ToastType { + position: string; + duration: number; + text: string; + barColor: string; + icon: string; + id: string; + barWidthAnimation: Animated.Value; + width: number; + height?: number; +}; + +export interface NotificationArgumentsType extends ToastType { + key?: string; + positionOffset?: number; +}; + + +export type AnimationStyleProps = Record \ No newline at end of file From 5b55528decb6a9760e021f2d0dcfea82c8f64142 Mon Sep 17 00:00:00 2001 From: Dumitru Gotca Date: Tue, 27 Feb 2024 13:35:53 +0200 Subject: [PATCH 2/4] format code --- components/ToastManager.tsx | 618 +++++++++++++++++++----------------- components/styles.ts | 42 +-- utils/default.ts | 60 ++-- utils/generateUUID.ts | 4 +- utils/interfaces.ts | 80 ++--- 5 files changed, 424 insertions(+), 380 deletions(-) diff --git a/components/ToastManager.tsx b/components/ToastManager.tsx index cde7892..4a39f89 100644 --- a/components/ToastManager.tsx +++ b/components/ToastManager.tsx @@ -1,301 +1,345 @@ -import React, {Component} from 'react' -import {Animated, EmitterSubscription, Keyboard, KeyboardEvent, Text, TouchableOpacity, View,} from 'react-native' -import Modal from 'react-native-modal' -import Icon from 'react-native-vector-icons/Ionicons' -import {Colors} from '../config/theme' - -import {defaultProps, defaultToastData} from '../utils/default' +import React, { Component } from "react"; +import { + Animated, + EmitterSubscription, + Keyboard, + KeyboardEvent, + Text, + TouchableOpacity, + View, +} from "react-native"; +import Modal from "react-native-modal"; +import Icon from "react-native-vector-icons/Ionicons"; +import { Colors } from "../config/theme"; + +import { defaultProps, defaultToastData } from "../utils/default"; import generateUUID from "../utils/generateUUID"; import { - AnimationStyleProps, - NotificationArgumentsType, - ToastManagerProps, - ToastManagerState, - ToastType -} from '../utils/interfaces' -import styles from './styles' - + AnimationStyleProps, + NotificationArgumentsType, + ToastManagerProps, + ToastManagerState, + ToastType, +} from "../utils/interfaces"; +import styles from "./styles"; const animationStyleOptions: AnimationStyleProps = { - upInUpOut: { - animationIn: 'slideInDown', - animationOut: 'slideOutUp', - }, - rightInOut: { - animationIn: 'slideInRight', - animationOut: 'slideOutRight', - }, - zoomInOut: { - animationIn: 'zoomInDown', - animationOut: 'zoomOutUp', - }, - slide: { - animationIn: 'slideInUp', - animationOut: 'slideOutDown', - }, -} + upInUpOut: { + animationIn: "slideInDown", + animationOut: "slideOutUp", + }, + rightInOut: { + animationIn: "slideInRight", + animationOut: "slideOutRight", + }, + zoomInOut: { + animationIn: "zoomInDown", + animationOut: "zoomOutUp", + }, + slide: { + animationIn: "slideInUp", + animationOut: "slideOutDown", + }, +}; class ToastManager extends Component { - static defaultProps = defaultProps - static __singletonRef: ToastManager | null - - constructor(props: ToastManagerProps) { - super(props) - ToastManager.__singletonRef = this - - this.state = { - toasts: [] as ToastType[], - keyboardHeight: 0, - } - } - - static info = (toastData: Partial) => { - ToastManager.__singletonRef?.show?.({ - ...defaultToastData, - ...toastData, - barColor: Colors.info, - icon: 'information-circle', - } as ToastType) + static defaultProps = defaultProps; + static __singletonRef: ToastManager | null; + + constructor(props: ToastManagerProps) { + super(props); + ToastManager.__singletonRef = this; + + this.state = { + toasts: [] as ToastType[], + keyboardHeight: 0, + }; + } + + static info = (toastData: Partial) => { + ToastManager.__singletonRef?.show?.({ + ...defaultToastData, + ...toastData, + barColor: Colors.info, + icon: "information-circle", + } as ToastType); + }; + + static success = (toastData: Partial) => { + ToastManager.__singletonRef?.show?.({ + ...defaultToastData, + ...toastData, + barColor: Colors.success, + icon: "checkmark-circle", + } as ToastType); + }; + + static warn = (toastData: Partial) => { + ToastManager.__singletonRef?.show?.({ + ...defaultToastData, + ...toastData, + barColor: Colors.warn, + icon: "warning", + } as ToastType); + }; + + static error = (toastData: Partial) => { + ToastManager.__singletonRef?.show?.({ + ...defaultToastData, + ...toastData, + barColor: Colors.error, + icon: "alert-circle", + } as ToastType); + }; + + show = ({ + text, + barColor, + icon, + duration, + id, + position, + width, + }: ToastType) => { + const toastId = id || generateUUID(); + const { toasts } = this.state; + + let newToasts: ToastType[] = []; + + const newToast: Omit = { + text, + duration, + barColor, + position, + icon: icon, + barWidthAnimation: new Animated.Value(width), + width, + }; + + const oldToast = toasts.find((toast) => toast.id === id); + + if (oldToast) { + newToasts = toasts.map((toast) => + toast.id === id + ? { + ...toast, + ...newToast, + } + : toast + ); + } else { + newToasts = [ + ...toasts, + { + ...newToast, + id: toastId, + }, + ]; } - - static success = (toastData: Partial) => { - ToastManager.__singletonRef?.show?.({ - ...defaultToastData, - ...toastData, - barColor: Colors.success, - icon: 'checkmark-circle', - } as ToastType) - } - - static warn = (toastData: Partial) => { - ToastManager.__singletonRef?.show?.({ - ...defaultToastData, - ...toastData, - barColor: Colors.warn, - icon: 'warning', - } as ToastType) + this.setState({ toasts: newToasts }, () => this.handleBar()); + }; + + keyboardDidShowListener: EmitterSubscription | null = null; + + keyboardDidHideListener: EmitterSubscription | null = null; + + keyboardDidShow = (e: KeyboardEvent) => { + this.setState({ + keyboardHeight: e.endCoordinates.height, + }); + }; + + keyboardDidHide = () => { + this.setState({ + keyboardHeight: 0, + }); + }; + + UNSAFE_componentWillMount = () => { + this.keyboardDidShowListener = Keyboard.addListener( + "keyboardDidShow", + this.keyboardDidShow + ); + this.keyboardDidHideListener = Keyboard.addListener( + "keyboardDidHide", + this.keyboardDidHide + ); + }; + + componentWillUnmount = () => { + this.keyboardDidShowListener?.remove(); + this.keyboardDidHideListener?.remove(); + }; + + handleBar = () => { + const { toasts } = this.state; + Animated.parallel( + toasts.map((toast) => { + const duration = + // @ts-ignore + (toast.barWidthAnimation._value * toast.duration) / toast.width; + + const animation = Animated.timing(toast.barWidthAnimation, { + toValue: 0, + duration, + useNativeDriver: false, + }); + + // @ts-ignore + animation.start( + () => toast.barWidthAnimation._value === 0 && this.hideToast(toast.id) + ); + return animation; + }) + ); + }; + + pause = (): void => { + const { toasts } = this.state; + toasts.forEach((toast) => { + return toast.barWidthAnimation.stopAnimation(); + }); + }; + + hideToast = (toastId: string): void => { + const { toasts } = this.state; + const filtredToasts = toasts.filter((toast) => toast.id !== toastId); + if (filtredToasts.length !== toasts.length) { + this.setState({ + toasts: filtredToasts, + }); + //@ts-ignore + this.state.toasts = filtredToasts; } - - static error = (toastData: Partial) => { - ToastManager.__singletonRef?.show?.({ - ...defaultToastData, - ...toastData, - barColor: Colors.error, - icon: 'alert-circle', - } as ToastType) - } - - show = ({text, barColor, icon, duration, id, position, width}: ToastType) => { - const toastId = id || generateUUID() - const {toasts} = this.state - - let newToasts: ToastType[] = [] - - const newToast: Omit = { - text, - duration, - barColor, - position, - icon: icon, - barWidthAnimation: new Animated.Value(width), - width, + }; + + renderToast = ({ + icon, + id, + barWidthAnimation, + barColor, + text, + key, + positionOffset, + height, + width, + }: NotificationArgumentsType) => { + const { + animationIn, + animationStyle, + animationOut, + backdropTransitionOutTiming, + backdropTransitionInTiming, + animationInTiming, + animationOutTiming, + backdropColor, + backdropOpacity, + hasBackdrop, + position, + style, + textStyle, + theme, + } = this.props; + return ( + toast.id === id) - - if (oldToast) { - newToasts = toasts.map((toast) => - toast.id === id - ? { - ...toast, - ...newToast, - } - : toast, - ) - } else { - newToasts = [ - ...toasts, - { - ...newToast, - id: toastId, - }, - ] + animationOut={ + animationOut || animationStyleOptions[animationStyle!].animationOut } - this.setState({toasts: newToasts}, () => this.handleBar()) - } - - keyboardDidShowListener: EmitterSubscription | null = null - - keyboardDidHideListener: EmitterSubscription | null = null - - keyboardDidShow = (e: KeyboardEvent) => { - this.setState({ - keyboardHeight: e.endCoordinates.height, - }) - } - - keyboardDidHide = () => { - this.setState({ - keyboardHeight: 0, - }) - } - - UNSAFE_componentWillMount = () => { - this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow) - this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide) - } - - componentWillUnmount = () => { - this.keyboardDidShowListener?.remove() - this.keyboardDidHideListener?.remove() - } - - handleBar = () => { - const {toasts} = this.state - Animated.parallel( - toasts.map((toast) => { - const duration = - // @ts-ignore - (toast.barWidthAnimation._value * toast.duration) / toast.width - - const animation = Animated.timing(toast.barWidthAnimation, { - toValue: 0, - duration, - useNativeDriver: false, - }) - - // @ts-ignore - animation.start(() => toast.barWidthAnimation._value === 0 && this.hideToast(toast.id)) - return animation - }), - ) - } - - pause = (): void => { - const {toasts} = this.state - toasts.forEach((toast) => { - return toast.barWidthAnimation.stopAnimation() - }) - } - - hideToast = (toastId: string): void => { - const {toasts} = this.state - const filtredToasts = toasts.filter((toast) => toast.id !== toastId) - if (filtredToasts.length !== toasts.length) { - this.setState({ - toasts: filtredToasts, - }) - //@ts-ignore - this.state.toasts = filtredToasts - } - } - - - renderToast = ({ - icon, - id, - barWidthAnimation, - barColor, - text, - key, - positionOffset, - height, - width, - }: NotificationArgumentsType) => { - const { - animationIn, - animationStyle, - animationOut, - backdropTransitionOutTiming, - backdropTransitionInTiming, - animationInTiming, - animationOutTiming, - backdropColor, - backdropOpacity, - hasBackdrop, - position, - style, - textStyle, - theme, - } = this.props - return ( - this.hideToast(id)} - onModalHide={() => this.hideToast(id)} - isVisible - coverScreen={false} - backdropColor={backdropColor} - backdropOpacity={backdropOpacity} - hasBackdrop={hasBackdrop} - style={styles.modalContainer} - hideModalContentWhileAnimating + backdropTransitionOutTiming={backdropTransitionOutTiming} + backdropTransitionInTiming={backdropTransitionInTiming} + animationInTiming={animationInTiming} + animationOutTiming={animationOutTiming} + onTouchEnd={this.handleBar} + onTouchStart={this.pause} + swipeDirection={["up", "down", "left", "right"]} + onSwipeComplete={() => this.hideToast(id)} + onModalHide={() => this.hideToast(id)} + isVisible + coverScreen={false} + backdropColor={backdropColor} + backdropOpacity={backdropOpacity} + hasBackdrop={hasBackdrop} + style={styles.modalContainer} + hideModalContentWhileAnimating + > + { + const layout = event.nativeEvent.layout; + const toast = this.state.toasts.find((toast) => toast.id === id); + if (toast) toast.height = layout.height; + }} + > + this.hideToast(id)} + activeOpacity={0.9} + style={styles.hideButton} + > + + + + + - { - const layout = event.nativeEvent.layout - const toast = this.state.toasts.find((toast) => toast.id === id) - if (toast) toast.height = layout.height - }} - > - this.hideToast(id)} - activeOpacity={0.9} - style={styles.hideButton} - > - - - - - - {text} - - - - - - - - ) - } - - render() { - const {toasts, keyboardHeight} = this.state - return toasts.map((toast, index) => { - const positionOffset: number = toasts.reduce((reduceBottom, reduceToast, reduceIndex) => { - if (reduceIndex >= index) return reduceBottom - return reduceBottom + (reduceToast.height! || 200) + 10 - }, 0) - - return this.renderToast({ - ...toast, - key: toast.id, - positionOffset: this.props.position === "top" ? this.props.positionValue + positionOffset : positionOffset + this.props.positionValue + keyboardHeight, - }) - }) - } + {text} + + + + + + + + ); + }; + + render() { + const { toasts, keyboardHeight } = this.state; + return toasts.map((toast, index) => { + const positionOffset: number = toasts.reduce( + (reduceBottom, reduceToast, reduceIndex) => { + if (reduceIndex >= index) return reduceBottom; + return reduceBottom + (reduceToast.height! || 200) + 10; + }, + 0 + ); + + return this.renderToast({ + ...toast, + key: toast.id, + positionOffset: + this.props.position === "top" + ? this.props.positionValue + positionOffset + : positionOffset + this.props.positionValue + keyboardHeight, + }); + }); + } } -ToastManager.defaultProps = defaultProps +ToastManager.defaultProps = defaultProps; -export default ToastManager +export default ToastManager; diff --git a/components/styles.ts b/components/styles.ts index 6edbd55..d49aa9c 100644 --- a/components/styles.ts +++ b/components/styles.ts @@ -1,21 +1,21 @@ -import { RFPercentage } from 'react-native-responsive-fontsize' -import { StyleSheet } from 'react-native' +import { StyleSheet } from "react-native"; +import { RFPercentage } from "react-native-responsive-fontsize"; const styles = StyleSheet.create({ modalContainer: { flex: 1, - alignItems: 'center', - justifyContent: 'center', + alignItems: "center", + justifyContent: "center", zIndex: 200, }, mainContainer: { borderRadius: 6, - position: 'absolute', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - shadowColor: '#000', + position: "absolute", + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + shadowColor: "#000", shadowOffset: { width: 0, height: 2, @@ -26,36 +26,36 @@ const styles = StyleSheet.create({ }, hideButton: { - position: 'absolute', + position: "absolute", top: RFPercentage(0.5), right: RFPercentage(0.5), - zIndex:1 + zIndex: 1, }, textStyle: { fontSize: RFPercentage(2.5), - fontWeight: '400', + fontWeight: "400", }, progressBarContainer: { - flexDirection: 'row', - position: 'absolute', + flexDirection: "row", + position: "absolute", height: 4, - width: '100%', + width: "100%", bottom: 0, }, content: { - width: '100%', + width: "100%", padding: RFPercentage(1.5), - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'flex-start', + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-start", }, iconWrapper: { marginRight: RFPercentage(0.7), }, -}) +}); -export default styles +export default styles; diff --git a/utils/default.ts b/utils/default.ts index a22261b..93ab22d 100644 --- a/utils/default.ts +++ b/utils/default.ts @@ -1,35 +1,33 @@ -import {Animated,} from 'react-native' -import {RFPercentage} from 'react-native-responsive-fontsize' -import {ToastType} from "./interfaces"; -import {Colors} from '../config/theme' - +import { Animated } from "react-native"; +import { RFPercentage } from "react-native-responsive-fontsize"; +import { ToastType } from "./interfaces"; +import { Colors } from "../config/theme"; export const defaultProps = { - theme: 'light', - style: {}, - textStyle: {}, - position: 'top', - positionValue: 50, - animationInTiming: 300, - animationOutTiming: 300, - backdropTransitionInTiming: 300, - backdropTransitionOutTiming: 300, - animationIn: '', - animationOut: '', - animationStyle: 'slide', - hasBackdrop: false, - backdropColor: 'black', - backdropOpacity: 0.2, -} - + theme: "light", + style: {}, + textStyle: {}, + position: "top", + positionValue: 50, + animationInTiming: 300, + animationOutTiming: 300, + backdropTransitionInTiming: 300, + backdropTransitionOutTiming: 300, + animationIn: "", + animationOut: "", + animationStyle: "slide", + hasBackdrop: false, + backdropColor: "black", + backdropOpacity: 0.2, +}; export const defaultToastData: Partial = { - position: 'top', - duration: 3000, - text: '', - barColor: Colors.default, - icon: 'checkmark-circle', - barWidthAnimation: new Animated.Value(RFPercentage(32)), - width: RFPercentage(32), - height: RFPercentage(8.5), -} \ No newline at end of file + position: "top", + duration: 3000, + text: "", + barColor: Colors.default, + icon: "checkmark-circle", + barWidthAnimation: new Animated.Value(RFPercentage(32)), + width: RFPercentage(32), + height: RFPercentage(8.5), +}; diff --git a/utils/generateUUID.ts b/utils/generateUUID.ts index e937060..01f4801 100644 --- a/utils/generateUUID.ts +++ b/utils/generateUUID.ts @@ -1,2 +1,2 @@ -const generateUUID = () => "id" + Math.random().toString(16).slice(2) -export default generateUUID \ No newline at end of file +const generateUUID = () => "id" + Math.random().toString(16).slice(2); +export default generateUUID; diff --git a/utils/interfaces.ts b/utils/interfaces.ts index ded6006..e09fdea 100644 --- a/utils/interfaces.ts +++ b/utils/interfaces.ts @@ -1,52 +1,54 @@ -import {Animated} from "react-native"; -import {ModalProps} from "react-native-modal/dist/modal"; +import { Animated } from "react-native"; +import { ModalProps } from "react-native-modal/dist/modal"; -type AnimationStyle = "upInUpOut" | "rightInOut" | "zoomInOut" +type AnimationStyle = "upInUpOut" | "rightInOut" | "zoomInOut"; -type Position = "top" | 'bottom' +type Position = "top" | "bottom"; export interface ToastManagerProps { - positionValue: number - position?: Position - animationIn?: ModalProps['animationIn'] - animationOut?: ModalProps['animationOut'] - backdropTransitionOutTiming: number - backdropTransitionInTiming: number - animationInTiming: number - animationOutTiming: number - backdropColor: string - backdropOpacity: number - hasBackdrop: boolean - style: any - textStyle: any - theme: any - animationStyle?: AnimationStyle + positionValue: number; + position?: Position; + animationIn?: ModalProps["animationIn"]; + animationOut?: ModalProps["animationOut"]; + backdropTransitionOutTiming: number; + backdropTransitionInTiming: number; + animationInTiming: number; + animationOutTiming: number; + backdropColor: string; + backdropOpacity: number; + hasBackdrop: boolean; + style: any; + textStyle: any; + theme: any; + animationStyle?: AnimationStyle; } export interface ToastManagerState { - toasts: ToastType[]; - keyboardHeight: number; + toasts: ToastType[]; + keyboardHeight: number; } export interface ToastType { - position: string; - duration: number; - text: string; - barColor: string; - icon: string; - id: string; - barWidthAnimation: Animated.Value; - width: number; - height?: number; -}; + position: string; + duration: number; + text: string; + barColor: string; + icon: string; + id: string; + barWidthAnimation: Animated.Value; + width: number; + height?: number; +} export interface NotificationArgumentsType extends ToastType { - key?: string; - positionOffset?: number; -}; - + key?: string; + positionOffset?: number; +} -export type AnimationStyleProps = Record \ No newline at end of file +export type AnimationStyleProps = Record< + string, + { + animationIn: ModalProps["animationIn"]; + animationOut: ModalProps["animationOut"]; + } +>; From 1fa505bc05fcf25247c1c4e103e0c5b28faf81a9 Mon Sep 17 00:00:00 2001 From: Dumitru Gotca Date: Tue, 27 Feb 2024 13:38:18 +0200 Subject: [PATCH 3/4] Code refactoring --- components/ToastManager.tsx | 26 +++++--------------------- utils/default.ts | 21 ++++++++++++++++++++- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/components/ToastManager.tsx b/components/ToastManager.tsx index 4a39f89..4929397 100644 --- a/components/ToastManager.tsx +++ b/components/ToastManager.tsx @@ -12,10 +12,13 @@ import Modal from "react-native-modal"; import Icon from "react-native-vector-icons/Ionicons"; import { Colors } from "../config/theme"; -import { defaultProps, defaultToastData } from "../utils/default"; +import { + animationStyleOptions, + defaultProps, + defaultToastData, +} from "../utils/default"; import generateUUID from "../utils/generateUUID"; import { - AnimationStyleProps, NotificationArgumentsType, ToastManagerProps, ToastManagerState, @@ -23,25 +26,6 @@ import { } from "../utils/interfaces"; import styles from "./styles"; -const animationStyleOptions: AnimationStyleProps = { - upInUpOut: { - animationIn: "slideInDown", - animationOut: "slideOutUp", - }, - rightInOut: { - animationIn: "slideInRight", - animationOut: "slideOutRight", - }, - zoomInOut: { - animationIn: "zoomInDown", - animationOut: "zoomOutUp", - }, - slide: { - animationIn: "slideInUp", - animationOut: "slideOutDown", - }, -}; - class ToastManager extends Component { static defaultProps = defaultProps; static __singletonRef: ToastManager | null; diff --git a/utils/default.ts b/utils/default.ts index 93ab22d..8c95337 100644 --- a/utils/default.ts +++ b/utils/default.ts @@ -1,6 +1,6 @@ import { Animated } from "react-native"; import { RFPercentage } from "react-native-responsive-fontsize"; -import { ToastType } from "./interfaces"; +import { AnimationStyleProps, ToastType } from "./interfaces"; import { Colors } from "../config/theme"; export const defaultProps = { @@ -31,3 +31,22 @@ export const defaultToastData: Partial = { width: RFPercentage(32), height: RFPercentage(8.5), }; + +export const animationStyleOptions: AnimationStyleProps = { + upInUpOut: { + animationIn: "slideInDown", + animationOut: "slideOutUp", + }, + rightInOut: { + animationIn: "slideInRight", + animationOut: "slideOutRight", + }, + zoomInOut: { + animationIn: "zoomInDown", + animationOut: "zoomOutUp", + }, + slide: { + animationIn: "slideInUp", + animationOut: "slideOutDown", + }, +}; From 588e69c7a27411779a569f3183aec4970f4d9d24 Mon Sep 17 00:00:00 2001 From: Dumitru Gotca Date: Tue, 27 Feb 2024 13:45:19 +0200 Subject: [PATCH 4/4] Small update to readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f50c0fe..9785995 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ import Another from './Another' const App = () => { const showToasts = () => { - Toast.success('Promised is resolved') + Toast.success({ text: 'Promised is resolved' }) } return ( @@ -93,7 +93,7 @@ import { Toast } from 'toastify-react-native' const Another = () => ( Toast.info('Lorem ipsum info', 'bottom')} + onPress={() => Toast.info({ text: 'Lorem ipsum info' })} style={styles.buttonStyle} > SHOW SOME AWESOMENESS! @@ -124,12 +124,12 @@ For a more complex example take a look at the `/example` directory. ## Available props | Name | Type | Default | Description | -| --------------------------- | ---------------------------------- | -------------- | ---------------------------------------------- | +| --------------------------- |------------------------------------| -------------- | ---------------------------------------------- | | width | number | 256 | Width of toast | | height | number | 68 | Height of the toast | | style | any | null | Style applied to the toast | | textStyle | any | null | Style applied to the toast content | -| position | top, center or bottom | top | Position of toast | +| position | top or bottom | top | Position of toast | | positionValue | number | 50 | position value of toast | | duration | number | 3000 | The display time of toast. | | animationStyle | upInUpOut, rightInOut or zoomInOut | upInUpOut | The animation style of toast |