react-native-1app
Version:
663 lines (615 loc) • 19.3 kB
JavaScript
"use strict";
import React, { Component } from "react";
import { StyleSheet, TouchableWithoutFeedback, StatusBar, Animated, Image, Text, View,TouchableOpacity } from "react-native";
import PropTypes from "prop-types";
import Icon from '../Icon'
import FlashMessageManager from "./FlashMessageManager";
import FlashMessageWrapper, { styleWithInset } from "./FlashMessageWrapper";
/**
* MessageComponent `minHeight` property used mainly in vertical transitions
*/
const OFFSET_HEIGHT = 48;
/**
* `message` prop it's expected to be some "object"
* The `message` attribute is mandatory.
* If you pass some `description` attribute your flash message will be displayed in two lines (first `message` as a title and after `description` as simple text)
* The `type` attribute set the type and color of your flash message, default options are "success" (green), "warning" (orange), "danger" (red), "info" (blue) and "default" (gray)
* If you need to customize the bg color or text color for a single message you can use the `backgroundColor` and `color` attributes
*/
const MessagePropType = PropTypes.shape({
message: PropTypes.string.isRequired,
description: PropTypes.string,
type: PropTypes.string,
backgroundColor: PropTypes.string,
color: PropTypes.string,
}).isRequired;
/**
* Non-operation func
*/
const noop = () => {};
/**
* Simple random ID for internal FlashMessage component usage
*/
function srid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return `${s4()}-${s4()}-${s4()}`;
}
/**
* Translates icon prop value into complex internal object
*/
function parseIcon(icon = "none") {
if (!!icon && icon !== "none") {
if (typeof icon === "string") {
return { icon, position: "left", style: {} };
}
return Object.assign({ position: "left", style: {} },icon);
}
return null;
}
/**
* Translates string positions like "top", "bottom" and "center" to style classes
*/
export function positionStyle(style, position) {
if (typeof position === "string") {
return [
style,
position === "top" && styles.rootTop,
position === "bottom" && styles.rootBottom,
position === "center" && styles.rootCenter,
];
}
return [style, position];
}
/**
* Global function to handle show messages that can be called anywhere in your app
* Pass some `message` object as first attribute to display flash messages in your app
*
* ```
* showMessage({ message: "Contact sent", description "Your message was sent with success", type: "success" })
* ```
*/
export function showMessage(...args) {
const ref = FlashMessageManager.getDefault();
if (!!ref) {
ref.showMessage(...args);
}
}
/**
* Global function to programmatically hide messages that can be called anywhere in your app
*
* ```
* hideMessage()
* ```
*/
export function hideMessage(...args) {
const ref = FlashMessageManager.getDefault();
if (!!ref) {
ref.hideMessage(...args);
}
}
/**
* Default transition config for FlashMessage component
* You can create your own transition config with interpolation, just remember to return some style object with transform options
*/
export function FlashMessageTransition(animValue, position = "top") {
const opacity = animValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
});
if (position === "top") {
const translateY = animValue.interpolate({
inputRange: [0, 1],
outputRange: [-OFFSET_HEIGHT, 0],
});
return {
transform: [{ translateY }],
opacity,
};
} else if (position === "bottom") {
const translateY = animValue.interpolate({
inputRange: [0, 1],
outputRange: [OFFSET_HEIGHT, 0],
});
return {
transform: [{ translateY }],
opacity,
};
}
return {
opacity,
};
}
export const renderFlashMessageIcon = (icon = "success", style = {}, customProps = {}) => {
// switch (icon) {
// case "success":
// return (
// <Image style={[styles.flashIcon, style]} source={require("./icons/fm_icon_success.png")} {...customProps} />
// );
// case "info":
// return <Image style={[styles.flashIcon, style]} source={require("./icons/fm_icon_info.png")} {...customProps} />;
// case "warning":
// return (
// <Image style={[styles.flashIcon, style]} source={require("./icons/fm_icon_warning.png")} {...customProps} />
// );
// case "danger":
// return (
// <Image style={[styles.flashIcon, style]} source={require("./icons/fm_icon_danger.png")} {...customProps} />
// );
// default:
// return null;
// }
if(icon){
// console.log(customProps,customProps.fromFontFamily);
return(<Icon
style={[styles.flashIcon, ...style]}
fromFontFamily={customProps.fromFontFamily&&customProps.fromFontFamily=="Material Design Icons"?"Material Design Icons":"Material Icons"}
name={icon}
/>)
}else {
return null;
}
};
/**
* Default MessageComponent used in FlashMessage
* This component it's wrapped in `FlashMessageWrapper` to handle orientation change and extra inset padding in special devices
* For most of uses this component doesn't need to be change for custom versions, cause it's very customizable
*/
export const DefaultFlash = ({
message,
style,
textStyle,
titleStyle,
renderFlashMessageIcon,
position = "top",
floating = false,
icon,
hideStatusBar = false,
action=null,
actions=null,
...props
}) => {
const hasDescription = !!message.description && message.description !== "";
const iconView =
!!icon &&
!!icon.icon &&
renderFlashMessageIcon(icon.icon === "auto" ? message.type : icon.icon, [
icon.position === "left" && styles.flashIconLeft,
icon.position === "right" && styles.flashIconRight,
icon.style,
],icon);
const hasIcon = !!iconView;
// console.log(action);
return (
<FlashMessageWrapper position={typeof position === "string" ? position : null}>
{wrapperInset => (
<View
style={styleWithInset(
[
styles.defaultFlash,
position === "center" && styles.defaultFlashCenter,
position !== "center" && floating && styles.defaultFlashFloating,
hasIcon && styles.defaultFlashWithIcon,
!!message.backgroundColor
? { backgroundColor: message.backgroundColor }
: !!message.type &&
!!FlashMessage.ColorTheme[message.type] && {
backgroundColor: FlashMessage.ColorTheme[message.type],
},
style,
],
wrapperInset,
!!hideStatusBar,
position !== "center" && floating ? "margin" : "padding"
)}
{...props}>
{hasIcon && icon.position === "left" && iconView}
<View style={styles.flashLabel}>
<Text
style={[
styles.flashText,
hasDescription && styles.flashTitle,
!!message.color && { color: message.color },
titleStyle,
]}>
{message.message}
</Text>
{hasDescription && (
<Text style={[styles.flashText, !!message.color && { color: message.color }, textStyle]}>
{message.description}
</Text>
)}
</View>
{action&&action}
{actions&&actions.map((item,index)=>(
<TouchableOpacity style={{padding:15}}
onPress={()=>{
if(item.onPress)item.onPress()
hideMessage();
}}>
<Text style={{color:"rgb(255, 181, 5)",fontWeight: "normal",fontSize:17,textTransform: "uppercase"}}>{item.label}</Text>
</TouchableOpacity>
))}
{hasIcon && icon.position === "right" && iconView}
</View>
)}
</FlashMessageWrapper>
);
};
DefaultFlash.propTypes = {
message: MessagePropType,
renderFlashMessageIcon: PropTypes.func,
};
/**
* Main component of this package
* The FlashMessage component it's a global utility to help you with easily and highly customizable flashbars, top notifications or alerts (with iPhone X "notch" support)
* You can instace and use this component once in your main app screen
* To global use, please add your <FlasshMessage /> as a last component in your root main screen
*
* ```
* <View style={{ flex: 1 }}>
* <YourMainApp />
* <FlasshMessage /> <--- here as last component
* <View>
* ```
*/
export default class FlashMessage extends Component {
static defaultProps = {
/**
* Use to handle if the instance can be registed as default/global instance
*/
canRegisterAsDefault: true,
/**
* Controls if the flash message can be closed on press
*/
hideOnPress: true,
/**
* `onPress` callback for flash message press
*/
onPress: noop,
/**
* Controls if the flash message will be shown with animation or not
*/
animated: true,
/**
* Animations duration/speed
*/
animationDuration: 225,
/**
* Controls if the flash message can hide itself after some `duration` time
*/
autoHide: true,
/**
* How many milliseconds the flash message will be shown if the `autoHide` it's true
*/
duration: 1850,
/**
* Controls if the flash message will auto hide the native status bar
* Note: Works OK in iOS, not all Android versions support this.
*/
hideStatusBar: false,
/**
* The `floating` prop unstick the message from the edges and applying some border radius to component
*/
floating: false,
/**
* The `position` prop set the position of a flash message
* Expected options: "top" (default), "bottom", "center" or a custom object with { top, left, right, bottom } position
*/
position: "top",
/**
* The `icon` prop set the graphical icon of a flash message
* Expected options: "none" (default), "auto" (guided by `type`), "success", "info", "warning", "danger" or a custom object with icon type/name and position (left or right) attributes, e.g.: { icon: "success", position: "right" }
*/
icon: "none",
/**
* The `renderFlashMessageIcon` prop set a custom render function for inside message icons
*/
renderFlashMessageIcon,
/**
* The `transitionConfig` prop set the transition config function used in shown/hide anim interpolations
*/
transitionConfig: FlashMessageTransition,
/**
* The `MessageComponent` prop set the default flash message render component used to show all the messages
*/
MessageComponent: DefaultFlash,
};
static propTypes = {
canRegisterAsDefault: PropTypes.bool,
hideOnPress: PropTypes.bool,
onShow: PropTypes.func,
onHide: PropTypes.func,
onPress: PropTypes.func,
animated: PropTypes.bool,
animationDuration: PropTypes.number,
duration: PropTypes.number,
autoHide: PropTypes.bool,
hideStatusBar: PropTypes.bool,
floating: PropTypes.bool,
position: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]),
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
renderFlashMessageIcon: PropTypes.func,
transitionConfig: PropTypes.func,
MessageComponent: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
};
/**
* Your can customize the default ColorTheme of this component
* Use `setColorTheme` static method to override the primary colors/types of flash messages
*/
static ColorTheme = {
success: "#5cb85c",
info: "#5bc0de",
warning: "#f0ad4e",
danger: "#d9534f",
};
static setColorTheme = theme => {
FlashMessage.ColorTheme = Object.assign(FlashMessage.ColorTheme, theme);
};
constructor(props) {
super(props);
this.prop = this.prop.bind(this);
this.pressMessage = this.pressMessage.bind(this);
this.toggleVisibility = this.toggleVisibility.bind(this);
if (!this._id) this._id = srid();
this.state = {
visibleValue: new Animated.Value(0),
isHidding: false,
message: props.message || null,
};
}
componentDidMount() {
if (this.props.canRegisterAsDefault) {
FlashMessageManager.register(this);
}
}
componentWillUnmount() {
if (this.props.canRegisterAsDefault) {
FlashMessageManager.unregister(this);
}
}
/**
* Non-public method
*/
prop(message, prop) {
return !!message && prop in message ? message[prop] : prop in this.props ? this.props[prop] : null;
}
/**
* Non-public method
*/
isAnimated(message) {
return this.prop(message, "animated");
}
/**
* Non-public method
*/
pressMessage(event) {
if (!this.state.isHidding) {
const { message } = this.state;
const hideOnPress = this.prop(message, "hideOnPress");
const onPress = this.prop(message, "onPress");
if (hideOnPress) {
this.hideMessage();
}
if (typeof onPress === "function") {
onPress(event, message);
}
}
}
/**
* Non-public method
*/
toggleVisibility(visible = true, animated = true, done) {
const { message } = this.state;
const position = this.prop(message, "position");
const animationDuration = this.prop(message, "animationDuration");
const duration = this.prop(message, "duration");
const autoHide = this.prop(message, "autoHide");
const hideStatusBar = this.prop(message, "hideStatusBar");
if (this._hideTimeout) {
clearTimeout(this._hideTimeout);
}
if (visible) {
const onShow = this.prop(message, "onShow") || noop;
const finish = () => {
if (!!autoHide && duration > 0) {
this._hideTimeout = setTimeout(() => this.toggleVisibility(false, animated), duration);
}
if (!!done && typeof done === "function") {
done();
}
};
this.setState({ isHidding: false });
this.state.visibleValue.setValue(0);
if (!!onShow && typeof onShow === "function") {
onShow(this);
}
if (!!hideStatusBar) {
StatusBar.setHidden(true, typeof hideStatusBar === "string" ? hideStatusBar : "slide");
}
if (animated) {
Animated.timing(this.state.visibleValue, {
toValue: 1,
duration: animationDuration,
useNativeDriver: true,
}).start(finish);
} else {
finish();
}
} else {
const onHide = this.prop(message, "onHide") || noop;
const finish = () => {
this.setState({ message: null, isHidding: false });
if (!!onHide && typeof onHide === "function") {
onHide(this);
}
if (!!done && typeof done === "function") {
done();
}
};
this.setState({ isHidding: true });
if (!!hideStatusBar) {
StatusBar.setHidden(false, typeof hideStatusBar === "string" ? hideStatusBar : "slide");
}
if (animated) {
Animated.timing(this.state.visibleValue, {
toValue: 0,
duration: animationDuration,
useNativeDriver: true,
}).start(finish);
} else {
finish();
}
}
}
/**
* Instace ref function to handle show messages
* Pass some `message` object as first attribute to display a flash message
*
* ```
* this.refs.YOUR_REF.showMessage({ message: "Contact sent", description "Your message was sent with success", type: "success" })
* ```
*/
showMessage(message, description = null, type = "default") {
if (!!message) {
let _message = {};
if (typeof message === "string") {
_message = { message, description, type };
} else if ("message" in message) {
_message = Object.assign({ description: null, type: "default" },message);
}
const animated = this.isAnimated(_message);
this.setState({ message: _message }, () => this.toggleVisibility(true, animated));
return;
}
this.setState({ message: null, isHidding: false });
}
/**
* Instace ref function to programmatically hide message
*
* ```
* this.refs.YOUR_REF.hideMessage()
* ```
*/
hideMessage() {
const animated = this.isAnimated(this.state.message);
this.toggleVisibility(false, animated);
}
render() {
const { renderFlashMessageIcon, MessageComponent } = this.props;
const { message, visibleValue } = this.state;
const style = this.prop(message, "style");
const textStyle = this.prop(message, "textStyle");
const titleStyle = this.prop(message, "titleStyle");
const floating = this.prop(message, "floating");
const position = this.prop(message, "position");
const icon = parseIcon(this.prop(message, "icon"));
const hideStatusBar = this.prop(message, "hideStatusBar");
const transitionConfig = this.prop(message, "transitionConfig");
const animated = this.isAnimated(message);
const animStyle = animated ? transitionConfig(visibleValue, position) : {};
// if(message)console.log(message.action);
return (
<Animated.View
style={[
positionStyle(styles.root, position),
position === "center" && !!message && styles.rootCenterEnabled,
animStyle,
]}>
{!!message && (
<TouchableWithoutFeedback onPress={this.pressMessage}>
<MessageComponent
position={position}
floating={floating}
message={message}
hideStatusBar={hideStatusBar}
renderFlashMessageIcon={renderFlashMessageIcon}
icon={icon}
style={style}
action={message.action}
actions={message.actions}
textStyle={textStyle}
titleStyle={titleStyle}
/>
</TouchableWithoutFeedback>
)}
</Animated.View>
);
}
_hideTimeout;
_id;
}
const styles = StyleSheet.create({
root: {
position: "absolute",
left: 0,
right: 0,
},
rootTop: {
top: 0,
},
rootBottom: {
bottom: 0,
},
rootCenter: {
justifyContent: "center",
alignItems: "center",
},
rootCenterEnabled: {
top: 0,
bottom: 0,
width: "100%",
height: "100%",
},
defaultFlash: {
justifyContent: "flex-start",
paddingVertical: 15,
paddingHorizontal: 20,
backgroundColor: "#696969",
minHeight: OFFSET_HEIGHT,
},
defaultFlashCenter: {
margin: 44,
borderRadius: 8,
overflow: "hidden",
},
defaultFlashFloating: {
marginTop: 10,
marginLeft: 12,
marginRight: 12,
borderRadius: 8,
},
defaultFlashWithIcon: {
flexDirection: "row",
},
flashLabel: {
flexDirection: "column",
flex:1
},
flashText: {
fontSize: 14,
lineHeight: 18,
color: "#fff",
},
flashTitle: {
fontSize: 16,
fontWeight: "600",
marginBottom: 5,
},
flashIcon: {
color: "#fff",
marginTop: -1,
fontSize:25,
},
flashIconLeft: {
marginLeft: -6,
marginRight: 9,
},
flashIconRight: {
marginRight: -6,
marginLeft: 9,
},
});