RN-7 Ability to search channels in the drawer (#602)
* RN-7 Ability to search channels in the drawer * Fix i18n locale data * Split channel_drawer into multiple components * Fix eslint
This commit is contained in:
parent
8b8cf6234a
commit
2980de0b19
28 changed files with 1317 additions and 705 deletions
33
NOTICE.txt
33
NOTICE.txt
|
|
@ -74,39 +74,6 @@ THE SOFTWARE.
|
|||
|
||||
---
|
||||
|
||||
## react-native-search-bar
|
||||
|
||||
This product contains a modified portion of 'react-native-search-bar', a high-quality iOS native search bar for react native by Zhao Han.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/umhan35/react-native-search-bar
|
||||
|
||||
* LICENSE:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2016 Zhao Han
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-keyboard-aware-scroll
|
||||
|
||||
This product contains 'react-native-keyboard-aware-scroll', a ScrollView component that handles keyboard appearance and automatically scrolls to focused `TextInput`
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ import {
|
|||
} from 'react-native';
|
||||
|
||||
import Drawer from 'app/components/drawer';
|
||||
import ChannelDrawerList from 'app/components/channel_drawer_list';
|
||||
import ChannelDrawerSwiper from 'app/components/channel_drawer_swiper';
|
||||
import ChannelDrawerTeams from 'app/components/channel_drawer_teams';
|
||||
|
||||
import ChannelsList from './channels_list';
|
||||
import Swiper from './swiper';
|
||||
import TeamsList from './teams_list';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
const DRAWER_INITIAL_OFFSET = 40;
|
||||
|
|
@ -25,17 +27,19 @@ export default class ChannelDrawer extends PureComponent {
|
|||
getTeams: PropTypes.func.isRequired,
|
||||
handleSelectChannel: PropTypes.func.isRequired,
|
||||
viewChannel: PropTypes.func.isRequired,
|
||||
makeDirectChannel: PropTypes.func.isRequired,
|
||||
markChannelAsRead: PropTypes.func.isRequired,
|
||||
setChannelLoading: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
blurPostTextBox: PropTypes.func.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
currentTeam: PropTypes.object,
|
||||
currentChannel: PropTypes.object,
|
||||
channels: PropTypes.object,
|
||||
currentChannel: PropTypes.object,
|
||||
channelMembers: PropTypes.object,
|
||||
currentTeam: PropTypes.object,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
myTeamMembers: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
|
@ -93,7 +97,9 @@ export default class ChannelDrawer extends PureComponent {
|
|||
|
||||
handleDrawerOpen = () => {
|
||||
this.setState({openDrawer: true});
|
||||
Keyboard.dismiss();
|
||||
if (this.state.openDrawerOffset === DRAWER_INITIAL_OFFSET) {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
setTimeout(() => {
|
||||
InteractionManager.clearInteractionHandle(this.openLeftHandle);
|
||||
});
|
||||
|
|
@ -148,10 +154,58 @@ export default class ChannelDrawer extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
joinChannel = (channel) => {
|
||||
const {
|
||||
actions,
|
||||
currentChannel,
|
||||
currentTeam,
|
||||
currentUserId
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
handleSelectChannel,
|
||||
joinChannel,
|
||||
makeDirectChannel,
|
||||
markChannelAsRead,
|
||||
setChannelLoading,
|
||||
viewChannel
|
||||
} = actions;
|
||||
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
markChannelAsRead(currentChannel.id);
|
||||
setChannelLoading();
|
||||
viewChannel(currentChannel.id);
|
||||
this.closeChannelDrawer();
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
makeDirectChannel(channel.id);
|
||||
});
|
||||
} else {
|
||||
markChannelAsRead(currentChannel.id);
|
||||
setChannelLoading();
|
||||
viewChannel(currentChannel.id);
|
||||
joinChannel(currentUserId, currentTeam.id, channel.id);
|
||||
this.closeChannelDrawer();
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
handleSelectChannel(channel.id);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onPageSelected = (index) => {
|
||||
this.swiperIndex = index;
|
||||
};
|
||||
|
||||
onSearchEnds = () => {
|
||||
//hack to update the drawer when the offset changes
|
||||
this.refs.drawer._syncAfterUpdate = true; //eslint-disable-line no-underscore-dangle
|
||||
this.setState({openDrawerOffset: DRAWER_INITIAL_OFFSET});
|
||||
};
|
||||
|
||||
onSearchStart = () => {
|
||||
this.refs.drawer._syncAfterUpdate = true; //eslint-disable-line no-underscore-dangle
|
||||
this.setState({openDrawerOffset: 0});
|
||||
};
|
||||
|
||||
showTeams = () => {
|
||||
const teamsCount = Object.keys(this.props.myTeamMembers).length;
|
||||
if (this.swiperIndex === 1 && teamsCount > 1) {
|
||||
|
|
@ -160,7 +214,9 @@ export default class ChannelDrawer extends PureComponent {
|
|||
};
|
||||
|
||||
resetDrawer = () => {
|
||||
this.refs.swiper.resetPage();
|
||||
if (this.swiperIndex !== 1) {
|
||||
this.refs.swiper.resetPage();
|
||||
}
|
||||
};
|
||||
|
||||
renderContent = () => {
|
||||
|
|
@ -173,13 +229,14 @@ export default class ChannelDrawer extends PureComponent {
|
|||
myTeamMembers,
|
||||
theme
|
||||
} = this.props;
|
||||
const showTeams = Object.keys(myTeamMembers).length > 1;
|
||||
const {openDrawerOffset} = this.state;
|
||||
const showTeams = openDrawerOffset === DRAWER_INITIAL_OFFSET && Object.keys(myTeamMembers).length > 1;
|
||||
|
||||
let teams;
|
||||
if (showTeams) {
|
||||
teams = (
|
||||
<View style={{flex: 1, marginBottom: 10}}>
|
||||
<ChannelDrawerTeams
|
||||
<TeamsList
|
||||
closeChannelDrawer={this.closeChannelDrawer}
|
||||
myTeamMembers={myTeamMembers}
|
||||
navigator={navigator}
|
||||
|
|
@ -190,7 +247,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
|
||||
const channelsList = (
|
||||
<View style={{flex: 1, marginBottom: 10}}>
|
||||
<ChannelDrawerList
|
||||
<ChannelsList
|
||||
currentTeam={currentTeam}
|
||||
currentChannel={currentChannel}
|
||||
channels={channels}
|
||||
|
|
@ -198,23 +255,26 @@ export default class ChannelDrawer extends PureComponent {
|
|||
myTeamMembers={myTeamMembers}
|
||||
theme={theme}
|
||||
onSelectChannel={this.selectChannel}
|
||||
onJoinChannel={this.joinChannel}
|
||||
navigator={navigator}
|
||||
onShowTeams={this.showTeams}
|
||||
onSearchStart={this.onSearchStart}
|
||||
onSearchEnds={this.onSearchEnds}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<ChannelDrawerSwiper
|
||||
<Swiper
|
||||
ref='swiper'
|
||||
onPageSelected={this.onPageSelected}
|
||||
openDrawerOffset={this.state.openDrawerOffset}
|
||||
openDrawerOffset={openDrawerOffset}
|
||||
showTeams={showTeams}
|
||||
theme={theme}
|
||||
>
|
||||
{teams}
|
||||
{channelsList}
|
||||
</ChannelDrawerSwiper>
|
||||
</Swiper>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -224,6 +284,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
|
||||
return (
|
||||
<Drawer
|
||||
ref='drawer'
|
||||
open={openDrawer}
|
||||
onOpenStart={this.handleDrawerOpenStart}
|
||||
onOpen={this.handleDrawerOpen}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ 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 {
|
||||
export default class ChannelItem extends PureComponent {
|
||||
static propTypes = {
|
||||
channel: PropTypes.object.isRequired,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import deepEqual from 'deep-equal';
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
FlatList,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {sortChannelsByDisplayName} from 'mattermost-redux/utils/channel_utils';
|
||||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import ChannelDrawerItem from 'app/components/channel_drawer/channels_list/channel_item';
|
||||
|
||||
class ChannelDrawerList extends Component {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
makeGroupMessageVisibleIfNecessary: PropTypes.func.isRequired,
|
||||
searchChannels: PropTypes.func.isRequired,
|
||||
searchProfiles: PropTypes.func.isRequired,
|
||||
setChannelDisplayName: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
channels: PropTypes.object.isRequired,
|
||||
channelMembers: PropTypes.object,
|
||||
currentTeam: PropTypes.object.isRequired,
|
||||
currentUserId: PropTypes.string,
|
||||
currentChannel: PropTypes.object,
|
||||
groupChannels: PropTypes.array,
|
||||
intl: intlShape.isRequired,
|
||||
myPreferences: PropTypes.object,
|
||||
myTeamMembers: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
onJoinChannel: PropTypes.func.isRequired,
|
||||
onSearchEnds: PropTypes.func.isRequired,
|
||||
onSearchStart: PropTypes.func.isRequired,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
onShowTeams: PropTypes.func.isRequired,
|
||||
otherChannels: PropTypes.array,
|
||||
profiles: PropTypes.object,
|
||||
profilesInChannel: PropTypes.object,
|
||||
statuses: PropTypes.object,
|
||||
styles: PropTypes.object.isRequired,
|
||||
term: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentTeam: {},
|
||||
currentChannel: {}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dataSource: this.buildData(props)
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !deepEqual(this.props, nextProps, {strict: true}) || !deepEqual(this.state, nextState, {strict: true});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.term !== nextProps.term) {
|
||||
const {actions, currentTeam} = this.props;
|
||||
const {term} = nextProps;
|
||||
const {searchChannels, searchProfiles} = actions;
|
||||
const dataSource = this.buildData(this.props, term);
|
||||
|
||||
this.setState({dataSource, term});
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
|
||||
this.searchTimeoutId = setTimeout(() => {
|
||||
searchProfiles(term);
|
||||
searchChannels(currentTeam.id, term);
|
||||
}, General.SEARCH_TIMEOUT_MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
onSelectChannel = (channel) => {
|
||||
const {
|
||||
makeGroupMessageVisibleIfNecessary,
|
||||
setChannelDisplayName
|
||||
} = this.props.actions;
|
||||
|
||||
setChannelDisplayName(channel.display_name);
|
||||
if (channel.type === General.GM_CHANNEL) {
|
||||
makeGroupMessageVisibleIfNecessary(channel.id);
|
||||
}
|
||||
|
||||
this.props.onSelectChannel(channel);
|
||||
};
|
||||
|
||||
createChannelElement = (channel) => {
|
||||
return (
|
||||
<ChannelDrawerItem
|
||||
ref={channel.id}
|
||||
channel={channel}
|
||||
hasUnread={false}
|
||||
mentions={0}
|
||||
onSelectChannel={this.onSelectChannel}
|
||||
isActive={channel.isCurrent || false}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
filterChannels = (channels, term) => {
|
||||
if (!term) {
|
||||
return channels;
|
||||
}
|
||||
|
||||
const text = term.toLowerCase();
|
||||
return channels.filter((c) => {
|
||||
return c.display_name.toLowerCase().includes(text);
|
||||
});
|
||||
};
|
||||
|
||||
completeDirectGroupInfo = (channel) => {
|
||||
const {currentUserId, myPreferences, profiles, profilesInChannel} = this.props;
|
||||
const profilesIds = profilesInChannel[channel.id];
|
||||
if (profilesIds) {
|
||||
function sortUsernames(a, b) {
|
||||
const locale = profiles[currentUserId].locale;
|
||||
return a.localeCompare(b, locale, {numeric: true});
|
||||
}
|
||||
|
||||
const displayName = [];
|
||||
profilesIds.forEach((teammateId) => {
|
||||
if (teammateId !== currentUserId) {
|
||||
displayName.push(displayUsername(profiles[teammateId], myPreferences));
|
||||
}
|
||||
});
|
||||
|
||||
const gm = {...channel};
|
||||
return Object.assign(gm, {
|
||||
display_name: displayName.sort(sortUsernames).join(', ')
|
||||
});
|
||||
}
|
||||
return channel;
|
||||
};
|
||||
|
||||
buildChannelsForSearch = (props, term) => {
|
||||
const data = [];
|
||||
const {groupChannels, otherChannels, styles} = props;
|
||||
const {
|
||||
unreadChannels,
|
||||
favoriteChannels,
|
||||
publicChannels,
|
||||
privateChannels
|
||||
} = props.channels;
|
||||
|
||||
const notMemberOf = otherChannels.map((o) => {
|
||||
return {
|
||||
...o,
|
||||
fake: true
|
||||
};
|
||||
});
|
||||
|
||||
const favorites = favoriteChannels.filter((c) => {
|
||||
return c.type !== General.DM_CHANNEL && c.type !== General.GM_CHANNEL;
|
||||
});
|
||||
|
||||
const unreads = this.filterChannels(unreadChannels, term);
|
||||
const channels = this.filterChannels([...favorites, ...publicChannels, ...privateChannels], term);
|
||||
const others = this.filterChannels(notMemberOf, term);
|
||||
const groups = this.filterChannels(groupChannels.map((g) => this.completeDirectGroupInfo(g)), term);
|
||||
const fakeDms = this.filterChannels(this.buildFakeDms(props), term);
|
||||
const directMessages = [...groups, ...fakeDms].sort(sortChannelsByDisplayName.bind(null, props.intl.locale));
|
||||
|
||||
if (unreads.length) {
|
||||
data.push(
|
||||
this.renderTitle(styles, 'mobile.channel_list.unreads', 'UNREADS', null, false, true),
|
||||
...unreads
|
||||
);
|
||||
}
|
||||
|
||||
if (channels.length) {
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.channels', 'CHANNELS', null, unreads.length > 0, true),
|
||||
...channels
|
||||
);
|
||||
}
|
||||
|
||||
if (others.length) {
|
||||
data.push(
|
||||
this.renderTitle(styles, 'mobile.channel_list.not_member', 'NOT A MEMBER', null, channels.length > 0, true),
|
||||
...others
|
||||
);
|
||||
}
|
||||
|
||||
if (directMessages.length) {
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.direct', 'DIRECT MESSAGES', null, others.length > 0, true),
|
||||
...directMessages
|
||||
);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
buildFakeDms = (props) => {
|
||||
const {myPreferences, profiles, statuses} = props;
|
||||
const users = Object.values(profiles);
|
||||
|
||||
return users.map((u) => {
|
||||
return {
|
||||
id: u.id,
|
||||
status: statuses[u.id],
|
||||
display_name: displayUsername(u, myPreferences),
|
||||
type: General.DM_CHANNEL,
|
||||
fake: true
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
buildData = (props, term) => {
|
||||
if (!props.currentChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.buildChannelsForSearch(props, term);
|
||||
};
|
||||
|
||||
renderSectionAction = (styles, action) => {
|
||||
const {theme} = this.props;
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.actionContainer}
|
||||
onPress={() => preventDoubleTap(action, this)}
|
||||
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
|
||||
>
|
||||
<MaterialIcon
|
||||
name='add'
|
||||
style={styles.action}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
};
|
||||
|
||||
renderDivider = (styles, marginLeft) => {
|
||||
return (
|
||||
<View
|
||||
style={[styles.divider, {marginLeft}]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderItem = ({item}) => {
|
||||
if (!item.isTitle) {
|
||||
return this.createChannelElement(item);
|
||||
}
|
||||
return item.title;
|
||||
};
|
||||
|
||||
renderTitle = (styles, id, defaultMessage, action, topDivider, bottomDivider) => {
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
return {
|
||||
id,
|
||||
isTitle: true,
|
||||
title: (
|
||||
<View>
|
||||
{topDivider && this.renderDivider(styles, 0)}
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title}>
|
||||
{formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
{action && this.renderSectionAction(styles, action)}
|
||||
</View>
|
||||
{bottomDivider && this.renderDivider(styles, 16)}
|
||||
</View>
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
render() {
|
||||
const {styles} = this.props;
|
||||
|
||||
const {dataSource} = this.state;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.container}
|
||||
>
|
||||
<FlatList
|
||||
ref='list'
|
||||
data={dataSource}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={(item) => item.id}
|
||||
onViewableItemsChanged={this.updateUnreadIndicators}
|
||||
keyboardDismissMode='on-drag'
|
||||
maxToRenderPerBatch={10}
|
||||
viewabilityConfig={{
|
||||
viewAreaCoveragePercentThreshold: 3,
|
||||
waitForInteraction: false
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(ChannelDrawerList);
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {searchChannels} from 'mattermost-redux/actions/channels';
|
||||
import {searchProfiles} from 'mattermost-redux/actions/users';
|
||||
import {makeGroupMessageVisibleIfNecessary} from 'mattermost-redux/actions/preferences';
|
||||
import {getUserIdsInChannels, getUsers, getUserStatuses} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getGroupChannels, getOtherChannels} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {setChannelDisplayName} from 'app/actions/views/channel';
|
||||
|
||||
import FilteredList from './filtered_list';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {currentUserId} = state.entities.users;
|
||||
|
||||
return {
|
||||
currentUserId,
|
||||
otherChannels: getOtherChannels(state),
|
||||
groupChannels: getGroupChannels(state),
|
||||
profiles: getUsers(state),
|
||||
profilesInChannel: getUserIdsInChannels(state),
|
||||
myPreferences: getMyPreferences(state),
|
||||
statuses: getUserStatuses(state),
|
||||
...ownProps
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
makeGroupMessageVisibleIfNecessary,
|
||||
searchChannels,
|
||||
searchProfiles,
|
||||
setChannelDisplayName
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FilteredList);
|
||||
396
app/components/channel_drawer/channels_list/index.js
Normal file
396
app/components/channel_drawer/channels_list/index.js
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import Badge from 'app/components/badge';
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import FilteredList from './filtered_list';
|
||||
import List from './list';
|
||||
|
||||
class ChannelsList extends Component {
|
||||
static propTypes = {
|
||||
channels: PropTypes.object.isRequired,
|
||||
channelMembers: PropTypes.object,
|
||||
currentTeam: PropTypes.object.isRequired,
|
||||
currentChannel: PropTypes.object,
|
||||
intl: intlShape.isRequired,
|
||||
myTeamMembers: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
onJoinChannel: PropTypes.func.isRequired,
|
||||
onSearchEnds: PropTypes.func.isRequired,
|
||||
onSearchStart: PropTypes.func.isRequired,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
onShowTeams: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentTeam: {},
|
||||
currentChannel: {}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.firstUnreadChannel = null;
|
||||
this.state = {
|
||||
searching: false,
|
||||
term: null
|
||||
};
|
||||
|
||||
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).
|
||||
then((source) => {
|
||||
this.closeButton = source;
|
||||
});
|
||||
}
|
||||
|
||||
onSelectChannel = (channel) => {
|
||||
if (channel.fake) {
|
||||
this.props.onJoinChannel(channel);
|
||||
} else {
|
||||
this.props.onSelectChannel(channel.id);
|
||||
}
|
||||
|
||||
this.refs.search_bar.cancel();
|
||||
};
|
||||
|
||||
openSettingsModal = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
screen: 'Settings',
|
||||
title: intl.formatMessage({id: 'mobile.routes.settings', defaultMessage: 'Settings'}),
|
||||
animationType: 'slide-up',
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-settings',
|
||||
icon: this.closeButton
|
||||
}]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onSearch = (term) => {
|
||||
this.setState({term});
|
||||
};
|
||||
|
||||
onSearchFocused = () => {
|
||||
this.setState({searching: true});
|
||||
this.props.onSearchStart();
|
||||
};
|
||||
|
||||
cancelSearch = () => {
|
||||
this.props.onSearchEnds();
|
||||
this.setState({searching: false});
|
||||
this.onSearch(null);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentChannel,
|
||||
currentTeam,
|
||||
intl,
|
||||
myTeamMembers,
|
||||
onShowTeams,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
if (!currentChannel) {
|
||||
return <Text>{'Loading'}</Text>;
|
||||
}
|
||||
|
||||
const {searching, term} = this.state;
|
||||
const teamMembers = Object.values(myTeamMembers);
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
let settings;
|
||||
let list;
|
||||
if (searching) {
|
||||
const listProps = {...this.props, onSelectChannel: this.onSelectChannel, styles, term};
|
||||
list = <FilteredList {...listProps}/>;
|
||||
} else {
|
||||
settings = (
|
||||
<TouchableHighlight
|
||||
style={styles.settingsContainer}
|
||||
onPress={() => preventDoubleTap(this.openSettingsModal)}
|
||||
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
|
||||
>
|
||||
<AwesomeIcon
|
||||
name='cog'
|
||||
style={styles.settings}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
|
||||
const listProps = {...this.props, onSelectChannel: this.onSelectChannel, styles};
|
||||
list = <List {...listProps}/>;
|
||||
}
|
||||
|
||||
const title = (
|
||||
<View style={styles.searchContainer}>
|
||||
<SearchBar
|
||||
ref='search_bar'
|
||||
placeholder={intl.formatMessage({id: 'mobile.channel_drawer.search', defaultMessage: 'Jump to a conversation'})}
|
||||
cancelTitle={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={Platform.OS === 'android' ? 32 : 33}
|
||||
inputStyle={{
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.4),
|
||||
color: theme.sidebarText,
|
||||
fontSize: 13
|
||||
}}
|
||||
placeholderTextColor={changeOpacity(theme.sidebarText, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.sidebarText, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.sidebarText, 0.5)}
|
||||
titleCancelColor={theme.sidebarHeaderTextColor}
|
||||
onSearchButtonPress={this.onSearch}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
onChangeText={this.onSearch}
|
||||
onFocus={this.onSearchFocused}
|
||||
/>
|
||||
</View >
|
||||
);
|
||||
|
||||
let badge;
|
||||
let switcher;
|
||||
if (teamMembers.length > 1 && !searching) {
|
||||
let mentionCount = 0;
|
||||
let messageCount = 0;
|
||||
teamMembers.forEach((m) => {
|
||||
if (m.team_id !== currentTeam.id) {
|
||||
mentionCount = mentionCount + (m.mention_count || 0);
|
||||
messageCount = messageCount + (m.msg_count || 0);
|
||||
}
|
||||
});
|
||||
|
||||
let badgeCount = 0;
|
||||
if (mentionCount) {
|
||||
badgeCount = mentionCount;
|
||||
} else if (messageCount) {
|
||||
badgeCount = -1;
|
||||
}
|
||||
|
||||
if (badgeCount) {
|
||||
badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={badgeCount}
|
||||
minHeight={5}
|
||||
minWidth={5}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
switcher = (
|
||||
<TouchableHighlight
|
||||
onPress={() => preventDoubleTap(onShowTeams)}
|
||||
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
|
||||
>
|
||||
<View style={styles.switcherContainer}>
|
||||
<AwesomeIcon
|
||||
name='chevron-left'
|
||||
size={12}
|
||||
color={theme.sidebarHeaderBg}
|
||||
/>
|
||||
<View style={styles.switcherDivider}/>
|
||||
<Text style={styles.switcherTeam}>
|
||||
{currentTeam.display_name.substr(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.container}
|
||||
>
|
||||
<View style={styles.statusBar}>
|
||||
<View style={styles.headerContainer}>
|
||||
{switcher}
|
||||
{title}
|
||||
{settings}
|
||||
{badge}
|
||||
</View>
|
||||
</View>
|
||||
{list}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: theme.sidebarBg,
|
||||
flex: 1
|
||||
},
|
||||
statusBar: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
...Platform.select({
|
||||
ios: {
|
||||
paddingTop: 20
|
||||
}
|
||||
})
|
||||
},
|
||||
headerContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
flexDirection: 'row',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: changeOpacity(theme.sidebarHeaderTextColor, 0.10),
|
||||
...Platform.select({
|
||||
android: {
|
||||
height: 46
|
||||
},
|
||||
ios: {
|
||||
height: 44
|
||||
}
|
||||
})
|
||||
},
|
||||
header: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
fontWeight: 'normal',
|
||||
paddingLeft: 16
|
||||
},
|
||||
settingsContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 10,
|
||||
...Platform.select({
|
||||
android: {
|
||||
height: 46
|
||||
},
|
||||
ios: {
|
||||
height: 44
|
||||
}
|
||||
})
|
||||
},
|
||||
settings: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 18,
|
||||
fontWeight: '300'
|
||||
},
|
||||
titleContainer: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: 48,
|
||||
marginLeft: 16
|
||||
},
|
||||
title: {
|
||||
flex: 1,
|
||||
color: theme.sidebarText,
|
||||
opacity: 1,
|
||||
fontSize: 15,
|
||||
fontWeight: '400',
|
||||
letterSpacing: 0.8,
|
||||
lineHeight: 18
|
||||
},
|
||||
searchContainer: {
|
||||
flex: 1,
|
||||
...Platform.select({
|
||||
android: {
|
||||
marginBottom: 1
|
||||
},
|
||||
ios: {
|
||||
marginBottom: 3
|
||||
}
|
||||
})
|
||||
},
|
||||
switcherContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.sidebarHeaderTextColor,
|
||||
borderRadius: 2,
|
||||
flexDirection: 'row',
|
||||
height: 32,
|
||||
justifyContent: 'center',
|
||||
marginLeft: 16,
|
||||
paddingHorizontal: 6
|
||||
},
|
||||
switcherDivider: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
height: 15,
|
||||
marginHorizontal: 6,
|
||||
width: 1
|
||||
},
|
||||
switcherTeam: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
fontFamily: 'OpenSans',
|
||||
fontSize: 14
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: theme.mentionBj,
|
||||
borderColor: theme.sidebarHeaderBg,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
flexDirection: 'row',
|
||||
height: 20,
|
||||
padding: 3,
|
||||
position: 'absolute',
|
||||
left: 5,
|
||||
top: 0,
|
||||
width: 20
|
||||
},
|
||||
mention: {
|
||||
color: theme.mentionColor,
|
||||
fontSize: 10
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.1),
|
||||
height: 1
|
||||
},
|
||||
actionContainer: {
|
||||
alignItems: 'center',
|
||||
height: 48,
|
||||
justifyContent: 'center',
|
||||
width: 50
|
||||
},
|
||||
action: {
|
||||
color: theme.sidebarText,
|
||||
fontSize: 20,
|
||||
fontWeight: '500',
|
||||
lineHeight: 18
|
||||
},
|
||||
above: {
|
||||
backgroundColor: theme.mentionBj,
|
||||
top: 9
|
||||
},
|
||||
indicatorText: {
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.mentionColor,
|
||||
fontSize: 14,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default injectIntl(ChannelsList);
|
||||
|
|
@ -11,7 +11,7 @@ import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
|||
|
||||
import {setChannelDisplayName} from 'app/actions/views/channel';
|
||||
|
||||
import ChannelDrawerList from './channel_drawer_list';
|
||||
import List from './list';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {config, license} = state.entities.general;
|
||||
|
|
@ -31,4 +31,4 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelDrawerList);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(List);
|
||||
|
|
@ -6,27 +6,23 @@ import React, {Component} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
FlatList,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import Badge from 'app/components/badge';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import ChannelDrawerItem from './channel_drawer_item';
|
||||
import UnreadIndicator from './unread_indicator';
|
||||
import ChannelItem from 'app/components/channel_drawer/channels_list/channel_item';
|
||||
import UnreadIndicator from 'app/components/channel_drawer/channels_list/unread_indicator';
|
||||
|
||||
class ChannelDrawerList extends Component {
|
||||
class List extends Component {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
setChannelDisplayName: PropTypes.func.isRequired
|
||||
|
|
@ -34,18 +30,15 @@ class ChannelDrawerList extends Component {
|
|||
canCreatePrivateChannels: PropTypes.bool.isRequired,
|
||||
channels: PropTypes.object.isRequired,
|
||||
channelMembers: PropTypes.object,
|
||||
currentTeam: PropTypes.object.isRequired,
|
||||
currentChannel: PropTypes.object,
|
||||
intl: intlShape.isRequired,
|
||||
myTeamMembers: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
onShowTeams: PropTypes.func.isRequired,
|
||||
styles: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentTeam: {},
|
||||
currentChannel: {}
|
||||
};
|
||||
|
||||
|
|
@ -53,9 +46,10 @@ class ChannelDrawerList extends Component {
|
|||
super(props);
|
||||
this.firstUnreadChannel = null;
|
||||
this.state = {
|
||||
showAbove: false,
|
||||
dataSource: this.buildData(props)
|
||||
dataSource: this.buildData(props),
|
||||
showAbove: false
|
||||
};
|
||||
|
||||
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).
|
||||
then((source) => {
|
||||
this.closeButton = source;
|
||||
|
|
@ -102,7 +96,7 @@ class ChannelDrawerList extends Component {
|
|||
|
||||
onSelectChannel = (channel) => {
|
||||
this.props.actions.setChannelDisplayName(channel.display_name);
|
||||
this.props.onSelectChannel(channel.id);
|
||||
this.props.onSelectChannel(channel);
|
||||
};
|
||||
|
||||
onLayout = (event) => {
|
||||
|
|
@ -150,7 +144,7 @@ class ChannelDrawerList extends Component {
|
|||
const unread = msgCount > 0;
|
||||
|
||||
return (
|
||||
<ChannelDrawerItem
|
||||
<ChannelItem
|
||||
ref={channel.id}
|
||||
channel={channel}
|
||||
hasUnread={unread}
|
||||
|
|
@ -184,16 +178,8 @@ class ChannelDrawerList extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
buildData = (props) => {
|
||||
const data = [];
|
||||
|
||||
if (!props.currentChannel) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const {canCreatePrivateChannels, theme} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
buildChannels = (props) => {
|
||||
const {canCreatePrivateChannels, styles} = props;
|
||||
const {
|
||||
unreadChannels,
|
||||
favoriteChannels,
|
||||
|
|
@ -202,16 +188,18 @@ class ChannelDrawerList extends Component {
|
|||
directAndGroupChannels
|
||||
} = props.channels;
|
||||
|
||||
const data = [];
|
||||
|
||||
if (unreadChannels.length) {
|
||||
data.push(
|
||||
this.renderTitle(styles, 'mobile.channel_list.unreads', 'UNREADS', null, false, unreadChannels.length > 0),
|
||||
this.renderTitle(styles, 'mobile.channel_list.unreads', 'UNREADS', null, false, true),
|
||||
...unreadChannels
|
||||
);
|
||||
}
|
||||
|
||||
if (favoriteChannels.length) {
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.favorite', 'FAVORITES', null, unreadChannels.length > 0, favoriteChannels.length > 0),
|
||||
this.renderTitle(styles, 'sidebar.favorite', 'FAVORITES', null, unreadChannels.length > 0, true),
|
||||
...favoriteChannels
|
||||
);
|
||||
}
|
||||
|
|
@ -235,36 +223,21 @@ class ChannelDrawerList extends Component {
|
|||
...directAndGroupChannels
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
buildData = (props) => {
|
||||
if (!props.currentChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = this.buildChannels(props);
|
||||
this.firstUnreadChannel = null;
|
||||
this.findUnreadChannels(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
openSettingsModal = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
screen: 'Settings',
|
||||
title: intl.formatMessage({id: 'mobile.routes.settings', defaultMessage: 'Settings'}),
|
||||
animationType: 'slide-up',
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-settings',
|
||||
icon: this.closeButton
|
||||
}]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
showDirectMessagesModal = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
|
|
@ -364,34 +337,9 @@ class ChannelDrawerList extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentChannel,
|
||||
currentTeam,
|
||||
myTeamMembers,
|
||||
onShowTeams,
|
||||
theme
|
||||
} = this.props;
|
||||
const {styles} = this.props;
|
||||
|
||||
const {dataSource, showAbove} = this.state;
|
||||
const teamMembers = Object.values(myTeamMembers);
|
||||
|
||||
if (!currentChannel) {
|
||||
return <Text>{'Loading'}</Text>;
|
||||
}
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const settings = (
|
||||
<TouchableHighlight
|
||||
style={styles.settingsContainer}
|
||||
onPress={() => preventDoubleTap(this.openSettingsModal)}
|
||||
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
|
||||
>
|
||||
<AwesomeIcon
|
||||
name='cog'
|
||||
style={styles.settings}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
|
||||
let above;
|
||||
if (showAbove) {
|
||||
|
|
@ -410,87 +358,18 @@ class ChannelDrawerList extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
const title = (
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={styles.header}
|
||||
onPress={onShowTeams}
|
||||
>
|
||||
{currentTeam.display_name}
|
||||
</Text>
|
||||
);
|
||||
|
||||
let badge;
|
||||
let switcher;
|
||||
if (teamMembers.length > 1) {
|
||||
let mentionCount = 0;
|
||||
let messageCount = 0;
|
||||
teamMembers.forEach((m) => {
|
||||
if (m.team_id !== currentTeam.id) {
|
||||
mentionCount = mentionCount + (m.mention_count || 0);
|
||||
messageCount = messageCount + (m.msg_count || 0);
|
||||
}
|
||||
});
|
||||
|
||||
let badgeCount = 0;
|
||||
if (mentionCount) {
|
||||
badgeCount = mentionCount;
|
||||
} else if (messageCount) {
|
||||
badgeCount = -1;
|
||||
}
|
||||
|
||||
if (badgeCount) {
|
||||
badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={badgeCount}
|
||||
minHeight={5}
|
||||
minWidth={5}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
switcher = (
|
||||
<TouchableHighlight
|
||||
onPress={() => preventDoubleTap(onShowTeams)}
|
||||
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
|
||||
>
|
||||
<View style={styles.switcherContainer}>
|
||||
<AwesomeIcon
|
||||
name='chevron-left'
|
||||
size={12}
|
||||
color={theme.sidebarHeaderBg}
|
||||
/>
|
||||
<View style={styles.switcherDivider}/>
|
||||
<Text style={styles.switcherTeam}>
|
||||
{currentTeam.display_name.substr(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.container}
|
||||
onLayout={this.onLayout}
|
||||
>
|
||||
<View style={styles.statusBar}>
|
||||
<View style={styles.headerContainer}>
|
||||
{switcher}
|
||||
{title}
|
||||
{settings}
|
||||
{badge}
|
||||
</View>
|
||||
</View>
|
||||
<FlatList
|
||||
ref='list'
|
||||
data={dataSource}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={(item) => item.id}
|
||||
onViewableItemsChanged={this.updateUnreadIndicators}
|
||||
keyboardDismissMode='on-drag'
|
||||
maxToRenderPerBatch={10}
|
||||
viewabilityConfig={{
|
||||
viewAreaCoveragePercentThreshold: 3,
|
||||
|
|
@ -503,144 +382,4 @@ class ChannelDrawerList extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: theme.sidebarBg,
|
||||
flex: 1
|
||||
},
|
||||
statusBar: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
...Platform.select({
|
||||
ios: {
|
||||
paddingTop: 20
|
||||
}
|
||||
})
|
||||
},
|
||||
headerContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
flexDirection: 'row',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: changeOpacity(theme.sidebarHeaderTextColor, 0.10),
|
||||
...Platform.select({
|
||||
android: {
|
||||
height: 46
|
||||
},
|
||||
ios: {
|
||||
height: 44
|
||||
}
|
||||
})
|
||||
},
|
||||
header: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
fontWeight: 'normal',
|
||||
paddingLeft: 16
|
||||
},
|
||||
settingsContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 50,
|
||||
...Platform.select({
|
||||
android: {
|
||||
height: 46
|
||||
},
|
||||
ios: {
|
||||
height: 44
|
||||
}
|
||||
})
|
||||
},
|
||||
settings: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 18,
|
||||
fontWeight: '300'
|
||||
},
|
||||
titleContainer: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: 48,
|
||||
marginLeft: 16
|
||||
},
|
||||
title: {
|
||||
flex: 1,
|
||||
color: theme.sidebarText,
|
||||
opacity: 1,
|
||||
fontSize: 15,
|
||||
fontWeight: '400',
|
||||
letterSpacing: 0.8,
|
||||
lineHeight: 18
|
||||
},
|
||||
switcherContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.sidebarHeaderTextColor,
|
||||
borderRadius: 2,
|
||||
flexDirection: 'row',
|
||||
height: 32,
|
||||
justifyContent: 'center',
|
||||
marginLeft: 16,
|
||||
paddingHorizontal: 6
|
||||
},
|
||||
switcherDivider: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
height: 15,
|
||||
marginHorizontal: 6,
|
||||
width: 1
|
||||
},
|
||||
switcherTeam: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
fontFamily: 'OpenSans',
|
||||
fontSize: 14
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: theme.mentionBj,
|
||||
borderColor: theme.sidebarHeaderBg,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
flexDirection: 'row',
|
||||
height: 20,
|
||||
padding: 3,
|
||||
position: 'absolute',
|
||||
left: 5,
|
||||
top: 0,
|
||||
width: 20
|
||||
},
|
||||
mention: {
|
||||
color: theme.mentionColor,
|
||||
fontSize: 10
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.1),
|
||||
height: 1
|
||||
},
|
||||
actionContainer: {
|
||||
alignItems: 'center',
|
||||
height: 48,
|
||||
justifyContent: 'center',
|
||||
width: 50
|
||||
},
|
||||
action: {
|
||||
color: theme.sidebarText,
|
||||
fontSize: 20,
|
||||
fontWeight: '500',
|
||||
lineHeight: 18
|
||||
},
|
||||
above: {
|
||||
backgroundColor: theme.mentionBj,
|
||||
top: 79
|
||||
},
|
||||
indicatorText: {
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.mentionColor,
|
||||
fontSize: 14,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default injectIntl(ChannelDrawerList);
|
||||
export default injectIntl(List);
|
||||
|
|
@ -4,21 +4,25 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {viewChannel, markChannelAsRead} from 'mattermost-redux/actions/channels';
|
||||
import {joinChannel, viewChannel, markChannelAsRead} from 'mattermost-redux/actions/channels';
|
||||
import {getTeams} from 'mattermost-redux/actions/teams';
|
||||
import {getChannelsWithUnreadSection, getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeam, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {handleSelectChannel, setChannelLoading} from 'app/actions/views/channel';
|
||||
import {makeDirectChannel} from 'app/actions/views/more_dms';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelDrawer from './channel_drawer.js';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {currentUserId} = state.entities.users;
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
currentTeam: getCurrentTeam(state),
|
||||
currentChannel: getCurrentChannel(state),
|
||||
currentUserId,
|
||||
channels: getChannelsWithUnreadSection(state),
|
||||
channelMembers: state.entities.channels.myMembers,
|
||||
myTeamMembers: getTeamMemberships(state),
|
||||
|
|
@ -31,7 +35,9 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
getTeams,
|
||||
handleSelectChannel,
|
||||
joinChannel,
|
||||
viewChannel,
|
||||
makeDirectChannel,
|
||||
markChannelAsRead,
|
||||
setChannelLoading
|
||||
}, dispatch)
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
// Used to leverage the platform specific components
|
||||
import ChannelDrawerSwiper from './channel_drawer_swiper';
|
||||
export default ChannelDrawerSwiper;
|
||||
import Swiper from './swiper';
|
||||
export default Swiper;
|
||||
|
|
@ -5,7 +5,7 @@ import React, {PureComponent} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {ViewPagerAndroid} from 'react-native';
|
||||
|
||||
export default class ChannelDrawerSwiper extends PureComponent {
|
||||
export default class SwiperAndroid extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
onPageSelected: PropTypes.func,
|
||||
|
|
@ -10,7 +10,7 @@ import {changeOpacity} from 'app/utils/theme';
|
|||
|
||||
const {height: deviceHeight, width: deviceWidth} = Dimensions.get('window');
|
||||
|
||||
export default class ChannelDrawerSwiper extends PureComponent {
|
||||
export default class SwiperIos extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
onPageSelected: PropTypes.func,
|
||||
|
|
@ -61,6 +61,7 @@ export default class ChannelDrawerSwiper extends PureComponent {
|
|||
automaticallyAdjustContentInsets={true}
|
||||
scrollEnabled={showTeams}
|
||||
showsPagination={showTeams}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
>
|
||||
{children}
|
||||
</Swiper>
|
||||
|
|
@ -14,7 +14,7 @@ import {handleTeamChange} from 'app/actions/views/select_team';
|
|||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {removeProtocol} from 'app/utils/url';
|
||||
|
||||
import ChannelDrawerTeams from './channel_drawer_teams';
|
||||
import TeamsList from './teams_list';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const user = getCurrentUser(state);
|
||||
|
|
@ -48,4 +48,4 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelDrawerTeams);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TeamsList);
|
||||
|
|
@ -21,7 +21,7 @@ import FormattedText from 'app/components/formatted_text';
|
|||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
class ChannelDrawerTeams extends PureComponent {
|
||||
class TeamsList extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
handleTeamChange: PropTypes.func.isRequired,
|
||||
|
|
@ -333,4 +333,4 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
});
|
||||
});
|
||||
|
||||
export default injectIntl(ChannelDrawerTeams);
|
||||
export default injectIntl(TeamsList);
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
|
|
@ -9,14 +10,17 @@ import {
|
|||
} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
function NewMessagesDivider(props) {
|
||||
const style = getStyleFromTheme(props.theme);
|
||||
|
||||
return (
|
||||
<View style={[style.container, props.style]}>
|
||||
<View style={style.line}/>
|
||||
<View style={style.textContainer}>
|
||||
<FormattedText
|
||||
id='post_list.newMsg'
|
||||
id='posts_view.newMsg'
|
||||
defaultMessage='New Messages'
|
||||
style={style.text}
|
||||
/>
|
||||
|
|
@ -27,28 +31,30 @@ function NewMessagesDivider(props) {
|
|||
}
|
||||
|
||||
NewMessagesDivider.propTypes = {
|
||||
style: ViewPropTypes.style
|
||||
style: ViewPropTypes.style,
|
||||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
height: 28
|
||||
},
|
||||
textContainer: {
|
||||
marginHorizontal: 15
|
||||
},
|
||||
line: {
|
||||
flex: 1,
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: '#f80'
|
||||
},
|
||||
text: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#ffaf53'
|
||||
}
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
height: 28
|
||||
},
|
||||
textContainer: {
|
||||
marginHorizontal: 15
|
||||
},
|
||||
line: {
|
||||
flex: 1,
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: theme.newMessageSeparator
|
||||
},
|
||||
text: {
|
||||
fontSize: 14,
|
||||
color: theme.newMessageSeparator
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default NewMessagesDivider;
|
||||
|
|
|
|||
|
|
@ -3,176 +3,225 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
TextInput,
|
||||
StyleSheet,
|
||||
View,
|
||||
TouchableOpacity
|
||||
TouchableWithoutFeedback
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
searchBar: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'transparent',
|
||||
margin: 8
|
||||
},
|
||||
searchBarInput: {
|
||||
flex: 1,
|
||||
fontWeight: 'normal',
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
});
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
export default class SearchBarAndroid extends PureComponent {
|
||||
static propTypes = {
|
||||
height: PropTypes.number.isRequired,
|
||||
fontSize: PropTypes.number.isRequired,
|
||||
text: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
showCancelButton: PropTypes.bool,
|
||||
textFieldBackgroundColor: PropTypes.string,
|
||||
placeholderTextColor: PropTypes.string,
|
||||
textColor: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onCancelButtonPress: PropTypes.func,
|
||||
onChangeText: PropTypes.func,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
onSearchButtonPress: PropTypes.func,
|
||||
onCancelButtonPress: PropTypes.func
|
||||
backgroundColor: PropTypes.string,
|
||||
placeholderTextColor: PropTypes.string,
|
||||
titleCancelColor: PropTypes.string,
|
||||
tintColorSearch: PropTypes.string,
|
||||
tintColorDelete: PropTypes.string,
|
||||
inputStyle: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.object
|
||||
]),
|
||||
placeholder: PropTypes.string,
|
||||
returnKeyType: PropTypes.string,
|
||||
keyboardType: PropTypes.string,
|
||||
autoCapitalize: PropTypes.string,
|
||||
inputHeight: PropTypes.number,
|
||||
inputBorderRadius: PropTypes.number,
|
||||
blurOnSubmit: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
blurOnSubmit: false,
|
||||
placeholder: 'Search',
|
||||
showCancelButton: true,
|
||||
placeholderTextColor: '#bdbdbd',
|
||||
textColor: '#212121',
|
||||
placeholderTextColor: changeOpacity('#000', 0.5),
|
||||
onSearchButtonPress: () => true,
|
||||
onCancelButtonPress: () => true,
|
||||
onChangeText: () => true,
|
||||
onFocus: () => true,
|
||||
onBlur: () => true
|
||||
onFocus: () => true
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOnFocus: false,
|
||||
value: this.props.text
|
||||
};
|
||||
this.onFocus = this.onFocus.bind(this);
|
||||
this.onBlur = this.onBlur.bind(this);
|
||||
this.onCancelButtonPress = this.onCancelButtonPress.bind(this);
|
||||
this.onSearchButtonPress = this.onSearchButtonPress.bind(this);
|
||||
this.onChangeText = this.onChangeText.bind(this);
|
||||
}
|
||||
|
||||
onSearchButtonPress() {
|
||||
const onSearchButtonPress = this.props.onSearchButtonPress;
|
||||
if (this.state.value) {
|
||||
onSearchButtonPress(this.state.value);
|
||||
}
|
||||
}
|
||||
|
||||
onCancelButtonPress() {
|
||||
const onCancelButtonPress = this.props.onCancelButtonPress;
|
||||
this.setState({
|
||||
isOnFocus: false,
|
||||
isFocused: false,
|
||||
value: ''
|
||||
};
|
||||
}
|
||||
|
||||
cancel = () => {
|
||||
this.onCancelButtonPress();
|
||||
};
|
||||
|
||||
onSearchButtonPress = () => {
|
||||
const {onSearchButtonPress} = this.props;
|
||||
const {value} = this.state;
|
||||
|
||||
if (value && onSearchButtonPress) {
|
||||
onSearchButtonPress(value);
|
||||
}
|
||||
};
|
||||
|
||||
onCancelButtonPress = () => {
|
||||
const {onCancelButtonPress} = this.props;
|
||||
|
||||
Keyboard.dismiss();
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.setState({
|
||||
isFocused: false,
|
||||
value: ''
|
||||
}, () => {
|
||||
if (onCancelButtonPress) {
|
||||
onCancelButtonPress();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onCancelButtonPress();
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
|
||||
onChangeText(value) {
|
||||
const onChangeText = this.props.onChangeText;
|
||||
onChangeText = (value) => {
|
||||
const {onChangeText} = this.props;
|
||||
this.setState({value});
|
||||
onChangeText(value);
|
||||
}
|
||||
if (onChangeText) {
|
||||
onChangeText(value);
|
||||
}
|
||||
};
|
||||
|
||||
onFocus() {
|
||||
const onFocus = this.props.onFocus;
|
||||
this.setState({isOnFocus: true});
|
||||
onFocus();
|
||||
}
|
||||
onFocus = () => {
|
||||
const {onFocus} = this.props;
|
||||
|
||||
onBlur() {
|
||||
const onBlur = this.props.onBlur;
|
||||
this.setState({isOnFocus: false});
|
||||
onBlur();
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
this.setState({isFocused: true});
|
||||
|
||||
blur = () => {
|
||||
this.onBlur();
|
||||
if (onFocus) {
|
||||
onFocus();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
height,
|
||||
autoCapitalize,
|
||||
backgroundColor,
|
||||
blurOnSubmit,
|
||||
inputHeight,
|
||||
inputStyle,
|
||||
keyboardType,
|
||||
placeholder,
|
||||
fontSize,
|
||||
placeholderTextColor,
|
||||
textColor,
|
||||
textFieldBackgroundColor
|
||||
returnKeyType,
|
||||
titleCancelColor,
|
||||
tintColorDelete,
|
||||
tintColorSearch
|
||||
} = this.props;
|
||||
const {isFocused, value} = this.state;
|
||||
|
||||
const searchBarStyle = {
|
||||
height: height + 10,
|
||||
paddingLeft: height * 0.25,
|
||||
backgroundColor: textFieldBackgroundColor
|
||||
const inputNoBackground = {
|
||||
...inputStyle
|
||||
};
|
||||
Reflect.deleteProperty(inputNoBackground, 'backgroundColor');
|
||||
|
||||
const inputStyle = {
|
||||
paddingLeft: height * 0.5,
|
||||
fontSize,
|
||||
color: textColor
|
||||
};
|
||||
let inputColor = styles.searchBarInput.backgroundColor;
|
||||
if (inputStyle) {
|
||||
inputColor = inputStyle.backgroundColor;
|
||||
} else {
|
||||
inputNoBackground.backgroundColor = '#fff';
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[styles.searchBar, searchBarStyle]}
|
||||
style={[
|
||||
styles.container,
|
||||
{height: inputHeight},
|
||||
backgroundColor && {backgroundColor}
|
||||
]}
|
||||
>
|
||||
{this.state.isOnFocus && this.props.showCancelButton ?
|
||||
<TouchableOpacity onPress={this.onCancelButtonPress}>
|
||||
<View
|
||||
style={[
|
||||
styles.searchBar,
|
||||
{
|
||||
backgroundColor: inputColor,
|
||||
height: inputHeight,
|
||||
paddingLeft: inputHeight * 0.25
|
||||
}
|
||||
]}
|
||||
>
|
||||
{isFocused ?
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.onCancelButtonPress}
|
||||
style={{paddingRight: 5}}
|
||||
>
|
||||
<Icon
|
||||
name='arrow-back'
|
||||
size={24}
|
||||
color={titleCancelColor || placeholderTextColor}
|
||||
/>
|
||||
</TouchableWithoutFeedback> :
|
||||
<Icon
|
||||
name='arrow-back'
|
||||
size={height}
|
||||
color={placeholderTextColor}
|
||||
name={'search'}
|
||||
size={16}
|
||||
color={tintColorSearch || placeholderTextColor}
|
||||
/>
|
||||
</TouchableOpacity> :
|
||||
<Icon
|
||||
name={'search'}
|
||||
size={height}
|
||||
color={placeholderTextColor}
|
||||
}
|
||||
<TextInput
|
||||
blurOnSubmit={blurOnSubmit}
|
||||
value={value}
|
||||
autoCapitalize={autoCapitalize}
|
||||
autoCorrect={false}
|
||||
returnKeyType={returnKeyType || 'search'}
|
||||
keyboardType={keyboardType || 'default'}
|
||||
onFocus={this.onFocus}
|
||||
onChangeText={this.onChangeText}
|
||||
onSubmitEditing={this.onSearchButtonPress}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor={placeholderTextColor}
|
||||
underlineColorAndroid='transparent'
|
||||
style={[
|
||||
styles.searchBarInput,
|
||||
inputNoBackground,
|
||||
{height: this.props.inputHeight}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
<TextInput
|
||||
value={this.state.value}
|
||||
returnKeyType='search'
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onChange={this.props.onChange}
|
||||
onChangeText={this.onChangeText}
|
||||
onSubmitEditing={this.onSearchButtonPress}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor={placeholderTextColor}
|
||||
underlineColorAndroid='transparent'
|
||||
style={[styles.searchBarInput, inputStyle]}
|
||||
/>
|
||||
{this.state.isOnFocus && this.state.value ?
|
||||
<TouchableOpacity onPress={() => this.setState({value: ''})}>
|
||||
<Icon
|
||||
style={{paddingRight: (height * 0.2)}}
|
||||
name='close'
|
||||
size={height}
|
||||
color={placeholderTextColor}
|
||||
/>
|
||||
</TouchableOpacity> : null
|
||||
}
|
||||
{isFocused && value ?
|
||||
<TouchableWithoutFeedback onPress={() => this.onChangeText('')}>
|
||||
<Icon
|
||||
style={[{paddingRight: (inputHeight * 0.2)}]}
|
||||
name='close'
|
||||
size={16}
|
||||
color={tintColorDelete || placeholderTextColor}
|
||||
/>
|
||||
</TouchableWithoutFeedback> : null
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: 'grey',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
padding: 5
|
||||
},
|
||||
searchBar: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: 'red',
|
||||
alignItems: 'center'
|
||||
},
|
||||
searchBarInput: {
|
||||
flex: 1,
|
||||
fontWeight: 'normal',
|
||||
textAlignVertical: 'center',
|
||||
padding: 0,
|
||||
includeFontPadding: false
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,96 +1,145 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import SearchBar from 'react-native-search-bar';
|
||||
|
||||
export default class SearchBarIos extends PureComponent {
|
||||
import React, {Component} from 'react';
|
||||
import {InteractionManager, Text, Keyboard} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Search from 'react-native-search-box';
|
||||
|
||||
export default class SearchBarIos extends Component {
|
||||
static propTypes = {
|
||||
barStyle: PropTypes.oneOf(['default', 'black']),
|
||||
searchBarStyle: PropTypes.oneOf(['default', 'prominent', 'minimal']),
|
||||
text: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
showCancelButton: PropTypes.bool,
|
||||
hideBackground: PropTypes.bool,
|
||||
textFieldBackgroundColor: PropTypes.string,
|
||||
placeholderTextColor: PropTypes.string,
|
||||
textColor: PropTypes.string,
|
||||
barTintColor: PropTypes.string, //color of the background container
|
||||
tintColor: PropTypes.string, // color of the carret and the cancel button
|
||||
onChange: PropTypes.func,
|
||||
onCancelButtonPress: PropTypes.func,
|
||||
onChangeText: PropTypes.func,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
onSearchButtonPress: PropTypes.func,
|
||||
onCancelButtonPress: PropTypes.func
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
barStyle: 'default',
|
||||
searchBarStyle: 'minimal',
|
||||
placeholder: 'Search',
|
||||
showCancelButton: true,
|
||||
hideBackground: true,
|
||||
onFocus: () => true,
|
||||
onBlur: () => true
|
||||
backgroundColor: PropTypes.string,
|
||||
placeholderTextColor: PropTypes.string,
|
||||
titleCancelColor: PropTypes.string,
|
||||
tintColorSearch: PropTypes.string,
|
||||
tintColorDelete: PropTypes.string,
|
||||
cancelButtonStyle: PropTypes.PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.object
|
||||
]),
|
||||
inputStyle: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.object
|
||||
]),
|
||||
placeholder: PropTypes.string,
|
||||
cancelTitle: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.object
|
||||
]),
|
||||
returnKeyType: PropTypes.string,
|
||||
keyboardType: PropTypes.string,
|
||||
autoCapitalize: PropTypes.string,
|
||||
inputHeight: PropTypes.number,
|
||||
inputBorderRadius: PropTypes.number,
|
||||
blurOnSubmit: PropTypes.bool
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
displayCancelButton: false
|
||||
placeholderWidth: 0
|
||||
};
|
||||
}
|
||||
|
||||
onFocus = (event) => {
|
||||
const onFocus = this.props.onFocus;
|
||||
if (this.props.showCancelButton) {
|
||||
this.setState({displayCancelButton: true});
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextState.placeholderWidth !== this.state.placeholderWidth;
|
||||
}
|
||||
|
||||
onFocus(event);
|
||||
afterDelete = () => {
|
||||
return new Promise((resolve) => {
|
||||
this.refs.search.focus();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
onBlur = (event) => {
|
||||
const onBlur = this.props.onBlur;
|
||||
if (this.props.showCancelButton) {
|
||||
this.setState({displayCancelButton: false});
|
||||
}
|
||||
|
||||
onBlur(event);
|
||||
cancel = () => {
|
||||
this.refs.search.onCancel();
|
||||
};
|
||||
|
||||
blur = () => {
|
||||
this.searchBar.blur();
|
||||
onCancel = () => {
|
||||
return new Promise((resolve) => {
|
||||
Keyboard.dismiss();
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
if (this.props.onCancelButtonPress) {
|
||||
this.props.onCancelButtonPress();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
searchBarRef = (ref) => {
|
||||
this.searchBar = ref;
|
||||
onChangeText = (text) => {
|
||||
return new Promise((resolve) => {
|
||||
if (this.props.onChangeText) {
|
||||
this.props.onChangeText(text);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
onDelete = () => {
|
||||
return new Promise((resolve) => {
|
||||
if (this.props.onChangeText) {
|
||||
this.props.onChangeText('');
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
onFocus = () => {
|
||||
return new Promise((resolve) => {
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
onSearch = (text) => {
|
||||
return new Promise((resolve) => {
|
||||
if (this.props.onSearchButtonPress) {
|
||||
this.props.onSearchButtonPress(text);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.placeholderWidth) {
|
||||
return (
|
||||
<Search
|
||||
{...this.props}
|
||||
ref='search'
|
||||
placeholderCollapsedMargin={this.state.placeholderWidth}
|
||||
placeholderExpandedMargin={20}
|
||||
searchIconCollapsedMargin={this.state.placeholderWidth + 10}
|
||||
searchIconExpandedMargin={10}
|
||||
shadowVisible={false}
|
||||
onCancel={this.onCancel}
|
||||
onChangeText={this.onChangeText}
|
||||
onFocus={this.onFocus}
|
||||
onSearch={this.onSearch}
|
||||
afterDelete={this.afterDelete}
|
||||
onDelete={this.onDelete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SearchBar
|
||||
ref={this.searchBarRef}
|
||||
barStyle={this.props.barStyle}
|
||||
searchBarStyle={this.props.searchBarStyle}
|
||||
text={this.props.text}
|
||||
placeholder={this.props.placeholder}
|
||||
showsCancelButton={this.state.displayCancelButton}
|
||||
hideBackground={false}
|
||||
textFieldBackgroundColor={this.props.textFieldBackgroundColor}
|
||||
placeholderTextColor={this.props.placeholderTextColor}
|
||||
textColor={this.props.textColor}
|
||||
barTintColor={this.props.barTintColor}
|
||||
tintColor={this.props.tintColor}
|
||||
onChange={this.props.onChange}
|
||||
onChangeText={this.props.onChangeText}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onSearchButtonPress={this.props.onSearchButtonPress}
|
||||
onCancelButtonPress={this.props.onCancelButtonPress}
|
||||
/>
|
||||
<Text
|
||||
style={{position: 'absolute', top: -3000, left: -1000}}
|
||||
onLayout={(event) => {
|
||||
const placeholderWidth = (event.nativeEvent.layout.width / 2);
|
||||
this.setState({placeholderWidth});
|
||||
}}
|
||||
>
|
||||
{this.props.placeholder}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,19 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import 'intl';
|
||||
import {addLocaleData} from 'react-intl';
|
||||
import deLocaleData from 'react-intl/locale-data/de';
|
||||
import enLocaleData from 'react-intl/locale-data/en';
|
||||
import esLocaleData from 'react-intl/locale-data/es';
|
||||
import frLocaleData from 'react-intl/locale-data/fr';
|
||||
import jaLocaleData from 'react-intl/locale-data/ja';
|
||||
import koLocaleData from 'react-intl/locale-data/ko';
|
||||
import nlLocaleData from 'react-intl/locale-data/nl';
|
||||
import plLocaleData from 'react-intl/locale-data/pl';
|
||||
import ptLocaleData from 'react-intl/locale-data/pt';
|
||||
import trLocaleData from 'react-intl/locale-data/tr';
|
||||
import ruLocaleData from 'react-intl/locale-data/ru';
|
||||
import zhLocaleData from 'react-intl/locale-data/zh';
|
||||
|
||||
import de from 'assets/i18n/de.json';
|
||||
import en from 'assets/i18n/en.json';
|
||||
|
|
@ -12,6 +25,7 @@ import ko from 'assets/i18n/ko.json';
|
|||
import nl from 'assets/i18n/nl.json';
|
||||
import pl from 'assets/i18n/pl.json';
|
||||
import ptBR from 'assets/i18n/pt-BR.json';
|
||||
import tr from 'assets/i18n/tr.json';
|
||||
import ru from 'assets/i18n/ru.json';
|
||||
import zhCN from 'assets/i18n/zh-CN.json';
|
||||
import zhTW from 'assets/i18n/zh-TW.json';
|
||||
|
|
@ -27,12 +41,28 @@ const TRANSLATIONS = {
|
|||
ko,
|
||||
nl,
|
||||
pl,
|
||||
ptBR,
|
||||
'pt-BR': ptBR,
|
||||
tr,
|
||||
ru,
|
||||
zhCN,
|
||||
zhTW
|
||||
'zh-CN': zhCN,
|
||||
'zh-TW': zhTW
|
||||
};
|
||||
|
||||
addLocaleData([
|
||||
deLocaleData,
|
||||
enLocaleData,
|
||||
esLocaleData,
|
||||
frLocaleData,
|
||||
jaLocaleData,
|
||||
koLocaleData,
|
||||
nlLocaleData,
|
||||
plLocaleData,
|
||||
ptLocaleData,
|
||||
trLocaleData,
|
||||
ruLocaleData,
|
||||
zhLocaleData
|
||||
]);
|
||||
|
||||
export function getTranslations(locale) {
|
||||
return TRANSLATIONS[locale] || TRANSLATIONS[DEFAULT_LOCALE];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {General} from 'mattermost-redux/constants';
|
|||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {goToNotification, loadConfigAndLicense, queueNotification} from 'app/actions/views/root';
|
||||
import {setChannelDisplayName} from 'app/actions/views/channel';
|
||||
import {NavigationTypes, ViewTypes} from 'app/constants';
|
||||
import initialState from 'app/initial_state';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
|
|
@ -40,6 +41,7 @@ export default class Mattermost {
|
|||
AppState.addEventListener('change', this.handleAppStateChange);
|
||||
EventEmitter.on(General.CONFIG_CHANGED, this.handleConfigChanged);
|
||||
EventEmitter.on(NavigationTypes.NAVIGATION_RESET, this.handleReset);
|
||||
EventEmitter.on(General.DEFAULT_CHANNEL, this.handleResetDisplayName);
|
||||
|
||||
this.handleAppStateChange(AppState.currentState);
|
||||
Client4.setUserAgent(DeviceInfo.getUserAgent());
|
||||
|
|
@ -78,6 +80,10 @@ export default class Mattermost {
|
|||
this.startApp('fade');
|
||||
};
|
||||
|
||||
handleResetDisplayName = (displayName) => {
|
||||
store.dispatch(setChannelDisplayName(displayName));
|
||||
};
|
||||
|
||||
handleVersionUpgrade = async () => {
|
||||
const {dispatch, getState} = store;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import PropTypes from 'prop-types';
|
|||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Alert,
|
||||
Keyboard,
|
||||
Platform,
|
||||
InteractionManager,
|
||||
StyleSheet,
|
||||
|
|
@ -74,10 +73,6 @@ class ChannelAddMembers extends PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.props.actions.getProfilesNotInChannel(this.props.currentTeam.id, this.props.currentChannel.id, 0);
|
||||
this.props.actions.getTeamStats(this.props.currentTeam.id);
|
||||
|
|
@ -86,12 +81,6 @@ class ChannelAddMembers extends PureComponent {
|
|||
this.emitCanAddMembers(false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {loadMoreRequestStatus} = this.props;
|
||||
if (loadMoreRequestStatus === RequestStatus.STARTED &&
|
||||
|
|
@ -170,10 +159,6 @@ class ChannelAddMembers extends PureComponent {
|
|||
actions.handleAddChannelMembers(currentChannel.id, membersToAdd);
|
||||
};
|
||||
|
||||
handleAndroidKeyboard = () => {
|
||||
this.onSearchButtonPress();
|
||||
};
|
||||
|
||||
handleRowSelect = (id) => {
|
||||
const selectedMembers = Object.assign({}, this.state.selectedMembers, {[id]: !this.state.selectedMembers[id]});
|
||||
|
||||
|
|
@ -215,16 +200,8 @@ class ChannelAddMembers extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
onSearchButtonPress = () => {
|
||||
this.searchBar.blur();
|
||||
};
|
||||
|
||||
searchBarRef = (ref) => {
|
||||
this.searchBar = ref;
|
||||
};
|
||||
|
||||
searchProfiles = (event) => {
|
||||
const term = event.nativeEvent.text.toLowerCase();
|
||||
searchProfiles = (text) => {
|
||||
const term = text.toLowerCase();
|
||||
const {actions, currentChannel, currentTeam} = this.props;
|
||||
|
||||
if (term) {
|
||||
|
|
@ -264,15 +241,22 @@ class ChannelAddMembers extends PureComponent {
|
|||
style={{marginVertical: 5}}
|
||||
>
|
||||
<SearchBar
|
||||
ref={this.searchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMesage: 'Search'})}
|
||||
height={27}
|
||||
fontSize={14}
|
||||
textColor={changeOpacity('#000', 0.5)}
|
||||
hideBackground={true}
|
||||
textFieldBackgroundColor={'#fff'}
|
||||
onChange={this.searchProfiles}
|
||||
onSearchButtonPress={this.onSearchButtonPress}
|
||||
ref='search_bar'
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={30}
|
||||
inputStyle={{
|
||||
backgroundColor: '#fff',
|
||||
color: changeOpacity('#000', 0.5),
|
||||
fontSize: 13
|
||||
}}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
tintColorSearch={changeOpacity('#000', 0.5)}
|
||||
tintColorDelete={changeOpacity('#000', 0.5)}
|
||||
titleCancelColor={Platform.OS === 'android' ? changeOpacity('#000', 0.5) : theme.sidebarHeaderBg}
|
||||
onChangeText={this.searchProfiles}
|
||||
onSearchButtonPress={this.searchProfiles}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {preventDoubleTap} from 'app/utils/tap';
|
|||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import ChannelInfoHeader from './channel_info_header';
|
||||
import ChannelInfoRow from './channel_info_row';
|
||||
|
|
@ -63,6 +64,11 @@ class ChannelInfo extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
close = () => {
|
||||
EventEmitter.emit(General.DEFAULT_CHANNEL, '');
|
||||
this.props.navigator.pop({animated: true});
|
||||
};
|
||||
|
||||
goToChannelAddMembers = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
navigator.push({
|
||||
|
|
@ -115,7 +121,7 @@ class ChannelInfo extends PureComponent {
|
|||
};
|
||||
onPressAction = () => {
|
||||
this.props.actions.leaveChannel(channel, true).then(() => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
this.close();
|
||||
});
|
||||
};
|
||||
} else if (eventType === 'delete') {
|
||||
|
|
@ -126,7 +132,7 @@ class ChannelInfo extends PureComponent {
|
|||
};
|
||||
onPressAction = () => {
|
||||
this.props.actions.deleteChannel(channel.id).then(() => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
this.close();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
@ -157,12 +163,12 @@ class ChannelInfo extends PureComponent {
|
|||
switch (channel.type) {
|
||||
case General.DM_CHANNEL:
|
||||
closeDMChannel(channel).then(() => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
this.close();
|
||||
});
|
||||
break;
|
||||
case General.GM_CHANNEL:
|
||||
closeGMChannel(channel).then(() => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
this.close();
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import React, {PureComponent} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Alert,
|
||||
Keyboard,
|
||||
Platform,
|
||||
InteractionManager,
|
||||
StyleSheet,
|
||||
|
|
@ -76,10 +75,6 @@ class ChannelMembers extends PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.props.actions.getProfilesInChannel(this.props.currentChannel.id, 0);
|
||||
});
|
||||
|
|
@ -87,12 +82,6 @@ class ChannelMembers extends PureComponent {
|
|||
this.emitCanRemoveMembers(false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {requestStatus} = this.props;
|
||||
if (requestStatus === RequestStatus.STARTED &&
|
||||
|
|
@ -159,10 +148,6 @@ class ChannelMembers extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
handleAndroidKeyboard = () => {
|
||||
this.onSearchButtonPress();
|
||||
};
|
||||
|
||||
handleRemoveMembersPress = () => {
|
||||
const {selectedMembers} = this.state;
|
||||
const membersToRemove = Object.keys(selectedMembers).filter((m) => selectedMembers[m]);
|
||||
|
|
@ -240,10 +225,6 @@ class ChannelMembers extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
onSearchButtonPress = () => {
|
||||
this.searchBar.blur();
|
||||
};
|
||||
|
||||
removeMembers = (membersToRemove) => {
|
||||
const {actions, currentChannel} = this.props;
|
||||
actions.handleRemoveChannelMembers(currentChannel.id, membersToRemove);
|
||||
|
|
@ -275,12 +256,8 @@ class ChannelMembers extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
searchBarRef = (ref) => {
|
||||
this.searchBar = ref;
|
||||
};
|
||||
|
||||
searchProfiles = (event) => {
|
||||
const term = event.nativeEvent.text.toLowerCase();
|
||||
searchProfiles = (text) => {
|
||||
const term = text.toLowerCase();
|
||||
|
||||
if (term) {
|
||||
this.setState({searching: true, term});
|
||||
|
|
@ -319,15 +296,22 @@ class ChannelMembers extends PureComponent {
|
|||
style={{marginVertical: 5}}
|
||||
>
|
||||
<SearchBar
|
||||
ref={this.searchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMesage: 'Search'})}
|
||||
height={27}
|
||||
fontSize={14}
|
||||
textColor={changeOpacity('#000', 0.5)}
|
||||
hideBackground={true}
|
||||
textFieldBackgroundColor={'#fff'}
|
||||
onChange={this.searchProfiles}
|
||||
onSearchButtonPress={this.onSearchButtonPress}
|
||||
ref='search_bar'
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={30}
|
||||
inputStyle={{
|
||||
backgroundColor: '#fff',
|
||||
color: changeOpacity('#000', 0.5),
|
||||
fontSize: 13
|
||||
}}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
tintColorSearch={changeOpacity('#000', 0.5)}
|
||||
tintColorDelete={changeOpacity('#000', 0.5)}
|
||||
titleCancelColor={Platform.OS === 'android' ? changeOpacity('#000', 0.5) : theme.sidebarHeaderBg}
|
||||
onChangeText={this.searchProfiles}
|
||||
onSearchButtonPress={this.searchProfiles}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import React, {PureComponent} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Keyboard,
|
||||
Platform,
|
||||
InteractionManager,
|
||||
StyleSheet,
|
||||
|
|
@ -79,10 +78,6 @@ class MoreChannels extends PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
|
||||
// set the timeout to 400 cause is the time that the modal takes to open
|
||||
// Somehow interactionManager doesn't care
|
||||
setTimeout(() => {
|
||||
|
|
@ -104,12 +99,6 @@ class MoreChannels extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.props.navigator.dismissModal({animationType: 'slide-down'});
|
||||
};
|
||||
|
|
@ -132,20 +121,8 @@ class MoreChannels extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
handleAndroidKeyboard = () => {
|
||||
this.onSearchButtonPress();
|
||||
};
|
||||
|
||||
searchBarRef = (ref) => {
|
||||
this.searchBar = ref;
|
||||
};
|
||||
|
||||
onSearchButtonPress = () => {
|
||||
this.searchBar.blur();
|
||||
};
|
||||
|
||||
searchProfiles = (event) => {
|
||||
const term = event.nativeEvent.text.toLowerCase();
|
||||
searchProfiles = (text) => {
|
||||
const term = text.toLowerCase();
|
||||
|
||||
if (term) {
|
||||
const channels = this.filterChannels(this.state.channels, term);
|
||||
|
|
@ -226,7 +203,6 @@ class MoreChannels extends PureComponent {
|
|||
onSelectChannel = async (id) => {
|
||||
this.emitCanCreateChannel(false);
|
||||
this.setState({adding: true});
|
||||
this.searchBar.blur();
|
||||
await this.props.actions.joinChannel(
|
||||
this.props.currentUserId,
|
||||
this.props.currentTeamId,
|
||||
|
|
@ -278,15 +254,22 @@ class MoreChannels extends PureComponent {
|
|||
style={{marginVertical: 5}}
|
||||
>
|
||||
<SearchBar
|
||||
ref={this.searchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMesage: 'Search'})}
|
||||
height={27}
|
||||
fontSize={14}
|
||||
textColor={changeOpacity('#000', 0.5)}
|
||||
hideBackground={true}
|
||||
textFieldBackgroundColor={'#fff'}
|
||||
onChange={this.searchProfiles}
|
||||
onSearchButtonPress={this.onSearchButtonPress}
|
||||
ref='search_bar'
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={30}
|
||||
inputStyle={{
|
||||
backgroundColor: '#fff',
|
||||
color: changeOpacity('#000', 0.5),
|
||||
fontSize: 13
|
||||
}}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
tintColorSearch={changeOpacity('#000', 0.5)}
|
||||
tintColorDelete={changeOpacity('#000', 0.5)}
|
||||
titleCancelColor={Platform.OS === 'android' ? changeOpacity('#000', 0.5) : theme.sidebarHeaderBg}
|
||||
onChangeText={this.searchProfiles}
|
||||
onSearchButtonPress={this.searchProfiles}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import React, {PureComponent} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Keyboard,
|
||||
Platform,
|
||||
InteractionManager,
|
||||
StyleSheet,
|
||||
|
|
@ -70,10 +69,6 @@ class MoreDirectMessages extends PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
|
||||
// set the timeout to 400 cause is the time that the modal takes to open
|
||||
// Somehow interactionManager doesn't care
|
||||
setTimeout(() => {
|
||||
|
|
@ -81,26 +76,12 @@ class MoreDirectMessages extends PureComponent {
|
|||
}, 400);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.props.navigator.dismissModal({
|
||||
animationType: 'slide-down'
|
||||
});
|
||||
};
|
||||
|
||||
handleAndroidKeyboard = () => {
|
||||
this.onSearchButtonPress();
|
||||
};
|
||||
|
||||
searchBarRef = (ref) => {
|
||||
this.searchBar = ref;
|
||||
};
|
||||
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'close-dms') {
|
||||
|
|
@ -109,15 +90,11 @@ class MoreDirectMessages extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
onSearchButtonPress = () => {
|
||||
this.searchBar.blur();
|
||||
};
|
||||
|
||||
searchProfiles = (event) => {
|
||||
const term = event.nativeEvent.text;
|
||||
searchProfiles = (text) => {
|
||||
const term = text.toLowerCase();
|
||||
|
||||
if (term) {
|
||||
this.setState({searching: true, term: term.toLowerCase()});
|
||||
this.setState({searching: true, term});
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
|
||||
this.searchTimeoutId = setTimeout(() => {
|
||||
|
|
@ -156,7 +133,6 @@ class MoreDirectMessages extends PureComponent {
|
|||
|
||||
onSelectMember = async (id) => {
|
||||
this.setState({adding: true});
|
||||
this.searchBar.blur();
|
||||
await this.props.actions.makeDirectChannel(id);
|
||||
|
||||
EventEmitter.emit('close_channel_drawer');
|
||||
|
|
@ -190,15 +166,22 @@ class MoreDirectMessages extends PureComponent {
|
|||
style={{marginVertical: 5}}
|
||||
>
|
||||
<SearchBar
|
||||
ref={this.searchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMesage: 'Search'})}
|
||||
height={27}
|
||||
fontSize={14}
|
||||
textColor={changeOpacity('#000', 0.5)}
|
||||
hideBackground={true}
|
||||
textFieldBackgroundColor={'#fff'}
|
||||
onChange={this.searchProfiles}
|
||||
onSearchButtonPress={this.onSearchButtonPress}
|
||||
ref='search_bar'
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={30}
|
||||
inputStyle={{
|
||||
backgroundColor: '#fff',
|
||||
color: changeOpacity('#000', 0.5),
|
||||
fontSize: 13
|
||||
}}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
tintColorSearch={changeOpacity('#000', 0.5)}
|
||||
tintColorDelete={changeOpacity('#000', 0.5)}
|
||||
titleCancelColor={Platform.OS === 'android' ? changeOpacity('#000', 0.5) : theme.sidebarHeaderBg}
|
||||
onChangeText={this.searchProfiles}
|
||||
onSearchButtonPress={this.searchProfiles}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -1684,6 +1684,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_drawer.search": "Jump to a conversation",
|
||||
"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",
|
||||
|
|
@ -1700,6 +1701,7 @@
|
|||
"mobile.channel_list.closeGM": "Close Group Message",
|
||||
"mobile.channel_list.dm": "Direct Message",
|
||||
"mobile.channel_list.gm": "Group Message",
|
||||
"mobile.channel_list.not_member": "NOT A MEMBER",
|
||||
"mobile.channel_list.open": "Open {term}",
|
||||
"mobile.channel_list.openDM": "Open Direct Message",
|
||||
"mobile.channel_list.openGM": "Open Group Message",
|
||||
|
|
@ -1716,6 +1718,7 @@
|
|||
"mobile.create_channel.private": "New Private Channel",
|
||||
"mobile.create_channel.public": "New Public Channel",
|
||||
"mobile.custom_list.no_results": "No Results",
|
||||
"mobile.drawer.teamsTitle": "Teams",
|
||||
"mobile.edit_post.title": "Editing Message",
|
||||
"mobile.file_upload.camera": "Take Photo or Video",
|
||||
"mobile.file_upload.library": "Photo Library",
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
"react-native-notifications": "enahum/react-native-notifications.git",
|
||||
"react-native-orientation": "enahum/react-native-orientation.git",
|
||||
"react-native-search-bar": "enahum/react-native-search-bar.git",
|
||||
"react-native-search-box": "0.0.9",
|
||||
"react-native-svg": "5.1.8",
|
||||
"react-native-swiper": "1.5.4",
|
||||
"react-native-tooltip": "enahum/react-native-tooltip",
|
||||
|
|
|
|||
10
yarn.lock
10
yarn.lock
|
|
@ -3645,7 +3645,7 @@ makeerror@1.0.x:
|
|||
|
||||
mattermost-redux@mattermost/mattermost-redux#master:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/9a2e8099daea436dab76aa612be60492311387b3"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/488faa22c03e1c187c714a5db7f0a42b69d50ae8"
|
||||
dependencies:
|
||||
deep-equal "1.0.1"
|
||||
harmony-reflect "1.5.1"
|
||||
|
|
@ -4556,6 +4556,10 @@ react-native-search-bar@enahum/react-native-search-bar.git:
|
|||
version "2.16.0"
|
||||
resolved "https://codeload.github.com/enahum/react-native-search-bar/tar.gz/88e6f5ba68012440ec21bde8388ca9e3124921c0"
|
||||
|
||||
react-native-search-box@0.0.9:
|
||||
version "0.0.9"
|
||||
resolved "https://registry.yarnpkg.com/react-native-search-box/-/react-native-search-box-0.0.9.tgz#c1677e127d6d2b346850b031b2cb216447fa66f0"
|
||||
|
||||
react-native-svg-mock@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-svg-mock/-/react-native-svg-mock-1.0.2.tgz#2dc35fb97ca1e0ea393ece6d23782ba4f58e178f"
|
||||
|
|
@ -4575,10 +4579,6 @@ react-native-tooltip@enahum/react-native-tooltip:
|
|||
version "5.0.0"
|
||||
resolved "https://codeload.github.com/enahum/react-native-tooltip/tar.gz/97d58d19636df8d8df66d6b5737154c9fab727c8"
|
||||
|
||||
react-native-tooltip@enahum/react-native-tooltip.git:
|
||||
version "5.0.0"
|
||||
resolved "https://codeload.github.com/enahum/react-native-tooltip/tar.gz/d4b491c7ef1f93cc6c62e5012e36be1087b45b34"
|
||||
|
||||
react-native-vector-icons@4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-vector-icons/-/react-native-vector-icons-4.1.1.tgz#9ac75bde77d9243346668c51dca7756775428087"
|
||||
|
|
|
|||
Loading…
Reference in a new issue