PLT-5840 Prevent double tapping (#464)
* PLT-5840 Prevent double tapping * handle double tapping on channel header * Review feedback
This commit is contained in:
parent
15ae765f52
commit
c675b17e3c
26 changed files with 287 additions and 211 deletions
|
|
@ -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 (
|
||||
<TouchableHighlight
|
||||
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
|
||||
onPress={() => this.props.onSelectChannel(channel)}
|
||||
delayLongPress={1000}
|
||||
onLongPress={() => {
|
||||
this.handleLongPress(channel);
|
||||
}}
|
||||
onPress={() => preventDoubleTap(onSelectChannel, this, channel)}
|
||||
>
|
||||
<View style={style.container}>
|
||||
{activeBorder}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<TouchableHighlight
|
||||
style={styles.actionContainer}
|
||||
onPress={action}
|
||||
onPress={() => preventDoubleTap(action, this)}
|
||||
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
|
||||
>
|
||||
<MaterialIcon
|
||||
|
|
@ -292,7 +293,7 @@ class ChannelDrawerList extends Component {
|
|||
const settings = (
|
||||
<TouchableHighlight
|
||||
style={styles.settingsContainer}
|
||||
onPress={() => this.props.actions.openSettingsModal()}
|
||||
onPress={() => preventDoubleTap(this.props.actions.openSettingsModal)}
|
||||
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
|
||||
>
|
||||
<AwesomeIcon
|
||||
|
|
|
|||
|
|
@ -12,58 +12,6 @@ import {
|
|||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function createTouchableComponent(children, action) {
|
||||
return (
|
||||
<TouchableOpacity onPress={action}>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
<TouchableOpacity
|
||||
key={file.id}
|
||||
onLongPress={this.props.onLongPress}
|
||||
onPress={() => this.handleOnPress(file)}
|
||||
onPress={() => preventDoubleTap(this.handleOnPress, this, file)}
|
||||
onPressIn={() => this.props.toggleSelected(true)}
|
||||
onPressOut={() => this.props.toggleSelected(false)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
>
|
||||
<TouchableOpacity
|
||||
style={style.item}
|
||||
onPress={action}
|
||||
onPress={() => preventDoubleTap(action)}
|
||||
>
|
||||
<View style={style.itemLeftIconContainer}>
|
||||
<Icon
|
||||
|
|
@ -76,7 +77,7 @@ export default class AccountSettings extends PureComponent {
|
|||
{separator && <View style={style.separator}/>}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
toValue: 0,
|
||||
duration: 500
|
||||
}).start();
|
||||
}
|
||||
};
|
||||
|
||||
didPostsLoad(nextProps, postsRequest) {
|
||||
const nextGetPostsStatus = nextProps.postsRequests[postsRequest].status;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
**/
|
||||
}
|
||||
<ChannelInfoRow
|
||||
action={this.props.actions.goToChannelMembers}
|
||||
action={() => preventDoubleTap(this.props.actions.goToChannelMembers)}
|
||||
defaultMessage={canManageUsers ? 'Manage Members' : 'View Members'}
|
||||
detail={currentChannelMemberCount}
|
||||
icon='users'
|
||||
|
|
@ -229,7 +230,7 @@ class ChannelInfo extends PureComponent {
|
|||
<View>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={this.props.actions.goToChannelAddMembers}
|
||||
action={() => preventDoubleTap(this.props.actions.goToChannelAddMembers)}
|
||||
defaultMessage='Add Members'
|
||||
icon='user-plus'
|
||||
textId='channel_header.addMembers'
|
||||
|
|
@ -239,7 +240,7 @@ class ChannelInfo extends PureComponent {
|
|||
</View>
|
||||
}
|
||||
<ChannelInfoRow
|
||||
action={() => 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 &&
|
||||
<View style={style.footer}>
|
||||
<ChannelInfoRow
|
||||
action={() => 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() &&
|
||||
<View style={style.footer}>
|
||||
<ChannelInfoRow
|
||||
action={this.handleClose}
|
||||
action={() => preventDoubleTap(this.handleClose, this)}
|
||||
defaultMessage={defaultMessage}
|
||||
icon='times'
|
||||
iconColor='#CA3B27'
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
<ActivityIndicator
|
||||
animating={true}
|
||||
size='small'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
proceed = (
|
||||
<Button
|
||||
onPress={this.preSignIn}
|
||||
containerStyle={GlobalStyles.signupButton}
|
||||
>
|
||||
<FormattedText
|
||||
id='login.signIn'
|
||||
defaultMessage='Sign in'
|
||||
style={GlobalStyles.signupButtonText}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior='padding'
|
||||
|
|
@ -274,16 +300,7 @@ class Login extends Component {
|
|||
returnKeyType='go'
|
||||
onSubmitEditing={this.preSignIn}
|
||||
/>
|
||||
<Button
|
||||
onPress={this.preSignIn}
|
||||
containerStyle={GlobalStyles.signupButton}
|
||||
>
|
||||
<FormattedText
|
||||
id='login.signIn'
|
||||
defaultMessage='Sign in'
|
||||
style={GlobalStyles.signupButtonText}
|
||||
/>
|
||||
</Button>
|
||||
{proceed}
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAvoidingView>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Button
|
||||
key='email'
|
||||
onPress={this.props.actions.goToLogin}
|
||||
onPress={() => preventDoubleTap(this.props.actions.goToLogin)}
|
||||
containerStyle={[GlobalStyles.signupButton, {backgroundColor: '#2389d7'}]}
|
||||
>
|
||||
<FormattedText
|
||||
|
|
@ -51,7 +52,7 @@ export default class LoginOptions extends PureComponent {
|
|||
return (
|
||||
<Button
|
||||
key='saml'
|
||||
onPress={this.props.actions.goToSaml}
|
||||
onPress={() => preventDoubleTap(this.props.actions.goToSaml)}
|
||||
containerStyle={[GlobalStyles.signupButton, {backgroundColor: '#34a28b'}]}
|
||||
>
|
||||
<Text
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import React, {Component} from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
|
|
@ -96,6 +97,32 @@ export default class Mfa extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const isLoading = this.props.loginRequest.status === RequestStatus.STARTED;
|
||||
|
||||
let proceed;
|
||||
if (isLoading) {
|
||||
proceed = (
|
||||
<ActivityIndicator
|
||||
animating={true}
|
||||
size='small'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
proceed = (
|
||||
<Button
|
||||
onPress={this.submit}
|
||||
loading={false}
|
||||
containerStyle={GlobalStyles.signupButton}
|
||||
>
|
||||
<FormattedText
|
||||
style={GlobalStyles.signupButtonText}
|
||||
id='mobile.components.select_server_view.proceed'
|
||||
defaultMessage='Proceed'
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior='padding'
|
||||
|
|
@ -128,17 +155,7 @@ export default class Mfa extends Component {
|
|||
returnKeyType='go'
|
||||
underlineColorAndroid='transparent'
|
||||
/>
|
||||
<Button
|
||||
onPress={this.submit}
|
||||
loading={false}
|
||||
containerStyle={GlobalStyles.signupButton}
|
||||
>
|
||||
<FormattedText
|
||||
style={GlobalStyles.signupButtonText}
|
||||
id='mobile.components.select_server_view.proceed'
|
||||
defaultMessage='Proceed'
|
||||
/>
|
||||
</Button>
|
||||
{proceed}
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAvoidingView>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={{flexDirection: 'column', justifyContent: 'center', alignItems: 'center', flex: 1}}>
|
||||
<TouchableOpacity
|
||||
onPress={() => props.emitter('new_channel')}
|
||||
style={{paddingHorizontal: 15}}
|
||||
>
|
||||
<FormattedText
|
||||
id='mobile.create_channel'
|
||||
defaultMessage='Create'
|
||||
style={{color: props.theme.sidebarHeaderTextColor}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ActionButton
|
||||
actionEventName='create_channel'
|
||||
emitter={props.emitter}
|
||||
enabled={true}
|
||||
enableEventName='can_create_channel'
|
||||
labelDefaultMessage='Create'
|
||||
labelId='mobile.create_channel'
|
||||
loadingEventName='creating_channel'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<TouchableOpacity
|
||||
key={index}
|
||||
onPress={item.action}
|
||||
onPress={() => preventDoubleTap(item.action, this)}
|
||||
style={[style.option, style.optionBorder]}
|
||||
>
|
||||
{textComponent}
|
||||
{item.icon &&
|
||||
<Font
|
||||
<IconFont
|
||||
name={item.icon}
|
||||
size={18}
|
||||
style={style.optionIcon}
|
||||
|
|
@ -55,7 +56,7 @@ export default class OptionsModalList extends PureComponent {
|
|||
const cancel = (
|
||||
<TouchableOpacity
|
||||
key={items.length}
|
||||
onPress={onCancelPress}
|
||||
onPress={() => preventDoubleTap(onCancelPress, this)}
|
||||
style={style.option}
|
||||
>
|
||||
<Text style={style.optionText}>{'Cancel'}</Text>
|
||||
|
|
@ -66,7 +67,7 @@ export default class OptionsModalList extends PureComponent {
|
|||
...options,
|
||||
cancel
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<TouchableOpacity
|
||||
key={index}
|
||||
onPress={item.action}
|
||||
onPress={() => preventDoubleTap(item.action, this)}
|
||||
style={[style.option, (index < items.length - 1 && style.optionBorder)]}
|
||||
>
|
||||
{textComponent}
|
||||
{item.icon &&
|
||||
<Font
|
||||
<IconFont
|
||||
name={item.icon}
|
||||
size={18}
|
||||
style={style.optionIcon}
|
||||
|
|
@ -84,7 +85,7 @@ export default class OptionsModalList extends PureComponent {
|
|||
title,
|
||||
...options
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {onCancelPress} = this.props;
|
||||
|
|
@ -96,7 +97,7 @@ export default class OptionsModalList extends PureComponent {
|
|||
</View>
|
||||
<View style={style.optionContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={onCancelPress}
|
||||
onPress={() => preventDoubleTap(onCancelPress, this)}
|
||||
style={style.option}
|
||||
>
|
||||
<Text style={style.optionCancelText}>{'Cancel'}</Text>
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
<ActivityIndicator
|
||||
animating={true}
|
||||
size='small'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
proceed = (
|
||||
<Button
|
||||
onPress={this.onClick}
|
||||
containerStyle={GlobalStyles.signupButton}
|
||||
>
|
||||
<FormattedText
|
||||
style={GlobalStyles.signupButtonText}
|
||||
id='mobile.components.select_server_view.proceed'
|
||||
defaultMessage='Proceed'
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
const error = pingRequest.error || configRequest.error || licenseRequest.error;
|
||||
|
|
@ -150,16 +170,7 @@ export default class SelectServer extends PureComponent {
|
|||
returnKeyType='go'
|
||||
underlineColorAndroid='transparent'
|
||||
/>
|
||||
<Button
|
||||
onPress={this.onClick}
|
||||
containerStyle={GlobalStyles.signupButton}
|
||||
>
|
||||
<FormattedText
|
||||
style={GlobalStyles.signupButtonText}
|
||||
id='mobile.components.select_server_view.proceed'
|
||||
defaultMessage='Proceed'
|
||||
/>
|
||||
</Button>
|
||||
{proceed}
|
||||
<ErrorText error={this.state.error || error}/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
<Icon
|
||||
|
|
|
|||
|
|
@ -10,15 +10,6 @@ import PostList from 'app/components/post_list';
|
|||
import PostTextbox from 'app/components/post_textbox';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const getStyle = makeStyleSheetFromTheme((theme) => {
|
||||
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
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
14
app/utils/tap.js
Normal file
14
app/utils/tap.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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}",
|
||||
|
|
|
|||
Loading…
Reference in a new issue