From 6c92e80a92b92548954fbb04cfef0fc92d23a56c Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 6 Oct 2023 12:52:00 -0400 Subject: [PATCH 1/6] make permissionStatus app-wide Now that we have an App component, we can think about hoisting state higher in the component tree so it can be shared instead of being redundant. Rather than hooking into usePermissionStatus from multiple different components, it will be cleaner to hook into it once in the App component and share it downstream via the AppContext. When there were multiple instances of usePermissionStatus, we ran into issues where only one was recieving updates (https://github.com/e-mission/e-mission-docs/issues/1002) Hoisting the permission state to the App component resolves these issues because now there is only one instance of usePermissionStatus --- www/js/App.tsx | 3 +++ www/js/appstatus/PermissionsControls.tsx | 10 ++++++---- www/js/control/AppStatusModal.tsx | 7 ++++--- www/js/onboarding/SaveQrPage.tsx | 5 ++--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/www/js/App.tsx b/www/js/App.tsx index 628baf21b..2187118fa 100644 --- a/www/js/App.tsx +++ b/www/js/App.tsx @@ -10,6 +10,7 @@ import OnboardingStack from './onboarding/OnboardingStack'; import { OnboardingRoute, OnboardingState, getPendingOnboardingState } from './onboarding/onboardingHelper'; import { setServerConnSettings } from './config/serverConn'; import AppStatusModal from './control/AppStatusModal'; +import usePermissionStatus from './usePermissionStatus'; const defaultRoutes = (t) => [ { key: 'label', title: t('diary.label-tab'), focusedIcon: 'check-bold', unfocusedIcon: 'check-outline' }, @@ -26,6 +27,7 @@ const App = () => { const [onboardingState, setOnboardingState] = useState(null); const [permissionsPopupVis, setPermissionsPopupVis] = useState(false); const appConfig = useAppConfig(); + const permissionStatus = usePermissionStatus(); const { colors } = useTheme(); const { t } = useTranslation(); @@ -55,6 +57,7 @@ const App = () => { const appContextValue = { appConfig, onboardingState, setOnboardingState, refreshOnboardingState, + permissionStatus, permissionsPopupVis, setPermissionsPopupVis, } diff --git a/www/js/appstatus/PermissionsControls.tsx b/www/js/appstatus/PermissionsControls.tsx index ded51b898..97ce7081a 100644 --- a/www/js/appstatus/PermissionsControls.tsx +++ b/www/js/appstatus/PermissionsControls.tsx @@ -1,17 +1,19 @@ //component to view and manage permission settings -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { StyleSheet, ScrollView, View } from "react-native"; import { Button, Text } from 'react-native-paper'; import { useTranslation } from "react-i18next"; import PermissionItem from "./PermissionItem"; -import usePermissionStatus, { refreshAllChecks } from "../usePermissionStatus"; +import { refreshAllChecks } from "../usePermissionStatus"; import ExplainPermissions from "./ExplainPermissions"; import AlertBar from "../control/AlertBar"; +import { AppContext } from "../App"; const PermissionsControls = ({ onAccept }) => { const { t } = useTranslation(); const [explainVis, setExplainVis] = useState(false); - const { checkList, overallStatus, error, errorVis, setErrorVis, explanationList } = usePermissionStatus(); + const { permissionStatus } = useContext(AppContext); + const { checkList, overallStatus, error, errorVis, setErrorVis, explanationList } = permissionStatus; return ( <> @@ -62,4 +64,4 @@ const styles = StyleSheet.create({ } }); -export default PermissionsControls; \ No newline at end of file +export default PermissionsControls; diff --git a/www/js/control/AppStatusModal.tsx b/www/js/control/AppStatusModal.tsx index c3f625f4e..e7f5aa97b 100644 --- a/www/js/control/AppStatusModal.tsx +++ b/www/js/control/AppStatusModal.tsx @@ -1,14 +1,15 @@ -import React, { useEffect } from "react"; +import React, { useContext, useEffect } from "react"; import { Modal, useWindowDimensions } from "react-native"; import { Dialog, useTheme } from 'react-native-paper'; import PermissionsControls from "../appstatus/PermissionsControls"; -import usePermissionStatus from "../usePermissionStatus"; import { settingStyles } from "./ProfileSettings"; +import { AppContext } from "../App"; //TODO -- import settings styles for dialog const AppStatusModal = ({ permitVis, setPermitVis }) => { const { height: windowHeight } = useWindowDimensions(); - const { overallStatus, checkList } = usePermissionStatus(); + const { permissionStatus } = useContext(AppContext); + const { overallStatus, checkList } = permissionStatus; const { colors } = useTheme(); /* Listen for permissions status changes to determine if we should show the modal. */ diff --git a/www/js/onboarding/SaveQrPage.tsx b/www/js/onboarding/SaveQrPage.tsx index 67ddd74e2..28af8fa77 100644 --- a/www/js/onboarding/SaveQrPage.tsx +++ b/www/js/onboarding/SaveQrPage.tsx @@ -3,7 +3,6 @@ import { View, StyleSheet } from "react-native"; import { ActivityIndicator, Button, Surface, Text } from "react-native-paper"; import { registerUserDone, setRegisterUserDone, setSaveQrDone } from "./onboardingHelper"; import { AppContext } from "../App"; -import usePermissionStatus from "../usePermissionStatus"; import { getAngularService } from "../angular-react-helper"; import { displayError, logDebug } from "../plugin/logger"; import { useTranslation } from "react-i18next"; @@ -14,8 +13,8 @@ import { preloadDemoSurveyResponse } from "./SurveyPage"; const SaveQrPage = ({ }) => { const { t } = useTranslation(); - const { onboardingState, refreshOnboardingState } = useContext(AppContext); - const { overallStatus } = usePermissionStatus(); + const { permissionStatus, onboardingState, refreshOnboardingState } = useContext(AppContext); + const { overallStatus } = permissionStatus; useEffect(() => { if (overallStatus == true && !registerUserDone) { From b8865a0f464be9a7726ee752fa6ec90079160958 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 6 Oct 2023 15:17:51 -0400 Subject: [PATCH 2/6] handle registration failure, add error boundaries If the token that the user enters belongs to a valid program, but is not a valid user token for that program, registration will fail. We should catch this and not allow the user to go any further through onboarding - rather, we should give them a message explaining what happened and send them back to the "Welcome" page so they can try again with a valid OPCode. Unfortunately they have to go through onboarding again - they will not have to re-do permissions though. The best UX would be achieved if we could ensure that the token is valid BEFORE they go through everything else. However, this would require contacting the server before the user consented, which we cannot do. --In addition, we'll add an error message if the user has not yet registered, but has advanced to preloading survey responses. This will not happen unless something is broken. --- www/i18n/en.json | 2 ++ www/js/onboarding/SaveQrPage.tsx | 7 ++++++- www/js/onboarding/SurveyPage.tsx | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/www/i18n/en.json b/www/i18n/en.json index 3299a2207..fc056e37e 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -362,6 +362,8 @@ "survey-missing-formpath": "Error while fetching resources in config: survey_info.surveys has a survey without a formPath" }, "errors": { + "registration-check-token": "User registration error. Please check your token and try again.", + "unable-preload-not-registered": "Unable to preload survey before registration", "while-populating-composite": "Error while populating composite trips", "while-loading-another-week": "Error while loading travel of {{when}} week", "while-loading-specific-week": "Error while loading travel for the week of {{day}}", diff --git a/www/js/onboarding/SaveQrPage.tsx b/www/js/onboarding/SaveQrPage.tsx index 28af8fa77..51f884886 100644 --- a/www/js/onboarding/SaveQrPage.tsx +++ b/www/js/onboarding/SaveQrPage.tsx @@ -9,6 +9,8 @@ import { useTranslation } from "react-i18next"; import QrCode, { shareQR } from "../components/QrCode"; import { onboardingStyles } from "./OnboardingStack"; import { preloadDemoSurveyResponse } from "./SurveyPage"; +import { resetDataAndRefresh } from "../config/dynamicConfig"; +import i18next from "i18next"; const SaveQrPage = ({ }) => { @@ -41,7 +43,10 @@ const SaveQrPage = ({ }) => { logDebug("registered user in CommHelper result " + successResult); refreshOnboardingState(); }, function(errorResult) { - displayError(errorResult, "User registration error"); + /* if registration fails, we should take the user back to the welcome page + so they can try again with a valid token */ + displayError(errorResult, i18next.t('errors.registration-check-token')); + resetDataAndRefresh(); }); }).catch((e) => { displayError(e, "Sign in error"); diff --git a/www/js/onboarding/SurveyPage.tsx b/www/js/onboarding/SurveyPage.tsx index 11e58c94a..41f714a6d 100644 --- a/www/js/onboarding/SurveyPage.tsx +++ b/www/js/onboarding/SurveyPage.tsx @@ -5,14 +5,20 @@ import EnketoModal from "../survey/enketo/EnketoModal"; import { DEMOGRAPHIC_SURVEY_DATAKEY, DEMOGRAPHIC_SURVEY_NAME } from "../control/DemographicsSettingRow"; import { loadPreviousResponseForSurvey } from "../survey/enketo/enketoHelper"; import { AppContext } from "../App"; -import { markIntroDone } from "./onboardingHelper"; +import { markIntroDone, registerUserDone } from "./onboardingHelper"; import { useTranslation } from "react-i18next"; import { DateTime } from "luxon"; import { onboardingStyles } from "./OnboardingStack"; +import { displayErrorMsg } from "../plugin/logger"; +import i18next from "i18next"; let preloadedResponsePromise: Promise = null; export const preloadDemoSurveyResponse = () => { if (!preloadedResponsePromise) { + if (!registerUserDone) { + displayErrorMsg(i18next.t('unable-preload-not-registered')); + return Promise.resolve(null); + } preloadedResponsePromise = loadPreviousResponseForSurvey(DEMOGRAPHIC_SURVEY_DATAKEY); } return preloadedResponsePromise; From 297a3bd3c9f1ef2fa4a6ab398e6f22c95663487a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 6 Oct 2023 14:22:50 -0600 Subject: [PATCH 3/6] ios "why we need this" update on staging, I noticed that "why we need this" had an explanation of "how to fix" rather than what the permissions are used for. I have fixed this by allowing the default explanation to persist for ios -- custom messages for how to fix permissions are maintained in the descriptions of the permissions in `checklist` --- www/js/usePermissionStatus.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index e98aff423..7d16f00f5 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -299,11 +299,6 @@ const usePermissionStatus = () => { let locExplanation = t('intro.appstatus.overall-loc-description'); if(window['device'].platform.toLowerCase() == "ios") { overallFitnessName = t('intro.appstatus.overall-fitness-name-ios'); - if(window['device'].version.split(".")[0] < 13) { - locExplanation = (t("intro.permissions.locationPermExplanation-ios-lt-13")); - } else { - locExplanation = (t("intro.permissions.locationPermExplanation-ios-gte-13")); - } } tempExplanations.push({name: t('intro.appstatus.overall-loc-name'), desc: locExplanation}); tempExplanations.push({name: overallFitnessName, desc: t('intro.appstatus.overall-fitness-description')}); From 86bed083dd76224b99675d79cc2a63c088bd55bb Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 6 Oct 2023 14:38:57 -0600 Subject: [PATCH 4/6] add scrollview for smaller form factors addressing comment from: Cleanup notes for new onboarding --- www/js/onboarding/WelcomePage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/js/onboarding/WelcomePage.tsx b/www/js/onboarding/WelcomePage.tsx index 8e7e43425..3589923c8 100644 --- a/www/js/onboarding/WelcomePage.tsx +++ b/www/js/onboarding/WelcomePage.tsx @@ -58,6 +58,7 @@ const WelcomePage = () => { + }} /> @@ -81,6 +82,7 @@ const WelcomePage = () => { {t('join.paste-hint')} + setPasteModalVis(false)}> setPasteModalVis(false)}> From c349ab78fc80730ced1a345fa015792d4f68599f Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 6 Oct 2023 14:53:15 -0600 Subject: [PATCH 5/6] "please allow" instead of scroll instructions As a result of https://github.com/e-mission/e-mission-data-collection/pull/209, we now have a popup for this permission, so all the user needs to do is allow it! Translate PR to follow with this small change to en.json --- www/i18n/en.json | 5 +---- www/js/usePermissionStatus.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/www/i18n/en.json b/www/i18n/en.json index fc056e37e..6a034780b 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -284,10 +284,7 @@ }, "ignorebatteryopt": { "name": "Ignore battery optimizations", - "description": { - "android-disable": "On the optimization page, go to all apps, search for this app and turn off optimizations.", - "ios": "Please allow." - } + "description": "Please allow." } }, "permissions": { diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index 7d16f00f5..035ba6b16 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -283,7 +283,7 @@ const usePermissionStatus = () => { } let ignoreBatteryOptCheck = { name: t("intro.appstatus.ignorebatteryopt.name"), - desc: t("intro.appstatus.ignorebatteryopt.description.android-disable"), + desc: t("intro.appstatus.ignorebatteryopt.description"), fix: fixBatteryOpt, refresh: checkBatteryOpt } From 6ed84b3fda8c3e2f29a69ee547b37e4e8e98e663 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 9 Oct 2023 17:58:08 -0400 Subject: [PATCH 6/6] revise verbage of 'not registered' error msg https://github.com/e-mission/e-mission-phone/pull/1059#discussion_r1350797906 --- www/i18n/en.json | 2 +- www/js/onboarding/SurveyPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/www/i18n/en.json b/www/i18n/en.json index 6a034780b..e47fdd62d 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -360,7 +360,7 @@ }, "errors": { "registration-check-token": "User registration error. Please check your token and try again.", - "unable-preload-not-registered": "Unable to preload survey before registration", + "not-registered-cant-contact": "User is not registered, so the server cannot be contacted.", "while-populating-composite": "Error while populating composite trips", "while-loading-another-week": "Error while loading travel of {{when}} week", "while-loading-specific-week": "Error while loading travel for the week of {{day}}", diff --git a/www/js/onboarding/SurveyPage.tsx b/www/js/onboarding/SurveyPage.tsx index 41f714a6d..c02439cbf 100644 --- a/www/js/onboarding/SurveyPage.tsx +++ b/www/js/onboarding/SurveyPage.tsx @@ -16,7 +16,7 @@ let preloadedResponsePromise: Promise = null; export const preloadDemoSurveyResponse = () => { if (!preloadedResponsePromise) { if (!registerUserDone) { - displayErrorMsg(i18next.t('unable-preload-not-registered')); + displayErrorMsg(i18next.t('errors.not-registered-cant-contact')); return Promise.resolve(null); } preloadedResponsePromise = loadPreviousResponseForSurvey(DEMOGRAPHIC_SURVEY_DATAKEY);