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:
enahum 2017-06-09 08:23:56 -04:00 committed by Harrison Healey
parent 8b8cf6234a
commit 2980de0b19
28 changed files with 1317 additions and 705 deletions

View file

@ -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`

View file

@ -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}

View file

@ -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,

View file

@ -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);

View file

@ -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);

View 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);

View file

@ -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);

View file

@ -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);

View file

@ -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)

View file

@ -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;

View file

@ -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,

View file

@ -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>

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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
}
});

View file

@ -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>
);
}
}

View file

@ -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];
}

View file

@ -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;

View file

@ -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>

View file

@ -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;
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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",

View file

@ -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",

View file

@ -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"