Prompt/require user to upgrade to new version (#948)
* Prompt/require user to upgrade to new version * Update en.json * Update app message text * Fix typo and add copyright
This commit is contained in:
parent
59ce8571c3
commit
54ddec3d46
18 changed files with 853 additions and 31 deletions
10
app/actions/views/client_upgrade.js
Normal file
10
app/actions/views/client_upgrade.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
export function setLastUpgradeCheck() {
|
||||
return {
|
||||
type: ViewTypes.SET_LAST_UPGRADE_CHECK
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Alert,
|
||||
Animated,
|
||||
Linking,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {UpgradeTypes} from 'app/constants/view';
|
||||
import checkUpgradeType from 'app/utils/client_upgrade';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
const UPDATE_TIMEOUT = 60000;
|
||||
|
||||
class ClientUpgradeListener extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
logError: PropTypes.func.isRequired,
|
||||
setLastUpgradeCheck: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentVersion: PropTypes.string,
|
||||
downloadLink: PropTypes.string,
|
||||
forceUpgrade: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
lastUpgradeCheck: PropTypes.number,
|
||||
latestVersion: PropTypes.string,
|
||||
minVersion: PropTypes.string,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
top: new Animated.Value(-100)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {forceUpgrade, lastUpgradeCheck, latestVersion, minVersion} = this.props;
|
||||
if (forceUpgrade || Date.now() - lastUpgradeCheck > UPDATE_TIMEOUT) {
|
||||
this.checkUpgrade(minVersion, latestVersion);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {forceUpgrade, latestVersion, minVersion} = this.props;
|
||||
const {latestVersion: nextLatestVersion, minVersion: nextMinVersion, lastUpgradeCheck} = nextProps;
|
||||
|
||||
const versionMismatch = latestVersion !== nextLatestVersion || minVersion !== nextMinVersion;
|
||||
if (versionMismatch && (forceUpgrade || Date.now() - lastUpgradeCheck > UPDATE_TIMEOUT)) {
|
||||
this.checkUpgrade(minVersion, latestVersion);
|
||||
}
|
||||
}
|
||||
|
||||
checkUpgrade = (minVersion, latestVersion) => {
|
||||
const {actions, currentVersion} = this.props;
|
||||
|
||||
const upgradeType = checkUpgradeType(currentVersion, minVersion, latestVersion, actions.logError);
|
||||
|
||||
if (upgradeType === UpgradeTypes.NO_UPGRADE) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({upgradeType});
|
||||
|
||||
setTimeout(this.toggleUpgradeMessage, 500);
|
||||
|
||||
actions.setLastUpgradeCheck();
|
||||
}
|
||||
|
||||
toggleUpgradeMessage = (show = true) => {
|
||||
const toValue = show ? 75 : -100;
|
||||
Animated.timing(this.state.top, {
|
||||
toValue,
|
||||
duration: 300
|
||||
}).start();
|
||||
}
|
||||
|
||||
handleDismiss = () => {
|
||||
this.toggleUpgradeMessage(false);
|
||||
}
|
||||
|
||||
handleDownload = () => {
|
||||
const {downloadLink, intl} = this.props;
|
||||
|
||||
Linking.canOpenURL(downloadLink).then((supported) => {
|
||||
if (supported) {
|
||||
return Linking.openURL(downloadLink);
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.client_upgrade.download_error.title',
|
||||
defaultMessage: 'Upgrade Error'
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.client_upgrade.download_error.message',
|
||||
defaultMessage: 'An error occurred while trying to open the download link.'
|
||||
})
|
||||
);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.toggleUpgradeMessage(false);
|
||||
}
|
||||
|
||||
handleLearnMore = () => {
|
||||
this.props.navigator.dismissAllModals({animationType: 'none'});
|
||||
|
||||
this.props.navigator.showModal({
|
||||
screen: 'ClientUpgrade',
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: true,
|
||||
statusBarHideWithNavBar: true
|
||||
},
|
||||
passProps: {
|
||||
upgradeType: this.state.upgradeType
|
||||
}
|
||||
});
|
||||
|
||||
this.toggleUpgradeMessage(false);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {forceUpgrade, theme} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<AnimatedView
|
||||
style={[styles.wrapper, {top: this.state.top}]}
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.message}>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.listener.message'
|
||||
defaultMessage='A client upgrade is available!'
|
||||
style={styles.messageText}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.bottom}>
|
||||
<TouchableOpacity onPress={this.handleDownload}>
|
||||
<FormattedText
|
||||
style={styles.button}
|
||||
id='mobile.client_upgrade.listener.upgrade_button'
|
||||
defaultMessage='Upgrade'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={this.handleLearnMore}>
|
||||
<FormattedText
|
||||
style={styles.button}
|
||||
id='mobile.client_upgrade.listener.learn_more_button'
|
||||
defaultMessage='Learn More'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
{!forceUpgrade &&
|
||||
<TouchableOpacity onPress={this.handleDismiss}>
|
||||
<FormattedText
|
||||
style={styles.button}
|
||||
id='mobile.client_upgrade.listener.dismiss_button'
|
||||
defaultMessage='Dismiss'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
</AnimatedView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
bottom: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
borderTopWidth: 1
|
||||
},
|
||||
button: {
|
||||
color: theme.linkColor,
|
||||
fontSize: 13,
|
||||
paddingHorizontal: 5,
|
||||
paddingVertical: 5
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelBg, 0.8),
|
||||
borderRadius: 5
|
||||
},
|
||||
message: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
messageText: {
|
||||
fontSize: 16,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.8),
|
||||
fontWeight: '600'
|
||||
},
|
||||
wrapper: {
|
||||
position: 'absolute',
|
||||
elevation: 5,
|
||||
left: 30,
|
||||
right: 30,
|
||||
height: 75,
|
||||
backgroundColor: 'white',
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderWidth: 2,
|
||||
borderRadius: 5,
|
||||
shadowColor: theme.centerChannelColor,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 3
|
||||
},
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 2
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export default injectIntl(ClientUpgradeListener);
|
||||
35
app/components/client_upgrade_listener/index.js
Normal file
35
app/components/client_upgrade_listener/index.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {logError} from 'mattermost-redux/actions/errors';
|
||||
|
||||
import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade';
|
||||
import getClientUpgrade from 'app/selectors/client_upgrade';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import ClientUpgradeListener from './client_upgrade_listener';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {currentVersion, downloadLink, forceUpgrade, latestVersion, minVersion} = getClientUpgrade(state);
|
||||
|
||||
return {
|
||||
currentVersion,
|
||||
downloadLink,
|
||||
forceUpgrade,
|
||||
lastUpgradeCheck: state.views.clientUpgrade.lastUpdateCheck,
|
||||
latestVersion,
|
||||
minVersion,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
logError,
|
||||
setLastUpgradeCheck
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ClientUpgradeListener);
|
||||
|
|
@ -3,6 +3,12 @@
|
|||
|
||||
import keyMirror from 'mattermost-redux/utils/key_mirror';
|
||||
|
||||
export const UpgradeTypes = {
|
||||
CAN_UPGRADE: 'can_upgrade',
|
||||
MUST_UPGRADE: 'must_upgrade',
|
||||
NO_UPGRADE: 'no_upgrade'
|
||||
};
|
||||
|
||||
const ViewTypes = keyMirror({
|
||||
SERVER_URL_CHANGED: null,
|
||||
|
||||
|
|
@ -44,7 +50,9 @@ const ViewTypes = keyMirror({
|
|||
RECEIVED_FOCUSED_POST: null,
|
||||
LOADING_POSTS: null,
|
||||
|
||||
RECEIVED_POSTS_FOR_CHANNEL_AT_TIME: null
|
||||
RECEIVED_POSTS_FOR_CHANNEL_AT_TIME: null,
|
||||
|
||||
SET_LAST_UPGRADE_CHECK: null
|
||||
});
|
||||
|
||||
export default {
|
||||
|
|
|
|||
16
app/reducers/views/client_upgrade.js
Normal file
16
app/reducers/views/client_upgrade.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const initialState = {
|
||||
lastUpdateCheck: 0
|
||||
};
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
export default function clientUpgrade(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.SET_LAST_UPGRADE_CHECK:
|
||||
return {
|
||||
lastUpdateCheck: Date.now()
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
import {combineReducers} from 'redux';
|
||||
|
||||
import channel from './channel';
|
||||
import clientUpgrade from './client_upgrade';
|
||||
import fetchCache from './fetch_cache';
|
||||
import i18n from './i18n';
|
||||
import login from './login';
|
||||
|
|
@ -15,6 +16,7 @@ import thread from './thread';
|
|||
|
||||
export default combineReducers({
|
||||
channel,
|
||||
clientUpgrade,
|
||||
fetchCache,
|
||||
i18n,
|
||||
login,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import ClientUpgradeListener from 'app/components/client_upgrade_listener';
|
||||
import ChannelDrawer from 'app/components/channel_drawer';
|
||||
import ChannelLoader from 'app/components/channel_loader';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
|
|
@ -23,6 +24,8 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
|||
import PostTextbox from 'app/components/post_textbox';
|
||||
import networkConnectionListener from 'app/utils/network';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
import ChannelDrawerButton from './channel_drawer_button';
|
||||
import ChannelPostList from './channel_post_list';
|
||||
import ChannelSearchButton from './channel_search_button';
|
||||
|
|
@ -43,12 +46,12 @@ class Channel extends PureComponent {
|
|||
stopPeriodicStatusUpdates: PropTypes.func.isRequired,
|
||||
viewChannel: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentChannelId: PropTypes.string,
|
||||
channelsRequestFailed: PropTypes.bool,
|
||||
currentTeamId: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
currentTeamId: PropTypes.string,
|
||||
currentChannelId: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
channelsRequestFailed: PropTypes.bool
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
|
|
@ -215,7 +218,9 @@ class Channel extends PureComponent {
|
|||
ref={this.attachPostTextbox}
|
||||
navigator={navigator}
|
||||
/>
|
||||
<ChannelLoader theme={theme}/>
|
||||
</KeyboardLayout>
|
||||
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
|
||||
</ChannelDrawer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ function mapStateToProps(state) {
|
|||
const {myChannels: channelsRequest} = state.requests.channels;
|
||||
|
||||
return {
|
||||
channelsRequestFailed: channelsRequest.status === RequestStatus.FAILURE,
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
theme: getTheme(state),
|
||||
channelsRequestFailed: channelsRequest.status === RequestStatus.FAILURE
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
322
app/screens/client_upgrade/client_upgrade.js
Normal file
322
app/screens/client_upgrade/client_upgrade.js
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Alert,
|
||||
Image,
|
||||
Linking,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {UpgradeTypes} from 'app/constants/view';
|
||||
import logo from 'assets/images/logo.png';
|
||||
import checkUpgradeType from 'app/utils/client_upgrade';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
class ClientUpgrade extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
logError: PropTypes.func.isRequired,
|
||||
setLastUpgradeCheck: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentVersion: PropTypes.string,
|
||||
closeAction: PropTypes.func,
|
||||
userCheckedForUpgrade: PropTypes.bool,
|
||||
downloadLink: PropTypes.string.isRequired,
|
||||
forceUpgrade: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
latestVersion: PropTypes.string,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired,
|
||||
upgradeType: PropTypes.string
|
||||
};
|
||||
|
||||
state = {
|
||||
upgradeType: UpgradeTypes.NO_UPGRADE
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.userCheckedForUpgrade) {
|
||||
this.checkUpgrade(this.props);
|
||||
}
|
||||
}
|
||||
|
||||
checkUpgrade = ({minVersion, latestVersion}) => {
|
||||
const {actions, currentVersion, downloadLink} = this.props;
|
||||
|
||||
// We need at least minVersion or latestVersion and the app downloadlink
|
||||
if (!(latestVersion || minVersion) || !downloadLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
const upgradeType = checkUpgradeType(currentVersion, minVersion, latestVersion, actions.logError);
|
||||
|
||||
if (upgradeType === UpgradeTypes.NO_UPGRADE) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
upgradeType
|
||||
});
|
||||
|
||||
actions.setLastUpgradeCheck();
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
if (this.props.closeAction) {
|
||||
this.props.closeAction();
|
||||
} else if (this.props.userCheckedForUpgrade) {
|
||||
this.props.navigator.pop();
|
||||
} else {
|
||||
this.props.navigator.dismissModal();
|
||||
}
|
||||
}
|
||||
|
||||
handleDownload = () => {
|
||||
const {downloadLink, intl} = this.props;
|
||||
|
||||
Linking.canOpenURL(downloadLink).then((supported) => {
|
||||
if (supported) {
|
||||
return Linking.openURL(downloadLink);
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.client_upgrade.download_error.title',
|
||||
defaultMessage: 'Upgrade Error'
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.client_upgrade.download_error.message',
|
||||
defaultMessage: 'An error occurred while trying to open the download link.'
|
||||
})
|
||||
);
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
handleUpgrade = () => {
|
||||
const {actions, downloadLink} = this.props;
|
||||
|
||||
try {
|
||||
Linking.openURL(downloadLink);
|
||||
} catch (error) {
|
||||
actions.logError(error);
|
||||
}
|
||||
}
|
||||
|
||||
renderMustUpgrade() {
|
||||
const {theme} = this.props;
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={styles.messageContent}>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.must_upgrade_title'
|
||||
defaultMessage='Upgrade Required'
|
||||
style={styles.messageTitle}
|
||||
/>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.must_upgrade_subtitle'
|
||||
defaultMessage='Please update the app to continue.'
|
||||
style={styles.messageSubtitle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderCanUpgrade() {
|
||||
const {theme} = this.props;
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={styles.messageContent}>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.can_upgrade_title'
|
||||
defaultMessage='Upgrade Available!'
|
||||
style={styles.messageTitle}
|
||||
/>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.can_upgrade_subtitle'
|
||||
defaultMessage={'There\'s a new upgrade ready for download.'}
|
||||
style={styles.messageSubtitle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderNoUpgrade() {
|
||||
const {theme} = this.props;
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={styles.messageContent}>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.no_upgrade_title'
|
||||
defaultMessage='Your App Is Up to Date'
|
||||
style={styles.messageTitle}
|
||||
/>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.no_upgrade_subtitle'
|
||||
defaultMessage={'You\'re already using the latest version.'}
|
||||
style={styles.messageSubtitle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderMessageContent = () => {
|
||||
const {currentVersion, forceUpgrade, latestVersion, upgradeType: passedUpgradeType, userCheckedForUpgrade, theme} = this.props;
|
||||
const styles = getStyleFromTheme(theme);
|
||||
const upgradeType = userCheckedForUpgrade ? this.state.upgradeType : passedUpgradeType;
|
||||
|
||||
let messageComponent;
|
||||
|
||||
switch (upgradeType) {
|
||||
case UpgradeTypes.CAN_UPGRADE:
|
||||
messageComponent = this.renderCanUpgrade();
|
||||
break;
|
||||
case UpgradeTypes.MUST_UPGRADE:
|
||||
messageComponent = this.renderMustUpgrade();
|
||||
break;
|
||||
default:
|
||||
case UpgradeTypes.NO_UPGRADE:
|
||||
messageComponent = this.renderNoUpgrade();
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.messageContainer}>
|
||||
{messageComponent}
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.current_version'
|
||||
defaultMessage='Lastest Version: {version}'
|
||||
style={styles.messageSubtitle}
|
||||
values={{
|
||||
version: latestVersion
|
||||
}}
|
||||
/>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.latest_version'
|
||||
defaultMessage='Your Version: {version}'
|
||||
style={styles.messageSubtitle}
|
||||
values={{
|
||||
version: currentVersion
|
||||
}}
|
||||
/>
|
||||
{upgradeType !== UpgradeTypes.NO_UPGRADE &&
|
||||
<View>
|
||||
<TouchableOpacity
|
||||
onPress={this.handleDownload}
|
||||
style={styles.messageButton}
|
||||
>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.upgrade'
|
||||
defaultMessage='Upgrade'
|
||||
style={styles.messageButtonText}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
{!forceUpgrade &&
|
||||
<TouchableOpacity
|
||||
onPress={this.handleClose}
|
||||
style={styles.messageCloseButton}
|
||||
>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.close'
|
||||
defaultMessage='Upgrade Later'
|
||||
style={styles.messageCloseButtonText}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollViewContent}
|
||||
>
|
||||
<StatusBar/>
|
||||
<Image
|
||||
source={logo}
|
||||
style={styles.image}
|
||||
/>
|
||||
{this.renderMessageContent()}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
image: {
|
||||
marginTop: 75,
|
||||
width: 76,
|
||||
height: 75
|
||||
},
|
||||
messageButton: {
|
||||
marginBottom: 15,
|
||||
borderWidth: 2,
|
||||
borderRadius: 2,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderColor: theme.buttonBg
|
||||
},
|
||||
messageButtonText: {
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 20,
|
||||
fontWeight: '600',
|
||||
color: theme.buttonBg
|
||||
},
|
||||
messageContainer: {
|
||||
marginTop: 25,
|
||||
flex: 1,
|
||||
alignSelf: 'stretch',
|
||||
alignItems: 'center'
|
||||
},
|
||||
messageCloseButton: {
|
||||
marginBottom: 15,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
messageSubtitle: {
|
||||
fontSize: 16,
|
||||
marginBottom: 20,
|
||||
color: theme.centerChannelColor,
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 30
|
||||
},
|
||||
messageTitle: {
|
||||
fontSize: 24,
|
||||
marginBottom: 20,
|
||||
fontWeight: '600',
|
||||
color: theme.centerChannelColor,
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 30
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1
|
||||
},
|
||||
scrollViewContent: {
|
||||
paddingBottom: 20,
|
||||
alignItems: 'center'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export default injectIntl(ClientUpgrade);
|
||||
34
app/screens/client_upgrade/index.js
Normal file
34
app/screens/client_upgrade/index.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {logError} from 'mattermost-redux/actions/errors';
|
||||
|
||||
import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade';
|
||||
import getClientUpgrade from 'app/selectors/client_upgrade';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import ClientUpgrade from './client_upgrade';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {currentVersion, downloadLink, forceUpgrade, latestVersion, minVersion} = getClientUpgrade(state);
|
||||
|
||||
return {
|
||||
currentVersion,
|
||||
downloadLink,
|
||||
forceUpgrade,
|
||||
latestVersion,
|
||||
minVersion,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
logError,
|
||||
setLastUpgradeCheck
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ClientUpgrade);
|
||||
|
|
@ -12,6 +12,7 @@ import Channel from 'app/screens/channel';
|
|||
import ChannelAddMembers from 'app/screens/channel_add_members';
|
||||
import ChannelInfo from 'app/screens/channel_info';
|
||||
import ChannelMembers from 'app/screens/channel_members';
|
||||
import ClientUpgrade from 'app/screens/client_upgrade';
|
||||
import Code from 'app/screens/code';
|
||||
import CreateChannel from 'app/screens/create_channel';
|
||||
import EditPost from 'app/screens/edit_post';
|
||||
|
|
@ -62,6 +63,7 @@ export function registerScreens(store, Provider) {
|
|||
Navigation.registerComponent('ChannelAddMembers', () => wrapWithContextProvider(ChannelAddMembers), store, Provider);
|
||||
Navigation.registerComponent('ChannelInfo', () => wrapWithContextProvider(ChannelInfo), store, Provider);
|
||||
Navigation.registerComponent('ChannelMembers', () => wrapWithContextProvider(ChannelMembers), store, Provider);
|
||||
Navigation.registerComponent('ClientUpgrade', () => wrapWithContextProvider(ClientUpgrade), store, Provider);
|
||||
Navigation.registerComponent('Code', () => wrapWithContextProvider(Code), store, Provider);
|
||||
Navigation.registerComponent('CreateChannel', () => wrapWithContextProvider(CreateChannel), store, Provider);
|
||||
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(EditPost), store, Provider);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import {connect} from 'react-redux';
|
|||
import {getPing, resetPing} from 'mattermost-redux/actions/general';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade';
|
||||
import {handleServerUrlChanged} from 'app/actions/views/select_server';
|
||||
import getClientUpgrade from 'app/selectors/client_upgrade';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import SelectServer from './select_server';
|
||||
|
|
@ -15,28 +17,33 @@ import SelectServer from './select_server';
|
|||
function mapStateToProps(state) {
|
||||
const {config: configRequest, license: licenseRequest, server: pingRequest} = state.requests.general;
|
||||
const {config, license} = state.entities.general;
|
||||
const {currentVersion, latestVersion, minVersion} = getClientUpgrade(state);
|
||||
|
||||
const success = RequestStatus.SUCCESS;
|
||||
const transition = (pingRequest.status === success && configRequest.status === success && licenseRequest.status === success);
|
||||
|
||||
return {
|
||||
...state.views.selectServer,
|
||||
pingRequest,
|
||||
configRequest,
|
||||
licenseRequest,
|
||||
config,
|
||||
configRequest,
|
||||
currentVersion,
|
||||
pingRequest,
|
||||
latestVersion,
|
||||
license,
|
||||
transition,
|
||||
theme: getTheme(state)
|
||||
licenseRequest,
|
||||
minVersion,
|
||||
theme: getTheme(state),
|
||||
transition
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
handleServerUrlChanged,
|
||||
getPing,
|
||||
resetPing
|
||||
handleServerUrlChanged,
|
||||
resetPing,
|
||||
setLastUpgradeCheck
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
View
|
||||
} from 'react-native';
|
||||
import Button from 'react-native-button';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import {Client, Client4} from 'mattermost-redux/client';
|
||||
|
|
@ -24,29 +25,34 @@ import FormattedText from 'app/components/formatted_text';
|
|||
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
|
||||
import {GlobalStyles} from 'app/styles';
|
||||
import {isValidUrl, stripTrailingSlashes} from 'app/utils/url';
|
||||
import {UpgradeTypes} from 'app/constants/view';
|
||||
import checkUpgradeType from 'app/utils/client_upgrade';
|
||||
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
import logo from 'assets/images/logo.png';
|
||||
|
||||
class SelectServer extends PureComponent {
|
||||
static propTypes = {
|
||||
allowOtherServers: PropTypes.bool,
|
||||
navigator: PropTypes.object,
|
||||
intl: intlShape.isRequired,
|
||||
config: PropTypes.object,
|
||||
license: PropTypes.object,
|
||||
theme: PropTypes.object,
|
||||
transition: PropTypes.bool.isRequired,
|
||||
serverUrl: PropTypes.string.isRequired,
|
||||
pingRequest: PropTypes.object.isRequired,
|
||||
configRequest: PropTypes.object.isRequired,
|
||||
licenseRequest: PropTypes.object.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
getPing: PropTypes.func.isRequired,
|
||||
handleServerUrlChanged: PropTypes.func.isRequired,
|
||||
resetPing: PropTypes.func.isRequired,
|
||||
handleServerUrlChanged: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
setLastUpgradeCheck: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
allowOtherServers: PropTypes.bool,
|
||||
config: PropTypes.object,
|
||||
configRequest: PropTypes.object.isRequired,
|
||||
currentVersion: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
latestVersion: PropTypes.string,
|
||||
license: PropTypes.object,
|
||||
licenseRequest: PropTypes.object.isRequired,
|
||||
minVersion: PropTypes.string,
|
||||
navigator: PropTypes.object,
|
||||
pingRequest: PropTypes.object.isRequired,
|
||||
serverUrl: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object,
|
||||
transition: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -72,7 +78,18 @@ class SelectServer extends PureComponent {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this.props.transition && nextProps.transition) {
|
||||
this.handleLoginOptions();
|
||||
if (LocalConfig.EnableMobileClientUpgrade) {
|
||||
this.props.actions.setLastUpgradeCheck();
|
||||
const {currentVersion, minVersion, latestVersion} = nextProps;
|
||||
const upgradeType = checkUpgradeType(currentVersion, minVersion, latestVersion);
|
||||
if (upgradeType === UpgradeTypes.NO_UPGRADE) {
|
||||
this.handleLoginOptions();
|
||||
} else {
|
||||
this.handleShowClientUpgrade(upgradeType);
|
||||
}
|
||||
} else {
|
||||
this.handleLoginOptions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +99,28 @@ class SelectServer extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleShowClientUpgrade = (upgradeType) => {
|
||||
const {intl, theme} = this.props;
|
||||
|
||||
this.props.navigator.push({
|
||||
screen: 'ClientUpgrade',
|
||||
title: intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Client Upgrade'}),
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarHidden: false,
|
||||
statusBarHidden: true,
|
||||
statusBarHideWithNavBar: true,
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor
|
||||
},
|
||||
passProps: {
|
||||
closeAction: this.handleLoginOptions,
|
||||
upgradeType
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleLoginOptions = () => {
|
||||
const {config, intl, license, theme} = this.props;
|
||||
const samlEnabled = config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true';
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import {preventDoubleTap} from 'app/utils/tap';
|
|||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {isValidUrl} from 'app/utils/url';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
class Settings extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
|
|
@ -134,6 +136,26 @@ class Settings extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
goToClientUpgrade = () => {
|
||||
const {intl, theme} = this.props;
|
||||
|
||||
this.props.navigator.push({
|
||||
screen: 'ClientUpgrade',
|
||||
title: intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Client Upgrade'}),
|
||||
navigatorStyle: {
|
||||
navBarHidden: false,
|
||||
statusBarHidden: true,
|
||||
statusBarHideWithNavBar: true,
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor
|
||||
},
|
||||
passProps: {
|
||||
userCheckedForUpgrade: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handlePress = (action) => {
|
||||
preventDoubleTap(action, this);
|
||||
};
|
||||
|
|
@ -229,6 +251,17 @@ class Settings extends PureComponent {
|
|||
showArrow={showArrow}
|
||||
theme={theme}
|
||||
/>
|
||||
{LocalConfig.EnableMobileClientUpgrade && LocalConfig.EnableMobileClientUpgradeUserSetting &&
|
||||
<SettingsItem
|
||||
defaultMessage='Check for Upgrade'
|
||||
i18nId='mobile.settings.modal.check_for_upgrade'
|
||||
iconName='update'
|
||||
iconType='material'
|
||||
onPress={() => this.handlePress(this.goToClientUpgrade)}
|
||||
showArrow={showArrow}
|
||||
theme={theme}
|
||||
/>
|
||||
}
|
||||
<SettingsItem
|
||||
defaultMessage='About Mattermost'
|
||||
i18nId='about.title'
|
||||
|
|
|
|||
33
app/selectors/client_upgrade.js
Normal file
33
app/selectors/client_upgrade.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import {createSelector} from 'reselect';
|
||||
import {Platform} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
const getClientUpgrade = createSelector(
|
||||
getConfig,
|
||||
(config) => {
|
||||
const {AndroidMinVersion, AndroidLatestVersion, IosMinVersion, IosLatestVersion} = config;
|
||||
|
||||
let minVersion = IosMinVersion;
|
||||
let latestVersion = IosLatestVersion;
|
||||
let downloadLink = LocalConfig.MobileClientUpgradeIosIpaLink;
|
||||
if (Platform.OS === 'android') {
|
||||
minVersion = AndroidMinVersion;
|
||||
latestVersion = AndroidLatestVersion;
|
||||
downloadLink = LocalConfig.MobileClientUpgradeAndroidApkLink;
|
||||
}
|
||||
|
||||
return {
|
||||
currentVersion: DeviceInfo.getVersion(),
|
||||
downloadLink,
|
||||
forceUpgrade: LocalConfig.EnableForceMobileClientUpgrade,
|
||||
latestVersion,
|
||||
minVersion
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export default getClientUpgrade;
|
||||
25
app/utils/client_upgrade.js
Normal file
25
app/utils/client_upgrade.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import semver from 'semver';
|
||||
|
||||
import {UpgradeTypes} from 'app/constants/view';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
export default function checkUpgradeType(currentVersion, minVersion, latestVersion, logError) {
|
||||
let upgradeType = UpgradeTypes.NO_UPGRADE;
|
||||
|
||||
try {
|
||||
if (minVersion && semver.lt(currentVersion, minVersion)) {
|
||||
upgradeType = UpgradeTypes.MUST_UPGRADE;
|
||||
} else if (latestVersion && semver.lt(currentVersion, latestVersion)) {
|
||||
if (LocalConfig.EnableForceMobileClientUpgrade) {
|
||||
upgradeType = UpgradeTypes.MUST_UPGRADE;
|
||||
} else {
|
||||
upgradeType = UpgradeTypes.CAN_UPGRADE;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logError(error.message);
|
||||
}
|
||||
|
||||
return upgradeType;
|
||||
}
|
||||
|
|
@ -21,5 +21,11 @@
|
|||
"xhr": false,
|
||||
"console": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"EnableMobileClientUpgrade": false,
|
||||
"EnableMobileClientUpgradeUserSetting": false,
|
||||
"EnableForceMobileClientUpgrade": false,
|
||||
"MobileClientUpgradeAndroidApkLink": "",
|
||||
"MobileClientUpgradeIosIpaLink": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1913,6 +1913,19 @@
|
|||
"mobile.channel_list.privateChannel": "Private Channel",
|
||||
"mobile.channel_list.publicChannel": "Public Channel",
|
||||
"mobile.channel_list.unreads": "UNREADS",
|
||||
"mobile.client_upgrade": "Update App",
|
||||
"mobile.client_upgrade.can_upgrade_title": "Update Available",
|
||||
"mobile.client_upgrade.can_upgrade_subtitle": "A new version is available for download.",
|
||||
"mobile.client_upgrade.close": "Update Later",
|
||||
"mobile.client_upgrade.current_version": "Latest Version: {version}",
|
||||
"mobile.client_upgrade.download_error.title": "Unable to Install Update",
|
||||
"mobile.client_upgrade.download_error.message": "An error occurred downloading the new version.",
|
||||
"mobile.client_upgrade.latest_version": "Your Version: {version}",
|
||||
"mobile.client_upgrade.must_upgrade_title": "Update Required",
|
||||
"mobile.client_upgrade.must_upgrade_subtitle": "Please update the app to continue.",
|
||||
"mobile.client_upgrade.no_upgrade_title": "Your App Is Up to Date",
|
||||
"mobile.client_upgrade.no_upgrade_subtitle": "You already have the latest version.",
|
||||
"mobile.client_upgrade.upgrade": "Update",
|
||||
"mobile.components.channels_list_view.yourChannels": "Your channels:",
|
||||
"mobile.components.error_list.dismiss_all": "Dismiss All",
|
||||
"mobile.components.select_server_view.continue": "Continue",
|
||||
|
|
@ -2044,6 +2057,7 @@
|
|||
"mobile.server_upgrade.title": "Server upgrade required",
|
||||
"mobile.server_url.invalid_format": "URL must start with http:// or https://",
|
||||
"mobile.session_expired": "Session Expired: Please log in to continue receiving notifications.",
|
||||
"mobile.settings.modal.check_for_upgrade": "Check for Updates",
|
||||
"mobile.settings.clear": "Clear Offline Store",
|
||||
"mobile.settings.clear_button": "Clear",
|
||||
"mobile.settings.clear_message": "\nThis will clear all offline data and restart the app. You will be automatically logged back in once the app restarts.\n",
|
||||
|
|
|
|||
Loading…
Reference in a new issue