[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",
|
"join": "JOIN",
|
||||||
"privacy": "Privacy",
|
"privacy": "Privacy",
|
||||||
"recentList": "Recent",
|
"recentList": "Recent",
|
||||||
|
"recentListDelete": "Delete",
|
||||||
"recentListEmpty": "Your recent list is currently empty. Chat with your team and you will find all your recent meetings here.",
|
"recentListEmpty": "Your recent list is currently empty. Chat with your team and you will find all your recent meetings here.",
|
||||||
"roomname": "Enter room name",
|
"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.",
|
"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": {
|
"performance-now": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
|
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
|
@ -12281,6 +12280,14 @@
|
||||||
"integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=",
|
"integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=",
|
||||||
"dev": true
|
"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": {
|
"raf-schd": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-2.1.2.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.10.9.tgz",
|
||||||
"integrity": "sha1-awCw9K/QF83gn7udFx3xtdW4Uag="
|
"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": {
|
"react-native-vector-icons": {
|
||||||
"version": "4.4.2",
|
"version": "4.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-4.4.2.tgz",
|
"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": {
|
"read-pkg": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||||
|
@ -15500,6 +15526,11 @@
|
||||||
"safe-buffer": "^5.0.1"
|
"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": {
|
"tweetnacl": {
|
||||||
"version": "0.14.5",
|
"version": "0.14.5",
|
||||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
"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-locale-detector": "github:jitsi/react-native-locale-detector#845281e9fd4af756f6d0f64afe5cce08c63e5ee9",
|
||||||
"react-native-prompt": "1.0.0",
|
"react-native-prompt": "1.0.0",
|
||||||
"react-native-sound": "0.10.9",
|
"react-native-sound": "0.10.9",
|
||||||
|
"react-native-swipeout": "2.3.6",
|
||||||
"react-native-vector-icons": "4.4.2",
|
"react-native-vector-icons": "4.4.2",
|
||||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#be3de15bb988cfabbb62cd4b3b06f4c920ee5ba0",
|
"react-native-webrtc": "github:jitsi/react-native-webrtc#be3de15bb988cfabbb62cd4b3b06f4c920ee5ba0",
|
||||||
"react-redux": "5.0.7",
|
"react-redux": "5.0.7",
|
||||||
|
|
|
@ -17,6 +17,11 @@ export type Item = {
|
||||||
*/
|
*/
|
||||||
elementAfter?: ?ComponentType<any>,
|
elementAfter?: ?ComponentType<any>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique ID of the item.
|
||||||
|
*/
|
||||||
|
id: Object | string,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item title
|
* 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
|
* 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 }
|
key = { key }
|
||||||
onPress = { url ? this._onPress(url) : undefined }
|
onPress = { url ? this._onPress(url) : undefined }
|
||||||
secondaryAction = {
|
secondaryAction = {
|
||||||
url ? undefined : this._onSecondaryAction(id) } />
|
url ? undefined : this._onSecondaryAction(id) }
|
||||||
|
slideActions = { this.props.slideActions } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './_';
|
export * from './_';
|
||||||
|
export { default as AbstractPage } from './AbstractPage';
|
||||||
export { default as NavigateSectionList } from './NavigateSectionList';
|
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
|
// @flow
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import Swipeout from 'react-native-swipeout';
|
||||||
|
|
||||||
|
import { ColorPalette } from '../../../styles';
|
||||||
|
|
||||||
import Container from './Container';
|
import Container from './Container';
|
||||||
import Text from './Text';
|
import Text from './Text';
|
||||||
|
@ -22,7 +25,13 @@ type Props = {
|
||||||
/**
|
/**
|
||||||
* Function to be invoked when secondary action was performed on an Item.
|
* 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,13 +138,33 @@ export default class NavigateSectionListItem extends Component<Props> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { colorBase, lines, title } = this.props.item;
|
const { slideActions } = this.props;
|
||||||
|
const { id, colorBase, lines, title } = this.props.item;
|
||||||
const avatarStyles = {
|
const avatarStyles = {
|
||||||
...styles.avatar,
|
...styles.avatar,
|
||||||
...this._getAvatarColor(colorBase)
|
...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 (
|
return (
|
||||||
|
<Swipeout
|
||||||
|
backgroundColor = { ColorPalette.transparent }
|
||||||
|
right = { right }>
|
||||||
<Container
|
<Container
|
||||||
onClick = { this.props.onPress }
|
onClick = { this.props.onPress }
|
||||||
style = { styles.listItem }
|
style = { styles.listItem }
|
||||||
|
@ -150,16 +179,18 @@ export default class NavigateSectionListItem extends Component<Props> {
|
||||||
<Container style = { styles.listItemDetails }>
|
<Container style = { styles.listItemDetails }>
|
||||||
<Text
|
<Text
|
||||||
numberOfLines = { 1 }
|
numberOfLines = { 1 }
|
||||||
style = {{
|
style = { [
|
||||||
...styles.listItemText,
|
styles.listItemText,
|
||||||
...styles.listItemTitle
|
styles.listItemTitle
|
||||||
}}>
|
] }>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
{this._renderItemLines(lines)}
|
{this._renderItemLines(lines)}
|
||||||
</Container>
|
</Container>
|
||||||
{ this.props.secondaryAction && this._renderSecondaryAction() }
|
{ this.props.secondaryAction
|
||||||
|
&& this._renderSecondaryAction() }
|
||||||
</Container>
|
</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 = {
|
const PAGED_LIST_STYLES = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outermost container of a page in {@code PagedList}.
|
||||||
|
*/
|
||||||
|
pageContainer: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Style of the page indicator (Android).
|
* Style of the page indicator (Android).
|
||||||
*/
|
*/
|
||||||
|
@ -214,7 +221,7 @@ const SECTION_LIST_STYLES = {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
paddingVertical: 5
|
padding: 5
|
||||||
},
|
},
|
||||||
|
|
||||||
listItemDetails: {
|
listItemDetails: {
|
||||||
|
@ -239,7 +246,8 @@ const SECTION_LIST_STYLES = {
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
padding: 5
|
paddingVertical: 5,
|
||||||
|
paddingHorizontal: 10
|
||||||
},
|
},
|
||||||
|
|
||||||
listSectionText: {
|
listSectionText: {
|
||||||
|
|
|
@ -26,6 +26,7 @@ export const ColorPalette = {
|
||||||
lightGrey: '#AAAAAA',
|
lightGrey: '#AAAAAA',
|
||||||
lighterGrey: '#EEEEEE',
|
lighterGrey: '#EEEEEE',
|
||||||
red: '#D00000',
|
red: '#D00000',
|
||||||
|
transparent: 'rgba(0, 0, 0, 0)',
|
||||||
white: 'white',
|
white: 'white',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { Text, TouchableOpacity, View } from 'react-native';
|
import { Text, TouchableOpacity, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { openSettings } from '../../mobile/permissions';
|
import { openSettings } from '../../mobile/permissions';
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
|
import { AbstractPage } from '../../base/react';
|
||||||
|
|
||||||
|
import { refreshCalendar } from '../actions';
|
||||||
import { isCalendarEnabled } from '../functions';
|
import { isCalendarEnabled } from '../functions';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
import BaseCalendarList from './BaseCalendarList';
|
import CalendarListContent from './CalendarListContent';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The tyoe of the React {@code Component} props of {@link CalendarList}.
|
* 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.
|
* 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.
|
* Initializes a new {@code CalendarList} instance.
|
||||||
*
|
*
|
||||||
|
@ -50,6 +52,21 @@ class CalendarList extends Component<Props> {
|
||||||
= this._getRenderListEmptyComponent.bind(this);
|
= 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}.
|
* Implements React's {@link Component#render}.
|
||||||
*
|
*
|
||||||
|
@ -59,8 +76,8 @@ class CalendarList extends Component<Props> {
|
||||||
const { disabled } = this.props;
|
const { disabled } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
BaseCalendarList
|
CalendarListContent
|
||||||
? <BaseCalendarList
|
? <CalendarListContent
|
||||||
disabled = { disabled }
|
disabled = { disabled }
|
||||||
renderListEmptyComponent
|
renderListEmptyComponent
|
||||||
= { this._getRenderListEmptyComponent() } />
|
= { this._getRenderListEmptyComponent() } />
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
import Button from '@atlaskit/button';
|
import Button from '@atlaskit/button';
|
||||||
import Spinner from '@atlaskit/spinner';
|
import Spinner from '@atlaskit/spinner';
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
|
import { AbstractPage } from '../../base/react';
|
||||||
import { openSettingsDialog, SETTINGS_TABS } from '../../settings';
|
import { openSettingsDialog, SETTINGS_TABS } from '../../settings';
|
||||||
import {
|
import {
|
||||||
createCalendarClickedEvent,
|
createCalendarClickedEvent,
|
||||||
|
@ -15,7 +16,7 @@ import {
|
||||||
import { refreshCalendar } from '../actions';
|
import { refreshCalendar } from '../actions';
|
||||||
import { isCalendarEnabled } from '../functions';
|
import { isCalendarEnabled } from '../functions';
|
||||||
|
|
||||||
import BaseCalendarList from './BaseCalendarList';
|
import CalendarListContent from './CalendarListContent';
|
||||||
|
|
||||||
declare var interfaceConfig: Object;
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ type Props = {
|
||||||
/**
|
/**
|
||||||
* Component to display a list of events from the user's calendar.
|
* 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.
|
* Initializes a new {@code CalendarList} instance.
|
||||||
*
|
*
|
||||||
|
@ -78,8 +79,8 @@ class CalendarList extends Component<Props> {
|
||||||
const { disabled } = this.props;
|
const { disabled } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
BaseCalendarList
|
CalendarListContent
|
||||||
? <BaseCalendarList
|
? <CalendarListContent
|
||||||
disabled = { disabled }
|
disabled = { disabled }
|
||||||
renderListEmptyComponent
|
renderListEmptyComponent
|
||||||
= { this._getRenderListEmptyComponent() } />
|
= { this._getRenderListEmptyComponent() } />
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { getLocalizedDateFormatter, translate } from '../../base/i18n';
|
||||||
import { NavigateSectionList } from '../../base/react';
|
import { NavigateSectionList } from '../../base/react';
|
||||||
|
|
||||||
import { refreshCalendar, openUpdateCalendarEventDialog } from '../actions';
|
import { refreshCalendar, openUpdateCalendarEventDialog } from '../actions';
|
||||||
|
|
||||||
import { isCalendarEnabled } from '../functions';
|
import { isCalendarEnabled } from '../functions';
|
||||||
|
|
||||||
import AddMeetingUrlButton from './AddMeetingUrlButton';
|
import AddMeetingUrlButton from './AddMeetingUrlButton';
|
||||||
|
@ -21,7 +20,7 @@ import JoinButton from './JoinButton';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of
|
* The type of the React {@code Component} props of
|
||||||
* {@link BaseCalendarList}.
|
* {@link CalendarListContent}.
|
||||||
*/
|
*/
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
|
@ -54,7 +53,7 @@ type Props = {
|
||||||
/**
|
/**
|
||||||
* Component to display a list of events from a connected calendar.
|
* 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.
|
* 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
|
* Initializes a new {@code CalendarListContent} instance.
|
||||||
* {@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.
|
|
||||||
*
|
*
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -318,5 +298,5 @@ function _mapStateToProps(state: Object) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default isCalendarEnabled()
|
export default isCalendarEnabled()
|
||||||
? translate(connect(_mapStateToProps)(BaseCalendarList))
|
? translate(connect(_mapStateToProps)(CalendarListContent))
|
||||||
: undefined;
|
: undefined;
|
|
@ -1,5 +1,15 @@
|
||||||
// @flow
|
// @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.
|
* Action type to signal a new addition to the list.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,9 +2,27 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
_STORE_CURRENT_CONFERENCE,
|
_STORE_CURRENT_CONFERENCE,
|
||||||
_UPDATE_CONFERENCE_DURATION
|
_UPDATE_CONFERENCE_DURATION,
|
||||||
|
DELETE_RECENT_LIST_ENTRY
|
||||||
} from './actionTypes';
|
} 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.
|
* Action to initiate a new addition to the list.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -9,9 +9,15 @@ import {
|
||||||
} from '../../analytics';
|
} from '../../analytics';
|
||||||
import { appNavigate, getDefaultURL } from '../../app';
|
import { appNavigate, getDefaultURL } from '../../app';
|
||||||
import { translate } from '../../base/i18n';
|
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 type { Section } from '../../base/react';
|
||||||
|
|
||||||
|
import { deleteRecentListEntry } from '../actions';
|
||||||
import { isRecentListEnabled, toDisplayableList } from '../functions';
|
import { isRecentListEnabled, toDisplayableList } from '../functions';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -51,7 +57,7 @@ type Props = {
|
||||||
* The cross platform container rendering the list of the recently joined rooms.
|
* 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.
|
* Initializes a new {@code RecentList} instance.
|
||||||
*
|
*
|
||||||
|
@ -60,6 +66,7 @@ class RecentList extends Component<Props> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this._onDelete = this._onDelete.bind(this);
|
||||||
this._onPress = this._onPress.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 { disabled, t, _defaultServerURL, _recentList } = this.props;
|
||||||
const recentList = toDisplayableList(_recentList, t, _defaultServerURL);
|
const recentList = toDisplayableList(_recentList, t, _defaultServerURL);
|
||||||
|
const slideActions = [ {
|
||||||
|
backgroundColor: 'red',
|
||||||
|
onPress: this._onDelete,
|
||||||
|
text: t('welcomepage.recentListDelete')
|
||||||
|
} ];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigateSectionList
|
<NavigateSectionList
|
||||||
|
@ -92,7 +104,8 @@ class RecentList extends Component<Props> {
|
||||||
onPress = { this._onPress }
|
onPress = { this._onPress }
|
||||||
renderListEmptyComponent
|
renderListEmptyComponent
|
||||||
= { this._getRenderListEmptyComponent() }
|
= { 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;
|
_onPress: string => Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,6 +20,10 @@ export function toDisplayableItem(item, defaultServerURL, t) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
colorBase: serverName,
|
colorBase: serverName,
|
||||||
|
id: {
|
||||||
|
date: item.date,
|
||||||
|
url: item.conference
|
||||||
|
},
|
||||||
key: `key-${item.conference}-${item.date}`,
|
key: `key-${item.conference}-${item.date}`,
|
||||||
lines: [
|
lines: [
|
||||||
_toDateString(item.date, t),
|
_toDateString(item.date, t),
|
||||||
|
|
|
@ -6,7 +6,8 @@ import { PersistenceRegistry } from '../base/storage';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
_STORE_CURRENT_CONFERENCE,
|
_STORE_CURRENT_CONFERENCE,
|
||||||
_UPDATE_CONFERENCE_DURATION
|
_UPDATE_CONFERENCE_DURATION,
|
||||||
|
DELETE_RECENT_LIST_ENTRY
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import { isRecentListEnabled } from './functions';
|
import { isRecentListEnabled } from './functions';
|
||||||
|
|
||||||
|
@ -54,6 +55,8 @@ ReducerRegistry.register(
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case APP_WILL_MOUNT:
|
case APP_WILL_MOUNT:
|
||||||
return _appWillMount(state);
|
return _appWillMount(state);
|
||||||
|
case DELETE_RECENT_LIST_ENTRY:
|
||||||
|
return _deleteRecentListEntry(state, action.entryId);
|
||||||
case _STORE_CURRENT_CONFERENCE:
|
case _STORE_CURRENT_CONFERENCE:
|
||||||
return _storeCurrentConference(state, action);
|
return _storeCurrentConference(state, action);
|
||||||
|
|
||||||
|
@ -67,6 +70,19 @@ ReducerRegistry.register(
|
||||||
return state;
|
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}.
|
* Reduces the redux action {@link APP_WILL_MOUNT}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Platform } from 'react-native';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
|
@ -37,16 +36,6 @@ type Props = {
|
||||||
t: Function
|
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.
|
* Implements the lists displayed on the mobile welcome screen.
|
||||||
*/
|
*/
|
||||||
|
@ -73,17 +62,16 @@ class WelcomePageLists extends Component<Props> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const { t } = props;
|
const { t } = props;
|
||||||
const android = Platform.OS === 'android';
|
|
||||||
|
|
||||||
this.pages = [
|
this.pages = [
|
||||||
{
|
{
|
||||||
component: RecentList,
|
component: RecentList,
|
||||||
icon: android ? 'restore' : IOS_RECENT_LIST_ICON,
|
icon: 'restore',
|
||||||
title: t('welcomepage.recentList')
|
title: t('welcomepage.recentList')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: CalendarList,
|
component: CalendarList,
|
||||||
icon: android ? 'event_note' : IOS_CALENDAR_ICON,
|
icon: 'event_note',
|
||||||
title: t('welcomepage.calendar')
|
title: t('welcomepage.calendar')
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in New Issue