[MM-29381] Show an indicator if channel is still loading after 10 seconds (#5031)
* Show still-loading indicator * Update snapshot test * Call retryLoadChannels on a 10 second interval * Select default team on retryLoad if no currentTeamId * Fix use of jest.useFakeTimers
This commit is contained in:
parent
0ee7b60e84
commit
68d76af4dd
5 changed files with 174 additions and 1 deletions
|
|
@ -16,6 +16,68 @@ exports[`ChannelLoader should match snapshot 1`] = `
|
|||
]
|
||||
}
|
||||
>
|
||||
<ForwardRef(AnimatedComponentWrapper)
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#3D3C40",
|
||||
"height": 38,
|
||||
"position": "absolute",
|
||||
"width": "100%",
|
||||
"zIndex": 9,
|
||||
},
|
||||
Object {
|
||||
"top": -38,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ForwardRef(AnimatedComponentWrapper)
|
||||
edges={
|
||||
Array [
|
||||
"left",
|
||||
"right",
|
||||
]
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"height": 38,
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "flex-start",
|
||||
"height": 24,
|
||||
"justifyContent": "center",
|
||||
"paddingRight": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ActivityIndicator
|
||||
animating={true}
|
||||
color="#FFFFFF"
|
||||
hidesWhenStopped={true}
|
||||
size="small"
|
||||
/>
|
||||
</View>
|
||||
<FormattedText
|
||||
defaultMessage="Still trying to load your content..."
|
||||
id="mobile.channel_loader.still_loading"
|
||||
style={
|
||||
Object {
|
||||
"color": "#fff",
|
||||
"fontWeight": "bold",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</ForwardRef(AnimatedComponentWrapper)>
|
||||
</ForwardRef(AnimatedComponentWrapper)>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
|
|
|
|||
|
|
@ -4,15 +4,22 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Animated,
|
||||
View,
|
||||
Dimensions,
|
||||
Platform,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import * as RNPlaceholder from 'rn-placeholder';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
import {INDICATOR_BAR_HEIGHT} from '@constants/view';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView);
|
||||
|
||||
function calculateMaxRows(height) {
|
||||
return Math.round(height / 100);
|
||||
}
|
||||
|
|
@ -35,6 +42,7 @@ export default class ChannelLoader extends PureComponent {
|
|||
style: CustomPropTypes.Style,
|
||||
theme: PropTypes.object.isRequired,
|
||||
height: PropTypes.number,
|
||||
retryLoad: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -48,6 +56,8 @@ export default class ChannelLoader extends PureComponent {
|
|||
switch: false,
|
||||
maxRows,
|
||||
};
|
||||
|
||||
this.top = new Animated.Value(-INDICATOR_BAR_HEIGHT);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
|
|
@ -64,6 +74,25 @@ export default class ChannelLoader extends PureComponent {
|
|||
return Object.keys(state) ? state : null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.stillLoadingTimeout = setTimeout(this.showIndicator, 10000);
|
||||
this.retryLoadInterval = setInterval(this.props.retryLoad, 10000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.stillLoadingTimeout);
|
||||
clearInterval(this.retryLoadInterval);
|
||||
}
|
||||
|
||||
showIndicator = () => {
|
||||
Animated.timing(this.top, {
|
||||
toValue: 0,
|
||||
duration: 300,
|
||||
delay: 500,
|
||||
useNativeDriver: false,
|
||||
}).start();
|
||||
}
|
||||
|
||||
buildSections({key, style, bg, color}) {
|
||||
return (
|
||||
<View
|
||||
|
|
@ -119,6 +148,26 @@ export default class ChannelLoader extends PureComponent {
|
|||
style={[style.container, styleProp, {backgroundColor: bg}]}
|
||||
onLayout={this.handleLayout}
|
||||
>
|
||||
<Animated.View
|
||||
style={[style.indicator, {top: this.top}]}
|
||||
>
|
||||
<AnimatedSafeAreaView
|
||||
edges={['left', 'right']}
|
||||
style={style.indicatorWrapper}
|
||||
>
|
||||
<View style={style.activityIndicator}>
|
||||
<ActivityIndicator
|
||||
color='#FFFFFF'
|
||||
size='small'
|
||||
/>
|
||||
</View>
|
||||
<FormattedText
|
||||
id='mobile.channel_loader.still_loading'
|
||||
defaultMessage='Still trying to load your content...'
|
||||
style={style.indicatorText}
|
||||
/>
|
||||
</AnimatedSafeAreaView>
|
||||
</Animated.View>
|
||||
{Array(this.state.maxRows).fill().map((item, index) => this.buildSections({
|
||||
key: index,
|
||||
style,
|
||||
|
|
@ -143,5 +192,36 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
paddingRight: 20,
|
||||
marginVertical: 10,
|
||||
},
|
||||
indicator: {
|
||||
position: 'absolute',
|
||||
height: INDICATOR_BAR_HEIGHT,
|
||||
width: '100%',
|
||||
...Platform.select({
|
||||
android: {
|
||||
elevation: 9,
|
||||
},
|
||||
ios: {
|
||||
zIndex: 9,
|
||||
},
|
||||
}),
|
||||
backgroundColor: '#3D3C40',
|
||||
},
|
||||
indicatorWrapper: {
|
||||
alignItems: 'center',
|
||||
height: INDICATOR_BAR_HEIGHT,
|
||||
flexDirection: 'row',
|
||||
paddingLeft: 12,
|
||||
paddingRight: 5,
|
||||
},
|
||||
indicatorText: {
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
activityIndicator: {
|
||||
alignItems: 'flex-start',
|
||||
height: 24,
|
||||
justifyContent: 'center',
|
||||
paddingRight: 10,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,14 +8,34 @@ import Preferences from '@mm-redux/constants/preferences';
|
|||
|
||||
import ChannelLoader from './channel_loader';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('ChannelLoader', () => {
|
||||
const baseProps = {
|
||||
channelIsLoading: true,
|
||||
theme: Preferences.THEMES.default,
|
||||
retryLoad: jest.fn(),
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(<ChannelLoader {...baseProps}/>);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call setTimeout and setInterval for showIndicator and retryLoad on mount', () => {
|
||||
const wrapper = shallow(<ChannelLoader {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledWith(instance.showIndicator, 10000);
|
||||
expect(setInterval).toHaveBeenCalledWith(baseProps.retryLoad, 10000);
|
||||
});
|
||||
|
||||
test('should clear timer and interval on unmount', () => {
|
||||
const wrapper = shallow(<ChannelLoader {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
instance.componentWillUnmount();
|
||||
|
||||
expect(clearTimeout).toHaveBeenCalledWith(instance.stillLoadingTimeout);
|
||||
expect(clearInterval).toHaveBeenCalledWith(instance.retryLoadInterval);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -233,6 +233,15 @@ export default class ChannelBase extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
retryLoad = () => {
|
||||
const {currentTeamId, actions} = this.props;
|
||||
if (currentTeamId) {
|
||||
this.loadChannels(currentTeamId);
|
||||
} else {
|
||||
actions.selectDefaultTeam();
|
||||
}
|
||||
}
|
||||
|
||||
retryLoadChannels = () => {
|
||||
this.loadChannels(this.props.currentTeamId);
|
||||
};
|
||||
|
|
@ -270,6 +279,7 @@ export default class ChannelBase extends PureComponent {
|
|||
<Loading
|
||||
channelIsLoading={true}
|
||||
color={theme.centerChannelColor}
|
||||
retryLoad={this.retryLoad}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@
|
|||
"mobile.channel_list.members": "MEMBERS",
|
||||
"mobile.channel_list.not_member": "NOT A MEMBER",
|
||||
"mobile.channel_list.unreads": "UNREADS",
|
||||
"mobile.channel_loader.still_loading": "Still trying to load your content...",
|
||||
"mobile.channel_members.add_members_alert": "You must select at least one member to add to the channel.",
|
||||
"mobile.client_upgrade": "Update App",
|
||||
"mobile.client_upgrade.can_upgrade_subtitle": "A new version is available for download.",
|
||||
|
|
|
|||
Loading…
Reference in a new issue