[RN] Add swipe to delete feature
This commit is contained in:
parent
9d27c36d80
commit
d8c1f107da
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 612 B |
Binary file not shown.
Before Width: | Height: | Size: 889 B |
|
@ -63,6 +63,7 @@
|
|||
"join": "JOIN",
|
||||
"privacy": "Privacy",
|
||||
"recentList": "Recent",
|
||||
"recentListDelete": "Delete",
|
||||
"recentListEmpty": "Your recent list is currently empty. Chat with your team and you will find all your recent meetings here.",
|
||||
"roomname": "Enter room name",
|
||||
"roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.",
|
||||
|
|
|
@ -11426,8 +11426,7 @@
|
|||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
|
||||
"dev": true
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
|
@ -12281,6 +12280,14 @@
|
|||
"integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=",
|
||||
"dev": true
|
||||
},
|
||||
"raf": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz",
|
||||
"integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==",
|
||||
"requires": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"raf-schd": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-2.1.2.tgz",
|
||||
|
@ -12657,6 +12664,16 @@
|
|||
"resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.10.9.tgz",
|
||||
"integrity": "sha1-awCw9K/QF83gn7udFx3xtdW4Uag="
|
||||
},
|
||||
"react-native-swipeout": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/react-native-swipeout/-/react-native-swipeout-2.3.6.tgz",
|
||||
"integrity": "sha512-t9suUCspzck4vp2pWggWe0frS/QOtX6yYCawHnEes75A7dZCEE74bxX2A1bQzGH9cUMjq6xsdfC94RbiDKIkJg==",
|
||||
"requires": {
|
||||
"create-react-class": "^15.6.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react-tween-state": "^0.1.5"
|
||||
}
|
||||
},
|
||||
"react-native-vector-icons": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-4.4.2.tgz",
|
||||
|
@ -12805,6 +12822,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"react-tween-state": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/react-tween-state/-/react-tween-state-0.1.5.tgz",
|
||||
"integrity": "sha1-6YsGZVHvuTy5LdG+FJlcLj3q4zk=",
|
||||
"requires": {
|
||||
"raf": "^3.1.0",
|
||||
"tween-functions": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||
|
@ -15500,6 +15526,11 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"tween-functions": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz",
|
||||
"integrity": "sha1-GuOlDnxguz3vd06scHrLynO7w/8="
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
"react-native-locale-detector": "github:jitsi/react-native-locale-detector#845281e9fd4af756f6d0f64afe5cce08c63e5ee9",
|
||||
"react-native-prompt": "1.0.0",
|
||||
"react-native-sound": "0.10.9",
|
||||
"react-native-swipeout": "2.3.6",
|
||||
"react-native-vector-icons": "4.4.2",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#be3de15bb988cfabbb62cd4b3b06f4c920ee5ba0",
|
||||
"react-redux": "5.0.7",
|
||||
|
|
|
@ -17,6 +17,11 @@ export type Item = {
|
|||
*/
|
||||
elementAfter?: ?ComponentType<any>,
|
||||
|
||||
/**
|
||||
* Unique ID of the item.
|
||||
*/
|
||||
id: Object | string,
|
||||
|
||||
/**
|
||||
* Item title
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
/**
|
||||
* Abstract component that defines a refreshable page to be rendered by
|
||||
* {@code PagedList}.
|
||||
*/
|
||||
export default class AbstractPage<P> extends Component<P> {
|
||||
/**
|
||||
* Method to be overriden by the implementing classes to refresh the data
|
||||
* content of the component.
|
||||
*
|
||||
* Note: It is a static method as the {@code Component} may not be
|
||||
* initialized yet when the UI invokes refresh (e.g. tab change).
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
static refresh() {
|
||||
// No implementation in abstract class.
|
||||
}
|
||||
}
|
|
@ -43,7 +43,13 @@ type Props = {
|
|||
/**
|
||||
* An array of sections
|
||||
*/
|
||||
sections: Array<Section>
|
||||
sections: Array<Section>,
|
||||
|
||||
/**
|
||||
* Optional array of on-slide actions this list should support. For details
|
||||
* see https://github.com/dancormier/react-native-swipeout.
|
||||
*/
|
||||
slideActions?: Array<Object>
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -205,7 +211,8 @@ class NavigateSectionList extends Component<Props> {
|
|||
key = { key }
|
||||
onPress = { url ? this._onPress(url) : undefined }
|
||||
secondaryAction = {
|
||||
url ? undefined : this._onSecondaryAction(id) } />
|
||||
url ? undefined : this._onSecondaryAction(id) }
|
||||
slideActions = { this.props.slideActions } />
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './_';
|
||||
export { default as AbstractPage } from './AbstractPage';
|
||||
export { default as NavigateSectionList } from './NavigateSectionList';
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AbstractPagedList}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The zero-based index of the page that should be rendered (selected) by
|
||||
* default.
|
||||
*/
|
||||
defaultPage: number,
|
||||
|
||||
/**
|
||||
* Indicates if the list is disabled or not.
|
||||
*/
|
||||
disabled: boolean,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Callback to execute on page change.
|
||||
*/
|
||||
onSelectPage: ?Function,
|
||||
|
||||
/**
|
||||
* The pages of the PagedList component to be rendered.
|
||||
*
|
||||
* Note: An element's {@code component} may be {@code undefined} and then it
|
||||
* won't need to be rendered.
|
||||
*/
|
||||
pages: Array<{
|
||||
component: ?Object,
|
||||
icon: string | number,
|
||||
title: string
|
||||
}>
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link AbstractPagedList}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The currently selected page.
|
||||
*/
|
||||
pageIndex: number
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class containing the platform independent logic of the paged lists.
|
||||
*/
|
||||
export default class AbstractPagedList extends Component<Props, State> {
|
||||
/**
|
||||
* Initializes a new {@code AbstractPagedList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pageIndex: this._validatePageIndex(props.defaultPage)
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._maybeRefreshSelectedPage
|
||||
= this._maybeRefreshSelectedPage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._maybeRefreshSelectedPage(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
const pages = this.props.pages.filter(({ component }) => component);
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
styles.pagedListContainer,
|
||||
disabled ? styles.pagedListContainerDisabled : null
|
||||
] }>
|
||||
{
|
||||
pages.length > 1
|
||||
? this._renderPagedList(disabled)
|
||||
: pages.length === 1
|
||||
? React.createElement(
|
||||
|
||||
// $FlowExpectedError
|
||||
/* type */ pages[0].component,
|
||||
/* props */ {
|
||||
disabled,
|
||||
style: styles.pagedList
|
||||
})
|
||||
: null
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_maybeRefreshSelectedPage: ?boolean => void;
|
||||
|
||||
/**
|
||||
* Components that this PagedList displays may have a refresh function to
|
||||
* refresh its content when displayed (or based on custom logic). This
|
||||
* function invokes this logic if it's present.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
_maybeRefreshSelectedPage(isInteractive: boolean = true) {
|
||||
const selectedPage = this.props.pages[this.state.pageIndex];
|
||||
let component;
|
||||
|
||||
if (selectedPage && (component = selectedPage.component)) {
|
||||
const { refresh } = component;
|
||||
|
||||
typeof refresh === 'function'
|
||||
&& refresh.call(component, this.props.dispatch, isInteractive);
|
||||
}
|
||||
}
|
||||
|
||||
_renderPagedList: boolean => React$Node;
|
||||
|
||||
_selectPage: number => void;
|
||||
|
||||
/**
|
||||
* Sets the selected page.
|
||||
*
|
||||
* @param {number} pageIndex - The index of the selected page.
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_selectPage(pageIndex: number) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
pageIndex = this._validatePageIndex(pageIndex);
|
||||
|
||||
const { onSelectPage } = this.props;
|
||||
|
||||
typeof onSelectPage === 'function' && onSelectPage(pageIndex);
|
||||
|
||||
this.setState({ pageIndex }, this._maybeRefreshSelectedPage);
|
||||
}
|
||||
|
||||
_validatePageIndex: number => number;
|
||||
|
||||
/**
|
||||
* Validates the requested page index and returns a safe value.
|
||||
*
|
||||
* @private
|
||||
* @param {number} pageIndex - The requested page index.
|
||||
* @returns {number}
|
||||
*/
|
||||
_validatePageIndex(pageIndex) {
|
||||
// pageIndex may point to a non-existing page if some of the pages are
|
||||
// disabled (their component property is undefined).
|
||||
const maxPageIndex
|
||||
= this.props.pages.filter(({ component }) => component).length - 1;
|
||||
|
||||
return Math.max(0, Math.min(maxPageIndex, pageIndex));
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import Swipeout from 'react-native-swipeout';
|
||||
|
||||
import { ColorPalette } from '../../../styles';
|
||||
|
||||
import Container from './Container';
|
||||
import Text from './Text';
|
||||
|
@ -22,7 +25,13 @@ type Props = {
|
|||
/**
|
||||
* Function to be invoked when secondary action was performed on an Item.
|
||||
*/
|
||||
secondaryAction: ?Function
|
||||
secondaryAction: ?Function,
|
||||
|
||||
/**
|
||||
* Optional array of on-slide actions this list should support. For details
|
||||
* see https://github.com/dancormier/react-native-swipeout.
|
||||
*/
|
||||
slideActions?: Array<Object>
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,37 +138,59 @@ export default class NavigateSectionListItem extends Component<Props> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { colorBase, lines, title } = this.props.item;
|
||||
const { slideActions } = this.props;
|
||||
const { id, colorBase, lines, title } = this.props.item;
|
||||
const avatarStyles = {
|
||||
...styles.avatar,
|
||||
...this._getAvatarColor(colorBase)
|
||||
};
|
||||
let right;
|
||||
|
||||
// NOTE: The {@code Swipeout} component has an onPress prop encapsulated
|
||||
// in the {@code right} array, but we need to bind it to the ID of the
|
||||
// item too.
|
||||
|
||||
if (slideActions) {
|
||||
right = [];
|
||||
for (const slideAction of slideActions) {
|
||||
right.push({
|
||||
backgroundColor: slideAction.backgroundColor,
|
||||
onPress: slideAction.onPress.bind(undefined, id),
|
||||
text: slideAction.text
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container
|
||||
onClick = { this.props.onPress }
|
||||
style = { styles.listItem }
|
||||
underlayColor = { UNDERLAY_COLOR }>
|
||||
<Container style = { styles.avatarContainer }>
|
||||
<Container style = { avatarStyles }>
|
||||
<Text style = { styles.avatarContent }>
|
||||
{title.substr(0, 1).toUpperCase()}
|
||||
</Text>
|
||||
<Swipeout
|
||||
backgroundColor = { ColorPalette.transparent }
|
||||
right = { right }>
|
||||
<Container
|
||||
onClick = { this.props.onPress }
|
||||
style = { styles.listItem }
|
||||
underlayColor = { UNDERLAY_COLOR }>
|
||||
<Container style = { styles.avatarContainer }>
|
||||
<Container style = { avatarStyles }>
|
||||
<Text style = { styles.avatarContent }>
|
||||
{title.substr(0, 1).toUpperCase()}
|
||||
</Text>
|
||||
</Container>
|
||||
</Container>
|
||||
<Container style = { styles.listItemDetails }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { [
|
||||
styles.listItemText,
|
||||
styles.listItemTitle
|
||||
] }>
|
||||
{title}
|
||||
</Text>
|
||||
{this._renderItemLines(lines)}
|
||||
</Container>
|
||||
{ this.props.secondaryAction
|
||||
&& this._renderSecondaryAction() }
|
||||
</Container>
|
||||
<Container style = { styles.listItemDetails }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = {{
|
||||
...styles.listItemText,
|
||||
...styles.listItemTitle
|
||||
}}>
|
||||
{title}
|
||||
</Text>
|
||||
{this._renderItemLines(lines)}
|
||||
</Container>
|
||||
{ this.props.secondaryAction && this._renderSecondaryAction() }
|
||||
</Container>
|
||||
</Swipeout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,199 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TouchableOpacity, View, ViewPagerAndroid } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Icon } from '../../../font-icons';
|
||||
|
||||
import AbstractPagedList from './AbstractPagedList';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* An Android specific component to render a paged list.
|
||||
*
|
||||
* @extends PagedList
|
||||
*/
|
||||
class PagedList extends AbstractPagedList {
|
||||
/**
|
||||
* A reference to the viewpager.
|
||||
*/
|
||||
_viewPager: ViewPagerAndroid;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code PagedList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onIconPress = this._onIconPress.bind(this);
|
||||
this._getIndicatorStyle = this._getIndicatorStyle.bind(this);
|
||||
this._onPageSelected = this._onPageSelected.bind(this);
|
||||
this._setViewPager = this._setViewPager.bind(this);
|
||||
}
|
||||
|
||||
_onIconPress: number => Function;
|
||||
|
||||
/**
|
||||
* Constructs a function to be used as a callback for the icons in the tab
|
||||
* bar.
|
||||
*
|
||||
* @param {number} pageIndex - The index of the page to activate via the
|
||||
* callback.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onIconPress(pageIndex) {
|
||||
return () => {
|
||||
this._viewPager.setPage(pageIndex);
|
||||
this._selectPage(pageIndex);
|
||||
};
|
||||
}
|
||||
|
||||
_getIndicatorStyle: number => Object;
|
||||
|
||||
/**
|
||||
* Constructs the style of an indicator.
|
||||
*
|
||||
* @param {number} indicatorIndex - The index of the indicator.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getIndicatorStyle(indicatorIndex) {
|
||||
if (this.state.pageIndex === indicatorIndex) {
|
||||
return styles.pageIndicatorActive;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_onPageSelected: Object => void;
|
||||
|
||||
/**
|
||||
* Updates the index of the currently selected page, based on the native
|
||||
* event received from the {@link ViewPagerAndroid} component.
|
||||
*
|
||||
* @param {Object} event - The native event of the callback.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onPageSelected({ nativeEvent: { position } }) {
|
||||
if (this.state.pageIndex !== position) {
|
||||
this._selectPage(position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single page of the page list.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page to render.
|
||||
* @param {number} index - The index of the rendered page.
|
||||
* @param {boolean} disabled - Renders the page disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPage(page, index, disabled) {
|
||||
return page.component
|
||||
? <View key = { index }>
|
||||
{
|
||||
React.createElement(
|
||||
page.component,
|
||||
{
|
||||
disabled
|
||||
})
|
||||
}
|
||||
</View>
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a page indicator (icon) for the page.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page the indicator is rendered for.
|
||||
* @param {number} index - The index of the page the indicator is rendered
|
||||
* for.
|
||||
* @param {boolean} disabled - Renders the indicator disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPageIndicator(page, index, disabled) {
|
||||
return page.component
|
||||
? <TouchableOpacity
|
||||
disabled = { disabled }
|
||||
key = { index }
|
||||
onPress = { this._onIconPress(index) }
|
||||
style = { styles.pageIndicator } >
|
||||
<View style = { styles.pageIndicator }>
|
||||
<Icon
|
||||
name = { page.icon }
|
||||
style = { [
|
||||
styles.pageIndicatorIcon,
|
||||
this._getIndicatorStyle(index)
|
||||
] } />
|
||||
<Text
|
||||
style = { [
|
||||
styles.pageIndicatorText,
|
||||
this._getIndicatorStyle(index)
|
||||
] }>
|
||||
{ page.title }
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the paged list if multiple pages are to be rendered. This is the
|
||||
* platform dependent part of the component.
|
||||
*
|
||||
* @param {boolean} disabled - True if the rendered lists should be
|
||||
* disabled.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderPagedList(disabled) {
|
||||
const { defaultPage, pages } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.pagedListContainer }>
|
||||
<ViewPagerAndroid
|
||||
initialPage = { defaultPage }
|
||||
onPageSelected = { this._onPageSelected }
|
||||
peekEnabled = { true }
|
||||
ref = { this._setViewPager }
|
||||
style = { styles.pagedList }>
|
||||
{
|
||||
pages.map((page, index) => this._renderPage(
|
||||
page, index, disabled
|
||||
))
|
||||
}
|
||||
</ViewPagerAndroid>
|
||||
<View style = { styles.pageIndicatorContainer }>
|
||||
{
|
||||
pages.map((page, index) => this._renderPageIndicator(
|
||||
page, index, disabled
|
||||
))
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_setViewPager: Object => void;
|
||||
|
||||
/**
|
||||
* Sets the {@link ViewPagerAndroid} instance.
|
||||
*
|
||||
* @param {ViewPagerAndroid} viewPager - The {@code ViewPagerAndroid}
|
||||
* instance.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setViewPager(viewPager) {
|
||||
this._viewPager = viewPager;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(PagedList);
|
|
@ -1,100 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { TabBarIOS } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import AbstractPagedList from './AbstractPagedList';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* An iOS specific component to render a paged list.
|
||||
*
|
||||
* @extends PagedList
|
||||
*/
|
||||
class PagedList extends AbstractPagedList {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code PagedList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onTabSelected = this._onTabSelected.bind(this);
|
||||
}
|
||||
|
||||
_onTabSelected: number => Function;
|
||||
|
||||
/**
|
||||
* Constructs a callback to update the selected tab when the bottom bar icon
|
||||
* is pressed.
|
||||
*
|
||||
* @param {number} tabIndex - The selected tab.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onTabSelected(tabIndex) {
|
||||
return () => super._selectPage(tabIndex);
|
||||
}
|
||||
|
||||
_renderPage: (Object, number, boolean) => React$Node
|
||||
|
||||
/**
|
||||
* Renders a single page of the page list.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page to render.
|
||||
* @param {number} index - The index of the rendered page.
|
||||
* @param {boolean} disabled - Renders the page disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPage(page, index, disabled) {
|
||||
const { pageIndex } = this.state;
|
||||
|
||||
return page.component
|
||||
? <TabBarIOS.Item
|
||||
icon = { page.icon }
|
||||
key = { index }
|
||||
onPress = { this._onTabSelected(index) }
|
||||
selected = { pageIndex === index }
|
||||
title = { page.title }>
|
||||
{
|
||||
React.createElement(
|
||||
page.component,
|
||||
{
|
||||
disabled
|
||||
})
|
||||
}
|
||||
</TabBarIOS.Item>
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the paged list if multiple pages are to be rendered. This is the
|
||||
* platform dependent part of the component.
|
||||
*
|
||||
* @param {boolean} disabled - True if the rendered lists should be
|
||||
* disabled.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderPagedList(disabled) {
|
||||
const { pages } = this.props;
|
||||
|
||||
return (
|
||||
<TabBarIOS
|
||||
itemPositioning = 'fill'
|
||||
style = { styles.pagedList }>
|
||||
{
|
||||
pages.map((page, index) => this._renderPage(
|
||||
page, index, disabled
|
||||
))
|
||||
}
|
||||
</TabBarIOS>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(PagedList);
|
|
@ -0,0 +1,286 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Icon } from '../../../font-icons';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link PagedList}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The zero-based index of the page that should be rendered (selected) by
|
||||
* default.
|
||||
*/
|
||||
defaultPage: number,
|
||||
|
||||
/**
|
||||
* Indicates if the list is disabled or not.
|
||||
*/
|
||||
disabled: boolean,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Callback to execute on page change.
|
||||
*/
|
||||
onSelectPage: ?Function,
|
||||
|
||||
/**
|
||||
* The pages of the PagedList component to be rendered.
|
||||
*
|
||||
* NOTE 1: An element's {@code component} may be {@code undefined} and then
|
||||
* it won't need to be rendered.
|
||||
*
|
||||
* NOTE 2: There must be at least one page available and enabled.
|
||||
*/
|
||||
pages: Array<{
|
||||
component: ?Object,
|
||||
icon: string | number,
|
||||
title: string
|
||||
}>
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link PagedList}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The currently selected page.
|
||||
*/
|
||||
pageIndex: number
|
||||
};
|
||||
|
||||
/**
|
||||
* A component that renders a paged list.
|
||||
*
|
||||
* @extends PagedList
|
||||
*/
|
||||
class PagedList extends Component<Props, State> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code PagedList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pageIndex: this._validatePageIndex(props.defaultPage)
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._maybeRefreshSelectedPage
|
||||
= this._maybeRefreshSelectedPage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
const pages = this.props.pages.filter(({ component }) => component);
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
styles.pagedListContainer,
|
||||
disabled ? styles.pagedListContainerDisabled : null
|
||||
] }>
|
||||
{
|
||||
pages.length > 1
|
||||
? this._renderPagedList(disabled)
|
||||
: React.createElement(
|
||||
|
||||
// $FlowExpectedError
|
||||
/* type */ pages[0].component,
|
||||
/* props */ {
|
||||
disabled,
|
||||
style: styles.pagedList
|
||||
})
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the style of an indicator.
|
||||
*
|
||||
* @param {number} indicatorIndex - The index of the indicator.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getIndicatorStyle(indicatorIndex) {
|
||||
if (this.state.pageIndex === indicatorIndex) {
|
||||
return styles.pageIndicatorActive;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_maybeRefreshSelectedPage: ?boolean => void;
|
||||
|
||||
/**
|
||||
* Components that this PagedList displays may have a refresh function to
|
||||
* refresh its content when displayed (or based on custom logic). This
|
||||
* function invokes this logic if it's present.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
_maybeRefreshSelectedPage(isInteractive: boolean = true) {
|
||||
const selectedPage = this.props.pages[this.state.pageIndex];
|
||||
let component;
|
||||
|
||||
if (selectedPage && (component = selectedPage.component)) {
|
||||
const { refresh } = component;
|
||||
|
||||
refresh.call(component, this.props.dispatch, isInteractive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected page.
|
||||
*
|
||||
* @param {number} pageIndex - The index of the selected page.
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSelectPage(pageIndex: number) {
|
||||
return () => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
pageIndex = this._validatePageIndex(pageIndex);
|
||||
|
||||
const { onSelectPage } = this.props;
|
||||
|
||||
onSelectPage && onSelectPage(pageIndex);
|
||||
|
||||
this.setState({ pageIndex }, this._maybeRefreshSelectedPage);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single page of the page list.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page to render.
|
||||
* @param {boolean} disabled - Renders the page disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPage(page, disabled) {
|
||||
if (!page.component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.pageContainer }>
|
||||
{
|
||||
React.createElement(
|
||||
page.component,
|
||||
{
|
||||
disabled
|
||||
})
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the paged list if multiple pages are to be rendered.
|
||||
*
|
||||
* @param {boolean} disabled - True if the rendered lists should be
|
||||
* disabled.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderPagedList(disabled) {
|
||||
const { pages } = this.props;
|
||||
const { pageIndex } = this.state;
|
||||
|
||||
return (
|
||||
<View style = { styles.pagedListContainer }>
|
||||
{
|
||||
this._renderPage(pages[pageIndex], disabled)
|
||||
}
|
||||
<View style = { styles.pageIndicatorContainer }>
|
||||
{
|
||||
pages.map((page, index) => this._renderPageIndicator(
|
||||
page, index, disabled
|
||||
))
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a page indicator (icon) for the page.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page the indicator is rendered for.
|
||||
* @param {number} index - The index of the page the indicator is rendered
|
||||
* for.
|
||||
* @param {boolean} disabled - Renders the indicator disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPageIndicator(page, index, disabled) {
|
||||
if (!page.component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
disabled = { disabled }
|
||||
key = { index }
|
||||
onPress = { this._onSelectPage(index) }
|
||||
style = { styles.pageIndicator } >
|
||||
<View style = { styles.pageIndicator }>
|
||||
<Icon
|
||||
name = { page.icon }
|
||||
style = { [
|
||||
styles.pageIndicatorIcon,
|
||||
this._getIndicatorStyle(index)
|
||||
] } />
|
||||
<Text
|
||||
style = { [
|
||||
styles.pageIndicatorText,
|
||||
this._getIndicatorStyle(index)
|
||||
] }>
|
||||
{ page.title }
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the requested page index and returns a safe value.
|
||||
*
|
||||
* @private
|
||||
* @param {number} pageIndex - The requested page index.
|
||||
* @returns {number}
|
||||
*/
|
||||
_validatePageIndex(pageIndex) {
|
||||
// pageIndex may point to a non-existing page if some of the pages are
|
||||
// disabled (their component property is undefined).
|
||||
const maxPageIndex
|
||||
= this.props.pages.filter(({ component }) => component).length - 1;
|
||||
|
||||
return Math.max(0, Math.min(maxPageIndex, pageIndex));
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(PagedList);
|
|
@ -73,6 +73,13 @@ const HEADER_STYLES = {
|
|||
*/
|
||||
const PAGED_LIST_STYLES = {
|
||||
|
||||
/**
|
||||
* Outermost container of a page in {@code PagedList}.
|
||||
*/
|
||||
pageContainer: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Style of the page indicator (Android).
|
||||
*/
|
||||
|
@ -214,7 +221,7 @@ const SECTION_LIST_STYLES = {
|
|||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
paddingVertical: 5
|
||||
padding: 5
|
||||
},
|
||||
|
||||
listItemDetails: {
|
||||
|
@ -239,7 +246,8 @@ const SECTION_LIST_STYLES = {
|
|||
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
padding: 5
|
||||
paddingVertical: 5,
|
||||
paddingHorizontal: 10
|
||||
},
|
||||
|
||||
listSectionText: {
|
||||
|
|
|
@ -26,6 +26,7 @@ export const ColorPalette = {
|
|||
lightGrey: '#AAAAAA',
|
||||
lighterGrey: '#EEEEEE',
|
||||
red: '#D00000',
|
||||
transparent: 'rgba(0, 0, 0, 0)',
|
||||
white: 'white',
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { openSettings } from '../../mobile/permissions';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { AbstractPage } from '../../base/react';
|
||||
|
||||
import { refreshCalendar } from '../actions';
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
import styles from './styles';
|
||||
|
||||
import BaseCalendarList from './BaseCalendarList';
|
||||
import CalendarListContent from './CalendarListContent';
|
||||
|
||||
/**
|
||||
* The tyoe of the React {@code Component} props of {@link CalendarList}.
|
||||
|
@ -36,7 +38,7 @@ type Props = {
|
|||
/**
|
||||
* Component to display a list of events from the (mobile) user's calendar.
|
||||
*/
|
||||
class CalendarList extends Component<Props> {
|
||||
class CalendarList extends AbstractPage<Props> {
|
||||
/**
|
||||
* Initializes a new {@code CalendarList} instance.
|
||||
*
|
||||
|
@ -50,6 +52,21 @@ class CalendarList extends Component<Props> {
|
|||
= this._getRenderListEmptyComponent.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public API method for {@code Component}s rendered in
|
||||
* {@link AbstractPagedList}. When invoked, refreshes the calendar entries
|
||||
* in the app.
|
||||
*
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @public
|
||||
* @returns {void}
|
||||
*/
|
||||
static refresh(dispatch, isInteractive) {
|
||||
dispatch(refreshCalendar(false, isInteractive));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render}.
|
||||
*
|
||||
|
@ -59,8 +76,8 @@ class CalendarList extends Component<Props> {
|
|||
const { disabled } = this.props;
|
||||
|
||||
return (
|
||||
BaseCalendarList
|
||||
? <BaseCalendarList
|
||||
CalendarListContent
|
||||
? <CalendarListContent
|
||||
disabled = { disabled }
|
||||
renderListEmptyComponent
|
||||
= { this._getRenderListEmptyComponent() } />
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
import Button from '@atlaskit/button';
|
||||
import Spinner from '@atlaskit/spinner';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { AbstractPage } from '../../base/react';
|
||||
import { openSettingsDialog, SETTINGS_TABS } from '../../settings';
|
||||
import {
|
||||
createCalendarClickedEvent,
|
||||
|
@ -15,7 +16,7 @@ import {
|
|||
import { refreshCalendar } from '../actions';
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
import BaseCalendarList from './BaseCalendarList';
|
||||
import CalendarListContent from './CalendarListContent';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
|
@ -53,7 +54,7 @@ type Props = {
|
|||
/**
|
||||
* Component to display a list of events from the user's calendar.
|
||||
*/
|
||||
class CalendarList extends Component<Props> {
|
||||
class CalendarList extends AbstractPage<Props> {
|
||||
/**
|
||||
* Initializes a new {@code CalendarList} instance.
|
||||
*
|
||||
|
@ -78,8 +79,8 @@ class CalendarList extends Component<Props> {
|
|||
const { disabled } = this.props;
|
||||
|
||||
return (
|
||||
BaseCalendarList
|
||||
? <BaseCalendarList
|
||||
CalendarListContent
|
||||
? <CalendarListContent
|
||||
disabled = { disabled }
|
||||
renderListEmptyComponent
|
||||
= { this._getRenderListEmptyComponent() } />
|
||||
|
|
|
@ -13,7 +13,6 @@ import { getLocalizedDateFormatter, translate } from '../../base/i18n';
|
|||
import { NavigateSectionList } from '../../base/react';
|
||||
|
||||
import { refreshCalendar, openUpdateCalendarEventDialog } from '../actions';
|
||||
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
import AddMeetingUrlButton from './AddMeetingUrlButton';
|
||||
|
@ -21,7 +20,7 @@ import JoinButton from './JoinButton';
|
|||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link BaseCalendarList}.
|
||||
* {@link CalendarListContent}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
|
@ -54,7 +53,7 @@ type Props = {
|
|||
/**
|
||||
* Component to display a list of events from a connected calendar.
|
||||
*/
|
||||
class BaseCalendarList extends Component<Props> {
|
||||
class CalendarListContent extends Component<Props> {
|
||||
/**
|
||||
* Default values for the component's props.
|
||||
*/
|
||||
|
@ -63,26 +62,7 @@ class BaseCalendarList extends Component<Props> {
|
|||
};
|
||||
|
||||
/**
|
||||
* Public API method for {@code Component}s rendered in
|
||||
* {@link AbstractPagedList}. When invoked, refreshes the calendar entries
|
||||
* in the app.
|
||||
*
|
||||
* Note: It is a static method as the {@code Component} may not be
|
||||
* initialized yet when the UI invokes refresh (e.g. {@link TabBarIOS} tab
|
||||
* change).
|
||||
*
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @public
|
||||
* @returns {void}
|
||||
*/
|
||||
static refresh(dispatch, isInteractive) {
|
||||
dispatch(refreshCalendar(false, isInteractive));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@code BaseCalendarList} instance.
|
||||
* Initializes a new {@code CalendarListContent} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -318,5 +298,5 @@ function _mapStateToProps(state: Object) {
|
|||
}
|
||||
|
||||
export default isCalendarEnabled()
|
||||
? translate(connect(_mapStateToProps)(BaseCalendarList))
|
||||
? translate(connect(_mapStateToProps)(CalendarListContent))
|
||||
: undefined;
|
|
@ -1,5 +1,15 @@
|
|||
// @flow
|
||||
|
||||
/**
|
||||
* Action type to signal the deletion of a list entry.
|
||||
*
|
||||
* {
|
||||
* type: DELETE_RECENT_LIST_ENTRY,
|
||||
* entryId: Object
|
||||
* }
|
||||
*/
|
||||
export const DELETE_RECENT_LIST_ENTRY = Symbol('DELETE_RECENT_LIST_ENTRY');
|
||||
|
||||
/**
|
||||
* Action type to signal a new addition to the list.
|
||||
*
|
||||
|
|
|
@ -2,9 +2,27 @@
|
|||
|
||||
import {
|
||||
_STORE_CURRENT_CONFERENCE,
|
||||
_UPDATE_CONFERENCE_DURATION
|
||||
_UPDATE_CONFERENCE_DURATION,
|
||||
DELETE_RECENT_LIST_ENTRY
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Deletes a recent list entry based on url and date.
|
||||
*
|
||||
* @param {Object} entryId - An object constructed of the url and the date of
|
||||
* the entry for easy identification.
|
||||
* @returns {{
|
||||
* type: DELETE_RECENT_LIST_ENTRY,
|
||||
* entryId: Object
|
||||
* }}
|
||||
*/
|
||||
export function deleteRecentListEntry(entryId: Object) {
|
||||
return {
|
||||
type: DELETE_RECENT_LIST_ENTRY,
|
||||
entryId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to initiate a new addition to the list.
|
||||
*
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
|
@ -9,9 +9,15 @@ import {
|
|||
} from '../../analytics';
|
||||
import { appNavigate, getDefaultURL } from '../../app';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Container, NavigateSectionList, Text } from '../../base/react';
|
||||
import {
|
||||
AbstractPage,
|
||||
Container,
|
||||
NavigateSectionList,
|
||||
Text
|
||||
} from '../../base/react';
|
||||
import type { Section } from '../../base/react';
|
||||
|
||||
import { deleteRecentListEntry } from '../actions';
|
||||
import { isRecentListEnabled, toDisplayableList } from '../functions';
|
||||
|
||||
import styles from './styles';
|
||||
|
@ -51,7 +57,7 @@ type Props = {
|
|||
* The cross platform container rendering the list of the recently joined rooms.
|
||||
*
|
||||
*/
|
||||
class RecentList extends Component<Props> {
|
||||
class RecentList extends AbstractPage<Props> {
|
||||
/**
|
||||
* Initializes a new {@code RecentList} instance.
|
||||
*
|
||||
|
@ -60,6 +66,7 @@ class RecentList extends Component<Props> {
|
|||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onDelete = this._onDelete.bind(this);
|
||||
this._onPress = this._onPress.bind(this);
|
||||
}
|
||||
|
||||
|
@ -85,6 +92,11 @@ class RecentList extends Component<Props> {
|
|||
}
|
||||
const { disabled, t, _defaultServerURL, _recentList } = this.props;
|
||||
const recentList = toDisplayableList(_recentList, t, _defaultServerURL);
|
||||
const slideActions = [ {
|
||||
backgroundColor: 'red',
|
||||
onPress: this._onDelete,
|
||||
text: t('welcomepage.recentListDelete')
|
||||
} ];
|
||||
|
||||
return (
|
||||
<NavigateSectionList
|
||||
|
@ -92,7 +104,8 @@ class RecentList extends Component<Props> {
|
|||
onPress = { this._onPress }
|
||||
renderListEmptyComponent
|
||||
= { this._getRenderListEmptyComponent() }
|
||||
sections = { recentList } />
|
||||
sections = { recentList }
|
||||
slideActions = { slideActions } />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -121,6 +134,19 @@ class RecentList extends Component<Props> {
|
|||
);
|
||||
}
|
||||
|
||||
_onDelete: Object => void
|
||||
|
||||
/**
|
||||
* Callback for the delete action of the list.
|
||||
*
|
||||
* @param {Object} itemId - The ID of the entry thats deletion is
|
||||
* requested.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDelete(itemId) {
|
||||
this.props.dispatch(deleteRecentListEntry(itemId));
|
||||
}
|
||||
|
||||
_onPress: string => Function;
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,6 +20,10 @@ export function toDisplayableItem(item, defaultServerURL, t) {
|
|||
|
||||
return {
|
||||
colorBase: serverName,
|
||||
id: {
|
||||
date: item.date,
|
||||
url: item.conference
|
||||
},
|
||||
key: `key-${item.conference}-${item.date}`,
|
||||
lines: [
|
||||
_toDateString(item.date, t),
|
||||
|
|
|
@ -6,7 +6,8 @@ import { PersistenceRegistry } from '../base/storage';
|
|||
|
||||
import {
|
||||
_STORE_CURRENT_CONFERENCE,
|
||||
_UPDATE_CONFERENCE_DURATION
|
||||
_UPDATE_CONFERENCE_DURATION,
|
||||
DELETE_RECENT_LIST_ENTRY
|
||||
} from './actionTypes';
|
||||
import { isRecentListEnabled } from './functions';
|
||||
|
||||
|
@ -54,6 +55,8 @@ ReducerRegistry.register(
|
|||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
return _appWillMount(state);
|
||||
case DELETE_RECENT_LIST_ENTRY:
|
||||
return _deleteRecentListEntry(state, action.entryId);
|
||||
case _STORE_CURRENT_CONFERENCE:
|
||||
return _storeCurrentConference(state, action);
|
||||
|
||||
|
@ -67,6 +70,19 @@ ReducerRegistry.register(
|
|||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* Deletes a recent list entry based on the url and date of the item.
|
||||
*
|
||||
* @param {Array<Object>} state - The Redux state.
|
||||
* @param {Object} entryId - The ID object of the entry.
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
function _deleteRecentListEntry(
|
||||
state: Array<Object>, entryId: Object): Array<Object> {
|
||||
return state.filter(entry =>
|
||||
entry.conference !== entryId.url || entry.date !== entryId.date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the redux action {@link APP_WILL_MOUNT}.
|
||||
*
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
|
@ -37,16 +36,6 @@ type Props = {
|
|||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Icon to be used for the calendar page on iOS.
|
||||
*/
|
||||
const IOS_CALENDAR_ICON = require('../../../../images/calendar.png');
|
||||
|
||||
/**
|
||||
* Icon to be used for the recent list page on iOS.
|
||||
*/
|
||||
const IOS_RECENT_LIST_ICON = require('../../../../images/history.png');
|
||||
|
||||
/**
|
||||
* Implements the lists displayed on the mobile welcome screen.
|
||||
*/
|
||||
|
@ -73,17 +62,16 @@ class WelcomePageLists extends Component<Props> {
|
|||
super(props);
|
||||
|
||||
const { t } = props;
|
||||
const android = Platform.OS === 'android';
|
||||
|
||||
this.pages = [
|
||||
{
|
||||
component: RecentList,
|
||||
icon: android ? 'restore' : IOS_RECENT_LIST_ICON,
|
||||
icon: 'restore',
|
||||
title: t('welcomepage.recentList')
|
||||
},
|
||||
{
|
||||
component: CalendarList,
|
||||
icon: android ? 'event_note' : IOS_CALENDAR_ICON,
|
||||
icon: 'event_note',
|
||||
title: t('welcomepage.calendar')
|
||||
}
|
||||
];
|
||||
|
|
Loading…
Reference in New Issue