From c675b17e3c95857200e4dd23cbd6da78eb8361fc Mon Sep 17 00:00:00 2001 From: enahum Date: Sun, 9 Apr 2017 22:43:18 -0300 Subject: [PATCH] PLT-5840 Prevent double tapping (#464) * PLT-5840 Prevent double tapping * handle double tapping on channel header * Review feedback --- .../channel_drawer_item.js | 17 +-- .../channel_drawer_list.js | 5 +- .../custom_list/channel_list_row.js | 104 +++++++++--------- app/components/custom_list/member_list_row.js | 2 +- .../file_attachment_list.js | 3 +- app/components/post/post.js | 9 +- .../account_notifications.js | 54 ++++----- .../account_settings/account_settings.js | 9 +- app/scenes/channel/channel.js | 7 +- .../channel_post_list/channel_post_list.js | 2 +- .../channel_add_members.js | 4 +- app/scenes/channel_info/channel_info.js | 27 ++--- app/scenes/channel_members/channel_members.js | 4 +- app/scenes/login/login.js | 37 +++++-- app/scenes/login_options/login_options.js | 5 +- app/scenes/mfa/mfa.js | 39 +++++-- app/scenes/more_channels/create_button.js | 28 ++--- app/scenes/more_channels/more_channels.js | 12 +- app/scenes/options_modal/options_modal.js | 10 +- .../options_modal_list.android.js | 13 ++- .../options_modal/options_modal_list.ios.js | 13 ++- app/scenes/select_server/select_server.js | 33 ++++-- app/scenes/select_team/select_team.js | 27 +++-- app/scenes/thread/thread.js | 18 +-- app/utils/tap.js | 14 +++ assets/base/i18n/en.json | 2 +- 26 files changed, 287 insertions(+), 211 deletions(-) create mode 100644 app/utils/tap.js diff --git a/app/components/channel_drawer_list/channel_drawer_item.js b/app/components/channel_drawer_list/channel_drawer_item.js index 1438ba13d..91aa5a840 100644 --- a/app/components/channel_drawer_list/channel_drawer_item.js +++ b/app/components/channel_drawer_list/channel_drawer_item.js @@ -10,32 +10,27 @@ import { } from 'react-native'; import ChanneIcon from 'app/components/channel_icon'; +import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; export default class ChannelDrawerItem extends PureComponent { static propTypes = { channel: PropTypes.object.isRequired, onSelectChannel: PropTypes.func.isRequired, - onLongPress: PropTypes.func, isActive: PropTypes.bool.isRequired, hasUnread: PropTypes.bool.isRequired, mentions: PropTypes.number.isRequired, theme: PropTypes.object.isRequired }; - handleLongPress = (channel) => { - if (this.props.onLongPress) { - this.props.onLongPress(channel); - } - }; - render() { const { channel, theme, mentions, hasUnread, - isActive + isActive, + onSelectChannel } = this.props; const style = getStyleSheet(theme); @@ -84,11 +79,7 @@ export default class ChannelDrawerItem extends PureComponent { return ( this.props.onSelectChannel(channel)} - delayLongPress={1000} - onLongPress={() => { - this.handleLongPress(channel); - }} + onPress={() => preventDoubleTap(onSelectChannel, this, channel)} > {activeBorder} diff --git a/app/components/channel_drawer_list/channel_drawer_list.js b/app/components/channel_drawer_list/channel_drawer_list.js index 3539fb75c..a0839779c 100644 --- a/app/components/channel_drawer_list/channel_drawer_list.js +++ b/app/components/channel_drawer_list/channel_drawer_list.js @@ -16,6 +16,7 @@ import AwesomeIcon from 'react-native-vector-icons/FontAwesome'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import FormattedText from 'app/components/formatted_text'; +import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {Constants} from 'mattermost-redux/constants'; @@ -235,7 +236,7 @@ class ChannelDrawerList extends Component { return ( preventDoubleTap(action, this)} underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)} > this.props.actions.openSettingsModal()} + onPress={() => preventDoubleTap(this.props.actions.openSettingsModal)} underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)} > { - return StyleSheet.create({ - container: { - flexDirection: 'column', - height: 65, - paddingHorizontal: 15, - justifyContent: 'center', - backgroundColor: theme.centerChannelBg - }, - titleContainer: { - alignItems: 'center', - flexDirection: 'row' - }, - displayName: { - fontSize: 16, - color: theme.centerChannelColor - }, - icon: { - fontSize: 16, - color: theme.centerChannelColor - }, - textContainer: { - flex: 1, - flexDirection: 'row', - marginLeft: 5 - }, - purpose: { - fontSize: 13, - color: changeOpacity(theme.centerChannelColor, 0.5) - }, - selector: { - height: 28, - width: 28, - borderRadius: 14, - borderWidth: 1, - borderColor: '#888', - alignItems: 'center', - justifyContent: 'center' - }, - selectorContainer: { - height: 50, - paddingRight: 15, - alignItems: 'center', - justifyContent: 'center' - }, - selectorFilled: { - backgroundColor: '#378FD2', - borderWidth: 0 - } - }); -}); - function createTouchableComponent(children, action) { return ( @@ -136,4 +84,56 @@ ChannelListRow.propTypes = { selected: PropTypes.bool }; +const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { + return StyleSheet.create({ + container: { + flexDirection: 'column', + height: 65, + paddingHorizontal: 15, + justifyContent: 'center', + backgroundColor: theme.centerChannelBg + }, + titleContainer: { + alignItems: 'center', + flexDirection: 'row' + }, + displayName: { + fontSize: 16, + color: theme.centerChannelColor + }, + icon: { + fontSize: 16, + color: theme.centerChannelColor + }, + textContainer: { + flex: 1, + flexDirection: 'row', + marginLeft: 5 + }, + purpose: { + fontSize: 13, + color: changeOpacity(theme.centerChannelColor, 0.5) + }, + selector: { + height: 28, + width: 28, + borderRadius: 14, + borderWidth: 1, + borderColor: '#888', + alignItems: 'center', + justifyContent: 'center' + }, + selectorContainer: { + height: 50, + paddingRight: 15, + alignItems: 'center', + justifyContent: 'center' + }, + selectorFilled: { + backgroundColor: '#378FD2', + borderWidth: 0 + } + }); +}); + export default ChannelListRow; diff --git a/app/components/custom_list/member_list_row.js b/app/components/custom_list/member_list_row.js index 5426c6920..0594eba6d 100644 --- a/app/components/custom_list/member_list_row.js +++ b/app/components/custom_list/member_list_row.js @@ -68,7 +68,7 @@ function MemberListRow(props) { if (typeof onPress === 'function') { return createTouchableComponent(RowComponent, () => onPress(id)); } else if (typeof props.onRowSelect === 'function') { - return createTouchableComponent(RowComponent, props.onRowSelect); + return createTouchableComponent(RowComponent, disableSelect ? () => false : props.onRowSelect); } return RowComponent; diff --git a/app/components/file_attachment_list/file_attachment_list.js b/app/components/file_attachment_list/file_attachment_list.js index 3152cab9f..014538b59 100644 --- a/app/components/file_attachment_list/file_attachment_list.js +++ b/app/components/file_attachment_list/file_attachment_list.js @@ -11,6 +11,7 @@ import { TouchableOpacity } from 'react-native'; +import {preventDoubleTap} from 'app/utils/tap'; import FileAttachment from './file_attachment'; export default class FileAttachmentList extends Component { @@ -40,7 +41,7 @@ export default class FileAttachmentList extends Component { this.handleOnPress(file)} + onPress={() => preventDoubleTap(this.handleOnPress, this, file)} onPressIn={() => this.props.toggleSelected(true)} onPressOut={() => this.props.toggleSelected(false)} > diff --git a/app/components/post/post.js b/app/components/post/post.js index 6ec9e53e7..6913f909b 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -14,15 +14,16 @@ import { } from 'react-native'; import {injectIntl, intlShape} from 'react-intl'; +import FileAttachmentList from 'app/components/file_attachment_list/file_attachment_list_container'; import FormattedText from 'app/components/formatted_text'; import FormattedTime from 'app/components/formatted_time'; import MattermostIcon from 'app/components/mattermost_icon'; import Markdown from 'app/components/markdown/markdown'; import OptionsContext from 'app/components/options_context'; import ProfilePicture from 'app/components/profile_picture'; -import FileAttachmentList from 'app/components/file_attachment_list/file_attachment_list_container'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import ReplyIcon from 'app/components/reply_icon'; +import {preventDoubleTap} from 'app/utils/tap'; +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import webhookIcon from 'assets/images/icons/webhook.jpg'; @@ -119,7 +120,7 @@ class Post extends PureComponent { handlePress = () => { const {post, onPress} = this.props; if (onPress && post.state !== Constants.POST_DELETED && !isSystemMessage(post)) { - onPress(post); + preventDoubleTap(onPress, null, post); } }; @@ -307,7 +308,7 @@ class Post extends PureComponent { }; viewUserProfile = () => { - this.props.actions.goToUserProfile(this.props.user.id); + preventDoubleTap(this.props.actions.goToUserProfile, null, this.props.user.id); }; toggleSelected = (selected) => { diff --git a/app/scenes/account_notifications/account_notifications.js b/app/scenes/account_notifications/account_notifications.js index e7809851b..e14f980f2 100644 --- a/app/scenes/account_notifications/account_notifications.js +++ b/app/scenes/account_notifications/account_notifications.js @@ -20,33 +20,6 @@ import Section from './section'; import SectionItem from './section_item'; import SaveNotificationsButton from './save_notifications_button'; -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return StyleSheet.create({ - input: { - color: theme.centerChannelColor, - fontSize: 12, - height: 40 - }, - separator: { - height: 1, - flex: 1, - backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), - marginHorizontal: 15 - }, - scrollView: { - flex: 1, - backgroundColor: changeOpacity(theme.centerChannelColor, 0.03) - }, - scrollViewContent: { - paddingBottom: 30 - }, - wrapper: { - flex: 1, - backgroundColor: theme.centerChannelBg - } - }); -}); - const SAVE_NOTIFY_PROPS = 'save_notify_props'; const SAVING_NOTIFY_PROPS = 'saving_notify_props'; @@ -548,3 +521,30 @@ export default class AccountNotifications extends PureComponent { ); } } + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return StyleSheet.create({ + input: { + color: theme.centerChannelColor, + fontSize: 12, + height: 40 + }, + separator: { + height: 1, + flex: 1, + backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), + marginHorizontal: 15 + }, + scrollView: { + flex: 1, + backgroundColor: changeOpacity(theme.centerChannelColor, 0.03) + }, + scrollViewContent: { + paddingBottom: 30 + }, + wrapper: { + flex: 1, + backgroundColor: theme.centerChannelBg + } + }); +}); diff --git a/app/scenes/account_settings/account_settings.js b/app/scenes/account_settings/account_settings.js index ce64e8893..6afeb73f5 100644 --- a/app/scenes/account_settings/account_settings.js +++ b/app/scenes/account_settings/account_settings.js @@ -10,6 +10,7 @@ import { import Icon from 'react-native-vector-icons/FontAwesome'; import FormattedText from 'app/components/formatted_text'; +import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; export default class AccountSettings extends PureComponent { @@ -18,7 +19,7 @@ export default class AccountSettings extends PureComponent { goToAccountNotifications: PropTypes.func.isRequired }), theme: PropTypes.object.isRequired - } + }; static navigationProps = { renderLeftComponent: (props, emitter, theme) => { @@ -53,7 +54,7 @@ export default class AccountSettings extends PureComponent { > preventDoubleTap(action)} > } ); - } + }; renderItems = () => { return [ @@ -86,7 +87,7 @@ export default class AccountSettings extends PureComponent { this.buildItemRow('mobile', 'user.settings.modal.display', 'Display', () => true, true, false), this.buildItemRow('wrench', 'user.settings.modal.advanced', 'Advanced', () => true, false, false) ]; - } + }; render() { const {theme} = this.props; diff --git a/app/scenes/channel/channel.js b/app/scenes/channel/channel.js index fc27ce787..a5c275bca 100644 --- a/app/scenes/channel/channel.js +++ b/app/scenes/channel/channel.js @@ -11,6 +11,7 @@ import { import KeyboardLayout from 'app/components/layout/keyboard_layout'; import Loading from 'app/components/loading'; import PostTextbox from 'app/components/post_textbox'; +import {preventDoubleTap} from 'app/utils/tap'; import {Constants} from 'mattermost-redux/constants'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; @@ -53,9 +54,11 @@ export default class Channel extends React.PureComponent { } }; + canPress = true; + componentWillMount() { - this.props.subscribeToHeaderEvent('open_channel_drawer', this.openChannelDrawer); - this.props.subscribeToHeaderEvent('show_channel_info', this.props.actions.goToChannelInfo); + this.props.subscribeToHeaderEvent('open_channel_drawer', () => preventDoubleTap(this.openChannelDrawer, this)); + this.props.subscribeToHeaderEvent('show_channel_info', () => preventDoubleTap(this.props.actions.goToChannelInfo)); EventEmitter.on('leave_team', this.handleLeaveTeam); this.props.actions.initWebSocket(Platform.OS); if (this.props.currentTeam) { diff --git a/app/scenes/channel/channel_post_list/channel_post_list.js b/app/scenes/channel/channel_post_list/channel_post_list.js index 4e61c0ac4..5d9d1b527 100644 --- a/app/scenes/channel/channel_post_list/channel_post_list.js +++ b/app/scenes/channel/channel_post_list/channel_post_list.js @@ -84,7 +84,7 @@ export default class ChannelPostList extends PureComponent { toValue: 0, duration: 500 }).start(); - } + }; didPostsLoad(nextProps, postsRequest) { const nextGetPostsStatus = nextProps.postsRequests[postsRequest].status; diff --git a/app/scenes/channel_add_members/channel_add_members.js b/app/scenes/channel_add_members/channel_add_members.js index 3f988007b..9ac9999b1 100644 --- a/app/scenes/channel_add_members/channel_add_members.js +++ b/app/scenes/channel_add_members/channel_add_members.js @@ -62,6 +62,7 @@ class ChannelAddMembers extends PureComponent { }; state = { + canSelect: true, profiles: [], selectedMembers: {} }; @@ -168,6 +169,7 @@ class ChannelAddMembers extends PureComponent { }; emitAdding = (loading) => { + this.setState({canSelect: false}); EventEmitter.emit('adding_members', loading); }; @@ -239,7 +241,7 @@ class ChannelAddMembers extends PureComponent { listScrollRenderAheadDistance={50} loading={isLoading} loadingText={loadingText} - selectable={true} + selectable={this.state.canSelect} onRowSelect={this.handleRowSelect} renderRow={renderMemberRow} createSections={createMembersSections} diff --git a/app/scenes/channel_info/channel_info.js b/app/scenes/channel_info/channel_info.js index 26515d764..5640e006e 100644 --- a/app/scenes/channel_info/channel_info.js +++ b/app/scenes/channel_info/channel_info.js @@ -10,6 +10,7 @@ import { View } from 'react-native'; +import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {Constants} from 'mattermost-redux/constants'; @@ -64,14 +65,6 @@ class ChannelInfo extends PureComponent { } } - handleFavorite = () => { - const {isFavorite, actions, currentChannel} = this.props; - const {markFavorite, unmarkFavorite} = actions; - const toggleFavorite = isFavorite ? unmarkFavorite : markFavorite; - this.setState({isFavorite: !isFavorite}); - toggleFavorite(currentChannel.id); - }; - handleDeleteOrLeave(eventType) { const {formatMessage} = this.props.intl; const channel = this.props.currentChannel; @@ -134,6 +127,14 @@ class ChannelInfo extends PureComponent { } }; + handleFavorite = () => { + const {isFavorite, actions, currentChannel} = this.props; + const {markFavorite, unmarkFavorite} = actions; + const toggleFavorite = isFavorite ? unmarkFavorite : markFavorite; + this.setState({isFavorite: !isFavorite}); + toggleFavorite(currentChannel.id); + }; + renderLeaveOrDeleteChannelRow() { const channel = this.props.currentChannel; const isDefaultChannel = channel.name === Constants.DEFAULT_CHANNEL; @@ -218,7 +219,7 @@ class ChannelInfo extends PureComponent { **/ } preventDoubleTap(this.props.actions.goToChannelMembers)} defaultMessage={canManageUsers ? 'Manage Members' : 'View Members'} detail={currentChannelMemberCount} icon='users' @@ -229,7 +230,7 @@ class ChannelInfo extends PureComponent { preventDoubleTap(this.props.actions.goToChannelAddMembers)} defaultMessage='Add Members' icon='user-plus' textId='channel_header.addMembers' @@ -239,7 +240,7 @@ class ChannelInfo extends PureComponent { } this.handleDeleteOrLeave('leave')} + action={() => preventDoubleTap(this.handleDeleteOrLeave, this, 'leave')} defaultMessage='Leave Channel' icon='sign-out' textId='navbar.leave' @@ -250,7 +251,7 @@ class ChannelInfo extends PureComponent { {this.renderLeaveOrDeleteChannelRow() && canDeleteChannel && this.handleDeleteOrLeave('delete')} + action={() => preventDoubleTap(this.handleDeleteOrLeave, this, 'delete')} defaultMessage='Delete Channel' icon='trash' iconColor='#CA3B27' @@ -263,7 +264,7 @@ class ChannelInfo extends PureComponent { {this.renderCloseDirect() && preventDoubleTap(this.handleClose, this)} defaultMessage={defaultMessage} icon='times' iconColor='#CA3B27' diff --git a/app/scenes/channel_members/channel_members.js b/app/scenes/channel_members/channel_members.js index 86542b018..8deabc29c 100644 --- a/app/scenes/channel_members/channel_members.js +++ b/app/scenes/channel_members/channel_members.js @@ -59,6 +59,7 @@ class ChannelMembers extends PureComponent { }; state = { + canSelect: true, profiles: [], selectedMembers: {} }; @@ -191,6 +192,7 @@ class ChannelMembers extends PureComponent { }; emitRemoving = (loading) => { + this.setState({canSelect: false}); EventEmitter.emit('removing_members', loading); }; @@ -288,7 +290,7 @@ class ChannelMembers extends PureComponent { listScrollRenderAheadDistance={50} loading={isLoading} loadingText={loadingText} - selectable={canManageUsers} + selectable={canManageUsers && this.state.canSelect} onRowSelect={this.handleRowSelect} renderRow={this.renderMemberRow} createSections={createMembersSections} diff --git a/app/scenes/login/login.js b/app/scenes/login/login.js index 65881e313..9c3e1d3d6 100644 --- a/app/scenes/login/login.js +++ b/app/scenes/login/login.js @@ -4,6 +4,7 @@ import React, {Component, PropTypes} from 'react'; import {injectIntl, intlShape} from 'react-intl'; import { + ActivityIndicator, Image, Keyboard, KeyboardAvoidingView, @@ -226,6 +227,31 @@ class Login extends Component { }; render() { + const isLoading = this.props.loginRequest.status === RequestStatus.STARTED; + + let proceed; + if (isLoading) { + proceed = ( + + ); + } else { + proceed = ( + + ); + } + return ( - + {proceed} diff --git a/app/scenes/login_options/login_options.js b/app/scenes/login_options/login_options.js index 9804a3a82..f6717c516 100644 --- a/app/scenes/login_options/login_options.js +++ b/app/scenes/login_options/login_options.js @@ -11,6 +11,7 @@ import { import Button from 'react-native-button'; import FormattedText from 'app/components/formatted_text'; import {GlobalStyles} from 'app/styles'; +import {preventDoubleTap} from 'app/utils/tap'; import logo from 'assets/images/logo.png'; @@ -30,7 +31,7 @@ export default class LoginOptions extends PureComponent { return ( + ); + } + return ( - + {proceed} diff --git a/app/scenes/more_channels/create_button.js b/app/scenes/more_channels/create_button.js index 59da6b84c..32700cef9 100644 --- a/app/scenes/more_channels/create_button.js +++ b/app/scenes/more_channels/create_button.js @@ -3,12 +3,9 @@ import React, {PropTypes} from 'react'; import {connect} from 'react-redux'; -import { - TouchableOpacity, - View -} from 'react-native'; -import FormattedText from 'app/components/formatted_text'; +import ActionButton from 'app/components/action_button'; + import {getTheme} from 'app/selectors/preferences'; import {Constants} from 'mattermost-redux/constants'; @@ -22,18 +19,15 @@ function CreateButton(props) { } return ( - - props.emitter('new_channel')} - style={{paddingHorizontal: 15}} - > - - - + ); } diff --git a/app/scenes/more_channels/more_channels.js b/app/scenes/more_channels/more_channels.js index 37c06bb18..7e29339a8 100644 --- a/app/scenes/more_channels/more_channels.js +++ b/app/scenes/more_channels/more_channels.js @@ -17,9 +17,10 @@ import ChannelListRow from 'app/components/custom_list/channel_list_row'; import FormattedText from 'app/components/formatted_text'; import Loading from 'app/components/loading'; import SearchBar from 'app/components/search_bar'; +import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; import {Constants, RequestStatus} from 'mattermost-redux/constants'; -import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; import CreateButton from './create_button'; @@ -81,7 +82,7 @@ class MoreChannels extends PureComponent { componentWillMount() { this.props.subscribeToHeaderEvent('close', this.props.actions.goBack); - this.props.subscribeToHeaderEvent('new_channel', this.onCreateChannel); + this.props.subscribeToHeaderEvent('create_channel', this.onCreateChannel); } componentWillReceiveProps(nextProps) { @@ -112,13 +113,17 @@ class MoreChannels extends PureComponent { componentWillUnmount() { this.props.unsubscribeFromHeaderEvent('close'); - this.props.unsubscribeFromHeaderEvent('new_channel'); + this.props.unsubscribeFromHeaderEvent('create_channel'); if (Platform.OS === 'android') { Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard); } } + emitCanCreateChannel = (enabled) => { + EventEmitter.emit('can_create_channel', enabled); + }; + filterChannels = (channels, term) => { return channels.filter((c) => { return (c.name.toLowerCase().indexOf(term) !== -1 || c.display_name.toLowerCase().indexOf(term) !== -1); @@ -204,6 +209,7 @@ class MoreChannels extends PureComponent { }; onSelectChannel = async (id) => { + this.emitCanCreateChannel(false); this.setState({adding: true}); this.searchBar.blur(); await this.props.actions.joinChannel( diff --git a/app/scenes/options_modal/options_modal.js b/app/scenes/options_modal/options_modal.js index ed9246a17..c5dc9468e 100644 --- a/app/scenes/options_modal/options_modal.js +++ b/app/scenes/options_modal/options_modal.js @@ -28,20 +28,20 @@ export default class OptionsModal extends PureComponent { PropTypes.string, PropTypes.object ]) - } + }; static defaultProps = { onCancelPress: () => false - } + }; static navigationProps = { hideNavBar: true, modalAnimationType: 'fade' - } + }; state = { top: new Animated.Value(deviceHeight) - } + }; componentDidMount() { Animated.timing(this.state.top, { @@ -64,7 +64,7 @@ export default class OptionsModal extends PureComponent { this.props.actions.closeModal(); this.props.onCancelPress(); }); - } + }; render() { const { diff --git a/app/scenes/options_modal/options_modal_list.android.js b/app/scenes/options_modal/options_modal_list.android.js index c088ec0d1..c75995e88 100644 --- a/app/scenes/options_modal/options_modal_list.android.js +++ b/app/scenes/options_modal/options_modal_list.android.js @@ -8,15 +8,16 @@ import { TouchableOpacity, View } from 'react-native'; -import Font from 'react-native-vector-icons/FontAwesome'; +import IconFont from 'react-native-vector-icons/FontAwesome'; import FormattedText from 'app/components/formatted_text'; +import {preventDoubleTap} from 'app/utils/tap'; export default class OptionsModalList extends PureComponent { static propTypes = { items: PropTypes.array.isRequired, onCancelPress: PropTypes.func - } + }; renderOptions = () => { const {items, onCancelPress} = this.props; @@ -37,12 +38,12 @@ export default class OptionsModalList extends PureComponent { return ( preventDoubleTap(item.action, this)} style={[style.option, style.optionBorder]} > {textComponent} {item.icon && - preventDoubleTap(onCancelPress, this)} style={style.option} > {'Cancel'} @@ -66,7 +67,7 @@ export default class OptionsModalList extends PureComponent { ...options, cancel ]; - } + }; render() { return ( diff --git a/app/scenes/options_modal/options_modal_list.ios.js b/app/scenes/options_modal/options_modal_list.ios.js index 83d620698..9e9134b35 100644 --- a/app/scenes/options_modal/options_modal_list.ios.js +++ b/app/scenes/options_modal/options_modal_list.ios.js @@ -8,9 +8,10 @@ import { TouchableOpacity, View } from 'react-native'; -import Font from 'react-native-vector-icons/FontAwesome'; +import IconFont from 'react-native-vector-icons/FontAwesome'; import FormattedText from 'app/components/formatted_text'; +import {preventDoubleTap} from 'app/utils/tap'; export default class OptionsModalList extends PureComponent { static propTypes = { @@ -20,7 +21,7 @@ export default class OptionsModalList extends PureComponent { PropTypes.string, PropTypes.object ]) - } + }; renderOptions = () => { const {items} = this.props; @@ -41,12 +42,12 @@ export default class OptionsModalList extends PureComponent { return ( preventDoubleTap(item.action, this)} style={[style.option, (index < items.length - 1 && style.optionBorder)]} > {textComponent} {item.icon && - preventDoubleTap(onCancelPress, this)} style={style.option} > {'Cancel'} diff --git a/app/scenes/select_server/select_server.js b/app/scenes/select_server/select_server.js index 0f5e83371..7678a7ad2 100644 --- a/app/scenes/select_server/select_server.js +++ b/app/scenes/select_server/select_server.js @@ -3,6 +3,7 @@ import React, {PropTypes, PureComponent} from 'react'; import { + ActivityIndicator, Image, Keyboard, KeyboardAvoidingView, @@ -112,8 +113,27 @@ export default class SelectServer extends PureComponent { configRequest.status === RequestStatus.STARTED || licenseRequest.status === RequestStatus.STARTED; + let proceed; if (isLoading) { - return null; + proceed = ( + + ); + } else { + proceed = ( + + ); } const error = pingRequest.error || configRequest.error || licenseRequest.error; @@ -150,16 +170,7 @@ export default class SelectServer extends PureComponent { returnKeyType='go' underlineColorAndroid='transparent' /> - + {proceed} diff --git a/app/scenes/select_team/select_team.js b/app/scenes/select_team/select_team.js index 6be97710c..68279a3d0 100644 --- a/app/scenes/select_team/select_team.js +++ b/app/scenes/select_team/select_team.js @@ -49,6 +49,10 @@ export default class SelectTeam extends PureComponent { } }; + state = { + disableButtons: false + }; + componentWillMount() { this.props.subscribeToHeaderEvent('close', this.props.actions.goBack); } @@ -58,15 +62,21 @@ export default class SelectTeam extends PureComponent { } onSelectTeam = async (team) => { - const { - closeDrawers, - closeModal, - handleTeamChange - } = this.props.actions; + if (!this.state.disableButtons) { + this.setState({disableButtons: true}); + const { + closeDrawers, + closeModal, + handleTeamChange + } = this.props.actions; - handleTeamChange(team); - closeDrawers(); - InteractionManager.runAfterInteractions(closeModal); + handleTeamChange(team); + closeDrawers(); + InteractionManager.runAfterInteractions(closeModal); + setTimeout(() => { + this.setState({disableButtons: false}); + }, 300); + } }; render() { @@ -77,6 +87,7 @@ export default class SelectTeam extends PureComponent { onPress={() => this.onSelectTeam(team)} style={GlobalStyles.buttonListItemText} containerStyle={GlobalStyles.buttonListItem} + disabled={this.state.disableButtons} > {team.display_name} { - return StyleSheet.create({ - container: { - flex: 1, - backgroundColor: theme.centerChannelBg - } - }); -}); - export default class Thread extends PureComponent { static propTypes = { actions: React.PropTypes.shape({ @@ -85,3 +76,12 @@ export default class Thread extends PureComponent { ); } } + +const getStyle = makeStyleSheetFromTheme((theme) => { + return StyleSheet.create({ + container: { + flex: 1, + backgroundColor: theme.centerChannelBg + } + }); +}); diff --git a/app/utils/tap.js b/app/utils/tap.js new file mode 100644 index 000000000..24cfcdec7 --- /dev/null +++ b/app/utils/tap.js @@ -0,0 +1,14 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +let canPress = true; +export function preventDoubleTap(action, thisArg, ...args) { + if (canPress) { + canPress = false; + Reflect.apply(action, thisArg || null, [...args]); + + setTimeout(() => { + canPress = true; + }, 300); + } +} diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index d9c8d3eaf..caaf3685c 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -1648,7 +1648,7 @@ "mobile.account_notifications.threads_mentions": "Mentions in threads", "mobile.account_notifications.threads_start": "Threads that I start", "mobile.account_notifications.threads_start_participate": "Threads that I start or participate in", - "mobile.channel_info.alertMessageDeleteChannel": "Are you sure you want to delete the {term} with {name}?", + "mobile.channel_info.alertMessageDeleteChannel": "Are you sure you want to delete the {term} {name}?", "mobile.channel_info.alertMessageLeaveChannel": "Are you sure you want to leave the {term} {name}?", "mobile.channel_info.alertNo": "No", "mobile.channel_info.alertTitleDeleteChannel": "Delete {term}",