Entry screen and Channel Loader improvements (perf part 4) (#1707)
* Entry screen and Channel Loader improvements * Feedback review * Removed unnecessary style array
This commit is contained in:
parent
b500df868e
commit
cf3683272e
11 changed files with 100 additions and 182 deletions
|
|
@ -6,152 +6,56 @@ import PropTypes from 'prop-types';
|
|||
import {
|
||||
Platform,
|
||||
View,
|
||||
Animated,
|
||||
Easing,
|
||||
} from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import Placeholder from 'rn-placeholder';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const GRADIENT_START_END = 0;
|
||||
const GRADIENT_MIDDLE = 0.8;
|
||||
|
||||
const START_VALUE = 0;
|
||||
const MIDDLE_VALUE = 50;
|
||||
const END_VALUE = 100;
|
||||
const DURATION = 2000;
|
||||
|
||||
export default class ChannelLoader extends PureComponent {
|
||||
static propTypes = {
|
||||
backgroundColor: PropTypes.string,
|
||||
channelIsLoading: PropTypes.bool.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.animation = new Animated.Value(0);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.channelIsLoading) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.animation.stopAnimation();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.channelIsLoading !== this.props.channelIsLoading) {
|
||||
if (nextProps.channelIsLoading) {
|
||||
this.start();
|
||||
} else {
|
||||
this.animation.stopAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
this.animation.setValue(START_VALUE);
|
||||
Animated.sequence([
|
||||
Animated.timing(this.animation, {
|
||||
toValue: END_VALUE,
|
||||
duration: DURATION,
|
||||
easing: Easing.linear,
|
||||
}),
|
||||
]).start((animation) => {
|
||||
if (animation.finished) {
|
||||
this.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
buildSections({key, style, top, bg}) {
|
||||
const left = this.animation.interpolate({
|
||||
inputRange: [START_VALUE, END_VALUE],
|
||||
outputRange: ['-10%', '50%'],
|
||||
});
|
||||
|
||||
const opacity = this.animation.interpolate({
|
||||
inputRange: [START_VALUE, MIDDLE_VALUE, END_VALUE],
|
||||
outputRange: [GRADIENT_START_END, GRADIENT_MIDDLE, GRADIENT_START_END],
|
||||
});
|
||||
|
||||
const bodyPosLeft = this.animation.interpolate({
|
||||
inputRange: [START_VALUE, END_VALUE],
|
||||
outputRange: ['-20%', '80%'],
|
||||
});
|
||||
|
||||
const AnimatedGradient = Animated.createAnimatedComponent(LinearGradient);
|
||||
|
||||
buildSections({key, style, bg, color}) {
|
||||
return (
|
||||
<View
|
||||
key={key}
|
||||
style={[style.section, (top && {marginTop: Platform.OS === 'android' ? 0 : -15, paddingTop: 10})]}
|
||||
style={[style.section, {backgroundColor: bg}]}
|
||||
>
|
||||
<View style={style.avatar}/>
|
||||
<View style={style.sectionMessage}>
|
||||
<View>
|
||||
<View style={[style.messageText]}/>
|
||||
<AnimatedGradient
|
||||
start={{x: 0.0, y: 1.0}}
|
||||
end={{x: 1.0, y: 1.0}}
|
||||
colors={[
|
||||
changeOpacity(bg, GRADIENT_START_END),
|
||||
changeOpacity(bg, GRADIENT_MIDDLE),
|
||||
changeOpacity(bg, GRADIENT_START_END),
|
||||
]}
|
||||
locations={[0, 0.5, 1]}
|
||||
style={[style.gradientText, {left, opacity}]}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<View style={[style.messageText, {width: '100%'}]}/>
|
||||
<AnimatedGradient
|
||||
start={{x: 0.0, y: 1.0}}
|
||||
end={{x: 1.0, y: 1.0}}
|
||||
colors={[
|
||||
changeOpacity(bg, GRADIENT_START_END),
|
||||
changeOpacity(bg, GRADIENT_MIDDLE),
|
||||
changeOpacity(bg, GRADIENT_START_END),
|
||||
]}
|
||||
locations={[0, 0.5, 1]}
|
||||
style={[style.gradientText, {left: bodyPosLeft, opacity}]}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<View style={[style.messageText, {width: '100%'}]}/>
|
||||
<AnimatedGradient
|
||||
start={{x: 0.0, y: 1.0}}
|
||||
end={{x: 1.0, y: 1.0}}
|
||||
colors={[
|
||||
changeOpacity(bg, GRADIENT_START_END),
|
||||
changeOpacity(bg, GRADIENT_MIDDLE),
|
||||
changeOpacity(bg, GRADIENT_START_END),
|
||||
]}
|
||||
locations={[0, 0.5, 1]}
|
||||
style={[style.gradientText, {left: bodyPosLeft, opacity}]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Placeholder.ImageContent
|
||||
size={32}
|
||||
animate='fade'
|
||||
lineNumber={3}
|
||||
lineSpacing={5}
|
||||
firstLineWidth='80%'
|
||||
hasRadius={true}
|
||||
textSize={14}
|
||||
color={changeOpacity(color, 0.2)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {channelIsLoading, deviceWidth, theme} = this.props;
|
||||
const {channelIsLoading, theme} = this.props;
|
||||
|
||||
if (!channelIsLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const bg = this.props.backgroundColor || theme.centerChannelBg;
|
||||
|
||||
return (
|
||||
<View style={[style.container, {width: deviceWidth}]}>
|
||||
{Array(20).fill().map((item, index) => this.buildSections({key: index, style, top: index === 0, bg: theme.centerChannelBg}))}
|
||||
<View style={style.container}>
|
||||
{Array(6).fill().map((item, index) => this.buildSections({
|
||||
key: index,
|
||||
style,
|
||||
bg,
|
||||
color: theme.centerChannelColor,
|
||||
}))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -162,7 +66,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
container: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flex: 1,
|
||||
position: 'absolute',
|
||||
...Platform.select({
|
||||
android: {
|
||||
top: 0,
|
||||
|
|
@ -172,35 +75,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
}),
|
||||
},
|
||||
avatar: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderRadius: 16,
|
||||
height: 32,
|
||||
width: 32,
|
||||
},
|
||||
gradientText: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelBg, 0.2),
|
||||
height: 10,
|
||||
marginBottom: 10,
|
||||
position: 'absolute',
|
||||
width: '40%',
|
||||
},
|
||||
section: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flexDirection: 'row',
|
||||
flex: 1,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 20,
|
||||
marginVertical: 10,
|
||||
},
|
||||
sectionMessage: {
|
||||
marginLeft: 12,
|
||||
flex: 1,
|
||||
},
|
||||
messageText: {
|
||||
width: '80%',
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
height: 10,
|
||||
marginBottom: 10,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
|||
import ChannelLoader from './channel_loader';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {deviceWidth} = state.device.dimension;
|
||||
return {
|
||||
channelIsLoading: ownProps.channelIsLoading || state.views.channel.loading,
|
||||
deviceWidth,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import ChannelDrawer from 'app/components/channel_drawer';
|
||||
import EmptyToolbar from 'app/components/start/empty_toolbar';
|
||||
import SettingsDrawer from 'app/components/settings_drawer';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
import OfflineIndicator from 'app/components/offline_indicator';
|
||||
|
|
@ -47,6 +48,7 @@ export default class Channel extends PureComponent {
|
|||
currentChannelId: PropTypes.string,
|
||||
channelsRequestFailed: PropTypes.bool,
|
||||
currentTeamId: PropTypes.string,
|
||||
isLandscape: PropTypes.bool,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
@ -257,9 +259,15 @@ export default class Channel extends PureComponent {
|
|||
|
||||
const Loading = require('app/components/channel_loader').default;
|
||||
return (
|
||||
<View style={style.loading}>
|
||||
<Loading channelIsLoading={true}/>
|
||||
</View>
|
||||
<SafeAreaView navigator={navigator}>
|
||||
<View style={style.loading}>
|
||||
<EmptyToolbar
|
||||
theme={theme}
|
||||
isLandscape={this.props.isLandscape}
|
||||
/>
|
||||
<Loading channelIsLoading={true}/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
|
@ -9,6 +9,7 @@ import {init as initWebSocket, close as closeWebSocket} from 'mattermost-redux/a
|
|||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {
|
||||
loadChannelsIfNecessary,
|
||||
|
|
@ -18,7 +19,7 @@ import {
|
|||
import {connection} from 'app/actions/device';
|
||||
import {recordLoadTime} from 'app/actions/views/root';
|
||||
import {selectDefaultTeam} from 'app/actions/views/select_team';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import Channel from './channel';
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ function mapStateToProps(state) {
|
|||
channelsRequestFailed: channelsRequest.status === RequestStatus.FAILURE,
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
isLandscape: isLandscape(state),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {loadFromPushNotification} from 'app/actions/views/root';
|
|||
import {ViewTypes} from 'app/constants';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import {stripTrailingSlashes} from 'app/utils/url';
|
||||
import {wrapWithContextProvider} from 'app/utils/wrap_context_provider';
|
||||
|
||||
import ChannelLoader from 'app/components/channel_loader';
|
||||
import EmptyToolbar from 'app/components/start/empty_toolbar';
|
||||
|
|
@ -212,22 +213,21 @@ export default class Entry extends PureComponent {
|
|||
|
||||
renderLogin = () => {
|
||||
const SelectServer = lazyLoadSelectServer();
|
||||
return (
|
||||
<SelectServer
|
||||
navigator={this.props.navigator}
|
||||
allowOtherServers={app.allowOtherServers}
|
||||
/>
|
||||
);
|
||||
const props = {
|
||||
allowOtherServers: app.allowOtherServers,
|
||||
navigator: this.props.navigator,
|
||||
};
|
||||
|
||||
return wrapWithContextProvider(SelectServer)(props);
|
||||
};
|
||||
|
||||
renderChannel = () => {
|
||||
const ChannelScreen = lazyLoadChannel();
|
||||
const props = {
|
||||
navigator: this.props.navigator,
|
||||
};
|
||||
|
||||
return (
|
||||
<ChannelScreen
|
||||
navigator={this.props.navigator}
|
||||
/>
|
||||
);
|
||||
return wrapWithContextProvider(ChannelScreen, true)(props);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
@ -264,9 +264,10 @@ export default class Entry extends PureComponent {
|
|||
);
|
||||
|
||||
loading = (
|
||||
<View>
|
||||
<ChannelLoader channelIsLoading={true}/>
|
||||
</View>
|
||||
<ChannelLoader
|
||||
backgroundColor={backgroundColor}
|
||||
channelIsLoading={true}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
loading = <Loading/>;
|
||||
|
|
|
|||
|
|
@ -1,27 +1,12 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import Channel from 'app/screens/channel';
|
||||
import Entry from 'app/screens/entry';
|
||||
import SelectServer from 'app/screens/select_server';
|
||||
import IntlWrapper from 'app/components/root';
|
||||
|
||||
function wrapWithContextProvider(Comp, excludeEvents = true) {
|
||||
return (props) => { //eslint-disable-line react/display-name
|
||||
const {navigator} = props; //eslint-disable-line react/prop-types
|
||||
return (
|
||||
<IntlWrapper
|
||||
navigator={navigator}
|
||||
excludeEvents={excludeEvents}
|
||||
>
|
||||
<Comp {...props}/>
|
||||
</IntlWrapper>
|
||||
);
|
||||
};
|
||||
}
|
||||
import {wrapWithContextProvider} from 'app/utils/wrap_context_provider';
|
||||
|
||||
export function registerScreens(store, Provider) {
|
||||
Navigation.registerComponent('About', () => wrapWithContextProvider(require('app/screens/about').default), store, Provider);
|
||||
|
|
@ -40,7 +25,7 @@ export function registerScreens(store, Provider) {
|
|||
Navigation.registerComponent('EditChannel', () => wrapWithContextProvider(require('app/screens/edit_channel').default), store, Provider);
|
||||
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(require('app/screens/edit_post').default), store, Provider);
|
||||
Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(require('app/screens/edit_profile').default), store, Provider);
|
||||
Navigation.registerComponent('Entry', () => wrapWithContextProvider(Entry), store, Provider);
|
||||
Navigation.registerComponent('Entry', () => Entry, store, Provider);
|
||||
Navigation.registerComponent('FlaggedPosts', () => wrapWithContextProvider(require('app/screens/flagged_posts').default), store, Provider);
|
||||
Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(require('app/screens/image_preview').default), store, Provider);
|
||||
Navigation.registerComponent('Login', () => wrapWithContextProvider(require('app/screens/login').default), store, Provider);
|
||||
|
|
|
|||
19
app/utils/wrap_context_provider.js
Normal file
19
app/utils/wrap_context_provider.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import IntlWrapper from 'app/components/root';
|
||||
|
||||
export function wrapWithContextProvider(Comp, excludeEvents = true) {
|
||||
return (props) => { //eslint-disable-line react/display-name
|
||||
const {navigator} = props; //eslint-disable-line react/prop-types
|
||||
return (
|
||||
<IntlWrapper
|
||||
navigator={navigator}
|
||||
excludeEvents={excludeEvents}
|
||||
>
|
||||
<Comp {...props}/>
|
||||
</IntlWrapper>
|
||||
);
|
||||
};
|
||||
}
|
||||
7
package-lock.json
generated
7
package-lock.json
generated
|
|
@ -15494,6 +15494,13 @@
|
|||
"integrity": "sha1-JC124vpIXEjXUUFuZbfM5ZaWnpE=",
|
||||
"dev": true
|
||||
},
|
||||
"rn-placeholder": {
|
||||
"version": "github:enahum/rn-placeholder#4b065f892d7a7f9d921a969f2e72e609ebc0a212",
|
||||
"from": "github:enahum/rn-placeholder",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
},
|
||||
"rst-selector-parser": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz",
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
"redux-persist-transform-filter": "0.0.16",
|
||||
"redux-thunk": "2.2.0",
|
||||
"reselect": "3.0.1",
|
||||
"rn-placeholder": "github:enahum/rn-placeholder#4b065f892d7a7f9d921a969f2e72e609ebc0a212",
|
||||
"semver": "5.5.0",
|
||||
"shallow-equals": "1.0.0",
|
||||
"tinycolor2": "1.4.1",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -337,6 +337,7 @@ module.exports = ['./node_modules/react-native/Libraries/ART/ARTSerializablePath
|
|||
'./node_modules/app/utils/time_tracker.js',
|
||||
'./node_modules/app/utils/tooltip.js',
|
||||
'./node_modules/app/utils/url.js',
|
||||
'./node_modules/app/utils/wrap_context_provider.js',
|
||||
'./node_modules/react-native/Libraries/Utilities/binaryToBase64.js',
|
||||
'./node_modules/react-native/Libraries/Network/convertRequestBody.js',
|
||||
'./node_modules/react-native/Libraries/Animated/src/createAnimatedComponent.js',
|
||||
|
|
@ -994,6 +995,21 @@ module.exports = ['./node_modules/react-native/Libraries/ART/ARTSerializablePath
|
|||
'./node_modules/node_modules/requires-port/index.js',
|
||||
'./node_modules/node_modules/reselect/lib/index.js',
|
||||
'./node_modules/node_modules/rn-host-detect/index.js',
|
||||
'./node_modules/node_modules/rn-placeholder/index.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/animation/animations.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/animation/fade.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/animation/shine.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/box/box.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/box/box.style.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/imageContent/imageContent.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/line/line.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/line/line.style.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/media/media.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/media/media.style.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/multiWords/multiWords.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/paragraph/paragraph.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/placeholder.js',
|
||||
'./node_modules/node_modules/rn-placeholder/src/placeholderContainer.js',
|
||||
'./node_modules/node_modules/sc-channel/index.js',
|
||||
'./node_modules/node_modules/sc-emitter/index.js',
|
||||
'./node_modules/node_modules/sc-emitter/node_modules/component-emitter/index.js',
|
||||
|
|
|
|||
Loading…
Reference in a new issue