[MM-18362] Sidebar improvements (#3223)
* sidebar lists displays past bottom safe area insets * Sidebar animation speed * Sidebar improvements * Add status icons and avatars to mattermost font * Bot icon the same size as other channel icons * improvements to the channel badge * Badge color and border * More precision when showing more unread above * Improve GM icon * Fix badge on team sidebar * Align channel sidebar badges * alignments * Fix tests * Remove unnecessary isLandscape in main sidebar connector
BIN
android/app/src/main/assets/fonts/Mattermost-Regular.otf
Normal file
|
|
@ -5,63 +5,75 @@ exports[`Badge should match snapshot 1`] = `
|
|||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#444",
|
||||
"borderRadius": 20,
|
||||
"height": 20,
|
||||
"padding": 12,
|
||||
"paddingBottom": 3,
|
||||
"paddingTop": 3,
|
||||
"position": "absolute",
|
||||
"right": 30,
|
||||
"top": 2,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
},
|
||||
Object {
|
||||
"opacity": 0,
|
||||
"borderColor": "#000000",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"alignSelf": "center",
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#444",
|
||||
"height": 20,
|
||||
"padding": 12,
|
||||
"paddingBottom": 3,
|
||||
"paddingTop": 3,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
},
|
||||
Object {
|
||||
"opacity": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"alignSelf": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"textAlignVertical": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
onLayout={[Function]}
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "white",
|
||||
"fontSize": 14,
|
||||
},
|
||||
Object {
|
||||
"color": "#145dbf",
|
||||
"fontSize": 10,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"textAlignVertical": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
1
|
||||
</Text>
|
||||
<Text
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "white",
|
||||
"fontSize": 14,
|
||||
},
|
||||
Object {
|
||||
"color": "#145dbf",
|
||||
"fontSize": 10,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
99+
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export default class Badge extends PureComponent {
|
|||
};
|
||||
|
||||
static propTypes = {
|
||||
containerStyle: ViewPropTypes.style,
|
||||
count: PropTypes.number.isRequired,
|
||||
extraPaddingHorizontal: PropTypes.number,
|
||||
style: ViewPropTypes.style,
|
||||
|
|
@ -66,9 +67,13 @@ export default class Badge extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
setBadgeRef = (ref) => {
|
||||
this.badgeContainerRef = ref;
|
||||
};
|
||||
|
||||
setNativeProps = (props) => {
|
||||
if (this.mounted && this.refs.badgeContainer) {
|
||||
this.refs.badgeContainer.setNativeProps(props);
|
||||
if (this.mounted && this.badgeContainerRef) {
|
||||
this.badgeContainerRef.setNativeProps(props);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -95,7 +100,7 @@ export default class Badge extends PureComponent {
|
|||
};
|
||||
|
||||
renderText = () => {
|
||||
const {count} = this.props;
|
||||
const {containerStyle, count, style} = this.props;
|
||||
let unreadCount = null;
|
||||
let unreadIndicator = null;
|
||||
if (count < 0) {
|
||||
|
|
@ -110,25 +115,33 @@ export default class Badge extends PureComponent {
|
|||
</View>
|
||||
);
|
||||
} else {
|
||||
let mentionCount = count;
|
||||
if (count > 99) {
|
||||
mentionCount = '99+';
|
||||
}
|
||||
|
||||
unreadCount = (
|
||||
<View style={styles.verticalAlign}>
|
||||
<Text
|
||||
style={[styles.text, this.props.countStyle]}
|
||||
onLayout={this.onLayout}
|
||||
>
|
||||
{count.toString()}
|
||||
{mentionCount.toString()}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
ref='badgeContainer'
|
||||
style={[styles.badge, this.props.style, {opacity: 0}]}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
{unreadCount}
|
||||
{unreadIndicator}
|
||||
<View style={[styles.badgeContainer, containerStyle]}>
|
||||
<View
|
||||
ref={this.setBadgeRef}
|
||||
style={[styles.badge, style, {opacity: 0}]}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
{unreadCount}
|
||||
{unreadIndicator}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -153,11 +166,13 @@ export default class Badge extends PureComponent {
|
|||
const styles = StyleSheet.create({
|
||||
badge: {
|
||||
backgroundColor: '#444',
|
||||
borderRadius: 20,
|
||||
height: 20,
|
||||
padding: 12,
|
||||
paddingTop: 3,
|
||||
paddingBottom: 3,
|
||||
},
|
||||
badgeContainer: {
|
||||
borderRadius: 20,
|
||||
position: 'absolute',
|
||||
right: 30,
|
||||
top: 2,
|
||||
|
|
@ -173,10 +188,10 @@ const styles = StyleSheet.create({
|
|||
color: 'white',
|
||||
},
|
||||
unreadIndicator: {
|
||||
height: 4,
|
||||
width: 4,
|
||||
height: 5,
|
||||
width: 5,
|
||||
backgroundColor: '#444',
|
||||
borderRadius: 4,
|
||||
borderRadius: 5,
|
||||
},
|
||||
verticalAlign: {
|
||||
flex: 1,
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ import Badge from './badge';
|
|||
|
||||
describe('Badge', () => {
|
||||
const baseProps = {
|
||||
count: 1,
|
||||
count: 100,
|
||||
countStyle: {color: '#145dbf', fontSize: 10},
|
||||
style: {backgroundColor: '#ffffff'},
|
||||
containerStyle: {borderColor: '#000000'},
|
||||
onPress: jest.fn(),
|
||||
};
|
||||
|
||||
|
|
@ -22,6 +23,6 @@ describe('Badge', () => {
|
|||
|
||||
expect(wrapper.instance().renderText()).toMatchSnapshot();
|
||||
expect(wrapper.find(TouchableWithoutFeedback).exists()).toEqual(true);
|
||||
expect(wrapper.find(Text).first().props().children).toContain('1');
|
||||
expect(wrapper.find(Text).first().props().children).toContain('99+');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,8 +41,6 @@ const createStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
alignSelf: 'center',
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.15),
|
||||
borderRadius: 2,
|
||||
color: theme.centerChannelColor,
|
||||
fontWeight: '600',
|
||||
marginRight: 2,
|
||||
marginBottom: 1,
|
||||
...Platform.select({
|
||||
|
|
@ -55,7 +53,9 @@ const createStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
paddingHorizontal: 4,
|
||||
},
|
||||
botText: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Image,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
|
@ -86,45 +85,45 @@ export default class ChannelIcon extends React.PureComponent {
|
|||
<Icon
|
||||
name='archive'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
|
||||
type='fontawesome'
|
||||
type='mattermost'
|
||||
/>
|
||||
);
|
||||
} else if (isBot) {
|
||||
icon = (
|
||||
<Icon
|
||||
name='robot'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}, style.iconBot]}
|
||||
type='fontawesome5'
|
||||
name='bot'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: (size - 1), left: -1.5, top: -1}]}
|
||||
type='mattermost'
|
||||
/>
|
||||
);
|
||||
} else if (hasDraft) {
|
||||
icon = (
|
||||
<Icon
|
||||
name='pencil'
|
||||
name='draft'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
|
||||
type='fontawesome'
|
||||
type='mattermost'
|
||||
/>
|
||||
);
|
||||
} else if (type === General.OPEN_CHANNEL) {
|
||||
icon = (
|
||||
<Icon
|
||||
name='globe'
|
||||
name='public'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
|
||||
type='fontawesome'
|
||||
type='mattermost'
|
||||
/>
|
||||
);
|
||||
} else if (type === General.PRIVATE_CHANNEL) {
|
||||
icon = (
|
||||
<Icon
|
||||
name='lock'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
|
||||
type='fontawesome'
|
||||
name='private'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: 0.5}]}
|
||||
type='mattermost'
|
||||
/>
|
||||
);
|
||||
} else if (type === General.GM_CHANNEL) {
|
||||
icon = (
|
||||
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: size, height: size}]}>
|
||||
<Text style={[style.group, unreadGroup, activeGroup, {fontSize: (size - 6)}]}>
|
||||
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: size + 1, height: size + 1}]}>
|
||||
<Text style={[style.group, unreadGroup, activeGroup, {fontSize: (size - 4)}]}>
|
||||
{membersCount}
|
||||
</Text>
|
||||
</View>
|
||||
|
|
@ -133,33 +132,37 @@ export default class ChannelIcon extends React.PureComponent {
|
|||
switch (status) {
|
||||
case General.AWAY:
|
||||
icon = (
|
||||
<Image
|
||||
source={require('assets/images/status/away_avatar.png')}
|
||||
style={{width: size, height: size, tintColor: theme.awayIndicator}}
|
||||
<Icon
|
||||
name='away-avatar'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.awayIndicator}]}
|
||||
type='mattermost'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case General.DND:
|
||||
icon = (
|
||||
<Image
|
||||
source={require('assets/images/status/dnd_avatar.png')}
|
||||
style={{width: size, height: size, tintColor: theme.dndIndicator}}
|
||||
<Icon
|
||||
name='dnd-avatar'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.dndIndicator}]}
|
||||
type='mattermost'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case General.ONLINE:
|
||||
icon = (
|
||||
<Image
|
||||
source={require('assets/images/status/online_avatar.png')}
|
||||
style={{width: size, height: size, tintColor: theme.onlineIndicator}}
|
||||
<Icon
|
||||
name='online-avatar'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.onlineIndicator}]}
|
||||
type='mattermost'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
icon = (
|
||||
<Image
|
||||
source={require('assets/images/status/offline_avatar.png')}
|
||||
style={{width: size, height: size, tintColor: offlineColor}}
|
||||
<Icon
|
||||
name='offline-avatar'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: offlineColor}]}
|
||||
type='mattermost'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
|
@ -167,7 +170,7 @@ export default class ChannelIcon extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<View style={[style.container, {width: size, height: size}]}>
|
||||
<View style={[style.container, {height: size}]}>
|
||||
{icon}
|
||||
</View>
|
||||
);
|
||||
|
|
@ -192,27 +195,26 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
iconInfo: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
iconBot: {
|
||||
marginLeft: -5,
|
||||
},
|
||||
groupBox: {
|
||||
alignSelf: 'flex-start',
|
||||
alignItems: 'center',
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.3),
|
||||
borderColor: changeOpacity(theme.sidebarText, 0.3),
|
||||
borderWidth: 1,
|
||||
borderColor: changeOpacity(theme.sidebarText, 0.4),
|
||||
borderRadius: 2,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
groupBoxActive: {
|
||||
borderColor: theme.sidebarTextActiveColor,
|
||||
backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.3),
|
||||
},
|
||||
groupBoxUnread: {
|
||||
borderColor: theme.sidebarUnreadText,
|
||||
backgroundColor: changeOpacity(theme.sidebarUnreadText, 0.3),
|
||||
},
|
||||
groupBoxInfo: {
|
||||
borderColor: theme.centerChannelColor,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.3),
|
||||
},
|
||||
group: {
|
||||
color: changeOpacity(theme.sidebarText, 0.4),
|
||||
color: changeOpacity(theme.sidebarText, 0.6),
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ export const FADE_DURATION = 100;
|
|||
export default class Fade extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
duration: PropTypes.number,
|
||||
style: PropTypes.object,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
disableScale: PropTypes.bool,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -27,7 +29,7 @@ export default class Fade extends PureComponent {
|
|||
this.state.fadeAnim,
|
||||
{
|
||||
toValue: prevProps.visible ? 0 : 1,
|
||||
duration: FADE_DURATION,
|
||||
duration: this.props.duration || FADE_DURATION,
|
||||
useNativeDriver: true,
|
||||
}
|
||||
).start();
|
||||
|
|
@ -35,6 +37,7 @@ export default class Fade extends PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {disableScale} = this.props;
|
||||
const {fadeAnim} = this.state;
|
||||
|
||||
return (
|
||||
|
|
@ -42,7 +45,7 @@ export default class Fade extends PureComponent {
|
|||
style={{
|
||||
...this.props.style,
|
||||
opacity: fadeAnim,
|
||||
transform: [{scale: fadeAnim}],
|
||||
transform: disableScale ? [] : [{scale: fadeAnim}],
|
||||
}}
|
||||
>
|
||||
{this.props.children}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import React, { Component } from 'react';
|
|||
import {
|
||||
Animated,
|
||||
Dimensions,
|
||||
Easing,
|
||||
Keyboard,
|
||||
PanResponder,
|
||||
StyleSheet,
|
||||
|
|
@ -183,7 +184,7 @@ export default class DrawerLayout extends Component {
|
|||
/* Overlay styles */
|
||||
const overlayOpacity = this.openValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 0.7],
|
||||
outputRange: [0, 0.5],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const animatedOverlayStyles = { opacity: overlayOpacity };
|
||||
|
|
@ -274,10 +275,10 @@ export default class DrawerLayout extends Component {
|
|||
openDrawer = (options: DrawerMovementOptionType = {}) => {
|
||||
if (!this.props.isTablet) {
|
||||
this._emitStateChanged(SETTLING);
|
||||
Animated.spring(this.openValue, {
|
||||
Animated.timing(this.openValue, {
|
||||
toValue: 1,
|
||||
bounciness: 0,
|
||||
restSpeedThreshold: 0.1,
|
||||
duration: 150,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
useNativeDriver: this.props.useNativeAnimations,
|
||||
...options,
|
||||
}).start(() => {
|
||||
|
|
@ -295,10 +296,10 @@ export default class DrawerLayout extends Component {
|
|||
|
||||
closeDrawer = (options: DrawerMovementOptionType = {}) => {
|
||||
this._emitStateChanged(SETTLING);
|
||||
Animated.spring(this.openValue, {
|
||||
Animated.timing(this.openValue, {
|
||||
toValue: 0,
|
||||
bounciness: 0,
|
||||
restSpeedThreshold: 1,
|
||||
duration: 150,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
useNativeDriver: this.props.useNativeAnimations,
|
||||
...options,
|
||||
}).start(() => {
|
||||
|
|
|
|||
|
|
@ -82,14 +82,16 @@ exports[`ChannelItem should match snapshot 1`] = `
|
|||
Array [
|
||||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
"color": "rgba(255,255,255,0.88)",
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -195,10 +197,11 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
Array [
|
||||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
"color": "rgba(255,255,255,0.88)",
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
@ -308,10 +311,11 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
Array [
|
||||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
"color": "rgba(255,255,255,0.88)",
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
@ -421,10 +425,11 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
|
|||
Array [
|
||||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
"color": "rgba(255,255,255,0.88)",
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
@ -523,14 +528,16 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
|
|||
Array [
|
||||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
"color": "rgba(255,255,255,0.88)",
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -631,14 +638,16 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
|
|||
Array [
|
||||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
"color": "rgba(255,255,255,0.88)",
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -735,14 +744,16 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
|
|||
Array [
|
||||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
"color": "rgba(255,255,255,0.88)",
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -750,26 +761,33 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
|
|||
display_name
|
||||
</Text>
|
||||
<Badge
|
||||
containerStyle={
|
||||
Object {
|
||||
"borderColor": "#1153ab",
|
||||
"borderRadius": 14,
|
||||
"borderWidth": 0,
|
||||
"marginRight": 16,
|
||||
"right": 0,
|
||||
"top": 11,
|
||||
}
|
||||
}
|
||||
count={1}
|
||||
countStyle={
|
||||
Object {
|
||||
"color": "#145dbf",
|
||||
"fontSize": 10,
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
extraPaddingHorizontal={10}
|
||||
minHeight={20}
|
||||
minWidth={20}
|
||||
minWidth={21}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"borderColor": "#1153ab",
|
||||
"borderRadius": 10,
|
||||
"borderWidth": 1,
|
||||
"height": 21,
|
||||
"padding": 3,
|
||||
"position": "relative",
|
||||
"right": 16,
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -160,10 +160,12 @@ export default class ChannelItem extends PureComponent {
|
|||
if (mentions) {
|
||||
badge = (
|
||||
<Badge
|
||||
containerStyle={style.badgeContainer}
|
||||
style={style.badge}
|
||||
countStyle={style.mention}
|
||||
count={mentions}
|
||||
onPress={this.onPress}
|
||||
minWidth={21}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -238,31 +240,38 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
paddingLeft: 11,
|
||||
},
|
||||
text: {
|
||||
color: changeOpacity(theme.sidebarText, 0.4),
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: changeOpacity(theme.sidebarText, 0.88),
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
paddingRight: 10,
|
||||
flex: 1,
|
||||
alignSelf: 'center',
|
||||
fontFamily: 'Open Sans',
|
||||
},
|
||||
textActive: {
|
||||
color: theme.sidebarTextActiveColor,
|
||||
},
|
||||
textUnread: {
|
||||
color: theme.sidebarUnreadText,
|
||||
fontWeight: '500',
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: theme.mentionBg,
|
||||
borderColor: theme.sidebarHeaderBg,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
padding: 3,
|
||||
position: 'relative',
|
||||
right: 16,
|
||||
height: 21,
|
||||
},
|
||||
badgeContainer: {
|
||||
borderColor: theme.sidebarHeaderBg,
|
||||
borderRadius: 14,
|
||||
borderWidth: 0,
|
||||
right: 0,
|
||||
top: 11,
|
||||
marginRight: 16,
|
||||
},
|
||||
mention: {
|
||||
color: theme.mentionColor,
|
||||
fontSize: 10,
|
||||
fontSize: 12,
|
||||
},
|
||||
muted: {
|
||||
opacity: 0.5,
|
||||
|
|
|
|||
|
|
@ -180,13 +180,34 @@ export default class ChannelsList extends PureComponent {
|
|||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
above: {
|
||||
backgroundColor: theme.mentionBg,
|
||||
top: 40,
|
||||
},
|
||||
action: {
|
||||
color: changeOpacity(theme.sidebarText, 0.4),
|
||||
fontSize: 26,
|
||||
fontWeight: '100',
|
||||
},
|
||||
actionContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 16,
|
||||
},
|
||||
container: {
|
||||
backgroundColor: theme.sidebarBg,
|
||||
flex: 1,
|
||||
},
|
||||
header: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
fontWeight: 'normal',
|
||||
paddingLeft: 16,
|
||||
},
|
||||
headerContainer: {
|
||||
alignItems: 'center',
|
||||
paddingLeft: 10,
|
||||
paddingLeft: 13,
|
||||
backgroundColor: theme.sidebarBg,
|
||||
flexDirection: 'row',
|
||||
borderBottomWidth: 1,
|
||||
|
|
@ -196,36 +217,15 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
height: ANDROID_TOP_PORTRAIT,
|
||||
},
|
||||
ios: {
|
||||
height: 44,
|
||||
height: 54,
|
||||
},
|
||||
}),
|
||||
},
|
||||
header: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
fontWeight: 'normal',
|
||||
paddingLeft: 16,
|
||||
},
|
||||
switchContainer: {
|
||||
position: 'relative',
|
||||
top: -1,
|
||||
},
|
||||
titleContainer: { // These aren't used by this component, but they are passed down to the list component
|
||||
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,
|
||||
hitSlop: {
|
||||
bottom: 10,
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
},
|
||||
searchContainer: {
|
||||
flex: 1,
|
||||
|
|
@ -239,26 +239,36 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
}),
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.1),
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.sidebarHeaderTextColor, 0.1),
|
||||
height: 1,
|
||||
width: '100%',
|
||||
},
|
||||
actionContainer: {
|
||||
alignItems: 'center',
|
||||
height: 48,
|
||||
separatorContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
width: 50,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
action: {
|
||||
switchContainer: {
|
||||
position: 'relative',
|
||||
top: -1,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarText,
|
||||
fontSize: 20,
|
||||
fontWeight: '500',
|
||||
opacity: 0.4,
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
letterSpacing: 0.2,
|
||||
lineHeight: 18,
|
||||
fontFamily: 'Open Sans',
|
||||
},
|
||||
above: {
|
||||
backgroundColor: theme.mentionBg,
|
||||
top: 9,
|
||||
titleContainer: { // These aren't used by this component, but they are passed down to the list component
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.sidebarBg,
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: 40,
|
||||
paddingLeft: 16,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,17 +5,13 @@ import deepEqual from 'deep-equal';
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
FlatList,
|
||||
Keyboard,
|
||||
Platform,
|
||||
SectionList,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {sortChannelsByDisplayName} from 'mattermost-redux/utils/channel_utils';
|
||||
|
|
@ -119,20 +115,6 @@ class FilteredList extends Component {
|
|||
this.props.onSelectChannel(channel, currentChannel.id);
|
||||
};
|
||||
|
||||
createChannelElement = (channel) => {
|
||||
return (
|
||||
<ChannelItem
|
||||
channelId={channel.id}
|
||||
channel={channel}
|
||||
isSearchResult={true}
|
||||
isUnread={channel.isUnread}
|
||||
mentions={0}
|
||||
onSelectChannel={this.onSelectChannel}
|
||||
previewChannel={this.props.previewChannel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
filterChannels = (channels, term) => {
|
||||
if (!term) {
|
||||
return channels;
|
||||
|
|
@ -330,11 +312,10 @@ class FilteredList extends Component {
|
|||
}
|
||||
|
||||
buildSectionsForSearch = (props, term) => {
|
||||
const items = [];
|
||||
const {searchOrder, styles} = props;
|
||||
const sections = [];
|
||||
const {searchOrder} = props;
|
||||
const sectionBuilders = this.getSectionBuilders();
|
||||
|
||||
let previousDataLength = 0;
|
||||
for (const section of searchOrder) {
|
||||
if (sectionBuilders.hasOwnProperty(section)) {
|
||||
const sectionBuilder = sectionBuilders[section];
|
||||
|
|
@ -342,14 +323,12 @@ class FilteredList extends Component {
|
|||
const data = builder(props, term);
|
||||
|
||||
if (data.length) {
|
||||
const title = this.renderTitle(styles, id, defaultMessage, null, previousDataLength > 0, true);
|
||||
items.push(title, ...data);
|
||||
previousDataLength = data.length;
|
||||
sections.push({id, defaultMessage, data});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
return sections;
|
||||
};
|
||||
|
||||
buildData = (props, term) => {
|
||||
|
|
@ -360,74 +339,59 @@ class FilteredList extends Component {
|
|||
return this.buildSectionsForSearch(props, term);
|
||||
};
|
||||
|
||||
renderSectionAction = (styles, action) => {
|
||||
const {theme} = this.props;
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.actionContainer}
|
||||
onPress={action}
|
||||
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
|
||||
>
|
||||
<MaterialIcon
|
||||
name='add'
|
||||
style={styles.action}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
};
|
||||
keyExtractor = (item) => item.id || item;
|
||||
|
||||
renderDivider = (styles, marginLeft) => {
|
||||
renderItem = ({item}) => {
|
||||
return (
|
||||
<View
|
||||
style={[styles.divider, {marginLeft}]}
|
||||
<ChannelItem
|
||||
channelId={item.id}
|
||||
channel={item}
|
||||
isSearchResult={true}
|
||||
isUnread={item.isUnread}
|
||||
mentions={0}
|
||||
onSelectChannel={this.onSelectChannel}
|
||||
previewChannel={this.props.previewChannel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
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 {
|
||||
renderSectionHeader = ({section}) => {
|
||||
const {intl, isLandscape, styles} = this.props;
|
||||
const {
|
||||
defaultMessage,
|
||||
id,
|
||||
isTitle: true,
|
||||
title: (
|
||||
<View>
|
||||
{topDivider && this.renderDivider(styles, 0)}
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={[styles.title, padding(this.props.isLandscape)]}>
|
||||
{formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
{action && this.renderSectionAction(styles, action)}
|
||||
} = section;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={[styles.titleContainer, padding(isLandscape)]}>
|
||||
<Text style={styles.title}>
|
||||
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
<View style={styles.separatorContainer}>
|
||||
<View style={styles.separator}/>
|
||||
</View>
|
||||
{bottomDivider && this.renderDivider(styles, 16)}
|
||||
</View>
|
||||
),
|
||||
};
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {styles} = this.props;
|
||||
const {dataSource} = this.state;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.container}
|
||||
>
|
||||
<FlatList
|
||||
data={dataSource}
|
||||
<SectionList
|
||||
sections={dataSource}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={(item) => item.id}
|
||||
onViewableItemsChanged={this.updateUnreadIndicators}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
keyExtractor={this.keyExtractor}
|
||||
{...this.keyboardDismissProp}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
maxToRenderPerBatch={10}
|
||||
stickySectionHeadersEnabled={true}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import PropTypes from 'prop-types';
|
|||
import {
|
||||
Dimensions,
|
||||
findNodeHandle,
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
Platform,
|
||||
SectionList,
|
||||
|
|
@ -16,18 +15,18 @@ import {
|
|||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
import FontAwesomePro from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
|
||||
import ChannelItem from 'app/components/sidebars/main/channels_list/channel_item';
|
||||
import {DeviceTypes, ListTypes, ViewTypes} from 'app/constants';
|
||||
import {DeviceTypes, ListTypes} from 'app/constants';
|
||||
import {SidebarSectionTypes} from 'app/constants/view';
|
||||
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
import {paddingLeft as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
|
||||
|
|
@ -81,6 +80,12 @@ export default class List extends PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!UnreadIndicator) {
|
||||
UnreadIndicator = require('app/components/sidebars/main/channels_list/unread_indicator').default;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {
|
||||
canCreatePrivateChannels,
|
||||
|
|
@ -162,12 +167,10 @@ export default class List extends PureComponent {
|
|||
orderedChannelIds,
|
||||
} = props;
|
||||
|
||||
return orderedChannelIds.map((s, i) => {
|
||||
return orderedChannelIds.map((s) => {
|
||||
return {
|
||||
...this.getSectionConfigByType(props, s.type),
|
||||
data: s.items,
|
||||
topSeparator: i !== 0,
|
||||
bottomSeparator: s.items.length > 0,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
@ -292,15 +295,15 @@ export default class List extends PureComponent {
|
|||
};
|
||||
|
||||
renderSectionAction = (styles, action, anchor) => {
|
||||
const {theme} = this.props;
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.actionContainer}
|
||||
onPress={action}
|
||||
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
|
||||
underlayColor={'transparent'}
|
||||
hitSlop={styles.hitSlop}
|
||||
>
|
||||
<MaterialIcon
|
||||
name='add'
|
||||
<FontAwesomePro
|
||||
name='ios-add-circle-outline'
|
||||
ref={anchor ? this.combinedActionsRef : null}
|
||||
style={styles.action}
|
||||
/>
|
||||
|
|
@ -308,13 +311,6 @@ export default class List extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
renderSectionSeparator = () => {
|
||||
const {styles} = this.props;
|
||||
return (
|
||||
<View style={[styles.divider]}/>
|
||||
);
|
||||
};
|
||||
|
||||
renderItem = ({item}) => {
|
||||
const {favoriteChannelIds, unreadChannelIds, previewChannel} = this.props;
|
||||
|
||||
|
|
@ -334,25 +330,24 @@ export default class List extends PureComponent {
|
|||
const {intl} = this.context;
|
||||
const {
|
||||
action,
|
||||
bottomSeparator,
|
||||
defaultMessage,
|
||||
id,
|
||||
topSeparator,
|
||||
} = section;
|
||||
|
||||
const anchor = (id === 'sidebar.types.recent' || id === 'mobile.channel_list.channels');
|
||||
|
||||
return (
|
||||
<View>
|
||||
{topSeparator && this.renderSectionSeparator()}
|
||||
<React.Fragment>
|
||||
<View style={[styles.titleContainer, padding(isLandscape)]}>
|
||||
<Text style={styles.title}>
|
||||
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
<View style={styles.separatorContainer}>
|
||||
<View style={styles.separator}/>
|
||||
</View>
|
||||
{action && this.renderSectionAction(styles, action, anchor)}
|
||||
</View>
|
||||
{bottomSeparator && this.renderSectionSeparator()}
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -367,24 +362,19 @@ export default class List extends PureComponent {
|
|||
};
|
||||
|
||||
emitUnreadIndicatorChange = debounce((showIndicator) => {
|
||||
if (showIndicator && !UnreadIndicator) {
|
||||
UnreadIndicator = require('app/components/sidebars/main/channels_list/unread_indicator').default;
|
||||
}
|
||||
this.setState({showIndicator});
|
||||
}, 100);
|
||||
}, 10);
|
||||
|
||||
updateUnreadIndicators = ({viewableItems}) => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
const {unreadChannelIds} = this.props;
|
||||
const firstUnread = unreadChannelIds.length && unreadChannelIds[0];
|
||||
if (firstUnread && viewableItems.length) {
|
||||
const isVisible = viewableItems.find((v) => v.item === firstUnread);
|
||||
const {unreadChannelIds} = this.props;
|
||||
const firstUnread = unreadChannelIds.length && unreadChannelIds[0];
|
||||
if (firstUnread && viewableItems.length) {
|
||||
const isVisible = viewableItems.find((v) => v.item === firstUnread);
|
||||
|
||||
return this.emitUnreadIndicatorChange(!isVisible);
|
||||
}
|
||||
return this.emitUnreadIndicatorChange(!isVisible);
|
||||
}
|
||||
|
||||
return this.emitUnreadIndicatorChange(false);
|
||||
});
|
||||
return this.emitUnreadIndicatorChange(false);
|
||||
};
|
||||
|
||||
scrollBeginDrag = () => {
|
||||
|
|
@ -408,15 +398,11 @@ export default class List extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {styles, theme, isLandscape} = this.props;
|
||||
const {sections, width, showIndicator} = this.state;
|
||||
const {styles, theme} = this.props;
|
||||
const {sections, showIndicator} = this.state;
|
||||
|
||||
const paddingBottom = this.listContentPadding();
|
||||
|
||||
const unreadBarStyles = DeviceTypes.IS_IPHONE_X && isLandscape ? {width: width - 32, marginLeft: ViewTypes.IOS_HORIZONTAL_LANDSCAPE + 16} : {width};
|
||||
|
||||
const unreadTextStyles = DeviceTypes.IS_IPHONE_X && isLandscape ? {marginLeft: -44} : null;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.container}
|
||||
|
|
@ -428,19 +414,18 @@ export default class List extends PureComponent {
|
|||
contentContainerStyle={{paddingBottom}}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
keyExtractor={this.keyExtractor}
|
||||
onViewableItemsChanged={this.updateUnreadIndicators}
|
||||
maxToRenderPerBatch={10}
|
||||
stickySectionHeadersEnabled={false}
|
||||
stickySectionHeadersEnabled={true}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
{...this.keyboardDismissProp}
|
||||
/>
|
||||
{showIndicator &&
|
||||
{UnreadIndicator &&
|
||||
<UnreadIndicator
|
||||
show={showIndicator}
|
||||
style={[styles.above, unreadBarStyles]}
|
||||
textStyle={unreadTextStyles}
|
||||
style={styles.above}
|
||||
onPress={this.scrollToTop}
|
||||
theme={theme}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeam, getMyTeamsCount, getChannelDrawerBadgeCount} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentTeamId, getMyTeamsCount, getChannelDrawerBadgeCount} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import SwitchTeamsButton from './switch_teams_button';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const team = getCurrentTeam(state) || {};
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
|
||||
return {
|
||||
currentTeamId: team.id,
|
||||
currentTeamId,
|
||||
mentionCount: getChannelDrawerBadgeCount(state),
|
||||
teamsCount: getMyTeamsCount(state),
|
||||
theme: getTheme(state),
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ export default class SwitchTeamsButton extends React.PureComponent {
|
|||
|
||||
const badge = (
|
||||
<Badge
|
||||
containerStyle={styles.badgeContainer}
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
minWidth={20}
|
||||
count={mentionCount}
|
||||
/>
|
||||
);
|
||||
|
|
@ -66,11 +68,11 @@ export default class SwitchTeamsButton extends React.PureComponent {
|
|||
size={12}
|
||||
style={styles.switcherArrow}
|
||||
/>
|
||||
<View style={styles.switcherDivider}/>
|
||||
<TeamIcon
|
||||
teamId={currentTeamId}
|
||||
styleContainer={styles.teamIconContainer}
|
||||
styleText={styles.teamIconText}
|
||||
styleImage={styles.teamIcon}
|
||||
/>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
|
|
@ -82,49 +84,51 @@ export default class SwitchTeamsButton extends React.PureComponent {
|
|||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
switcherContainer: {
|
||||
backgroundColor: theme.sidebarHeaderTextColor,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: 32,
|
||||
borderRadius: 2,
|
||||
marginLeft: 6,
|
||||
marginRight: 6,
|
||||
paddingHorizontal: 3,
|
||||
},
|
||||
switcherArrow: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
marginRight: 3,
|
||||
},
|
||||
switcherDivider: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
height: 15,
|
||||
marginHorizontal: 6,
|
||||
width: 1,
|
||||
},
|
||||
teamIconContainer: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
marginLeft: 3,
|
||||
},
|
||||
teamIconText: {
|
||||
fontSize: 14,
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: theme.mentionBg,
|
||||
borderColor: theme.sidebarHeaderBg,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
flexDirection: 'row',
|
||||
height: 20,
|
||||
padding: 3,
|
||||
},
|
||||
badgeContainer: {
|
||||
borderColor: theme.sidebarBg,
|
||||
borderRadius: 14,
|
||||
borderWidth: 2,
|
||||
position: 'absolute',
|
||||
left: -5,
|
||||
top: -5,
|
||||
right: 0,
|
||||
top: -9,
|
||||
},
|
||||
mention: {
|
||||
color: theme.mentionColor,
|
||||
fontSize: 10,
|
||||
},
|
||||
switcherArrow: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
marginRight: 8,
|
||||
top: 1,
|
||||
},
|
||||
switcherContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.sidebarHeaderTextColor,
|
||||
borderRadius: 2,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
height: 32,
|
||||
marginRight: 12,
|
||||
width: 56,
|
||||
},
|
||||
teamIcon: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
teamIconContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: 24,
|
||||
width: 24,
|
||||
},
|
||||
teamIconText: {
|
||||
fontFamily: 'Open Sans',
|
||||
fontSize: 14,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from 'react-native';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
import Fade from 'app/components/fade';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
|
|
@ -28,45 +29,47 @@ export default class UnreadIndicator extends PureComponent {
|
|||
|
||||
render() {
|
||||
const {onPress, show, theme} = this.props;
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={onPress}>
|
||||
<View
|
||||
style={[style.container, this.props.style]}
|
||||
>
|
||||
<FormattedText
|
||||
style={[style.indicatorText, this.props.textStyle]}
|
||||
id='sidebar.unreads'
|
||||
defaultMessage='More unreads'
|
||||
/>
|
||||
<IonIcon
|
||||
size={14}
|
||||
name='md-arrow-round-up'
|
||||
color={theme.mentionColor}
|
||||
style={style.arrow}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<Fade
|
||||
visible={show}
|
||||
style={style.container}
|
||||
duration={150}
|
||||
disableScale={true}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={onPress}>
|
||||
<View style={[style.wrapper, this.props.style]}>
|
||||
<FormattedText
|
||||
style={[style.indicatorText, this.props.textStyle]}
|
||||
id='sidebar.unreads'
|
||||
defaultMessage='More unreads'
|
||||
/>
|
||||
<IonIcon
|
||||
size={14}
|
||||
name='md-arrow-round-up'
|
||||
color={theme.mentionColor}
|
||||
style={style.arrow}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</Fade>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
arrow: {
|
||||
position: 'relative',
|
||||
bottom: -1,
|
||||
},
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
position: 'absolute',
|
||||
borderRadius: 15,
|
||||
marginHorizontal: 25,
|
||||
height: 25,
|
||||
right: 0,
|
||||
left: 0,
|
||||
},
|
||||
indicatorText: {
|
||||
backgroundColor: 'transparent',
|
||||
|
|
@ -77,9 +80,14 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
textAlign: 'center',
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
arrow: {
|
||||
position: 'relative',
|
||||
bottom: -1,
|
||||
wrapper: {
|
||||
borderRadius: 15,
|
||||
height: 25,
|
||||
flexDirection: 'row',
|
||||
paddingLeft: 10,
|
||||
paddingRight: 12,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ import Swiper from 'app/components/swiper';
|
|||
export default class DrawerSwiper extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
drawerOpened: PropTypes.bool,
|
||||
drawerWidth: PropTypes.number.isRequired,
|
||||
hasSafeAreaInsets: PropTypes.bool,
|
||||
onPageSelected: PropTypes.func,
|
||||
showTeams: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
drawerOpened: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -78,8 +79,9 @@ export default class DrawerSwiper extends Component {
|
|||
onIndexChanged={this.swiperPageSelected}
|
||||
width={drawerWidth}
|
||||
style={{backgroundColor: theme.sidebarBg}}
|
||||
paginationBackgroundColor={changeOpacity(theme.sidebarText, 0.1)}
|
||||
activeDotColor={theme.sidebarText}
|
||||
dotColor={changeOpacity(theme.sidebarText, 0.5)}
|
||||
dotColor={changeOpacity(theme.sidebarText, 0.3)}
|
||||
scrollEnabled={showTeams}
|
||||
showsPagination={showTeams}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {getCurrentTeamId, getMyTeamsCount} from 'mattermost-redux/selectors/enti
|
|||
|
||||
import {setChannelDisplayName, setChannelLoading} from 'app/actions/views/channel';
|
||||
import {makeDirectChannel} from 'app/actions/views/more_dms';
|
||||
import {isLandscape, getDimensions} from 'app/selectors/device';
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
import telemetry from 'app/telemetry';
|
||||
|
||||
import MainSidebar from './main_sidebar.js';
|
||||
|
|
@ -41,7 +41,6 @@ function mapStateToProps(state) {
|
|||
...getDimensions(state),
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentUserId,
|
||||
isLandscape: isLandscape(state),
|
||||
teamsCount: getMyTeamsCount(state),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import TeamsList from './teams_list';
|
|||
import telemetry from 'app/telemetry';
|
||||
|
||||
const DRAWER_INITIAL_OFFSET = 40;
|
||||
const DRAWER_LANDSCAPE_OFFSET = 150;
|
||||
|
||||
export default class ChannelSidebar extends Component {
|
||||
static propTypes = {
|
||||
|
|
@ -46,7 +45,6 @@ export default class ChannelSidebar extends Component {
|
|||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
teamsCount: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
previewChannel: PropTypes.func,
|
||||
|
|
@ -59,17 +57,12 @@ export default class ChannelSidebar extends Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
let openDrawerOffset = DRAWER_INITIAL_OFFSET;
|
||||
if (props.isLandscape || DeviceTypes.IS_TABLET) {
|
||||
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
|
||||
}
|
||||
|
||||
this.swiperIndex = 1;
|
||||
this.drawerRef = React.createRef();
|
||||
this.channelListRef = React.createRef();
|
||||
this.state = {
|
||||
show: false,
|
||||
openDrawerOffset,
|
||||
openDrawerOffset: DRAWER_INITIAL_OFFSET,
|
||||
drawerOpened: false,
|
||||
searching: false,
|
||||
isSplitView: false,
|
||||
|
|
@ -79,7 +72,7 @@ export default class ChannelSidebar extends Component {
|
|||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.props.actions.getTeams();
|
||||
this.handleDimensions();
|
||||
this.handleDimensions({window: Dimensions.get('window')});
|
||||
this.handlePermanentSidebar();
|
||||
EventEmitter.on('close_channel_drawer', this.closeChannelDrawer);
|
||||
EventEmitter.on('renderDrawer', this.handleShowDrawerContent);
|
||||
|
|
@ -89,23 +82,8 @@ export default class ChannelSidebar extends Component {
|
|||
Dimensions.addEventListener('change', this.handleDimensions);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {isLandscape} = this.props;
|
||||
|
||||
if (nextProps.isLandscape !== isLandscape) {
|
||||
if (this.state.openDrawerOffset !== 0) {
|
||||
let openDrawerOffset = DRAWER_INITIAL_OFFSET;
|
||||
if (nextProps.isLandscape || DeviceTypes.IS_TABLET) {
|
||||
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
|
||||
}
|
||||
|
||||
this.setState({openDrawerOffset});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {currentTeamId, deviceWidth, isLandscape, teamsCount, theme} = this.props;
|
||||
const {currentTeamId, deviceWidth, teamsCount, theme} = this.props;
|
||||
const {openDrawerOffset, isSplitView, permanentSidebar, show, searching} = this.state;
|
||||
|
||||
if (nextState.openDrawerOffset !== openDrawerOffset || nextState.show !== show || nextState.searching !== searching) {
|
||||
|
|
@ -113,7 +91,7 @@ export default class ChannelSidebar extends Component {
|
|||
}
|
||||
|
||||
return nextProps.currentTeamId !== currentTeamId ||
|
||||
nextProps.isLandscape !== isLandscape || nextProps.deviceWidth !== deviceWidth ||
|
||||
nextProps.deviceWidth !== deviceWidth ||
|
||||
nextProps.teamsCount !== teamsCount ||
|
||||
nextProps.theme !== theme ||
|
||||
nextState.isSplitView !== isSplitView ||
|
||||
|
|
@ -139,12 +117,23 @@ export default class ChannelSidebar extends Component {
|
|||
return false;
|
||||
};
|
||||
|
||||
handleDimensions = () => {
|
||||
if (DeviceTypes.IS_TABLET && this.mounted) {
|
||||
mattermostManaged.isRunningInSplitView().then((result) => {
|
||||
const isSplitView = Boolean(result.isSplitView);
|
||||
this.setState({isSplitView});
|
||||
});
|
||||
handleDimensions = ({window}) => {
|
||||
if (this.mounted) {
|
||||
if (DeviceTypes.IS_TABLET) {
|
||||
mattermostManaged.isRunningInSplitView().then((result) => {
|
||||
const isSplitView = Boolean(result.isSplitView);
|
||||
this.setState({isSplitView});
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.openDrawerOffset !== 0) {
|
||||
let openDrawerOffset = DRAWER_INITIAL_OFFSET;
|
||||
if ((window.width > window.height) || DeviceTypes.IS_TABLET) {
|
||||
openDrawerOffset = window.width * 0.5;
|
||||
}
|
||||
|
||||
this.setState({openDrawerOffset});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -343,6 +332,7 @@ export default class ChannelSidebar extends Component {
|
|||
return null;
|
||||
}
|
||||
|
||||
const hasSafeAreaInsets = DeviceTypes.IS_IPHONE_X || mattermostManaged.hasSafeAreaInsets;
|
||||
const multipleTeams = teamsCount > 1;
|
||||
const showTeams = !searching && multipleTeams;
|
||||
if (this.drawerSwiper) {
|
||||
|
|
@ -389,6 +379,7 @@ export default class ChannelSidebar extends Component {
|
|||
|
||||
return (
|
||||
<SafeAreaView
|
||||
excludeFooter={true}
|
||||
navBarBackgroundColor={theme.sidebarBg}
|
||||
backgroundColor={theme.sidebarHeaderBg}
|
||||
footerColor={theme.sidebarBg}
|
||||
|
|
@ -399,6 +390,7 @@ export default class ChannelSidebar extends Component {
|
|||
showTeams={showTeams}
|
||||
drawerOpened={this.state.drawerOpened}
|
||||
drawerWidth={drawerWidth}
|
||||
hasSafeAreaInsets={hasSafeAreaInsets}
|
||||
>
|
||||
{lists}
|
||||
</DrawerSwiper>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,16 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const badge = (
|
||||
<Badge
|
||||
containerStyle={styles.badgeContainer}
|
||||
countStyle={styles.mention}
|
||||
count={mentionCount}
|
||||
minWidth={20}
|
||||
style={styles.badge}
|
||||
/>
|
||||
);
|
||||
|
||||
let current;
|
||||
if (teamId === currentTeamId) {
|
||||
current = (
|
||||
|
|
@ -58,14 +68,6 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={mentionCount}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.teamWrapper}>
|
||||
<TouchableHighlight
|
||||
|
|
@ -73,11 +75,14 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
onPress={this.selectTeam}
|
||||
>
|
||||
<View style={[styles.teamContainer, padding(isLandscape)]}>
|
||||
<TeamIcon
|
||||
teamId={teamId}
|
||||
styleContainer={styles.teamIconContainer}
|
||||
styleText={styles.teamIconText}
|
||||
/>
|
||||
<View>
|
||||
<TeamIcon
|
||||
teamId={teamId}
|
||||
styleContainer={styles.teamIconContainer}
|
||||
styleText={styles.teamIconText}
|
||||
/>
|
||||
{badge}
|
||||
</View>
|
||||
<View style={styles.teamNameContainer}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
|
|
@ -97,7 +102,6 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
{current}
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
{badge}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -145,14 +149,16 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
badge: {
|
||||
backgroundColor: theme.mentionBg,
|
||||
borderColor: theme.sidebarHeaderBg,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
flexDirection: 'row',
|
||||
height: 20,
|
||||
padding: 3,
|
||||
},
|
||||
badgeContainer: {
|
||||
borderColor: theme.sidebarBg,
|
||||
borderRadius: 14,
|
||||
borderWidth: 2,
|
||||
position: 'absolute',
|
||||
left: 45,
|
||||
top: -2,
|
||||
right: -12,
|
||||
top: -10,
|
||||
},
|
||||
mention: {
|
||||
color: theme.mentionColor,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export default class Swiper extends PureComponent {
|
|||
activeDotColor: PropTypes.string,
|
||||
children: PropTypes.node.isRequired,
|
||||
dotColor: PropTypes.string,
|
||||
paginationBackgroundColor: PropTypes.string,
|
||||
initialPage: PropTypes.number,
|
||||
keyboardShouldPersistTaps: PropTypes.string,
|
||||
onIndexChanged: PropTypes.func,
|
||||
|
|
@ -138,7 +139,7 @@ export default class Swiper extends PureComponent {
|
|||
};
|
||||
|
||||
renderPagination = () => {
|
||||
if (this.state.total <= 1 || !this.props.showsPagination) {
|
||||
if (this.state.total <= 1 || !this.props.showsPagination || Platform.OS === 'android') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -171,10 +172,10 @@ export default class Swiper extends PureComponent {
|
|||
const bottom = this.paginationBottom(width, height);
|
||||
const drawerWidth = (width > height) ? width - ViewTypes.IOS_HORIZONTAL_LANDSCAPE : width;
|
||||
let style;
|
||||
if (DeviceTypes.IS_IPHONE_X) {
|
||||
if (DeviceTypes.IS_IPHONE_X && (width < height)) {
|
||||
style = {
|
||||
bottom,
|
||||
drawerWidth,
|
||||
width: drawerWidth,
|
||||
};
|
||||
} else {
|
||||
style = {
|
||||
|
|
@ -188,7 +189,9 @@ export default class Swiper extends PureComponent {
|
|||
pointerEvents='none'
|
||||
style={[styles.pagination, style]}
|
||||
>
|
||||
{dots}
|
||||
<View style={[styles.paginationWrapper, {backgroundColor: this.props.paginationBackgroundColor}]}>
|
||||
{dots}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
@ -217,7 +220,7 @@ export default class Swiper extends PureComponent {
|
|||
paginationBottom = (width, height) => {
|
||||
if (DeviceTypes.IS_TABLET) {
|
||||
if (Platform.OS === 'ios' && mattermostManaged.hasSafeAreaInsets) {
|
||||
return 14;
|
||||
return 34;
|
||||
}
|
||||
|
||||
return 24;
|
||||
|
|
@ -225,7 +228,7 @@ export default class Swiper extends PureComponent {
|
|||
|
||||
const landscape = width > height;
|
||||
if (DeviceTypes.IS_IPHONE_X) {
|
||||
return landscape ? 14 : 4;
|
||||
return landscape ? 14 : 34;
|
||||
}
|
||||
|
||||
return 24;
|
||||
|
|
@ -278,9 +281,17 @@ const styles = StyleSheet.create({
|
|||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
},
|
||||
paginationWrapper: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: 24,
|
||||
borderRadius: 15,
|
||||
width: 44,
|
||||
},
|
||||
dotStyle: {
|
||||
width: 8,
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
|||
import TeamIcon from './team_icon';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const team = getTeam(state, ownProps.teamId);
|
||||
const lastIconUpdate = team.last_team_icon_update;
|
||||
|
||||
return {
|
||||
team: getTeam(state, ownProps.teamId),
|
||||
displayName: team.display_name,
|
||||
lastIconUpdate,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,20 +17,21 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
|||
|
||||
export default class TeamIcon extends React.PureComponent {
|
||||
static propTypes = {
|
||||
displayName: PropTypes.string,
|
||||
lastIconUpdate: PropTypes.number,
|
||||
teamId: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
|
||||
styleContainer: PropTypes.any,
|
||||
styleText: PropTypes.any,
|
||||
styleImage: PropTypes.any,
|
||||
team: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const {team} = props;
|
||||
if (team.last_team_icon_update) {
|
||||
ImageCacheManager.cache('', Client4.getTeamIconUrl(team.id, team.last_team_icon_update), this.setImageURL);
|
||||
const {lastIconUpdate, teamId} = props;
|
||||
if (lastIconUpdate) {
|
||||
ImageCacheManager.cache('', Client4.getTeamIconUrl(teamId, lastIconUpdate), this.setImageURL);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
|
|
@ -40,12 +41,18 @@ export default class TeamIcon extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({imageError: false});
|
||||
const newState = {imageError: false};
|
||||
|
||||
if (this.props.team.last_team_icon_update !== nextProps.team.last_team_icon_update) {
|
||||
const {team} = nextProps;
|
||||
ImageCacheManager.cache('', Client4.getTeamIconUrl(team.id, team.last_team_icon_update), this.setImageURL);
|
||||
if (this.props.teamId !== nextProps.teamId) {
|
||||
newState.teamIcon = null;
|
||||
}
|
||||
|
||||
if (nextProps.lastIconUpdate && this.props.lastIconUpdate !== nextProps.lastIconUpdate) {
|
||||
const {lastIconUpdate, teamId} = nextProps;
|
||||
ImageCacheManager.cache('', Client4.getTeamIconUrl(teamId, lastIconUpdate), this.setImageURL);
|
||||
}
|
||||
|
||||
this.setState(newState);
|
||||
}
|
||||
|
||||
setImageURL = (teamIcon) => {
|
||||
|
|
@ -54,7 +61,7 @@ export default class TeamIcon extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
const {
|
||||
team,
|
||||
displayName,
|
||||
theme,
|
||||
styleContainer,
|
||||
styleText,
|
||||
|
|
@ -75,7 +82,7 @@ export default class TeamIcon extends React.PureComponent {
|
|||
} else {
|
||||
teamIconContent = (
|
||||
<Text style={[styles.text, styleText]}>
|
||||
{team.display_name.substr(0, 2).toUpperCase()}
|
||||
{displayName.substr(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,69 +1,57 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UserStatus should match snapshot, away status 1`] = `
|
||||
<Image
|
||||
source={
|
||||
Object {
|
||||
"testUri": "../../../dist/assets/images/status/away.png",
|
||||
}
|
||||
}
|
||||
<VectorIcon
|
||||
name="away"
|
||||
size={14}
|
||||
style={
|
||||
Object {
|
||||
"height": 32,
|
||||
"tintColor": "#ffbc42",
|
||||
"width": 32,
|
||||
"color": "#ffbc42",
|
||||
"fontSize": 32,
|
||||
}
|
||||
}
|
||||
type="mattermost"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`UserStatus should match snapshot, dnd status 1`] = `
|
||||
<Image
|
||||
source={
|
||||
Object {
|
||||
"testUri": "../../../dist/assets/images/status/dnd.png",
|
||||
}
|
||||
}
|
||||
<VectorIcon
|
||||
name="dnd"
|
||||
size={14}
|
||||
style={
|
||||
Object {
|
||||
"height": 32,
|
||||
"tintColor": "#f74343",
|
||||
"width": 32,
|
||||
"color": "#f74343",
|
||||
"fontSize": 32,
|
||||
}
|
||||
}
|
||||
type="mattermost"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`UserStatus should match snapshot, online status 1`] = `
|
||||
<Image
|
||||
source={
|
||||
Object {
|
||||
"testUri": "../../../dist/assets/images/status/online.png",
|
||||
}
|
||||
}
|
||||
<VectorIcon
|
||||
name="online"
|
||||
size={14}
|
||||
style={
|
||||
Object {
|
||||
"height": 32,
|
||||
"tintColor": "#06d6a0",
|
||||
"width": 32,
|
||||
"color": "#06d6a0",
|
||||
"fontSize": 32,
|
||||
}
|
||||
}
|
||||
type="mattermost"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`UserStatus should match snapshot, should default to offline status 1`] = `
|
||||
<Image
|
||||
source={
|
||||
Object {
|
||||
"testUri": "../../../dist/assets/images/status/offline.png",
|
||||
}
|
||||
}
|
||||
<VectorIcon
|
||||
name="offline"
|
||||
size={14}
|
||||
style={
|
||||
Object {
|
||||
"height": 32,
|
||||
"tintColor": "rgba(61,60,64,0.3)",
|
||||
"width": 32,
|
||||
"color": "rgba(61,60,64,0.3)",
|
||||
"fontSize": 32,
|
||||
}
|
||||
}
|
||||
type="mattermost"
|
||||
/>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -2,41 +2,38 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Image} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import Icon from 'app/components/vector_icon';
|
||||
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
import away from 'assets/images/status/away.png';
|
||||
import dnd from 'assets/images/status/dnd.png';
|
||||
import offline from 'assets/images/status/offline.png';
|
||||
import online from 'assets/images/status/online.png';
|
||||
|
||||
const statusToIcon = {
|
||||
away,
|
||||
dnd,
|
||||
offline,
|
||||
ooo: offline,
|
||||
online,
|
||||
away: General.AWAY,
|
||||
dnd: General.DND,
|
||||
offline: General.OFFLINE,
|
||||
ooo: General.OFFLINE,
|
||||
online: General.ONLINE,
|
||||
};
|
||||
|
||||
export default class UserStatus extends PureComponent {
|
||||
static propTypes = {
|
||||
isAvatar: PropTypes.bool,
|
||||
size: PropTypes.number,
|
||||
status: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
size: 14,
|
||||
size: 6,
|
||||
status: General.OFFLINE,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {size, status, theme} = this.props;
|
||||
const Icon = statusToIcon[status];
|
||||
const iconName = statusToIcon[status];
|
||||
|
||||
let iconColor;
|
||||
switch (status) {
|
||||
|
|
@ -55,9 +52,10 @@ export default class UserStatus extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<Image
|
||||
source={Icon}
|
||||
style={{height: size, width: size, tintColor: iconColor}}
|
||||
<Icon
|
||||
name={iconName}
|
||||
style={{fontSize: size, color: iconColor}}
|
||||
type='mattermost'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,21 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Text} from 'react-native';
|
||||
import {createIconSetFromIcoMoon} from 'react-native-vector-icons';
|
||||
import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
import FontAwesome5Icon from 'react-native-vector-icons/FontAwesome5';
|
||||
import FoundationIcon from 'react-native-vector-icons/Foundation';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import icoMoonConfig from 'assets/mattermost-fonts.json';
|
||||
|
||||
const Mattermost = createIconSetFromIcoMoon(
|
||||
icoMoonConfig,
|
||||
'Mattermost',
|
||||
'Mattermost-Regular.otf'
|
||||
);
|
||||
|
||||
export default class VectorIcon extends PureComponent {
|
||||
static propTypes = {
|
||||
name: PropTypes.string,
|
||||
|
|
@ -66,6 +75,14 @@ export default class VectorIcon extends PureComponent {
|
|||
size={size}
|
||||
/>
|
||||
);
|
||||
case 'mattermost':
|
||||
return (
|
||||
<Mattermost
|
||||
name={name}
|
||||
style={style}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
const VISIBILITY_CONFIG_DEFAULTS = {
|
||||
viewAreaCoveragePercentThreshold: 3,
|
||||
itemVisiblePercentThreshold: 100,
|
||||
waitForInteraction: false,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -77,10 +77,12 @@ class ChannelDrawerButton extends PureComponent {
|
|||
if (badgeCount && visible) {
|
||||
badge = (
|
||||
<Badge
|
||||
containerStyle={style.badgeContainer}
|
||||
style={style.badge}
|
||||
countStyle={style.mention}
|
||||
count={badgeCount}
|
||||
onPress={this.handlePress}
|
||||
minWidth={19}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -137,14 +139,16 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
badge: {
|
||||
backgroundColor: theme.mentionBg,
|
||||
borderColor: theme.sidebarHeaderBg,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
left: -13,
|
||||
height: 19,
|
||||
padding: 3,
|
||||
},
|
||||
badgeContainer: {
|
||||
borderColor: theme.sidebarHeaderBg,
|
||||
borderRadius: 14,
|
||||
borderWidth: 2,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: -4,
|
||||
right: -14,
|
||||
top: -7,
|
||||
},
|
||||
mention: {
|
||||
color: theme.mentionColor,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import React, {PureComponent} from 'react';
|
|||
import {
|
||||
Keyboard,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
|
|
@ -18,6 +17,7 @@ import PostList from 'app/components/post_list';
|
|||
import RetryBarIndicator from 'app/components/retry_bar_indicator';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import telemetry from 'app/telemetry';
|
||||
|
||||
let ChannelIntro = null;
|
||||
|
|
@ -216,8 +216,11 @@ export default class ChannelPostList extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<View style={style.separator}/>
|
||||
{component}
|
||||
<AnnouncementBanner/>
|
||||
<RetryBarIndicator/>
|
||||
|
|
@ -226,8 +229,12 @@ export default class ChannelPostList extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
height: 1,
|
||||
},
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,14 @@ exports[`thread should match snapshot, has root post 1`] = `
|
|||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(StatusBar) />
|
||||
<React.Fragment>
|
||||
<Connect(PostList)
|
||||
|
|
@ -67,6 +75,14 @@ exports[`thread should match snapshot, no root post, loading 1`] = `
|
|||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(StatusBar) />
|
||||
<Loading
|
||||
color="grey"
|
||||
|
|
@ -128,6 +144,14 @@ exports[`thread should match snapshot, render footer 3`] = `
|
|||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(StatusBar) />
|
||||
<Loading
|
||||
color="grey"
|
||||
|
|
|
|||
|
|
@ -2,15 +2,16 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import {THREAD} from 'app/constants/screen';
|
||||
|
||||
import Loading from 'app/components/loading';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
import Loading from 'app/components/loading';
|
||||
import PostList from 'app/components/post_list';
|
||||
import PostTextbox from 'app/components/post_textbox';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import ThreadBase from './thread_base';
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ export default class ThreadAndroid extends ThreadBase {
|
|||
postIds,
|
||||
rootId,
|
||||
channelIsArchived,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
let content;
|
||||
|
|
@ -54,10 +56,12 @@ export default class ThreadAndroid extends ThreadBase {
|
|||
);
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<StatusBar/>
|
||||
<KeyboardLayout>
|
||||
<View style={style.separator}/>
|
||||
{content}
|
||||
{postTextBox}
|
||||
</KeyboardLayout>
|
||||
|
|
@ -65,3 +69,10 @@ export default class ThreadAndroid extends ThreadBase {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
height: 1,
|
||||
},
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {View} from 'react-native';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
|
||||
|
|
@ -16,6 +15,7 @@ import PostTextbox from 'app/components/post_textbox';
|
|||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {THREAD} from 'app/constants/screen';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import ThreadBase from './thread_base';
|
||||
|
||||
|
|
@ -32,6 +32,7 @@ export default class ThreadIOS extends ThreadBase {
|
|||
postIds,
|
||||
rootId,
|
||||
channelIsArchived,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
let content;
|
||||
|
|
@ -87,9 +88,11 @@ export default class ThreadIOS extends ThreadBase {
|
|||
);
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SafeAreaView excludeHeader={true}>
|
||||
<View style={style.separator}/>
|
||||
<StatusBar/>
|
||||
{content}
|
||||
</SafeAreaView>
|
||||
|
|
@ -98,3 +101,10 @@ export default class ThreadIOS extends ThreadBase {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
height: 1,
|
||||
},
|
||||
}));
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 205 B |
|
Before Width: | Height: | Size: 520 B |
|
Before Width: | Height: | Size: 438 B |
|
Before Width: | Height: | Size: 430 B |
|
Before Width: | Height: | Size: 367 B |
|
Before Width: | Height: | Size: 543 B |
|
Before Width: | Height: | Size: 468 B |
|
Before Width: | Height: | Size: 547 B |
|
Before Width: | Height: | Size: 438 B |
1
assets/base/mattermost-fonts.json
Executable file
BIN
assets/fonts/Mattermost-Regular.otf
Normal file
|
|
@ -5,7 +5,6 @@
|
|||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
|
||||
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
|
||||
|
|
@ -123,6 +122,7 @@
|
|||
F006936FC2884C24A1321FC0 /* MaterialIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 356C9186FA374641A00EB2EA /* MaterialIcons.ttf */; };
|
||||
F083DB472349411A8E6E7AAD /* OpenSans-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BE17F630DB5D41FD93F32D22 /* OpenSans-LightItalic.ttf */; };
|
||||
F23C99AA5FA10E457A76803A /* libPods-MattermostTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4246DC09024BB33A3E491E25 /* libPods-MattermostTests.a */; };
|
||||
1A892F126F594A27B6AA095E /* Mattermost-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = E2D17ED040D0465DBF9112D3 /* Mattermost-Regular.otf */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
|
@ -903,6 +903,7 @@
|
|||
EE671DF7637347CD8C069819 /* libRNDeviceInfo.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNDeviceInfo.a; sourceTree = "<group>"; };
|
||||
F1F071EE85494E269A50AE88 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
|
||||
FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBoldItalic.ttf"; path = "../assets/fonts/OpenSans-ExtraBoldItalic.ttf"; sourceTree = "<group>"; };
|
||||
E2D17ED040D0465DBF9112D3 /* Mattermost-Regular.otf */ = {isa = PBXFileReference; name = "Mattermost-Regular.otf"; path = "../assets/fonts/Mattermost-Regular.otf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -1086,6 +1087,7 @@
|
|||
5C5FEE2D22F048709FB330F3 /* FontAwesome5_Brands.ttf */,
|
||||
59A2AAD141C34AA7B4655F27 /* FontAwesome5_Regular.ttf */,
|
||||
DC1D660B55BE462A9C3B8028 /* FontAwesome5_Solid.ttf */,
|
||||
E2D17ED040D0465DBF9112D3 /* Mattermost-Regular.otf */,
|
||||
);
|
||||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -2561,6 +2563,7 @@
|
|||
E08B278B7BA14C41AD908BE2 /* FontAwesome5_Brands.ttf in Resources */,
|
||||
DA32194224944F1DBD27C485 /* FontAwesome5_Regular.ttf in Resources */,
|
||||
7BD159C40A68467FB5A17141 /* FontAwesome5_Solid.ttf in Resources */,
|
||||
1A892F126F594A27B6AA095E /* Mattermost-Regular.otf in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@
|
|||
<string>FontAwesome5_Brands.ttf</string>
|
||||
<string>FontAwesome5_Regular.ttf</string>
|
||||
<string>FontAwesome5_Solid.ttf</string>
|
||||
<string>Mattermost-Regular.otf</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
|
|
|
|||