feat(mobile): add 1 liner notifications
Adds 1 liner notifications to mobile. Only the title is displayed. In case the title is missing there's a fallback to the description.
This commit is contained in:
parent
15fd27543a
commit
c7979a3944
|
@ -458,9 +458,9 @@
|
||||||
},
|
},
|
||||||
"me": "me",
|
"me": "me",
|
||||||
"notify": {
|
"notify": {
|
||||||
"connectedOneMember": "__name__ connected",
|
"connectedOneMember": "__name__ joined the meeting",
|
||||||
"connectedThreePlusMembers": "__name__ and __count__ others connected",
|
"connectedThreePlusMembers": "__name__ and __count__ others joined the meeting",
|
||||||
"connectedTwoMembers": "__first__ and __second__ connected",
|
"connectedTwoMembers": "__first__ and __second__ joined the meeting",
|
||||||
"disconnected": "disconnected",
|
"disconnected": "disconnected",
|
||||||
"focus": "Conference focus",
|
"focus": "Conference focus",
|
||||||
"focusFail": "__component__ not available - retry in __ms__ sec",
|
"focusFail": "__component__ not available - retry in __ms__ sec",
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { NotificationsContainer } from '../../notifications/components';
|
||||||
|
|
||||||
|
import { shouldDisplayNotifications } from '../functions';
|
||||||
import { shouldDisplayTileView } from '../../video-layout';
|
import { shouldDisplayTileView } from '../../video-layout';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,6 +12,14 @@ import { shouldDisplayTileView } from '../../video-layout';
|
||||||
*/
|
*/
|
||||||
export type AbstractProps = {
|
export type AbstractProps = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to {@code true} when the notifications are to be displayed.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_notificationsVisible: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Conference room name.
|
* Conference room name.
|
||||||
*
|
*
|
||||||
|
@ -24,6 +37,34 @@ export type AbstractProps = {
|
||||||
_shouldDisplayTileView: boolean
|
_shouldDisplayTileView: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container to hold video status labels, including recording status and
|
||||||
|
* current large video quality.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
export class AbstractConference<P: AbstractProps, S>
|
||||||
|
extends Component<P, S> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the {@code LocalRecordingLabel}.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The properties to be passed to
|
||||||
|
* the {@code NotificationsContainer}.
|
||||||
|
* @protected
|
||||||
|
* @returns {React$Element}
|
||||||
|
*/
|
||||||
|
renderNotificationsContainer(props: ?Object) {
|
||||||
|
if (this.props._notificationsVisible) {
|
||||||
|
return (
|
||||||
|
React.createElement(NotificationsContainer, props)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps (parts of) the redux state to the associated props of the {@link Labels}
|
* Maps (parts of) the redux state to the associated props of the {@link Labels}
|
||||||
* {@code Component}.
|
* {@code Component}.
|
||||||
|
@ -34,6 +75,7 @@ export type AbstractProps = {
|
||||||
*/
|
*/
|
||||||
export function abstractMapStateToProps(state: Object) {
|
export function abstractMapStateToProps(state: Object) {
|
||||||
return {
|
return {
|
||||||
|
_notificationsVisible: shouldDisplayNotifications(state),
|
||||||
_room: state['features/base/conference'].room,
|
_room: state['features/base/conference'].room,
|
||||||
_shouldDisplayTileView: shouldDisplayTileView(state)
|
_shouldDisplayTileView: shouldDisplayTileView(state)
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { BackHandler, StatusBar, View } from 'react-native';
|
import { BackHandler, SafeAreaView, StatusBar, View } from 'react-native';
|
||||||
import { connect as reactReduxConnect } from 'react-redux';
|
import { connect as reactReduxConnect } from 'react-redux';
|
||||||
|
|
||||||
import { appNavigate } from '../../../app';
|
import { appNavigate } from '../../../app';
|
||||||
|
@ -10,6 +10,7 @@ import { connect, disconnect } from '../../../base/connection';
|
||||||
import { getParticipantCount } from '../../../base/participants';
|
import { getParticipantCount } from '../../../base/participants';
|
||||||
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
|
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
|
||||||
import {
|
import {
|
||||||
|
isNarrowAspectRatio,
|
||||||
makeAspectRatioAware
|
makeAspectRatioAware
|
||||||
} from '../../../base/responsive-ui';
|
} from '../../../base/responsive-ui';
|
||||||
import { TestConnectionInfo } from '../../../base/testing';
|
import { TestConnectionInfo } from '../../../base/testing';
|
||||||
|
@ -17,6 +18,7 @@ import { createDesiredLocalTracks } from '../../../base/tracks';
|
||||||
import { ConferenceNotification } from '../../../calendar-sync';
|
import { ConferenceNotification } from '../../../calendar-sync';
|
||||||
import { Chat } from '../../../chat';
|
import { Chat } from '../../../chat';
|
||||||
import {
|
import {
|
||||||
|
FILMSTRIP_SIZE,
|
||||||
Filmstrip,
|
Filmstrip,
|
||||||
isFilmstripVisible,
|
isFilmstripVisible,
|
||||||
TileView
|
TileView
|
||||||
|
@ -26,7 +28,10 @@ import { AddPeopleDialog, CalleeInfoContainer } from '../../../invite';
|
||||||
import { Captions } from '../../../subtitles';
|
import { Captions } from '../../../subtitles';
|
||||||
import { setToolboxVisible, Toolbox } from '../../../toolbox';
|
import { setToolboxVisible, Toolbox } from '../../../toolbox';
|
||||||
|
|
||||||
import { abstractMapStateToProps } from '../AbstractConference';
|
import {
|
||||||
|
AbstractConference,
|
||||||
|
abstractMapStateToProps
|
||||||
|
} from '../AbstractConference';
|
||||||
import DisplayNameLabel from './DisplayNameLabel';
|
import DisplayNameLabel from './DisplayNameLabel';
|
||||||
import Labels from './Labels';
|
import Labels from './Labels';
|
||||||
import NavigationBar from './NavigationBar';
|
import NavigationBar from './NavigationBar';
|
||||||
|
@ -134,7 +139,7 @@ type Props = AbstractProps & {
|
||||||
/**
|
/**
|
||||||
* The conference page of the mobile (i.e. React Native) application.
|
* The conference page of the mobile (i.e. React Native) application.
|
||||||
*/
|
*/
|
||||||
class Conference extends Component<Props> {
|
class Conference extends AbstractConference<Props, *> {
|
||||||
/**
|
/**
|
||||||
* Initializes a new Conference instance.
|
* Initializes a new Conference instance.
|
||||||
*
|
*
|
||||||
|
@ -296,7 +301,12 @@ class Conference extends Component<Props> {
|
||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<NavigationBar />
|
<SafeAreaView
|
||||||
|
pointerEvents = 'box-none'
|
||||||
|
style = { styles.navBarSafeView }>
|
||||||
|
<NavigationBar />
|
||||||
|
{ this.renderNotificationsContainer() }
|
||||||
|
</SafeAreaView>
|
||||||
|
|
||||||
<TestConnectionInfo />
|
<TestConnectionInfo />
|
||||||
|
|
||||||
|
@ -341,6 +351,37 @@ class Conference extends Component<Props> {
|
||||||
? <ConferenceNotification />
|
? <ConferenceNotification />
|
||||||
: undefined);
|
: undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a container for notifications to be displayed by the
|
||||||
|
* base/notifications feature.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {React$Element}
|
||||||
|
*/
|
||||||
|
renderNotificationsContainer() {
|
||||||
|
const notificationsStyle = {};
|
||||||
|
|
||||||
|
// In the landscape mode (wide) there's problem with notifications being
|
||||||
|
// shadowed by the filmstrip rendered on the right. This makes the "x"
|
||||||
|
// button not clickable. In order to avoid that a margin of the
|
||||||
|
// filmstrip's size is added to the right.
|
||||||
|
//
|
||||||
|
// Pawel: after many attempts I failed to make notifications adjust to
|
||||||
|
// their contents width because of column and rows being used in the
|
||||||
|
// flex layout. The only option that seemed to limit the notification's
|
||||||
|
// size was explicit 'width' value which is not better than the margin
|
||||||
|
// added here.
|
||||||
|
if (this.props._filmstripVisible && !isNarrowAspectRatio(this)) {
|
||||||
|
notificationsStyle.marginRight = FILMSTRIP_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.renderNotificationsContainer(
|
||||||
|
{
|
||||||
|
style: notificationsStyle
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,6 +21,7 @@ import AbstractLabels, {
|
||||||
_abstractMapStateToProps,
|
_abstractMapStateToProps,
|
||||||
type Props as AbstractLabelsProps
|
type Props as AbstractLabelsProps
|
||||||
} from '../AbstractLabels';
|
} from '../AbstractLabels';
|
||||||
|
import { shouldDisplayNotifications } from '../../functions';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -363,7 +364,9 @@ function _mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
..._abstractMapStateToProps(state),
|
..._abstractMapStateToProps(state),
|
||||||
_reducedUI: state['features/base/responsive-ui'].reducedUI,
|
_reducedUI: state['features/base/responsive-ui'].reducedUI,
|
||||||
_visible: !isToolboxVisible(state) && !shouldDisplayTileView(state)
|
_visible: !isToolboxVisible(state)
|
||||||
|
&& !shouldDisplayTileView(state)
|
||||||
|
&& !shouldDisplayNotifications(state)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,39 +40,33 @@ class NavigationBar extends Component<Props> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return [
|
||||||
<View
|
<LinearGradient
|
||||||
pointerEvents = 'box-none'
|
colors = { NAVBAR_GRADIENT_COLORS }
|
||||||
style = { styles.navBarContainer }>
|
key = { 1 }
|
||||||
<LinearGradient
|
pointerEvents = 'none'
|
||||||
colors = { NAVBAR_GRADIENT_COLORS }
|
style = { styles.gradient }>
|
||||||
pointerEvents = 'none'
|
<SafeAreaView>
|
||||||
style = { styles.gradient }>
|
<View style = { styles.gradientStretch } />
|
||||||
<SafeAreaView>
|
|
||||||
<View style = { styles.gradientStretch } />
|
|
||||||
</SafeAreaView>
|
|
||||||
</LinearGradient>
|
|
||||||
<SafeAreaView
|
|
||||||
pointerEvents = 'box-none'
|
|
||||||
style = { styles.navBarSafeView }>
|
|
||||||
<View
|
|
||||||
pointerEvents = 'box-none'
|
|
||||||
style = { styles.navBarWrapper }>
|
|
||||||
<PictureInPictureButton
|
|
||||||
styles = { styles.navBarButton } />
|
|
||||||
<View
|
|
||||||
pointerEvents = 'box-none'
|
|
||||||
style = { styles.roomNameWrapper }>
|
|
||||||
<Text
|
|
||||||
numberOfLines = { 1 }
|
|
||||||
style = { styles.roomName }>
|
|
||||||
{ this.props._meetingName }
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
</LinearGradient>,
|
||||||
|
<View
|
||||||
|
key = { 2 }
|
||||||
|
pointerEvents = 'box-none'
|
||||||
|
style = { styles.navBarWrapper }>
|
||||||
|
<PictureInPictureButton
|
||||||
|
styles = { styles.navBarButton } />
|
||||||
|
<View
|
||||||
|
pointerEvents = 'box-none'
|
||||||
|
style = { styles.roomNameWrapper }>
|
||||||
|
<Text
|
||||||
|
numberOfLines = { 1 }
|
||||||
|
style = { styles.roomName }>
|
||||||
|
{ this.props._meetingName }
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,10 @@ export default createStyleSheet({
|
||||||
},
|
},
|
||||||
|
|
||||||
gradient: {
|
gradient: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { connect as reactReduxConnect } from 'react-redux';
|
import { connect as reactReduxConnect } from 'react-redux';
|
||||||
|
|
||||||
import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
|
import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
|
||||||
|
@ -13,7 +13,6 @@ import { Chat } from '../../../chat';
|
||||||
import { Filmstrip } from '../../../filmstrip';
|
import { Filmstrip } from '../../../filmstrip';
|
||||||
import { CalleeInfoContainer } from '../../../invite';
|
import { CalleeInfoContainer } from '../../../invite';
|
||||||
import { LargeVideo } from '../../../large-video';
|
import { LargeVideo } from '../../../large-video';
|
||||||
import { NotificationsContainer } from '../../../notifications';
|
|
||||||
import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
|
import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -28,7 +27,10 @@ import { maybeShowSuboptimalExperienceNotification } from '../../functions';
|
||||||
import Labels from './Labels';
|
import Labels from './Labels';
|
||||||
import { default as Notice } from './Notice';
|
import { default as Notice } from './Notice';
|
||||||
import { default as Subject } from './Subject';
|
import { default as Subject } from './Subject';
|
||||||
import { abstractMapStateToProps } from '../AbstractConference';
|
import {
|
||||||
|
AbstractConference,
|
||||||
|
abstractMapStateToProps
|
||||||
|
} from '../AbstractConference';
|
||||||
|
|
||||||
import type { AbstractProps } from '../AbstractConference';
|
import type { AbstractProps } from '../AbstractConference';
|
||||||
|
|
||||||
|
@ -87,7 +89,7 @@ type Props = AbstractProps & {
|
||||||
/**
|
/**
|
||||||
* The conference page of the Web application.
|
* The conference page of the Web application.
|
||||||
*/
|
*/
|
||||||
class Conference extends Component<Props> {
|
class Conference extends AbstractConference<Props, *> {
|
||||||
_onFullScreenChange: Function;
|
_onFullScreenChange: Function;
|
||||||
_onShowToolbar: Function;
|
_onShowToolbar: Function;
|
||||||
_originalOnShowToolbar: Function;
|
_originalOnShowToolbar: Function;
|
||||||
|
@ -218,7 +220,7 @@ class Conference extends Component<Props> {
|
||||||
{ filmstripOnly || <Toolbox /> }
|
{ filmstripOnly || <Toolbox /> }
|
||||||
{ filmstripOnly || <Chat /> }
|
{ filmstripOnly || <Chat /> }
|
||||||
|
|
||||||
<NotificationsContainer />
|
{ this.renderNotificationsContainer() }
|
||||||
|
|
||||||
<CalleeInfoContainer />
|
<CalleeInfoContainer />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
import { getName } from '../app';
|
|
||||||
import { translateToHTML } from '../base/i18n';
|
import { translateToHTML } from '../base/i18n';
|
||||||
import { browser } from '../base/lib-jitsi-meet';
|
import { browser } from '../base/lib-jitsi-meet';
|
||||||
import { showWarningNotification } from '../notifications';
|
import { toState } from '../base/redux';
|
||||||
|
|
||||||
|
import { getName } from '../app';
|
||||||
|
import {
|
||||||
|
areThereNotifications,
|
||||||
|
showWarningNotification
|
||||||
|
} from '../notifications';
|
||||||
|
import { getOverlayToRender } from '../overlay';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the suboptimal experience notification if needed.
|
* Shows the suboptimal experience notification if needed.
|
||||||
|
@ -36,3 +42,20 @@ export function maybeShowSuboptimalExperienceNotification(dispatch, t) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether or not the notifications should be displayed within
|
||||||
|
* the conference feature based on the current Redux state.
|
||||||
|
*
|
||||||
|
* @param {Object|Function} stateful - The redux store state.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function shouldDisplayNotifications(stateful) {
|
||||||
|
const state = toState(stateful);
|
||||||
|
const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
|
||||||
|
const { calleeInfoVisible } = state['features/invite'];
|
||||||
|
|
||||||
|
return areThereNotifications(state)
|
||||||
|
&& !isAnyOverlayVisible
|
||||||
|
&& !calleeInfoVisible;
|
||||||
|
}
|
||||||
|
|
|
@ -2,9 +2,8 @@
|
||||||
|
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
|
|
||||||
import { getOverlayToRender } from '../../overlay';
|
|
||||||
|
|
||||||
import { hideNotification } from '../actions';
|
import { hideNotification } from '../actions';
|
||||||
|
import { areThereNotifications } from '../functions';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
|
|
||||||
|
@ -165,12 +164,10 @@ export default class AbstractNotificationsContainer<P: Props>
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function _abstractMapStateToProps(state: Object) {
|
export function _abstractMapStateToProps(state: Object) {
|
||||||
const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
|
const { notifications } = state['features/notifications'];
|
||||||
const { enabled, notifications } = state['features/notifications'];
|
const _visible = areThereNotifications(state);
|
||||||
const { calleeInfoVisible } = state['features/invite'];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_notifications: enabled && !isAnyOverlayVisible && !calleeInfoVisible
|
_notifications: _visible ? notifications : []
|
||||||
? notifications : []
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
|
import { Icon } from '../../base/font-icons';
|
||||||
|
import { translate } from '../../base/i18n';
|
||||||
|
|
||||||
|
import AbstractNotification, {
|
||||||
|
type Props
|
||||||
|
} from './AbstractNotification';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React {@link Component} to display a notification.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class Notification extends AbstractNotification<Props> {
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isDismissAllowed
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
pointerEvents = 'box-none'
|
||||||
|
style = { styles.notification }>
|
||||||
|
<View style = { styles.contentColumn }>
|
||||||
|
<View
|
||||||
|
pointerEvents = 'box-none'
|
||||||
|
style = { styles.notificationContent }>
|
||||||
|
{
|
||||||
|
this._renderContent()
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{
|
||||||
|
isDismissAllowed
|
||||||
|
&& <TouchableOpacity onPress = { this._onDismissed }>
|
||||||
|
<Icon
|
||||||
|
name = { 'close' }
|
||||||
|
style = { styles.dismissIcon } />
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the notification's content. If the title or title key is present
|
||||||
|
* it will be just the title. Otherwise it will fallback to description.
|
||||||
|
*
|
||||||
|
* @returns {Array<ReactElement>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_renderContent() {
|
||||||
|
const { t, title, titleArguments, titleKey } = this.props;
|
||||||
|
const titleText = title || (titleKey && t(titleKey, titleArguments));
|
||||||
|
|
||||||
|
if (titleText) {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
numberOfLines = { 1 }
|
||||||
|
style = { styles.contentText } >
|
||||||
|
{ titleText }
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._getDescription().map((line, index) => (
|
||||||
|
<Text
|
||||||
|
key = { index }
|
||||||
|
numberOfLines = { 1 }
|
||||||
|
style = { styles.contentText }>
|
||||||
|
{ line }
|
||||||
|
</Text>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDescription: () => Array<string>;
|
||||||
|
|
||||||
|
_onDismissed: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(Notification);
|
|
@ -0,0 +1,67 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import AbstractNotificationsContainer, {
|
||||||
|
_abstractMapStateToProps,
|
||||||
|
type Props as AbstractProps
|
||||||
|
} from './AbstractNotificationsContainer';
|
||||||
|
import Notification from './Notification';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
type Props = AbstractProps & {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any custom styling applied to the notifications container.
|
||||||
|
*/
|
||||||
|
style: Object
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React {@link Component} which displays notifications and handles
|
||||||
|
* automatic dismissmal after a notification is shown for a defined timeout
|
||||||
|
* period.
|
||||||
|
*
|
||||||
|
* @extends {Component}
|
||||||
|
*/
|
||||||
|
class NotificationsContainer
|
||||||
|
extends AbstractNotificationsContainer<Props> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { _notifications } = this.props;
|
||||||
|
|
||||||
|
if (!_notifications || !_notifications.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
pointerEvents = 'box-none'
|
||||||
|
style = { [
|
||||||
|
styles.notificationContainer,
|
||||||
|
this.props.style
|
||||||
|
] } >
|
||||||
|
{
|
||||||
|
_notifications.map(
|
||||||
|
({ props, uid }) => (
|
||||||
|
<Notification
|
||||||
|
{ ...props }
|
||||||
|
key = { uid }
|
||||||
|
onDismissed = { this._onDismissed }
|
||||||
|
uid = { uid } />))
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDismissed: number => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_abstractMapStateToProps)(NotificationsContainer);
|
|
@ -0,0 +1,61 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { BoxModel, createStyleSheet, ColorPalette } from '../../base/styles';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The styles of the React {@code Components} of the feature notifications.
|
||||||
|
*/
|
||||||
|
export default createStyleSheet({
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content (left) column of the notification.
|
||||||
|
*/
|
||||||
|
contentColumn: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'column',
|
||||||
|
paddingLeft: 1.5 * BoxModel.padding
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test style of the notification.
|
||||||
|
*/
|
||||||
|
contentText: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
color: ColorPalette.white
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dismiss icon style.
|
||||||
|
*/
|
||||||
|
dismissIcon: {
|
||||||
|
color: ColorPalette.white,
|
||||||
|
fontSize: 20,
|
||||||
|
padding: 1.5 * BoxModel.padding
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outermost view of a single notification.
|
||||||
|
*/
|
||||||
|
notification: {
|
||||||
|
backgroundColor: '#768898',
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 48,
|
||||||
|
marginTop: 0.5 * BoxModel.margin
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outermost container of a list of notifications.
|
||||||
|
*/
|
||||||
|
notificationContainer: {
|
||||||
|
flexGrow: 0,
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for the message.
|
||||||
|
*/
|
||||||
|
notificationContent: {
|
||||||
|
flexDirection: 'column'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { toState } from '../base/redux';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether or not the notifications are enabled and if there are any
|
||||||
|
* notifications to be displayed based on the current Redux state.
|
||||||
|
*
|
||||||
|
* @param {Object|Function} stateful - The redux store state.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function areThereNotifications(stateful) {
|
||||||
|
const state = toState(stateful);
|
||||||
|
const { enabled, notifications } = state['features/notifications'];
|
||||||
|
|
||||||
|
return enabled && notifications.length > 0;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
export * from './actions';
|
export * from './actions';
|
||||||
export * from './actionTypes';
|
export * from './actionTypes';
|
||||||
export * from './components';
|
export * from './components';
|
||||||
|
export * from './functions';
|
||||||
|
|
||||||
import './middleware';
|
import './middleware';
|
||||||
import './reducer';
|
import './reducer';
|
||||||
|
|
Loading…
Reference in New Issue