From db19f352f22ad88c2adb77b8f30c6ce17f2c5957 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Thu, 3 Oct 2024 14:24:12 -0400 Subject: [PATCH 01/12] simplify usePermissionStatus Unify setupLocChecks and setupFitnessChecks, which were split into different versions for ios and android but were mostly the same repeated code Use AlertManager to trigger alerts directly instead of passing error, errorVis, and setErrorVis through the hook and triggering them from PermissionsControls Split the 'description tag' logic for location settings & permissions into separate functions since they vary by platform and version In the checks object: Rename "statusState" to "status" Remove "statusColor" and "statusIcon" since they can be derived from "status". Handle the color and icon in PermissionItem.tsx. Add "isOptional" property Remove other unused code like haveSetText and setHaveSetText --- www/js/appstatus/PermissionItem.tsx | 12 +- www/js/appstatus/PermissionsControls.tsx | 13 +- www/js/usePermissionStatus.ts | 268 +++++++---------------- 3 files changed, 91 insertions(+), 202 deletions(-) diff --git a/www/js/appstatus/PermissionItem.tsx b/www/js/appstatus/PermissionItem.tsx index cd111f3b3..5dd2a4119 100644 --- a/www/js/appstatus/PermissionItem.tsx +++ b/www/js/appstatus/PermissionItem.tsx @@ -1,17 +1,25 @@ import React from 'react'; import { List, Button } from 'react-native-paper'; import { useTranslation } from 'react-i18next'; +import { useAppTheme } from '../appTheme'; const PermissionItem = ({ check }) => { const { t } = useTranslation(); + const { colors } = useAppTheme(); + + const icon = check.status ? 'check-circle-outline' : 'alpha-x-circle-outline'; + const color = check.status ? colors.success : colors.error; return ( check.fix()} title={t(check.name)} description={t(check.desc)} descriptionNumberOfLines={5} - left={() => } - right={() => } + left={() => } + right={() => } + style={{ paddingHorizontal: 0 }} + contentStyle={{ paddingHorizontal: 16 }} /> ); }; diff --git a/www/js/appstatus/PermissionsControls.tsx b/www/js/appstatus/PermissionsControls.tsx index 0eb9fdd60..562e41d4d 100644 --- a/www/js/appstatus/PermissionsControls.tsx +++ b/www/js/appstatus/PermissionsControls.tsx @@ -13,14 +13,7 @@ const PermissionsControls = ({ onAccept }) => { const { t } = useTranslation(); const [explainVis, setExplainVis] = useState(false); const { permissionStatus } = useContext(AppContext); - const { checkList, overallStatus, error, explanationList } = permissionStatus; - - useEffect(() => { - if (!error) return; - AlertManager.addMessage({ - text: error, - }); - }, [error]); + const { checkList, overallStatus, explanationList } = permissionStatus; return ( <> @@ -34,7 +27,9 @@ const PermissionsControls = ({ onAccept }) => { explanationList={explanationList} visible={explainVis} setVisible={setExplainVis}> - {checkList?.map((lc) => )} + {checkList?.map((lc) => ( + + ))} diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index f8fef085b..be47ba525 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -4,8 +4,12 @@ import useAppConfig from './useAppConfig'; import { useTranslation } from 'react-i18next'; import { useAppTheme } from './appTheme'; import { logDebug, logWarn } from './plugin/logger'; +import { AlertManager } from './components/AlertBar'; -//refreshing checks with the plugins to update the check's statusState +let DEVICE_PLATFORM: 'android' | 'ios'; +let DEVICE_VERSION: number; + +//refreshing checks with the plugins to update the check's status export function refreshAllChecks(checkList) { //refresh each check checkList.forEach((lc) => { @@ -19,149 +23,75 @@ type Check = { desc: string; fix: () => Promise; refresh: () => Promise; - statusState?: boolean; - statusIcon?: string; - statusColor?: string; + status?: boolean; + isOptional?: boolean; }; const usePermissionStatus = () => { const { t } = useTranslation(); - const { colors } = useAppTheme(); const appConfig = useAppConfig(); - const [error, setError] = useState(''); - const [errorVis, setErrorVis] = useState(false); - const [checkList, setCheckList] = useState([]); const [explanationList, setExplanationList] = useState>([]); - const [haveSetText, setHaveSetText] = useState(false); - - let iconMap = (statusState) => (statusState ? 'check-circle-outline' : 'alpha-x-circle-outline'); - let colorMap = (statusState) => (statusState ? colors.success : colors.danger); const overallStatus = useMemo(() => { - let status = true; if (!checkList?.length) return undefined; // if checks not loaded yet, status is undetermined - checkList.forEach((lc) => { - logDebug('check in permission status for ' + lc.name + ':' + lc.statusState); - if (lc.statusState === false) { - status = false; - } - }); - return status; + return checkList.some((check) => !check.status && !check.isOptional); }, [checkList]); //using this function to update checks rather than mutate //this cues React to update UI - function updateCheck(newObject) { - const tempList = [...checkList]; //make a copy rather than mutate - //update the visiblility pieces here, rather than mutating - newObject.statusIcon = iconMap(newObject.statusState); - newObject.statusColor = colorMap(newObject.statusState); + function updateCheck(check, newProps) { + const tempList = [...checkList]; //"find and replace" the check - tempList.forEach((item, i) => { - if (item.name == newObject.name) { - tempList[i] = newObject; - } - }); + const i = tempList.findIndex((item) => item.name == check.name); + tempList[i] = { ...tempList[i], ...newProps }; setCheckList(tempList); } async function checkOrFix(checkObj, nativeFn, showError = true) { logDebug('checking object ' + checkObj.name + ' ' + JSON.stringify(checkObj)); let newCheck = checkObj; - return nativeFn() - .then((status) => { - logDebug('availability = ' + status); - newCheck.statusState = true; - updateCheck(newCheck); - logDebug(`after checking obj ${checkObj.name}, checkList is ${JSON.stringify(checkList)}`); - return status; - }) - .catch((error) => { - if (showError) { - setError(error); - setErrorVis(true); - } - newCheck.statusState = false; - updateCheck(newCheck); - logDebug(`after checking obj ${checkObj.name}, checkList is ${JSON.stringify(checkList)}`); - return error; - }); + try { + const status = await nativeFn(); + logDebug(`${checkObj.name} status = ${status}`); + updateCheck(newCheck, { status: true }); + return status; + } catch (error) { + if (showError) { + AlertManager.addMessage({ text: error }); + } + updateCheck(newCheck, { status: false }); + return error; + } } - function setupAndroidLocChecks() { - let fixSettings = () => { - logDebug('Fix and refresh location settings'); - return checkOrFix( - locSettingsCheck, - window['cordova'].plugins.BEMDataCollection.fixLocationSettings, - true, - ); - }; - let checkSettings = () => { - logDebug('Refresh location settings'); - return checkOrFix( - locSettingsCheck, - window['cordova'].plugins.BEMDataCollection.isValidLocationSettings, - false, - ); - }; - let fixPerms = () => { - logDebug('fix and refresh location permissions'); - return checkOrFix( - locPermissionsCheck, - window['cordova'].plugins.BEMDataCollection.fixLocationPermissions, - true, - ).then((error) => { - if (error) { - locPermissionsCheck.desc = error; - } - }); - }; - let checkPerms = () => { - logDebug('fix and refresh location permissions'); - return checkOrFix( - locPermissionsCheck, - window['cordova'].plugins.BEMDataCollection.isValidLocationPermissions, - false, - ); - }; - const androidVersion = window['device'].version.split('.')[0]; - const androidSettingsDescTag = - androidVersion < 9 + function getLocSettingsDescriptionTag() { + if (DEVICE_PLATFORM == 'android') { + return DEVICE_VERSION < 9 ? 'intro.appstatus.locsettings.description.android-lt-9' : 'intro.appstatus.locsettings.description.android-gte-9'; - const androidPermDescTag = - androidVersion < 6 - ? 'intro.appstatus.locperms.description.android-lt-6' - : androidVersion < 10 - ? 'intro.appstatus.locperms.description.android-6-9' - : androidVersion < 11 - ? 'intro.appstatus.locperms.description.android-10' - : androidVersion < 12 - ? 'intro.appstatus.locperms.description.android-11' - : 'intro.appstatus.locperms.description.android-gte-12'; - logDebug('description tags are ' + androidSettingsDescTag + ' ' + androidPermDescTag); - // location settings - let locSettingsCheck = { - name: t('intro.appstatus.locsettings.name'), - desc: t(androidSettingsDescTag), - fix: fixSettings, - refresh: checkSettings, - }; - let locPermissionsCheck = { - name: t('intro.appstatus.locperms.name'), - desc: t(androidPermDescTag), - fix: fixPerms, - refresh: checkPerms, - }; - let tempChecks = checkList; - tempChecks.push(locSettingsCheck, locPermissionsCheck); - setCheckList(tempChecks); + } else if (DEVICE_PLATFORM == 'ios') { + return 'intro.appstatus.locsettings.description.ios'; + } + throw new Error(`Unknown platform ${DEVICE_PLATFORM} – unable to proceed`); + } + + function getLocPermissionsDescriptionTag() { + if (DEVICE_PLATFORM == 'android') { + if (DEVICE_VERSION < 6) return 'intro.appstatus.locperms.description.android-lt-6'; + if (DEVICE_VERSION < 10) return 'intro.appstatus.locperms.description.android-6-9'; + if (DEVICE_VERSION < 11) return 'intro.appstatus.locperms.description.android-10'; + if (DEVICE_VERSION < 12) return 'intro.appstatus.locperms.description.android-11'; + return 'intro.appstatus.locperms.description.android-gte-12'; + } else if (DEVICE_PLATFORM == 'ios') { + if (DEVICE_VERSION < 13) return 'intro.appstatus.locperms.description.ios-lt-13'; + return 'intro.appstatus.locperms.description.ios-gte-13'; + } + throw new Error(`Unknown platform ${DEVICE_PLATFORM} – unable to proceed`); } - function setupIOSLocChecks() { + function setupLocChecks() { let fixSettings = () => { logDebug('Fix and refresh location settings'); return checkOrFix( @@ -191,30 +121,24 @@ const usePermissionStatus = () => { }); }; let checkPerms = () => { - logDebug('fix and refresh location permissions'); + logDebug('refresh location permissions'); return checkOrFix( locPermissionsCheck, window['cordova'].plugins.BEMDataCollection.isValidLocationPermissions, false, ); }; - const iOSVersion = window['device'].version.split('.')[0]; - const iOSSettingsDescTag = 'intro.appstatus.locsettings.description.ios'; - const iOSPermDescTag = - iOSVersion < 13 - ? 'intro.appstatus.locperms.description.ios-lt-13' - : 'intro.appstatus.locperms.description.ios-gte-13'; - logDebug('description tags are ' + iOSSettingsDescTag + ' ' + iOSPermDescTag); - const locSettingsCheck = { + // location settings + let locSettingsCheck = { name: t('intro.appstatus.locsettings.name'), - desc: t(iOSSettingsDescTag), + desc: t(getLocSettingsDescriptionTag()), fix: fixSettings, refresh: checkSettings, }; - const locPermissionsCheck = { + let locPermissionsCheck = { name: t('intro.appstatus.locperms.name'), - desc: t(iOSPermDescTag), + desc: t(getLocPermissionsDescriptionTag()), fix: fixPerms, refresh: checkPerms, }; @@ -223,42 +147,12 @@ const usePermissionStatus = () => { setCheckList(tempChecks); } - function setupAndroidFitnessChecks() { - if (window['device'].version.split('.')[0] >= 10) { - let fixPerms = () => { - logDebug('fix and refresh fitness permissions'); - return checkOrFix( - fitnessPermissionsCheck, - window['cordova'].plugins.BEMDataCollection.fixFitnessPermissions, - true, - ).then((error) => { - if (error) { - fitnessPermissionsCheck.desc = error; - } - }); - }; - let checkPerms = () => { - logDebug('fix and refresh fitness permissions'); - return checkOrFix( - fitnessPermissionsCheck, - window['cordova'].plugins.BEMDataCollection.isValidFitnessPermissions, - false, - ); - }; - - let fitnessPermissionsCheck = { - name: t('intro.appstatus.fitnessperms.name'), - desc: t('intro.appstatus.fitnessperms.description.android'), - fix: fixPerms, - refresh: checkPerms, - }; - let tempChecks = checkList; - tempChecks.push(fitnessPermissionsCheck); - setCheckList(tempChecks); + function setupFitnessChecks() { + if (DEVICE_PLATFORM == 'android' && DEVICE_VERSION < 10) { + logDebug('Android version < 10, skipping fitness checks'); + return; } - } - function setupIOSFitnessChecks() { let fixPerms = () => { logDebug('fix and refresh fitness permissions'); return checkOrFix( @@ -272,7 +166,7 @@ const usePermissionStatus = () => { }); }; let checkPerms = () => { - logDebug('fix and refresh fitness permissions'); + logDebug('refresh fitness permissions'); return checkOrFix( fitnessPermissionsCheck, window['cordova'].plugins.BEMDataCollection.isValidFitnessPermissions, @@ -282,7 +176,7 @@ const usePermissionStatus = () => { let fitnessPermissionsCheck = { name: t('intro.appstatus.fitnessperms.name'), - desc: t('intro.appstatus.fitnessperms.description.ios'), + desc: t(`intro.appstatus.fitnessperms.description.${DEVICE_PLATFORM}`), fix: fixPerms, refresh: checkPerms, }; @@ -292,7 +186,7 @@ const usePermissionStatus = () => { } function setupAndroidBluetoothChecks() { - if (window['device'].version.split('.')[0] >= 10) { + if (DEVICE_VERSION >= 10) { let fixPerms = () => { logDebug('fix and refresh bluetooth permissions'); return checkOrFix( @@ -306,7 +200,7 @@ const usePermissionStatus = () => { }); }; let checkPerms = () => { - logDebug('fix and refresh bluetooth permissions'); + logDebug('refresh bluetooth permissions'); return checkOrFix( bluetoothPermissionsCheck, window['cordova'].plugins.BEMDataCollection.isValidBluetoothPermissions, @@ -326,7 +220,7 @@ const usePermissionStatus = () => { } } - function setupAndroidNotificationChecks() { + function setupNotificationChecks() { let fixPerms = () => { logDebug('fix and refresh notification permissions'); return checkOrFix( @@ -336,7 +230,7 @@ const usePermissionStatus = () => { ); }; let checkPerms = () => { - logDebug('fix and refresh notification permissions'); + logDebug('refresh notification permissions'); return checkOrFix( appAndChannelNotificationsCheck, window['cordova'].plugins.BEMDataCollection.isValidShowNotifications, @@ -348,6 +242,7 @@ const usePermissionStatus = () => { desc: t('intro.appstatus.notificationperms.description.android-enable'), fix: fixPerms, refresh: checkPerms, + isOptional: true, }; let tempChecks = checkList; tempChecks.push(appAndChannelNotificationsCheck); @@ -364,7 +259,7 @@ const usePermissionStatus = () => { ); }; let checkPerms = () => { - logDebug('fix and refresh backgroundRestriction permissions'); + logDebug('refresh backgroundRestriction permissions'); return checkOrFix( unusedAppsUnrestrictedCheck, window['cordova'].plugins.BEMDataCollection.isUnusedAppUnrestricted, @@ -380,20 +275,19 @@ const usePermissionStatus = () => { ); }; let checkBatteryOpt = () => { - logDebug('fix and refresh battery optimization permissions'); + logDebug('refresh battery optimization permissions'); return checkOrFix( ignoreBatteryOptCheck, window['cordova'].plugins.BEMDataCollection.isIgnoreBatteryOptimizations, false, ); }; - const androidVersion = window['device'].version.split('.')[0]; const androidUnusedDescTag = - androidVersion == 12 + DEVICE_VERSION == 12 ? 'intro.appstatus.unusedapprestrict.description.android-disable-12' - : androidVersion < 12 - ? 'intro.appstatus.unusedapprestrict.description.android-disable-lt-12' - : 'intro.appstatus.unusedapprestrict.description.android-disable-gte-13'; + : DEVICE_VERSION < 12 + ? 'intro.appstatus.unusedapprestrict.description.android-disable-lt-12' + : 'intro.appstatus.unusedapprestrict.description.android-disable-gte-13'; let unusedAppsUnrestrictedCheck = { name: t('intro.appstatus.unusedapprestrict.name'), desc: t(androidUnusedDescTag), @@ -416,7 +310,7 @@ const usePermissionStatus = () => { let overallFitnessName = t('intro.appstatus.overall-fitness-name-android'); let locExplanation = t('intro.appstatus.overall-loc-description'); - if (window['device'].platform.toLowerCase() == 'ios') { + if (DEVICE_PLATFORM == 'ios') { overallFitnessName = t('intro.appstatus.overall-fitness-name-ios'); } tempExplanations.push({ name: t('intro.appstatus.overall-loc-name'), desc: locExplanation }); @@ -441,24 +335,15 @@ const usePermissionStatus = () => { } function createChecklist() { - if (window['device'].platform.toLowerCase() == 'android') { - setupAndroidLocChecks(); - setupAndroidFitnessChecks(); + setupLocChecks(); + setupFitnessChecks(); + if (DEVICE_PLATFORM == 'android') { if (appConfig.tracking?.bluetooth_only) { setupAndroidBluetoothChecks(); } - setupAndroidNotificationChecks(); setupAndroidBackgroundRestrictionChecks(); - } else if (window['device'].platform.toLowerCase() == 'ios') { - setupIOSLocChecks(); - setupIOSFitnessChecks(); - setupAndroidNotificationChecks(); - } else { - setError('Alert! unknownplatform, no tracking'); - setErrorVis(true); - logWarn('Alert! unknownplatform, no tracking'); //need an alert, can use AlertBar? } - + setupNotificationChecks(); refreshAllChecks(checkList); } @@ -470,14 +355,15 @@ const usePermissionStatus = () => { //load when ready useEffect(() => { if (appConfig && window['device']?.platform) { + DEVICE_PLATFORM = window['device'].platform.toLowerCase(); + DEVICE_VERSION = window['device'].version.split('.')[0]; setupPermissionText(); - setHaveSetText(true); logDebug('setting up permissions'); createChecklist(); } }, [appConfig]); - return { checkList, overallStatus, error, errorVis, setErrorVis, explanationList }; + return { checkList, overallStatus, explanationList }; }; export default usePermissionStatus; From 58d1009b9a3485654268a3ddeec3e70845ed666c Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 4 Oct 2024 17:04:42 -0400 Subject: [PATCH 02/12] fix overallStatus, use 'every' instead of 'some' --- www/js/usePermissionStatus.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index be47ba525..01be9cd51 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -36,7 +36,7 @@ const usePermissionStatus = () => { const overallStatus = useMemo(() => { if (!checkList?.length) return undefined; // if checks not loaded yet, status is undetermined - return checkList.some((check) => !check.status && !check.isOptional); + return checkList.every((check) => check.status || check.isOptional); }, [checkList]); //using this function to update checks rather than mutate From f2a78b48af540c729052ff0e80a56c3048d8d537 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 4 Oct 2024 17:05:13 -0400 Subject: [PATCH 03/12] adjust iOS permissions strings --- www/i18n/en.json | 15 ++++++++------- www/js/usePermissionStatus.ts | 5 +++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/www/i18n/en.json b/www/i18n/en.json index b9318b16b..245de0051 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -296,7 +296,7 @@ "description": { "android-lt-9": "Location services should be enabled and set to High Accuracy. This allows us to accurately record the trajectory of the travel", "android-gte-9": "Location services should be enabled. This allows us to access location data and generate the trip log", - "ios": "Location services should be enabled. This allows us to access location data and generate the trip log" + "ios": "Turn on location services to enable location tracking" } }, "locperms": { @@ -307,27 +307,28 @@ "android-10": "Please select 'Allow all the time'", "android-11": "On the app settings page, choose the 'Location' permission and set it to 'Allow all the time'", "android-gte-12": "On the app settings page, choose the 'Location' permission and set it to 'Allow all the time' and 'Precise'", - "ios-lt-13": "Please select 'Always allow'", - "ios-gte-13": "On the app settings page, please select 'Always' and 'Precise' and return here to continue" + "ios-lt-13": "Select 'Always Allow' to enable location tracking", + "ios-13-13.3": "Select 'Always' and 'Precise' on the next screen to enable location tracking, then return here to continue", + "ios-gte-13.4": "Select 'Precise' and 'Allow While Using App' on the next screen, then 'Change to Always Allow' to enable location tracking" } }, "overall-fitness-name-android": "Physical activity", "overall-fitness-name-ios": "Motion and Fitness", - "overall-fitness-description": "The fitness sensors distinguish between walking, bicycling and motorized modes. We use this data in order to separate the parts of multi-modal travel such as transit. We also use it to as a cross-check potentially spurious trips - if the location sensor jumps across town but the fitness sensor is stationary, we can guess that the trip was invalid.", + "overall-fitness-description": "The fitness sensors distinguish between walking, bicycling and motorized modes. We use this data in order to separate the parts of multi-modal travel such as transit. We also use it to as a cross-check potentially spurious trips - if the location sensor jumps across town but the fitness sensor is stationary, we can guess that the trip was invalid.", "fitnessperms": { "name": "Fitness Permission", "description": { "android": "Please allow.", - "ios": "Please allow." + "ios": "Allow motion and fitness tracking to enable mode detection" } }, "overall-notification-name": "Notifications", - "overall-notification-description": "We need to use notifications to inform you if the settings are incorrect. We also use hourly invisible push notifications to wake up the app and allow it to upload data and check app status. We also use notifications to remind you to label your trips.", + "overall-notification-description": "We use notifications to inform you if the settings are incorrect. We also use hourly invisible push notifications to wake up the app and allow it to upload data and check app status. We also use notifications to remind you to label your trips.", "notificationperms": { "app-enabled-name": "App Notifications", "description": { "android-enable": "On the app settings page, ensure that all notifications and channels are enabled.", - "ios-enable": "Please allow, on the popup or the app settings page if necessary" + "ios-enable": "Allow notifications for reminders and app status alerts" } }, "overall-background-restrictions-name": "Background restrictions", diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index 01be9cd51..0eb53e107 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -86,7 +86,8 @@ const usePermissionStatus = () => { return 'intro.appstatus.locperms.description.android-gte-12'; } else if (DEVICE_PLATFORM == 'ios') { if (DEVICE_VERSION < 13) return 'intro.appstatus.locperms.description.ios-lt-13'; - return 'intro.appstatus.locperms.description.ios-gte-13'; + if (DEVICE_VERSION < 13.4) return 'intro.appstatus.locperms.description.ios-13-13.3'; + return 'intro.appstatus.locperms.description.ios-gte-13.4'; } throw new Error(`Unknown platform ${DEVICE_PLATFORM} – unable to proceed`); } @@ -239,7 +240,7 @@ const usePermissionStatus = () => { }; let appAndChannelNotificationsCheck = { name: t('intro.appstatus.notificationperms.app-enabled-name'), - desc: t('intro.appstatus.notificationperms.description.android-enable'), + desc: t(`intro.appstatus.notificationperms.description.${DEVICE_PLATFORM}-enable`), fix: fixPerms, refresh: checkPerms, isOptional: true, From 3d59ae7073841df058597aa37856d2714f132ba4 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 4 Oct 2024 17:09:02 -0400 Subject: [PATCH 04/12] adjust UI to better reflect required vs. optional permissions --- www/js/appTheme.ts | 2 +- www/js/appstatus/PermissionItem.tsx | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/www/js/appTheme.ts b/www/js/appTheme.ts index 430848907..a3268c510 100644 --- a/www/js/appTheme.ts +++ b/www/js/appTheme.ts @@ -31,7 +31,7 @@ const AppTheme = { level5: '#e1e9f1', // lch(92% 5 250) }, success: '#00a665', // lch(60% 55 155) - warn: '#f8cf53', //lch(85% 65 85) + warn: '#ebac3e', // lch(75% 65 75) danger: '#f23934', // lch(55% 85 35), silver: '#d9d9d9', skyblue: '#7fcaea', diff --git a/www/js/appstatus/PermissionItem.tsx b/www/js/appstatus/PermissionItem.tsx index 5dd2a4119..f14bb23cd 100644 --- a/www/js/appstatus/PermissionItem.tsx +++ b/www/js/appstatus/PermissionItem.tsx @@ -7,8 +7,14 @@ const PermissionItem = ({ check }) => { const { t } = useTranslation(); const { colors } = useAppTheme(); - const icon = check.status ? 'check-circle-outline' : 'alpha-x-circle-outline'; - const color = check.status ? colors.success : colors.error; + let icon, color; + if (check.status) { + icon = 'check-circle'; + color = colors.success; + } else { + icon = check.isOptional ? 'minus-circle-off' : 'alert-circle-outline'; + color = check.isOptional ? colors.warn : colors.error; + } return ( Date: Fri, 4 Oct 2024 17:09:15 -0400 Subject: [PATCH 05/12] reorder com.unarin.cordova.beacon in plugins list Having com.unarin.cordova.beacon after cordova-plugin-em-datacollection turned out to be a problem because strings such as NSLocationAlwaysUsageDescription were being overwritten, so the location requests said things like "This app would like to scan for iBeacons even when in the background." I moved com.unarin.cordova.beacon just above all the "cordova-*" plugins because that is where it would go alphabetically anyway --- package.cordovabuild.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.cordovabuild.json b/package.cordovabuild.json index 5018b3b05..b9de6eb33 100644 --- a/package.cordovabuild.json +++ b/package.cordovabuild.json @@ -57,6 +57,7 @@ "FCM_VERSION": "23.+", "IOS_FIREBASE_MESSAGING_VERSION": "~> 8.1.1" }, + "com.unarin.cordova.beacon": {}, "cordova-plugin-ionic-keyboard": {}, "cordova-plugin-app-version": {}, "cordova-plugin-file": {}, @@ -97,8 +98,7 @@ "ANDROID_SUPPORT_V4_VERSION": "27.+" }, "cordova-plugin-bluetooth-classic-serial-port": {}, - "cordova-custom-config": {}, - "com.unarin.cordova.beacon": {} + "cordova-custom-config": {} } }, "dependencies": { @@ -113,6 +113,7 @@ "chart.js": "^4.3.0", "chartjs-adapter-luxon": "^1.3.1", "chartjs-plugin-annotation": "^3.0.1", + "com.unarin.cordova.beacon": "github:e-mission/cordova-plugin-ibeacon", "cordova-android": "13.0.0", "cordova-ios": "7.1.1", "cordova-plugin-advanced-http": "3.3.1", @@ -135,7 +136,6 @@ "cordova-plugin-x-socialsharing": "6.0.4", "cordova-plugin-bluetooth-classic-serial-port": "git+https://github.com/e-mission/cordova-plugin-bluetooth-classic-serial-port.git", "cordova-custom-config": "^5.1.1", - "com.unarin.cordova.beacon": "github:e-mission/cordova-plugin-ibeacon", "core-js": "^2.5.7", "e-mission-common": "github:JGreenlee/e-mission-common#semver:0.6.2", "enketo-core": "^6.1.7", From 11216762a6f6592fc2c1d61f2e097d58906615f1 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Sat, 5 Oct 2024 02:28:48 -0400 Subject: [PATCH 06/12] go back to original updateCheck I changed this to accept the unmodified check with newProps as a separate parameter. It turns out the old implementation where the check was mutated was actually a behavior being relied on; so I am reverting --- www/js/usePermissionStatus.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index 0eb53e107..efc9c1b2b 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -41,11 +41,11 @@ const usePermissionStatus = () => { //using this function to update checks rather than mutate //this cues React to update UI - function updateCheck(check, newProps) { + function updateCheck(newCheck) { const tempList = [...checkList]; //"find and replace" the check - const i = tempList.findIndex((item) => item.name == check.name); - tempList[i] = { ...tempList[i], ...newProps }; + const i = tempList.findIndex((item) => item.name == newCheck.name); + tempList[i] = newCheck; setCheckList(tempList); } @@ -55,13 +55,15 @@ const usePermissionStatus = () => { try { const status = await nativeFn(); logDebug(`${checkObj.name} status = ${status}`); - updateCheck(newCheck, { status: true }); + newCheck.status = true; + updateCheck(newCheck); return status; } catch (error) { if (showError) { AlertManager.addMessage({ text: error }); } - updateCheck(newCheck, { status: false }); + newCheck.status = false; + updateCheck(newCheck); return error; } } From 112e9de163492445f7ac38e5e3fabf2fcf199a83 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Sat, 5 Oct 2024 02:34:48 -0400 Subject: [PATCH 07/12] add "wasRequested" for optional notif permission; update UI Although the notifs perm is optional, we do want to make sure the user is at least prompted for it. We need to ensure that overallStatus stays false until notif perms have been requested. We keep track of 'wasRequested', overallStatus will only pass if all checks are status=true or they are optional and have already been requested (ie they were explictly denied) We can also have this reflected in the UI; use 'outline' icons to indicate a check that was not yet prompted and use 'filled in' icons to indicate an explicit choice. Moved this to a separate function iconAndColorForCheck for tidiness --- www/js/appstatus/PermissionItem.tsx | 23 ++++++++++------------- www/js/usePermissionStatus.ts | 11 +++++++++-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/www/js/appstatus/PermissionItem.tsx b/www/js/appstatus/PermissionItem.tsx index f14bb23cd..0bca015b0 100644 --- a/www/js/appstatus/PermissionItem.tsx +++ b/www/js/appstatus/PermissionItem.tsx @@ -1,26 +1,23 @@ import React from 'react'; -import { List, Button } from 'react-native-paper'; +import { List } from 'react-native-paper'; import { useTranslation } from 'react-i18next'; -import { useAppTheme } from '../appTheme'; +import { colors } from '../appTheme'; + +function iconAndColorForCheck(check) { + if (check.status) return ['check-circle', colors.success]; + if (!check.isOptional) return ['alert-circle', colors.error]; + return [check.wasRequested ? 'minus-circle-off' : 'minus-circle-off-outline', colors.warn]; +} const PermissionItem = ({ check }) => { const { t } = useTranslation(); - const { colors } = useAppTheme(); - - let icon, color; - if (check.status) { - icon = 'check-circle'; - color = colors.success; - } else { - icon = check.isOptional ? 'minus-circle-off' : 'alert-circle-outline'; - color = check.isOptional ? colors.warn : colors.error; - } + const [icon, color] = iconAndColorForCheck(check); return ( check.fix()} title={t(check.name)} - description={t(check.desc)} + description={!check.status ? t(check.desc) : null} descriptionNumberOfLines={5} left={() => } right={() => } diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index efc9c1b2b..0ca9bbca9 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -25,6 +25,7 @@ type Check = { refresh: () => Promise; status?: boolean; isOptional?: boolean; + wasRequested?: boolean; }; const usePermissionStatus = () => { @@ -36,7 +37,7 @@ const usePermissionStatus = () => { const overallStatus = useMemo(() => { if (!checkList?.length) return undefined; // if checks not loaded yet, status is undetermined - return checkList.every((check) => check.status || check.isOptional); + return checkList.every((check) => check.status || (check.isOptional && check.wasRequested)); }, [checkList]); //using this function to update checks rather than mutate @@ -226,11 +227,16 @@ const usePermissionStatus = () => { function setupNotificationChecks() { let fixPerms = () => { logDebug('fix and refresh notification permissions'); + appAndChannelNotificationsCheck.wasRequested = true; return checkOrFix( appAndChannelNotificationsCheck, window['cordova'].plugins.BEMDataCollection.fixShowNotifications, true, - ); + ).then((error) => { + if (error) { + appAndChannelNotificationsCheck.desc = error; + } + }); }; let checkPerms = () => { logDebug('refresh notification permissions'); @@ -246,6 +252,7 @@ const usePermissionStatus = () => { fix: fixPerms, refresh: checkPerms, isOptional: true, + wasRequested: false, }; let tempChecks = checkList; tempChecks.push(appAndChannelNotificationsCheck); From 8fa1ce8e64f94140f4e3b5146d65524b283dfee8 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Sat, 5 Oct 2024 11:24:41 -0400 Subject: [PATCH 08/12] fix prettier --- package.cordovabuild.json | 1 + www/js/appstatus/PermissionsControls.tsx | 4 +--- www/js/usePermissionStatus.ts | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package.cordovabuild.json b/package.cordovabuild.json index b9de6eb33..520ff39d0 100644 --- a/package.cordovabuild.json +++ b/package.cordovabuild.json @@ -36,6 +36,7 @@ "expose-loader": "^4.1.0", "file-loader": "^6.2.0", "phonegap": "9.0.0+cordova.9.0.0", + "prettier": "3.1.0", "process": "^0.11.10", "sass": "^1.62.1", "sass-loader": "^13.3.1", diff --git a/www/js/appstatus/PermissionsControls.tsx b/www/js/appstatus/PermissionsControls.tsx index 562e41d4d..9e3a9a8fc 100644 --- a/www/js/appstatus/PermissionsControls.tsx +++ b/www/js/appstatus/PermissionsControls.tsx @@ -27,9 +27,7 @@ const PermissionsControls = ({ onAccept }) => { explanationList={explanationList} visible={explainVis} setVisible={setExplainVis}> - {checkList?.map((lc) => ( - - ))} + {checkList?.map((lc) => )} diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index 0ca9bbca9..4d1f9bee9 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -296,8 +296,8 @@ const usePermissionStatus = () => { DEVICE_VERSION == 12 ? 'intro.appstatus.unusedapprestrict.description.android-disable-12' : DEVICE_VERSION < 12 - ? 'intro.appstatus.unusedapprestrict.description.android-disable-lt-12' - : 'intro.appstatus.unusedapprestrict.description.android-disable-gte-13'; + ? 'intro.appstatus.unusedapprestrict.description.android-disable-lt-12' + : 'intro.appstatus.unusedapprestrict.description.android-disable-gte-13'; let unusedAppsUnrestrictedCheck = { name: t('intro.appstatus.unusedapprestrict.name'), desc: t(androidUnusedDescTag), From 0ffee6a3cc52390711b69ed112cfc28d1dea9e8c Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 7 Oct 2024 11:58:26 -0400 Subject: [PATCH 09/12] remove "precise" from ios-13-13.3 loc permission request string 'Precise' location did not exist until ios 14 so there is no point in describing it in the ios 13-13.3 prompt --- www/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/i18n/en.json b/www/i18n/en.json index 245de0051..199e60a6f 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -308,7 +308,7 @@ "android-11": "On the app settings page, choose the 'Location' permission and set it to 'Allow all the time'", "android-gte-12": "On the app settings page, choose the 'Location' permission and set it to 'Allow all the time' and 'Precise'", "ios-lt-13": "Select 'Always Allow' to enable location tracking", - "ios-13-13.3": "Select 'Always' and 'Precise' on the next screen to enable location tracking, then return here to continue", + "ios-13-13.3": "Select 'Always' location permissions on the next screen to enable location tracking, then return here to continue", "ios-gte-13.4": "Select 'Precise' and 'Allow While Using App' on the next screen, then 'Change to Always Allow' to enable location tracking" } }, From 5209b46bec4720f9036f191e5ca21b89acbfeb46 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 7 Oct 2024 12:03:34 -0400 Subject: [PATCH 10/12] don't show alert if notification permissions are denied If notification permissions are denied, the description text will be updated telling the user they will not receive reminders or alerts. We do not need to also put this text in an alert (SnackBar) --- www/js/usePermissionStatus.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index 4d1f9bee9..636d9c5e9 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -231,7 +231,7 @@ const usePermissionStatus = () => { return checkOrFix( appAndChannelNotificationsCheck, window['cordova'].plugins.BEMDataCollection.fixShowNotifications, - true, + false, ).then((error) => { if (error) { appAndChannelNotificationsCheck.desc = error; From 039f64792a39a97d707dbb8c9c17e0a9eb7fc40d Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 7 Oct 2024 17:11:21 -0400 Subject: [PATCH 11/12] update more strings - update overall-notification-description to not mention "hourly invisible push notifications" since they do not depend on the permission -adjust some Android strings to be more congruent with the iOS versions --- www/i18n/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/i18n/en.json b/www/i18n/en.json index 199e60a6f..afd4e2600 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -318,16 +318,16 @@ "fitnessperms": { "name": "Fitness Permission", "description": { - "android": "Please allow.", + "android": "Allow motion and fitness tracking to enable mode detection", "ios": "Allow motion and fitness tracking to enable mode detection" } }, "overall-notification-name": "Notifications", - "overall-notification-description": "We use notifications to inform you if the settings are incorrect. We also use hourly invisible push notifications to wake up the app and allow it to upload data and check app status. We also use notifications to remind you to label your trips.", + "overall-notification-description": "We use notifications to alert you if settings are incorrect and to remind you to label your trips.", "notificationperms": { "app-enabled-name": "App Notifications", "description": { - "android-enable": "On the app settings page, ensure that all notifications and channels are enabled.", + "android-enable": "On the app settings page, enable notifications for reminders and app status alerts.", "ios-enable": "Allow notifications for reminders and app status alerts" } }, From 7c346692141ccf2c40b7745ea0a01b1cc5a75968 Mon Sep 17 00:00:00 2001 From: "K. Shankari" Date: Mon, 7 Oct 2024 14:12:24 -0700 Subject: [PATCH 12/12] Bump up the data collection version for the native onboarding changes Related release: https://github.com/e-mission/e-mission-data-collection/releases/tag/v1.9.2 --- package.cordovabuild.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.cordovabuild.json b/package.cordovabuild.json index 520ff39d0..c8fa65973 100644 --- a/package.cordovabuild.json +++ b/package.cordovabuild.json @@ -122,7 +122,7 @@ "cordova-plugin-app-version": "0.1.14", "cordova-plugin-customurlscheme": "5.0.2", "cordova-plugin-device": "2.1.0", - "cordova-plugin-em-datacollection": "git+https://github.com/e-mission/e-mission-data-collection.git#v1.9.1", + "cordova-plugin-em-datacollection": "git+https://github.com/e-mission/e-mission-data-collection.git#v1.9.2", "cordova-plugin-em-opcodeauth": "git+https://github.com/e-mission/cordova-jwt-auth.git#v1.7.2", "cordova-plugin-em-server-communication": "git+https://github.com/e-mission/cordova-server-communication.git#v1.2.7", "cordova-plugin-em-serversync": "git+https://github.com/e-mission/cordova-server-sync.git#v1.3.3",