mattermost-mobile/app/components/offline_indicator/offline_indicator.js

309 lines
8.6 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
ActivityIndicator,
Animated,
Platform,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import IonIcon from 'react-native-vector-icons/Ionicons';
import FormattedText from 'app/components/formatted_text';
import {ViewTypes} from 'app/constants';
import mattermostBucket from 'app/mattermost_bucket';
import checkNetwork from 'app/utils/network';
import {t} from 'app/utils/i18n';
import LocalConfig from 'assets/config';
import {RequestStatus} from 'mattermost-redux/constants';
const HEIGHT = 38;
const OFFLINE = 'offline';
const CONNECTING = 'connecting';
const CONNECTED = 'connected';
const {
ANDROID_TOP_LANDSCAPE,
ANDROID_TOP_PORTRAIT,
IOS_TOP_LANDSCAPE,
IOS_TOP_PORTRAIT,
IOSX_TOP_PORTRAIT,
STATUS_BAR_HEIGHT,
} = ViewTypes;
export default class OfflineIndicator extends Component {
static propTypes = {
actions: PropTypes.shape({
connection: PropTypes.func.isRequired,
initWebSocket: PropTypes.func.isRequired,
}).isRequired,
isConnecting: PropTypes.bool,
isLandscape: PropTypes.bool,
isOnline: PropTypes.bool,
webSocketStatus: PropTypes.string,
};
static defaultProps = {
isOnline: true,
};
constructor(props) {
super(props);
this.isX = DeviceInfo.getModel().includes('iPhone X');
const navBar = this.getNavBarHeight(props.isLandscape);
this.state = {
network: null,
navBar,
top: new Animated.Value(navBar - HEIGHT),
};
this.backgroundColor = new Animated.Value(0);
}
componentDidMount() {
this.mounted = true;
}
componentWillReceiveProps(nextProps) {
const {isLandscape, webSocketStatus} = this.props;
if (nextProps.isLandscape !== isLandscape && this.state.network) {
const navBar = this.getNavBarHeight(nextProps.isLandscape);
const top = new Animated.Value(navBar - HEIGHT);
this.setLocalState({navBar, top});
}
if (nextProps.isOnline) {
if (this.state.network && webSocketStatus === RequestStatus.STARTED && nextProps.webSocketStatus === RequestStatus.SUCCESS) {
// Show the connected animation only if we had a previous network status
this.connected();
} else if (webSocketStatus === RequestStatus.STARTED && nextProps.webSocketStatus === RequestStatus.FAILURE && nextProps.isConnecting) {
// Show the connecting bar if it failed to connect at least twice
this.connecting();
}
} else {
this.offline();
}
}
shouldComponentUpdate(nextProps, nextState) {
return (nextState.network !== this.state.network || nextProps.isLandscape !== this.props.isLandscape);
}
componentWillUnmount() {
this.mounted = false;
}
connect = () => {
checkNetwork((result) => {
this.setLocalState({network: CONNECTING}, () => {
if (result) {
this.props.actions.connection(true);
this.initializeWebSocket();
} else {
this.setLocalState({network: OFFLINE});
}
});
});
};
connected = () => {
this.setState({network: CONNECTED});
Animated.sequence([
Animated.timing(
this.backgroundColor, {
toValue: 1,
duration: 100,
}
),
Animated.timing(
this.state.top, {
toValue: (this.state.navBar - HEIGHT),
duration: 300,
delay: 500,
}
),
]).start(() => {
this.backgroundColor.setValue(0);
this.setLocalState({network: null});
});
};
connecting = () => {
const prevState = this.state.network;
this.setLocalState({network: CONNECTING}, () => {
if (prevState !== OFFLINE) {
this.show();
}
});
};
getNavBarHeight = (isLandscape) => {
if (Platform.OS === 'android') {
if (isLandscape) {
return ANDROID_TOP_LANDSCAPE;
}
return ANDROID_TOP_PORTRAIT;
}
if (this.isX && isLandscape) {
return IOS_TOP_LANDSCAPE;
} else if (this.isX) {
return IOSX_TOP_PORTRAIT;
} else if (isLandscape) {
return IOS_TOP_LANDSCAPE + STATUS_BAR_HEIGHT;
}
return IOS_TOP_PORTRAIT;
};
initializeWebSocket = async () => {
const {actions} = this.props;
const {initWebSocket} = actions;
const platform = Platform.OS;
let certificate = null;
if (platform === 'ios') {
certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId);
}
initWebSocket(platform, null, null, null, {certificate});
};
offline = () => {
this.setLocalState({network: OFFLINE}, () => {
this.show();
});
};
setLocalState = (state, callback) => {
if (!this.mounted) {
return;
}
this.setState(state, callback);
};
show = () => {
Animated.timing(
this.state.top, {
toValue: this.state.navBar,
duration: 300,
}
).start();
};
render() {
if (!this.state.network) {
return null;
}
const background = this.backgroundColor.interpolate({
inputRange: [0, 1],
outputRange: ['#939393', '#629a41'],
});
let i18nId;
let defaultMessage;
let action;
switch (this.state.network) {
case OFFLINE:
i18nId = t('mobile.offlineIndicator.offline');
defaultMessage = 'Cannot connect to the server';
action = (
<TouchableOpacity
onPress={this.connect}
style={[styles.actionContainer, styles.actionButton]}
>
<IonIcon
color='#FFFFFF'
name='ios-refresh'
size={20}
/>
</TouchableOpacity>
);
break;
case CONNECTING:
i18nId = t('mobile.offlineIndicator.connecting');
defaultMessage = 'Connecting...';
action = (
<View style={styles.actionContainer}>
<ActivityIndicator
color='#FFFFFF'
size='small'
/>
</View>
);
break;
case CONNECTED:
default:
i18nId = t('mobile.offlineIndicator.connected');
defaultMessage = 'Connected';
action = (
<View style={styles.actionContainer}>
<IonIcon
color='#FFFFFF'
name='md-checkmark'
size={20}
/>
</View>
);
break;
}
return (
<Animated.View style={[styles.container, {top: this.state.top, backgroundColor: background}]}>
<Animated.View style={styles.wrapper}>
<FormattedText
defaultMessage={defaultMessage}
id={i18nId}
style={styles.message}
/>
{action}
</Animated.View>
</Animated.View>
);
}
}
const styles = StyleSheet.create({
container: {
height: HEIGHT,
width: '100%',
zIndex: 9,
position: 'absolute',
},
wrapper: {
alignItems: 'center',
flex: 1,
height: HEIGHT,
flexDirection: 'row',
paddingLeft: 12,
paddingRight: 5,
},
message: {
color: '#FFFFFF',
fontSize: 14,
fontWeight: '600',
flex: 1,
},
actionButton: {
alignItems: 'center',
borderWidth: 1,
borderColor: '#FFFFFF',
},
actionContainer: {
alignItems: 'flex-end',
height: 24,
justifyContent: 'center',
paddingRight: 10,
width: 60,
},
});