From 84fc3a75842dd1ca7e9dc343ba987f1964d8063e Mon Sep 17 00:00:00 2001 From: Edigio Date: Thu, 26 Mar 2020 18:32:50 +0100 Subject: [PATCH] [#167319140]Added modal with a message to invite the user to update app (#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 --- .gitignore | 1 + img/icons/update-icon.png | Bin 0 -> 4264 bytes img/icons/update-icon@2x.png | Bin 0 -> 9081 bytes img/icons/update-icon@3x.png | Bin 0 -> 14441 bytes locales/en/index.yml | 4 + locales/it/index.yml | 4 + mock-google-services.json | 2 +- package.json | 2 + ts/RootContainer.tsx | 16 ++- ts/UpdateAppModal.tsx | 175 ++++++++++++++++++++++++++ ts/store/reducers/backendInfo.ts | 5 + ts/utils/__tests__/appVersion.test.ts | 71 +++++++++++ ts/utils/appVersion.ts | 48 +++++++ yarn.lock | 10 ++ 14 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 img/icons/update-icon.png create mode 100644 img/icons/update-icon@2x.png create mode 100644 img/icons/update-icon@3x.png create mode 100644 ts/UpdateAppModal.tsx create mode 100644 ts/utils/__tests__/appVersion.test.ts create mode 100644 ts/utils/appVersion.ts diff --git a/.gitignore b/.gitignore index 42a536a72fe..f3792bacc79 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ package-lock.json # App Config .env +.env.local # BUCK buck-out/ diff --git a/img/icons/update-icon.png b/img/icons/update-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b753577f9fb4a690967666cc0a912d79091410a3 GIT binary patch literal 4264 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 mRlVVn%0E$audJ+MW0vHMa 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 literal 0 HcmV?d00001 diff --git a/img/icons/update-icon@3x.png b/img/icons/update-icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..df440295d2b818a1e3e6ac925e53637eabbfb5fe GIT binary patch literal 14441 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 literal 0 HcmV?d00001 diff --git a/locales/en/index.yml b/locales/en/index.yml index 664870f2309..1fc204b2631 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -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 diff --git a/locales/it/index.yml b/locales/it/index.yml index 62c8cfe6166..d234a7b9942 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -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 diff --git a/mock-google-services.json b/mock-google-services.json index 40ceec202a2..690b9a7ef7f 100644 --- a/mock-google-services.json +++ b/mock-google-services.json @@ -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": [ diff --git a/package.json b/package.json index 39943dec7c4..9b3d6eee101 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/ts/RootContainer.tsx b/ts/RootContainer.tsx index 701e6120c89..e44c91206bf 100644 --- a/ts/RootContainer.tsx +++ b/ts/RootContainer.tsx @@ -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 mapDispatchToProps; @@ -113,6 +119,11 @@ class RootContainer extends React.PureComponent { // 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 ( @@ -123,7 +134,7 @@ class RootContainer extends React.PureComponent { )} {shouldDisplayVersionInfoOverlay && } - + {isAppOutOfDate ? : } ); @@ -132,7 +143,8 @@ class RootContainer extends React.PureComponent { const mapStateToProps = (state: GlobalState) => ({ deepLinkState: state.deepLink, - isDebugModeEnabled: state.debug.isDebugModeEnabled + isDebugModeEnabled: state.debug.isDebugModeEnabled, + backendInfo: serverInfoDataSelector(state) }); const mapDispatchToProps = { diff --git a/ts/UpdateAppModal.tsx b/ts/UpdateAppModal.tsx new file mode 100644 index 00000000000..fe0e86e17b3 --- /dev/null +++ b/ts/UpdateAppModal.tsx @@ -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 { + 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 ( + + + + + + + ); + } + + /** + * 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 ( + + ); + } + + // Different footer according to OS + get footer() { + return Platform.select({ + ios: this.renderIosFooter(), + android: this.renderAndroidFooter() + }); + } + + public render() { + // Current version not supported + return ( + + + + +

{I18n.t("titleUpdateApp")}

+ {I18n.t("messageUpdateApp")} + + {this.state.hasError && ( + + {I18n.t("msgErrorUpdateApp")} + + )} +
+
+
+ {this.footer} +
+ ); + } +} + +export default connect()(UpdateAppModal); diff --git a/ts/store/reducers/backendInfo.ts b/ts/store/reducers/backendInfo.ts index 76de8bb37e9..1e81b5eef83 100644 --- a/ts/store/reducers/backendInfo.ts +++ b/ts/store/reducers/backendInfo.ts @@ -10,6 +10,7 @@ import { backendInfoLoadFailure, backendInfoLoadSuccess } from "../actions/backendInfo"; +import { GlobalState } from "./types"; export type BackendInfoState = Readonly<{ serverInfo?: ServerInfo; @@ -40,3 +41,7 @@ export default function backendInfo( return state; } } + +// Selectors +export const serverInfoDataSelector = (state: GlobalState) => + state.backendInfo.serverInfo; diff --git a/ts/utils/__tests__/appVersion.test.ts b/ts/utils/__tests__/appVersion.test.ts new file mode 100644 index 00000000000..7a71430f56c --- /dev/null +++ b/ts/utils/__tests__/appVersion.test.ts @@ -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); + }); +}); diff --git a/ts/utils/appVersion.ts b/ts/utils/appVersion.ts new file mode 100644 index 00000000000..9ff12b35c14 --- /dev/null +++ b/ts/utils/appVersion.ts @@ -0,0 +1,48 @@ +import { fromNullable } from "fp-ts/lib/Option"; +import { Platform } from "react-native"; +import DeviceInfo from "react-native-device-info"; +import semver from "semver"; +import { ServerInfo } from "../../definitions/backend/ServerInfo"; + +/** + * return true if appVersion >= minAppVersion + * @param minAppVersion the min version supported + * @param appVersion the version to be tested + */ +export const isVersionAppSupported = ( + minAppVersion: string, + appVersion: string +): boolean => { + const minVersion = semver.coerce(minAppVersion); + const currentAppVersion = semver.coerce(appVersion); + // cant compare + if (!minVersion || !currentAppVersion) { + return true; + } + return semver.satisfies(minVersion, `<=${currentAppVersion}`); +}; + +export const getAppVersion = () => { + const version = DeviceInfo.getVersion(); + // if the version includes only major.minor (we manually ad the buildnumber as patch number) + if (version.split(".").length === 2) { + return `${version}.${DeviceInfo.getBuildNumber()}`; + } + return version; +}; + +/** + * return true if the app must be updated + * @param serverInfo the backend info + */ +export const isUpdatedNeeded = (serverInfo: ServerInfo) => + fromNullable(serverInfo) + .map(si => { + const minAppVersion = Platform.select({ + ios: si.min_app_version.ios, + android: si.min_app_version.android + }); + + return !isVersionAppSupported(minAppVersion, getAppVersion()); + }) + .getOrElse(false); diff --git a/yarn.lock b/yarn.lock index df2d3c17e32..1185746f865 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1425,6 +1425,11 @@ dependencies: redux-saga "*" +"@types/semver@^6.2.0": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.2.1.tgz#a236185670a7860f1597cf73bea2e16d001461ba" + integrity sha512-+beqKQOh9PYxuHvijhVl+tIHvT6tuwOrE9m14zd+MT2A38KoKZhh7pYJ0SNleLtwDsiIxHDsIk9bv01oOxvSvA== + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -8880,6 +8885,11 @@ semver@^5.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== +semver@^5.7.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver@^6.1.2: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"