代码之家  ›  专栏  ›  技术社区  ›  Cacoon

全局应用内通知

  •  0
  • Cacoon  · 技术社区  · 7 年前

    我希望利用一个应用程序内通知系统,也就是一个更具吸引力和较少面对面'使用警报,让用户知道正在做什么动作,特别是当例如条形码已被检测到,但它需要发送该条形码到服务器和用户需要等待。

    我已经找到了这个lib并尝试实现它;但是由于我使用的是React导航,并且我希望在应用程序的最顶端呈现该项,因此它会被React Native header切断

    有没有可能有一个函数,我可以创建和引用每当我想要一个全局通知,它将呈现在最顶端,我想它将需要呈现在这里:

    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 回复  |  直到 7 年前
        1
  •  1
  •   Alexandre Rivest    7 年前

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

    我用它做了什么的一个例子。暗层和底部的框是同级组件的一部分。 https://gyazo.com/7ad3fc3fea767ea84243aaa493294670

    兄弟节点的用法与React Native的Alert类似,因此用作函数(非常有用!)

    消息菜单.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,
          })),
        };