Skip to content

Commit

Permalink
[#167319140]Added modal with a message to invite the user to update a…
Browse files Browse the repository at this point in the history
…pp (#1377)

* [#167319140] Added popup message to invite the user to update app

Added popup message to invite the user to update app

* [#167319140] Fix - removed number

Fix - removed number

* [#167319140] Fixed name boolean

Fixed name boolean

* [#167319140] Removed timeout

Removed timeout

* [#167319140] Check on rendering

Check on rendering

* [#167319140] Removed alert and created screen - wip

Removed alert and created screen - wip

* [#167319140] Added footers with buttons

Added footers with buttons

* [#167319140] Redirect to testflight to update app

Redirect to testflight to update app only for ios

* [#167319140] Reduced text size

Reduced text size

* [#167319140] Hide identification modal if version app not supported

Hide identification modal if version app not supported

* [#167319140] Used modal instead screen

Used modal instead screen

* [#167319140] Fix if-else statememt to show modal

Fix if-else statememt to show modal

* [#167319140] Little fix locales

Little fix locales

* [#167319140] Removed unused route

Removed unused route

* [#167319140] Added selector in backendinfo reducer

Added selector in backendinfo reducer

* [#167319140] Moved check version app in the root container

Moved check version app in the root container

* [#167319140] Tested function to check if version is supported

Tested function to check if version is supported

* [#167319140] Tested function to check if version is supported

Tested function to check if version is supported

* [#167319140] Refactoring and added module to check version

Refactoring and added module to check version

* [#167319140] Check if return a valid version from backend

Check if return a valid version from backend

* [#167319140] Tested the new function

Tested the new function

* [#167319140] Changed version node-cache for circle ci

Changed version node-cache for circle ci

* [#167319140] Added new version semserv module

Added new version semserv module

* [#167319140] remove semver

* [#167319140] update node cache

* [#167319140] restore circle ci node cache

* [#167319140] rollback

* [#167319140] rollback

* [#167319140] add semver

* [#167319140] rollback

* [#167319140] rollback

* [#167319140] add sem version 5.5.1

* [#167319140] restore circle ci node cache

* [#167319140] restore circle ci node cache

* [#167319140] add semver

* [#167319140] remove semver

* [#167319140] remove semver types

* [#167319140] remove semver types

* [#167319140] add semver

* [#167319140] remove semver

* [#167319140] add semver

* [#167319140] add semver

* [#167319140] remove semver

* [#167319140] update node version in circleci

* [#167319140] add semver

* [#167319140] rollback

* [#167319140] add semver types in dev dependencies

* [#167319140] Removed a version of semver unnecessary

Removed a version of semver unnecessary

* [#167319140] Added an oldest version of semver

Added an oldest version of semver

* [#167319140] Footer management improvements - added error message

Footer management improvements - added error message

* [#167319140] Used selector to get min app version

Used selector to get min app version

* [#167319140] Removed the toast - refactoring render

Removed the toast used for the error message and add a text label - refactoring render of rootcontainer

* [#167319140] Defined a const

Defined a const

* [#167319140] Fix to display the error message on little device

Fix to display the error message on little device

* [#167319140] Adjustment to new backend spec

Adjustment to new backend spec

* [#167319140] Used Platform select

Used Platform select

* [#167319140] Minor fixes and added build version verification for ios devices

Minor fixes and added build version verification for ios devices

* [#167319140] refactoring

* [#167319140] Fixed declaration of constant

Fixed declaration of constant

* [#167319140] refactoring and tests

* update yarn lock

* [#171875582] update stores references

* [#171875582] update mock google services

Co-authored-by: Matteo Boschi <[email protected]>
  • Loading branch information
Edigio and Undermaken authored Mar 26, 2020
1 parent 0e9b74f commit 84fc3a7
Show file tree
Hide file tree
Showing 14 changed files with 335 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ package-lock.json

# App Config
.env
.env.local

# BUCK
buck-out/
Expand Down
Binary file added img/icons/update-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/icons/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/icons/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -954,3 +954,7 @@ reminders:
openMaps:
genericError: An error occurred while opening the map
genericError: An error occurred
titleUpdateApp: Update IO!
msgErrorUpdateApp: "An error occurred while opening the app store"
messageUpdateApp: "IO often introduces small improvements and new features: to continue using the app you need to update it to the latest version."
btnUpdateApp: Update the IO app
4 changes: 4 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -965,3 +965,7 @@ reminders:
openMaps:
genericError: Si è verificato un errore durante l'apertura della mappa
genericError: Si è verificato un errore
titleUpdateApp: Aggiorna IO!
msgErrorUpdateApp: "Si è verificato un errore durante l'apertura dello store delle app"
messageUpdateApp: "IO introduce spesso piccole migliorie e nuove funzioni: per continuare ad usare l'app è necessario aggiornarla all'ultima versione."
btnUpdateApp: Aggiorna l'app IO
2 changes: 1 addition & 1 deletion mock-google-services.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"client_info": {
"mobilesdk_app_id": "1:111111111111:android:1111111111111111",
"android_client_info": {
"package_name": "it.teamdigitale.app.italiaapp"
"package_name": "it.pagopa.io.app"
}
},
"oauth_client": [
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"remark-html": "9.0.0",
"reselect": "4.0.0",
"rn-placeholder": "^1.3.3",
"semver": "^5.7.0",
"source-map": "^0.6.1",
"stacktrace-js": "^2.0.0",
"tslib": "^1.9.3",
Expand Down Expand Up @@ -119,6 +120,7 @@
"@types/react-test-renderer": "16.0.3",
"@types/redux-logger": "^3.0.6",
"@types/redux-saga": "^0.10.5",
"@types/semver": "^6.2.0",
"@types/stacktrace-js": "^0.0.32",
"@types/uuid": "^3.4.4",
"@types/validator": "^9.4.2",
Expand Down
16 changes: 14 additions & 2 deletions ts/RootContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ import {
import { navigateToDeepLink, setDeepLink } from "./store/actions/deepLink";
import { navigateBack } from "./store/actions/navigation";
import { GlobalState } from "./store/reducers/types";
import UpdateAppModal from "./UpdateAppModal";
import { getNavigateActionFromDeepLink } from "./utils/deepLink";

// Check min version app supported
import { fromNullable } from "fp-ts/lib/Option";
import { serverInfoDataSelector } from "./store/reducers/backendInfo";
import { isUpdatedNeeded } from "./utils/appVersion";

// tslint:disable-next-line:no-use-before-declare
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps;

Expand Down Expand Up @@ -113,6 +119,11 @@ class RootContainer extends React.PureComponent<Props> {
// FIXME: perhaps instead of navigating to a "background"
// screen, we can make this screen blue based on
// the redux state (i.e. background)

// if we have no information about the backend, don't force the update
const isAppOutOfDate = fromNullable(this.props.backendInfo)
.map(bi => isUpdatedNeeded(bi))
.getOrElse(false);
return (
<Root>
<StatusBar barStyle="dark-content" />
Expand All @@ -123,7 +134,7 @@ class RootContainer extends React.PureComponent<Props> {
)}
{shouldDisplayVersionInfoOverlay && <VersionInfoOverlay />}
<Navigation />
<IdentificationModal />
{isAppOutOfDate ? <UpdateAppModal /> : <IdentificationModal />}
<LightModalRoot />
</Root>
);
Expand All @@ -132,7 +143,8 @@ class RootContainer extends React.PureComponent<Props> {

const mapStateToProps = (state: GlobalState) => ({
deepLinkState: state.deepLink,
isDebugModeEnabled: state.debug.isDebugModeEnabled
isDebugModeEnabled: state.debug.isDebugModeEnabled,
backendInfo: serverInfoDataSelector(state)
});

const mapDispatchToProps = {
Expand Down
175 changes: 175 additions & 0 deletions ts/UpdateAppModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/**
* A screen to invite the user to update the app because current version is not supported yet
*
*/

import { Millisecond } from "italia-ts-commons/lib/units";
import { Button, Container, H2, Text, View } from "native-base";
import * as React from "react";
import {
BackHandler,
Image,
Linking,
Modal,
Platform,
StyleSheet
} from "react-native";
import { connect } from "react-redux";
import BaseScreenComponent from "./components/screens/BaseScreenComponent";
import FooterWithButtons from "./components/ui/FooterWithButtons";
import I18n from "./i18n";
import customVariables from "./theme/variables";

const timeoutErrorMsg: Millisecond = 5000 as Millisecond;

const storeUrl = Platform.select({
ios: "itms-apps://itunes.apple.com/it/app/testflight/id899247664?mt=8",
android: "market://details?id=it.pagopa.io.app"
});

const styles = StyleSheet.create({
text: {
marginTop: customVariables.contentPadding,
fontSize: 18
},
textDanger: {
marginTop: customVariables.contentPadding,
fontSize: 18,
textAlign: "center",
color: customVariables.brandDanger
},
container: {
margin: customVariables.contentPadding,
flex: 1,
alignItems: "flex-start"
},
img: {
marginTop: customVariables.contentPaddingLarge,
alignSelf: "center"
}
});

type State = { hasError: boolean };

class UpdateAppModal extends React.PureComponent<never, State> {
constructor(props: never) {
super(props);
this.state = {
hasError: false
};
}
private idTimeout?: number;
// No Event on back button android
private handleBackPress = () => {
return true;
};

public componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.handleBackPress);
}

public componentWillUnmount() {
if (this.idTimeout) {
clearTimeout(this.idTimeout);
}
BackHandler.removeEventListener("hardwareBackPress", this.handleBackPress);
}

private openAppStore = () => {
// the error is already displayed
if (this.state.hasError) {
return;
}
Linking.openURL(storeUrl).catch(() => {
// Change state to show the error message
this.setState({
hasError: true
});
// After 5 seconds restore state
// tslint:disable-next-line: no-object-mutation
this.idTimeout = setTimeout(() => {
this.setState({
hasError: false
});
}, timeoutErrorMsg);
});
};

/**
* Footer iOS button
*/
private renderIosFooter() {
return (
<View footer={true}>
<React.Fragment>
<Button block={true} primary={true} onPress={this.openAppStore}>
<Text>{I18n.t("btnUpdateApp")}</Text>
</Button>
<View spacer={true} />
</React.Fragment>
</View>
);
}

/**
* Footer Android buttons
*/
private renderAndroidFooter() {
const cancelButtonProps = {
cancel: true,
block: true,
onPress: () => BackHandler.exitApp(),
title: I18n.t("global.buttons.close")
};
const updateButtonProps = {
block: true,
primary: true,
onPress: this.openAppStore,
title: I18n.t("btnUpdateApp")
};

return (
<FooterWithButtons
type="TwoButtonsInlineThird"
leftButton={cancelButtonProps}
rightButton={updateButtonProps}
/>
);
}

// Different footer according to OS
get footer() {
return Platform.select({
ios: this.renderIosFooter(),
android: this.renderAndroidFooter()
});
}

public render() {
// Current version not supported
return (
<Modal>
<BaseScreenComponent appLogo={true} goBack={false}>
<Container>
<View style={styles.container}>
<H2>{I18n.t("titleUpdateApp")}</H2>
<Text style={styles.text}>{I18n.t("messageUpdateApp")}</Text>
<Image
style={styles.img}
source={require("../img/icons/update-icon.png")}
/>
{this.state.hasError && (
<Text style={styles.textDanger}>
{I18n.t("msgErrorUpdateApp")}
</Text>
)}
</View>
</Container>
</BaseScreenComponent>
{this.footer}
</Modal>
);
}
}

export default connect()(UpdateAppModal);
5 changes: 5 additions & 0 deletions ts/store/reducers/backendInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
backendInfoLoadFailure,
backendInfoLoadSuccess
} from "../actions/backendInfo";
import { GlobalState } from "./types";

export type BackendInfoState = Readonly<{
serverInfo?: ServerInfo;
Expand Down Expand Up @@ -40,3 +41,7 @@ export default function backendInfo(
return state;
}
}

// Selectors
export const serverInfoDataSelector = (state: GlobalState) =>
state.backendInfo.serverInfo;
71 changes: 71 additions & 0 deletions ts/utils/__tests__/appVersion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
jest.mock("react-native-device-info", () => {
return {
getVersion: jest
.fn()
.mockReturnValueOnce("1.1")
.mockReturnValueOnce("1.1.9")
.mockReturnValueOnce("1.2.3.4"),
getBuildNumber: () => 3
};
});
import { getAppVersion, isVersionAppSupported } from "../appVersion";

describe("check if getVersion works properly", () => {
it("should be 1.1.3", () => {
expect(getAppVersion()).toEqual("1.1.3");
});

it("should be 1.1.9", () => {
expect(getAppVersion()).toEqual("1.1.9");
});

it("should be 1.2.3.4", () => {
expect(getAppVersion()).toEqual("1.2.3.4");
});
});

describe("check if app version is supported by backend version", () => {
it("supported", () => {
expect(isVersionAppSupported("0.0.0", "1.2")).toEqual(true);
});

it("supported", () => {
expect(isVersionAppSupported("1.1.0", "1.1.0")).toEqual(true);
});

it("supported", () => {
expect(isVersionAppSupported("1.4", "1.5.57")).toEqual(true);
});

it("supported", () => {
expect(isVersionAppSupported("1.4", "1.4.0.1")).toEqual(true);
});

it("supported", () => {
expect(isVersionAppSupported("1.4.0.2", "1.4.0.3")).toEqual(true);
});

it("not supported", () => {
expect(isVersionAppSupported("1.4.5", "1.4.1")).toEqual(false);
});

it("not supported", () => {
expect(isVersionAppSupported("5", "1.4.1")).toEqual(false);
});

it("not supported", () => {
expect(isVersionAppSupported("3.0", "1.4.1")).toEqual(false);
});

it("not supported", () => {
expect(isVersionAppSupported("1.1.23", "1.1.21")).toEqual(false);
});

it("not supported", () => {
expect(isVersionAppSupported("1.1.2.23", "1.1.1")).toEqual(false);
});

it("not supported", () => {
expect(isVersionAppSupported("SOME STRANGE DATA", "1.4.1")).toEqual(true);
});
});
Loading

0 comments on commit 84fc3a7

Please sign in to comment.