全局应用内通知

时间:2018-08-11 23:40:14

标签: android ios reactjs react-native

我希望使用一个应用程序内通知系统,也就是一种更具吸引力并且在您的面部表情中更少使用的警报,以使用户知道正在执行的操作,尤其是例如在检测到条形码但需要将该条形码发送到服务器,用户需要等待。

我找到了这个库并试图实现它;但是当我使用React Navigation并希望在应用程序的最顶部渲染项目时,它会被React Native标头切断

是否有可能在我想要全局通知时创建并引用一个函数,并且它将在最顶层呈现,我想它需要在此处呈现:

import React from 'react';
import { createBottomTabNavigator,createStackNavigator } from 'react-navigation';
import SearchTab from './components/Tabs/SearchTab';
import HomeTab from './components/Tabs/HomeTab';
import ScannerTab from './components/Tabs/ScannerTab';
import SettingsTab from './components/Tabs/SettingsTab';
import Ionicons from 'react-native-vector-icons/Ionicons';
import StockModal from './components/Modals/StockModal';

const MainStack = createBottomTabNavigator(
    {
        Home: HomeTab,
        Search: SearchTab,
        Scanner: ScannerTab,
        Settings: SettingsTab,
        //Todo: Total overlay modals HERE
    },
    {
        navigationOptions: ({ navigation }) => ({
            tabBarIcon: ({ focused, tintColor }) => {
                const { routeName } = navigation.state;
                let iconName;

                if (routeName === 'Home') {
                    iconName = `ios-information-circle${focused ? '' : '-outline'}`;
                } else if (routeName === 'Settings') {
                    iconName = `ios-options${focused ? '' : '-outline'}`;
                }else if (routeName === 'Scanner') {
                    iconName = `ios-barcode${focused ? '' : '-outline'}`;
                }else if (routeName === 'Search') {
                    iconName = `ios-search${focused ? '' : '-outline'}`;
                }
                return <Ionicons name={iconName} size={25} color={tintColor} />;
            },
        }),
        tabBarOptions: {
            activeTintColor: 'tomato',
            inactiveTintColor: 'gray',
        },
    }
);

export default RootStack = createStackNavigator(
    {
        Main: {
            screen: MainStack,
        },
        QuickStockScreen: {
            screen: StockModal,
        },
    },
    {
        mode: 'modal',
        headerMode: 'none',
    }
);

但是即使有可能,我也不确定如何建立一个通知显示的功能;我想到了React Redux,但我不希望仅为一项功能实现如此繁琐的系统,这是我在创建其应用程序时决定考虑的事情。

  

有问题的通知系统(遗憾的是,文档或示例并不十分清楚)https://www.npmjs.com/package/react-native-in-app-notification

     

这是我正在使用的导航库:https://reactnavigation.org/

1 个答案:

答案 0 :(得分:1)

您想要的是一个与导航相同级别的组件(以便可以在其上显示)。在多个项目中,我使用react-native-root-siblings这样做。它允许您在应用程序以及导航上添加UI。

一个例子,我是怎么做的。深色层和底部的框是“兄弟姐妹”组件的一部分。 https://gyazo.com/7ad3fc3fea767ea84243aaa493294670

兄弟姐妹的用法类似于React-Native的Alert,因此是一个功能(非常有用!)

messageMenu.js

import React, { Component } from 'react';
import RootSiblings from 'react-native-root-siblings';
import MessageMenuContainer from './MessageMenuContainer';

export default class Dialog extends Component {
  static show = (props) => new RootSiblings(<MessageMenuContainer {...props} />);

  static update = (menu, props) => {
    if (menu instanceof RootSiblings) {
      menu.update(<MessageMenuContainer {...props} />);
    } else {
      console.warn(`Dialog.update expected a \`RootSiblings\` instance as argument.\nBut got \`${typeof menu}\` instead.`);
    }
  }

  static close = (menu) => {
    if (menu instanceof RootSiblings) {
      menu.destroy();
    } else {
      console.warn(`Dialog.destroy expected a \`RootSiblings\` instance as argument.\nBut got \`${typeof menu}\` instead.`);
    }
  }

  render() {
    return null;
  }
}

export {
  RootSiblings as Manager,
};

MessageMenuContainer是要在顶部呈现的组件。

使用根兄弟姐妹的组件:

 import React from 'react';
import PropTypes from 'prop-types';
import I18n from 'react-native-i18n';
import { BackHandler, Keyboard, Platform, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import DraftMenu from './messageMenu'; //HERE IS THE IMPORT YOU WANT

import { Metrics, Colors, Fonts } from '../../main/themes';

class DraftBackButton extends React.Component {

  state = {
    draftMenu: undefined,
  }

  componentDidMount() {
    BackHandler.addEventListener('hardwareBackPress', this.handleBackAndroid);
  }
  componentWillUnmount() {
    BackHandler.removeEventListener('hardwareBackPress', this.handleBackAndroid);
  }

  handleBackAndroid = () => {
    this.handleBack();
    return true;
  }

  handleBack = async () => {
    Keyboard.dismiss();
    await this.openDraftMenu();
  }

  openDraftMenu = async () => {
    if (this.state.draftMenu) {
      await DraftMenu.update(this.state.draftMenu, this.draftMenuProps());
    } else {
      const draftMenu = await DraftMenu.show(this.draftMenuProps());
      this.setState({ draftMenu: draftMenu });
    }
  }

  draftMenuProps = () => ({
    options: [
      { title: I18n.t('message.deleteDraft'), onPress: this.deleteDraft, icon: 'trash' },
      { title: I18n.t('message.saveDraft'), onPress: this.saveOrUpdateDraft, icon: 'documents' },
      { title: I18n.t('cancel'), icon: 'close', style: { backgroundColor: Colors.tertiaryBackground } },
    ],
    destroyMenuComponent: async () => {
      DraftMenu.close(this.state.draftMenu);
      await this.setState({ draftMenu: undefined });
    },
    withIcon: true,
  })

  saveOrUpdateDraft = async () => {
   // SAVE OR UPDATE DRAFT. NOT IMPORTANT
  }

  saveDraft = async () => {
    // SAVING THE DRAFT
  }

  updateDraft = async () => {
   // UPDATING THE DRAFT
  }

  deleteDraft = async () => {
    // DELETING THE DRAFT
  }

  render() {
    return (
      <TouchableOpacity
        hitSlop={Metrics.touchable.largeHitSlop}
        onPress={() => {
          this.handleBack();
        }}
      >
        <Text>BUTTON</Text>
      </TouchableOpacity>
    );
  }
}

DraftBackButton.propTypes = {
  // ALL THE PROPTYPES
};

function mapStateToProps(state, ownProps) {
 //
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators({ fetchMessages }, dispatch),
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(DraftBackButton);

此库的最好之处在于,您可以在应用程序中的任何位置调用.show,它将在最顶部呈现! 希望这就是您想要的!

编辑: 我更新了如何使用“根兄弟姐妹”的示例。

这是我的MessageContainer的内容,该内容将显示在所有内容的顶部

    import React from 'react';
    import PropTypes from 'prop-types';
    import { Animated, Dimensions, InteractionManager, StyleSheet, TouchableOpacity, View } from 'react-native';
    import MessageMenuItem from './MessageMenuItem';
    import { Colors } from '../../../main/themes';

    const { width, height } = Dimensions.get('window');

    const OPTION_HEIGHT = 55;
    const OVERLAY_OPACITY = 0.5;

    export default class DraftMenuContainer extends React.Component {

      constructor(props) {
        super(props);

        this.state = {
          animatedHeight: new Animated.Value(0),
          animatedOpacity: new Animated.Value(0),
          menuHeight: props.options.length * OPTION_HEIGHT,
        };
      }

      componentDidMount() {
        this.onOpen();
      }

// Using Animated from react-native to make the animation (fade in/out of the dark layer and the dimensions of the actual content)

      onOpen = async () => {
        await this.state.animatedHeight.setValue(0);
        await this.state.animatedOpacity.setValue(0);

        Animated.parallel([
          Animated.timing(this.state.animatedHeight, { toValue: this.state.menuHeight, duration: 200 }),
          Animated.timing(this.state.animatedOpacity, { toValue: OVERLAY_OPACITY, duration: 200 }),
        ]).start();
      }

      onClose = async () => {
        await this.state.animatedHeight.setValue(this.state.menuHeight);
        await this.state.animatedOpacity.setValue(OVERLAY_OPACITY);

        Animated.parallel([
          Animated.timing(this.state.animatedHeight, { toValue: 0, duration: 200 }),
          Animated.timing(this.state.animatedOpacity, { toValue: 0, duration: 200 }),
        ]).start(() => this.props.destroyMenuComponent()); // HERE IS IMPORTANT. Once you're done with the component, you need to destroy it. To do so, you need to set a props 'destroyMenuComponent' which is set at the creation of the initial view. See the other code what it actually do
      }

      render() {
        return (
          <View style={styles.menu}>
            <Animated.View style={[styles.backgroundOverlay, { opacity: this.state.animatedOpacity }]}>
              <TouchableOpacity
                activeOpacity={1}
                onPress={() => this.onClose()}
                style={{ flex: 1 }}
              />
            </Animated.View>

            <Animated.View style={[styles.container, { height: this.state.animatedHeight }]}>
              {this.props.options.map((option, index) => (
                <MessageMenuItem
                  height={OPTION_HEIGHT}
                  icon={option.icon}
                  key={index}
                  onPress={async () => {
                    await this.onClose();
                    InteractionManager.runAfterInteractions(() => {
                      if (option.onPress) {
                        option.onPress();
                      }
                    });
                  }}
                  style={option.style}
                  title={option.title}
                  withIcon={this.props.withIcon}
                />
              ))}
            </Animated.View>
          </View>
        );
      }
    }

    DraftMenuContainer.propTypes = {
      destroyMenuComponent: PropTypes.func.isRequired,
      withIcon: PropTypes.bool,
      options: PropTypes.arrayOf(PropTypes.shape({
        icon: PropTypes.string.isRequired,
        onPress: PropTypes.func,
        title: PropTypes.string.isRequired,
      })),
    };