From 31d472e85814f898236972c9aa249c3c7996b597 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 26 Jan 2024 00:09:29 -0500 Subject: [PATCH 01/36] unify function declarations: `const` vs `function` (initiative previously described in https://github.com/e-mission/e-mission-phone/commit/05275693c5fbb2f2efa7cba060969dc0ce9a4760) >To be more consistent with the rest of the codebase, as a rule of thumb we're having top-level functions use 'function' synax while one-liners or nested functions are arrow functions. Named functions that are at the top-level of a file, React component, or custom hook, should be declared as `function foo() { ... }`. Anonymous / inline functions, and one-liner functions, will be declared as `const bar = () = { ... };` These general rules should help to keep some consistency throughout the codebase. --- www/__mocks__/fileSystemMocks.ts | 2 +- www/__mocks__/pushNotificationMocks.ts | 14 +- www/js/appTheme.ts | 4 +- www/js/components/LeafletView.tsx | 4 +- www/js/components/QrCode.tsx | 4 +- www/js/components/charting.ts | 4 +- www/js/config/useImperialConfig.ts | 12 +- www/js/control/ControlCollectionHelper.tsx | 44 ++--- www/js/control/ControlSyncHelper.tsx | 42 ++--- www/js/control/LogPage.tsx | 12 +- www/js/control/PopOpCode.tsx | 4 +- www/js/control/ProfileSettings.tsx | 18 +- www/js/control/SensedPage.tsx | 6 +- www/js/control/uploadService.ts | 14 +- www/js/diary/LabelTab.tsx | 4 +- www/js/diary/addressNamesHelper.ts | 4 +- www/js/diary/list/TimelineScrollList.tsx | 4 +- www/js/diary/timelineHelper.ts | 158 +++++++++--------- www/js/i18nextInit.ts | 4 +- www/js/metrics/CarbonFootprintCard.tsx | 4 +- www/js/metrics/ChangeIndicator.tsx | 40 ++--- www/js/metrics/metricsHelper.ts | 23 +-- www/js/onboarding/SurveyPage.tsx | 4 +- www/js/onboarding/WelcomePage.tsx | 25 +-- www/js/plugin/clientStats.ts | 20 +-- www/js/plugin/storage.ts | 16 +- www/js/services/controlHelper.ts | 103 +++++------- www/js/services/unifiedDataLoader.ts | 32 ++-- www/js/splash/customURL.ts | 10 +- www/js/splash/notifScheduler.ts | 47 +++--- www/js/splash/remoteNotifyHandler.ts | 15 +- www/js/splash/startprefs.ts | 17 +- www/js/splash/storeDeviceSettings.ts | 35 ++-- www/js/survey/enketo/enketoHelper.ts | 4 +- .../survey/enketo/infinite_scroll_filters.ts | 4 +- www/js/survey/inputMatcher.ts | 45 +++-- www/js/survey/multilabel/confirmHelper.ts | 8 +- .../multilabel/infinite_scroll_filters.ts | 8 +- www/js/usePermissionStatus.ts | 40 ++--- 39 files changed, 385 insertions(+), 473 deletions(-) diff --git a/www/__mocks__/fileSystemMocks.ts b/www/__mocks__/fileSystemMocks.ts index 1648c2f4b..64865e843 100644 --- a/www/__mocks__/fileSystemMocks.ts +++ b/www/__mocks__/fileSystemMocks.ts @@ -4,7 +4,7 @@ export const mockFileSystem = () => { onerror: (e: any) => void; write: (obj: Blob) => void; }; - window['resolveLocalFileSystemURL'] = function (parentDir, handleFS) { + window['resolveLocalFileSystemURL'] = (parentDir, handleFS) => { const fs = { filesystem: { root: { diff --git a/www/__mocks__/pushNotificationMocks.ts b/www/__mocks__/pushNotificationMocks.ts index 53a81e16c..4b2098076 100644 --- a/www/__mocks__/pushNotificationMocks.ts +++ b/www/__mocks__/pushNotificationMocks.ts @@ -18,16 +18,10 @@ export const mockPushNotification = () => { }; }; -export const clearNotifMock = function () { +export function clearNotifMock() { notifSettings = {}; onList = {}; called = null; -}; - -export const getOnList = function () { - return onList; -}; - -export const getCalled = function () { - return called; -}; +} +export const getOnList = () => onList; +export const getCalled = () => called; diff --git a/www/js/appTheme.ts b/www/js/appTheme.ts index 615a48c7e..ecf01bd0d 100644 --- a/www/js/appTheme.ts +++ b/www/js/appTheme.ts @@ -85,7 +85,7 @@ const flavorOverrides = { /* This function is used to retrieve the theme for a given flavor. If no valid flavor is specified, it returns the default theme. */ -export const getTheme = (flavor?: keyof typeof flavorOverrides) => { +export function getTheme(flavor?: keyof typeof flavorOverrides) { if (!flavor || !flavorOverrides[flavor]) return AppTheme; const typeStyle = flavorOverrides[flavor]; const scopedElevation = { ...AppTheme.colors.elevation, ...typeStyle?.colors?.elevation }; @@ -94,4 +94,4 @@ export const getTheme = (flavor?: keyof typeof flavorOverrides) => { ...{ ...typeStyle.colors, elevation: scopedElevation }, }; return { ...AppTheme, colors: scopedColors }; -}; +} diff --git a/www/js/components/LeafletView.tsx b/www/js/components/LeafletView.tsx index 822719654..e1d9748a6 100644 --- a/www/js/components/LeafletView.tsx +++ b/www/js/components/LeafletView.tsx @@ -110,7 +110,7 @@ const LeafletView = ({ geojson, opts, ...otherProps }) => { const startIcon = L.divIcon({ className: 'leaflet-div-icon-start', iconSize: [18, 18] }); const stopIcon = L.divIcon({ className: 'leaflet-div-icon-stop', iconSize: [18, 18] }); -const pointToLayer = (feature, latlng) => { +function pointToLayer(feature, latlng) { switch (feature.properties.feature_type) { case 'start_place': return L.marker(latlng, { icon: startIcon }); @@ -121,6 +121,6 @@ const pointToLayer = (feature, latlng) => { alert('Found unknown type in feature' + feature); return L.marker(latlng); } -}; +} export default LeafletView; diff --git a/www/js/components/QrCode.tsx b/www/js/components/QrCode.tsx index 7e16ac28e..fb9315c17 100644 --- a/www/js/components/QrCode.tsx +++ b/www/js/components/QrCode.tsx @@ -28,11 +28,11 @@ export function shareQR(message) { window['plugins'].socialsharing.shareWithOptions( prepopulateQRMessage, - function (result) { + (result) => { console.log('Share completed? ' + result.completed); // On Android apps mostly return false even while it's true console.log('Shared to app: ' + result.app); // On Android result.app is currently empty. On iOS it's empty when sharing is cancelled (result.completed=false) }, - function (msg) { + (msg) => { console.log('Sharing failed with message: ' + msg); }, ); diff --git a/www/js/components/charting.ts b/www/js/components/charting.ts index 6d0f75ed5..85b98bbbe 100644 --- a/www/js/components/charting.ts +++ b/www/js/components/charting.ts @@ -170,7 +170,7 @@ export function darkenOrLighten(baseColor: string, change: number) { * @param colors an array of colors, each of which is an array of [key, color string] * @returns an object mapping keys to colors, with duplicates darkened/lightened to be distinguishable */ -export const dedupColors = (colors: string[][]) => { +export function dedupColors(colors: string[][]) { const dedupedColors = {}; const maxAdjustment = 0.7; // more than this is too drastic and the colors approach black/white for (const [key, clr] of colors) { @@ -187,4 +187,4 @@ export const dedupColors = (colors: string[][]) => { } } return dedupedColors; -}; +} diff --git a/www/js/config/useImperialConfig.ts b/www/js/config/useImperialConfig.ts index d55fdffb0..04125aaf4 100644 --- a/www/js/config/useImperialConfig.ts +++ b/www/js/config/useImperialConfig.ts @@ -20,23 +20,23 @@ const MPS_TO_KMPH = 3.6; e.g. "7.02 km", "11.3 mph" - if value < 1, round to 2 decimal places e.g. "0.07 mi", "0.75 km" */ -export const formatForDisplay = (value: number): string => { +export function formatForDisplay(value: number): string { let opts: Intl.NumberFormatOptions = {}; if (value >= 100) opts.maximumFractionDigits = 0; else if (value >= 1) opts.maximumSignificantDigits = 3; else opts.maximumFractionDigits = 2; return Intl.NumberFormat(i18next.language, opts).format(value); -}; +} -export const convertDistance = (distMeters: number, imperial: boolean): number => { +export function convertDistance(distMeters: number, imperial: boolean): number { if (imperial) return (distMeters / 1000) * KM_TO_MILES; return distMeters / 1000; -}; +} -export const convertSpeed = (speedMetersPerSec: number, imperial: boolean): number => { +export function convertSpeed(speedMetersPerSec: number, imperial: boolean): number { if (imperial) return speedMetersPerSec * MPS_TO_KMPH * KM_TO_MILES; return speedMetersPerSec * MPS_TO_KMPH; -}; +} export function useImperialConfig(): ImperialConfig { const appConfig = useAppConfig(); diff --git a/www/js/control/ControlCollectionHelper.tsx b/www/js/control/ControlCollectionHelper.tsx index 4c6a21eaa..1b6caf749 100644 --- a/www/js/control/ControlCollectionHelper.tsx +++ b/www/js/control/ControlCollectionHelper.tsx @@ -92,9 +92,12 @@ export async function helperToggleLowAccuracy() { * Simple read/write wrappers */ -export const getState = function () { - return window['cordova'].plugins.BEMDataCollection.getState(); -}; +export const getState = () => window['cordova'].plugins.BEMDataCollection.getState(); +const setConfig = (config) => window['cordova'].plugins.BEMDataCollection.setConfig(config); +const getConfig = () => window['cordova'].plugins.BEMDataCollection.getConfig(); +const getAccuracyOptions = () => window['cordova'].plugins.BEMDataCollection.getAccuracyOptions(); +export const forceTransitionWrapper = (transition) => + window['cordova'].plugins.BEMDataCollection.forceTransition(transition); export async function getHelperCollectionSettings() { let promiseList: Promise[] = []; @@ -106,22 +109,7 @@ export async function getHelperCollectionSettings() { return formatConfigForDisplay(tempConfig, tempAccuracyOptions); } -const setConfig = function (config) { - return window['cordova'].plugins.BEMDataCollection.setConfig(config); -}; - -const getConfig = function () { - return window['cordova'].plugins.BEMDataCollection.getConfig(); -}; -const getAccuracyOptions = function () { - return window['cordova'].plugins.BEMDataCollection.getAccuracyOptions(); -}; - -export const forceTransitionWrapper = function (transition) { - return window['cordova'].plugins.BEMDataCollection.forceTransition(transition); -}; - -const formatConfigForDisplay = function (config, accuracyOptions) { +function formatConfigForDisplay(config, accuracyOptions) { const retVal: { key: string; val: string }[] = []; for (let prop in config) { if (prop == 'accuracy') { @@ -135,7 +123,7 @@ const formatConfigForDisplay = function (config, accuracyOptions) { } } return retVal; -}; +} const ControlCollectionHelper = ({ editVis, setEditVis }) => { const { colors } = useTheme(); @@ -158,13 +146,13 @@ const ControlCollectionHelper = ({ editVis, setEditVis }) => { getCollectionSettings(); }, [editVis]); - const formatAccuracyForActions = function (accuracyOptions) { + function formatAccuracyForActions(accuracyOptions) { let tempAccuracyActions: AccuracyAction[] = []; for (var name in accuracyOptions) { tempAccuracyActions.push({ text: name, value: accuracyOptions[name] }); } return tempAccuracyActions; - }; + } /* * Functions to edit and save values @@ -180,23 +168,23 @@ const ControlCollectionHelper = ({ editVis, setEditVis }) => { } } - const onToggle = function (config_key) { + function onToggle(config_key) { let tempConfig = { ...localConfig } as collectionConfig; tempConfig[config_key] = !(localConfig as collectionConfig)[config_key]; setLocalConfig(tempConfig); - }; + } - const onChooseAccuracy = function (accuracyOption) { + function onChooseAccuracy(accuracyOption) { let tempConfig = { ...localConfig } as collectionConfig; tempConfig.accuracy = accuracyOption.value; setLocalConfig(tempConfig); - }; + } - const onChangeText = function (newText, config_key) { + function onChangeText(newText, config_key) { let tempConfig = { ...localConfig } as collectionConfig; tempConfig[config_key] = parseInt(newText); setLocalConfig(tempConfig); - }; + } /*ios vs android*/ let filterComponent; diff --git a/www/js/control/ControlSyncHelper.tsx b/www/js/control/ControlSyncHelper.tsx index e46c38af6..8f33d2aaa 100644 --- a/www/js/control/ControlSyncHelper.tsx +++ b/www/js/control/ControlSyncHelper.tsx @@ -14,25 +14,17 @@ import { DateTime } from 'luxon'; /* * BEGIN: Simple read/write wrappers */ -export function forcePluginSync() { - return window['cordova'].plugins.BEMServerSync.forceSync(); -} +export const forcePluginSync = () => window['cordova'].plugins.BEMServerSync.forceSync(); +const setConfig = (config) => window['cordova'].plugins.BEMServerSync.setConfig(config); +const getConfig = () => window['cordova'].plugins.BEMServerSync.getConfig(); -const formatConfigForDisplay = (configToFormat) => { +function formatConfigForDisplay(configToFormat) { const formatted: any[] = []; for (let prop in configToFormat) { formatted.push({ key: prop, val: configToFormat[prop] }); } return formatted; -}; - -const setConfig = function (config) { - return window['cordova'].plugins.BEMServerSync.setConfig(config); -}; - -const getConfig = function () { - return window['cordova'].plugins.BEMServerSync.getConfig(); -}; +} export async function getHelperSyncSettings() { let tempConfig = await getConfig(); @@ -68,9 +60,7 @@ export const ForceSyncRow = ({ getState }) => { // If everything has been pushed, we should // have no more trip end transitions left - let isTripEnd = function (entry) { - return entry.metadata == getEndTransitionKey(); - }; + let isTripEnd = (entry) => entry.metadata == getEndTransitionKey(); let syncLaunchedCalls = sensorDataList.filter(isTripEnd); let syncPending = syncLaunchedCalls.length > 0; logDebug(`sensorDataList.length = ${sensorDataList.length}, @@ -88,29 +78,29 @@ export const ForceSyncRow = ({ getState }) => { } } - const getStartTransitionKey = function () { + function getStartTransitionKey() { if (window['cordova'].platformId == 'android') { return 'local.transition.exited_geofence'; } else if (window['cordova'].platformId == 'ios') { return 'T_EXITED_GEOFENCE'; } - }; + } - const getEndTransitionKey = function () { + function getEndTransitionKey() { if (window['cordova'].platformId == 'android') { return 'local.transition.stopped_moving'; } else if (window['cordova'].platformId == 'ios') { return 'T_TRIP_ENDED'; } - }; + } - const getOngoingTransitionState = function () { + function getOngoingTransitionState() { if (window['cordova'].platformId == 'android') { return 'local.state.ongoing_trip'; } else if (window['cordova'].platformId == 'ios') { return 'STATE_ONGOING_TRIP'; } - }; + } async function getTransition(transKey) { var entry_data = {}; @@ -226,17 +216,17 @@ const ControlSyncHelper = ({ editVis, setEditVis }) => { displayError(err, 'Error while setting sync config'); } } - const onChooseInterval = function (interval) { + function onChooseInterval(interval) { let tempConfig = { ...localConfig } as syncConfig; tempConfig.sync_interval = interval.value; setLocalConfig(tempConfig); - }; + } - const onTogglePush = function () { + function onTogglePush() { let tempConfig = { ...localConfig } as syncConfig; tempConfig.ios_use_remote_push = !(localConfig as syncConfig).ios_use_remote_push; setLocalConfig(tempConfig); - }; + } /* * configure the UI diff --git a/www/js/control/LogPage.tsx b/www/js/control/LogPage.tsx index ff29685a4..e71b17c78 100644 --- a/www/js/control/LogPage.tsx +++ b/www/js/control/LogPage.tsx @@ -52,14 +52,14 @@ const LogPage = ({ pageVis, setPageVis }) => { return loadStats?.gotMaxIndex && !loadStats?.reachedEnd; }, [loadStats]); - const clear = function () { + function clear() { window?.['Logger'].clearAll(); window?.['Logger'].log( window['Logger'].LEVEL_INFO, 'Finished clearing entries from unified log', ); refreshEntries(); - }; + } async function addEntries() { console.log('calling addEntries'); @@ -79,7 +79,7 @@ const LogPage = ({ pageVis, setPageVis }) => { } } - const processEntries = function (entryList) { + function processEntries(entryList) { let tempEntries: any[] = []; let tempLoadStats: loadStats = { ...loadStats } as loadStats; entryList.forEach((e) => { @@ -95,11 +95,11 @@ const LogPage = ({ pageVis, setPageVis }) => { } setEntries([...entries].concat(tempEntries)); //push the new entries onto the list setLoadStats(tempLoadStats); - }; + } - const emailLog = function () { + function emailLog() { sendEmail('loggerDB'); - }; + } const separator = () => ; const logItem = ({ item: logItem }) => ( diff --git a/www/js/control/PopOpCode.tsx b/www/js/control/PopOpCode.tsx index 451294867..6b6220eec 100644 --- a/www/js/control/PopOpCode.tsx +++ b/www/js/control/PopOpCode.tsx @@ -15,11 +15,11 @@ const PopOpCode = ({ visibilityValue, tokenURL, action, setVis }) => { const [copyAlertVis, setCopyAlertVis] = useState(false); - const copyText = function (textToCopy) { + function copyText(textToCopy) { navigator.clipboard.writeText(textToCopy).then(() => { setCopyAlertVis(true); }); - }; + } let copyButton; if (window['cordova'].platformId == 'ios') { diff --git a/www/js/control/ProfileSettings.tsx b/www/js/control/ProfileSettings.tsx index 56c19efe9..6951e889b 100644 --- a/www/js/control/ProfileSettings.tsx +++ b/www/js/control/ProfileSettings.tsx @@ -189,7 +189,7 @@ const ProfileSettings = () => { //ensure ui table updated when editor closes useEffect(() => { if (editCollectionVis == false) { - setTimeout(function () { + setTimeout(() => { console.log('closed editor, time to refresh collect'); refreshCollectSettings(); }, 1000); @@ -238,7 +238,7 @@ const ProfileSettings = () => { async function getSyncSettings() { console.log('getting sync settings'); const newSyncSettings: any = {}; - getHelperSyncSettings().then(function (showConfig) { + getHelperSyncSettings().then((showConfig) => { newSyncSettings.show_config = showConfig; setSyncSettings(newSyncSettings); console.log('sync settings are ', syncSettings); @@ -252,13 +252,13 @@ const ProfileSettings = () => { async function getConnectURL() { getSettings().then( - function (response) { + (response) => { const newConnectSettings: any = {}; newConnectSettings.url = response.connectUrl; console.log(response); setConnectSettings(newConnectSettings); }, - function (error) { + (error) => { displayError(error, 'While getting connect url'); }, ); @@ -324,7 +324,7 @@ const ProfileSettings = () => { async function toggleLowAccuracy() { let toggle = await helperToggleLowAccuracy(); - setTimeout(function () { + setTimeout(() => { refreshCollectSettings(); }, 1500); } @@ -348,12 +348,12 @@ const ProfileSettings = () => { async function invalidateCache() { window['cordova'].plugins.BEMUserCache.invalidateAllCache().then( - function (result) { + (result) => { console.log('invalidate result', result); setCacheResult(result); setInvalidateSuccessVis(true); }, - function (error) { + (error) => { displayError(error, 'while invalidating cache, error->'); }, ); @@ -362,7 +362,7 @@ const ProfileSettings = () => { //in ProfileSettings in DevZone (above two functions are helpers) async function checkConsent() { getConsentDocument().then( - function (resultDoc) { + (resultDoc) => { setConsentDoc(resultDoc); logDebug(`In profile settings, consent doc found = ${JSON.stringify(resultDoc)}`); if (resultDoc == null) { @@ -371,7 +371,7 @@ const ProfileSettings = () => { setConsentVis(true); } }, - function (error) { + (error) => { displayError(error, 'Error reading consent document from cache'); }, ); diff --git a/www/js/control/SensedPage.tsx b/www/js/control/SensedPage.tsx index d4fe12451..71343b7b6 100644 --- a/www/js/control/SensedPage.tsx +++ b/www/js/control/SensedPage.tsx @@ -12,10 +12,6 @@ const SensedPage = ({ pageVis, setPageVis }) => { const [entries, setEntries] = useState([]); - const emailCache = function () { - sendEmail('userCacheDB'); - }; - async function updateEntries() { //hardcoded function and keys after eliminating bit-rotted options let userCacheFn = window['cordova'].plugins.BEMUserCache.getAllMessages; @@ -66,7 +62,7 @@ const SensedPage = ({ pageVis, setPageVis }) => { updateEntries()} /> - emailCache()} /> + sendEmail('userCacheDB')} /> (async function (resolve, reject) { + return new Promise(async (resolve, reject) => { logInfo('About to get email config'); let url: string[] = []; try { @@ -39,31 +39,31 @@ function onUploadError(err) { } function readDBFile(parentDir, database, callbackFn) { - return new Promise(function (resolve, reject) { - window['resolveLocalFileSystemURL'](parentDir, function (fs) { + return new Promise((resolve, reject) => { + window['resolveLocalFileSystemURL'](parentDir, (fs) => { console.log('resolving file system as ', fs); fs.filesystem.root.getFile( fs.fullPath + database, null, (fileEntry) => { console.log(fileEntry); - fileEntry.file(function (file) { + fileEntry.file((file) => { console.log(file); var reader = new FileReader(); - reader.onprogress = function (report) { + reader.onprogress = (report) => { console.log('Current progress is ' + JSON.stringify(report)); if (callbackFn != undefined) { callbackFn((report.loaded * 100) / report.total); } }; - reader.onerror = function (error) { + reader.onerror = (error) => { console.log(this.error); reject({ error: { message: this.error } }); }; - reader.onload = function () { + reader.onload = () => { console.log( 'Successful file read with ' + this.result?.['byteLength'] + ' characters', ); diff --git a/www/js/diary/LabelTab.tsx b/www/js/diary/LabelTab.tsx index 946ccb91c..8cbe7e24b 100644 --- a/www/js/diary/LabelTab.tsx +++ b/www/js/diary/LabelTab.tsx @@ -249,9 +249,7 @@ const LabelTab = () => { tripsRead .slice() .reverse() - .forEach(function (trip, index) { - fillLocationNamesOfTrip(trip); - }); + .forEach((trip, index) => fillLocationNamesOfTrip(trip)); const readTimelineMap = compositeTrips2TimelineMap(tripsRead, showPlaces); logDebug(`LabelTab: after composite trips converted, readTimelineMap = ${[...readTimelineMap.entries()]}`); diff --git a/www/js/diary/addressNamesHelper.ts b/www/js/diary/addressNamesHelper.ts index 0364bffaa..b66eee8e8 100644 --- a/www/js/diary/addressNamesHelper.ts +++ b/www/js/diary/addressNamesHelper.ts @@ -58,7 +58,7 @@ export function useLocalStorage(key: string, initialValue: T) { LocalStorageObserver.subscribe(key, setStoredValue); - const setValue = (value: T) => { + function setValue(value: T) { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); @@ -69,7 +69,7 @@ export function useLocalStorage(key: string, initialValue: T) { } catch (error) { console.error(error); } - }; + } return [storedValue, setValue]; } diff --git a/www/js/diary/list/TimelineScrollList.tsx b/www/js/diary/list/TimelineScrollList.tsx index 06fd149c0..33d71d288 100644 --- a/www/js/diary/list/TimelineScrollList.tsx +++ b/www/js/diary/list/TimelineScrollList.tsx @@ -9,7 +9,7 @@ import LoadMoreButton from './LoadMoreButton'; import { useTranslation } from 'react-i18next'; import { Icon } from '../../components/Icon'; -const renderCard = ({ item: listEntry }) => { +function renderCard({ item: listEntry }) { if (listEntry.origin_key.includes('trip')) { return ; } else if (listEntry.origin_key.includes('place')) { @@ -19,7 +19,7 @@ const renderCard = ({ item: listEntry }) => { } else { throw new Error(`Unknown listEntry type: ${JSON.stringify(listEntry)}`); } -}; +} const separator = () => ; const bigSpinner = ; diff --git a/www/js/diary/timelineHelper.ts b/www/js/diary/timelineHelper.ts index 01f40fa99..c8337c4fb 100644 --- a/www/js/diary/timelineHelper.ts +++ b/www/js/diary/timelineHelper.ts @@ -213,11 +213,11 @@ const location2GeojsonPoint = (locationPoint: Point, featureType: string): Featu * @param trajectoryColor The color to use for the whole trajectory, if any. Otherwise, a color will be lookup up for the sensed mode of each section. * @returns for each section of the trip, a GeoJSON feature with type "LineString" and an array of coordinates. */ -const locations2GeojsonTrajectory = ( +function locations2GeojsonTrajectory( trip: CompositeTrip, locationList: Array, trajectoryColor?: string, -) => { +) { let sectionsPoints; if (!trip.sections) { // this is a unimodal trip so we put all the locations in one section @@ -244,7 +244,7 @@ const locations2GeojsonTrajectory = ( }, }; }); -}; +} // DB entries retrieved from the server have '_id', 'metadata', and 'data' fields. // This function returns a shallow copy of the obj, which flattens the @@ -276,20 +276,19 @@ export function readAllCompositeTrips(startTs: number, endTs: number) { return []; }); } -const dateTime2localdate = function (currtime: DateTime, tz: string) { - return { - timezone: tz, - year: currtime.year, - month: currtime.month, - day: currtime.day, - weekday: currtime.weekday, - hour: currtime.hour, - minute: currtime.minute, - second: currtime.second, - }; -}; -const points2TripProps = function (locationPoints: Array>) { +const dateTime2localdate = (currtime: DateTime, tz: string) => ({ + timezone: tz, + year: currtime.year, + month: currtime.month, + day: currtime.day, + weekday: currtime.weekday, + hour: currtime.hour, + minute: currtime.minute, + second: currtime.second, +}); + +function points2TripProps(locationPoints: Array>) { const startPoint = locationPoints[0]; const endPoint = locationPoints[locationPoints.length - 1]; const tripAndSectionId = `unprocessed_${startPoint.data.ts}_${endPoint.data.ts}`; @@ -339,12 +338,10 @@ const points2TripProps = function (locationPoints: Array, e2: BEMData) { - // compare timestamps - return e1.data.ts - e2.data.ts; -}; +const tsEntrySort = (e1: BEMData, e2: BEMData) => + e1.data.ts - e2.data.ts; // compare timestamps function transitionTrip2TripObj(trip: Array): Promise { const tripStartTransition = trip[0]; @@ -362,17 +359,13 @@ function transitionTrip2TripObj(trip: Array): Promise>) { + (locationList: Array>) => { if (locationList.length == 0) { return undefined; } const sortedLocationList = locationList.sort(tsEntrySort); - const retainInRange = function (loc) { - return ( - tripStartTransition.data.ts <= loc.data.ts && loc.data.ts <= tripEndTransition.data.ts - ); - }; - + const retainInRange = (loc) => + tripStartTransition.data.ts <= loc.data.ts && loc.data.ts <= tripEndTransition.data.ts; const filteredLocationList = sortedLocationList.filter(retainInRange); // Fix for https://github.com/e-mission/e-mission-docs/issues/417 @@ -419,7 +412,8 @@ function transitionTrip2TripObj(trip: Array): Promise) { + +function isStartingTransition(transWrapper: BEMData) { if ( transWrapper.data.transition == 'local.transition.exited_geofence' || transWrapper.data.transition == 'T_EXITED_GEOFENCE' || @@ -428,9 +422,9 @@ const isStartingTransition = function (transWrapper: BEMData) { return true; } return false; -}; +} -const isEndingTransition = function (transWrapper: BEMData) { +function isEndingTransition(transWrapper: BEMData) { // Logger.log("isEndingTransition: transWrapper.data.transition = "+transWrapper.data.transition); if ( transWrapper.data.transition == 'T_TRIP_ENDED' || @@ -442,7 +436,7 @@ const isEndingTransition = function (transWrapper: BEMData) { } // Logger.log("Returning false"); return false; -}; +} /* * This is going to be a bit tricky. As we can see from * https://github.com/e-mission/e-mission-phone/issues/214#issuecomment-286279163, @@ -458,7 +452,7 @@ const isEndingTransition = function (transWrapper: BEMData) { * * Let's abstract this out into our own minor state machine. */ -const transitions2Trips = function (transitionList: Array>) { +function transitions2Trips(transitionList: Array>) { let inTrip = false; const tripList: [BEMData, BEMData][] = []; let currStartTransitionIndex = -1; @@ -512,9 +506,9 @@ const transitions2Trips = function (transitionList: Array>, - ) { - if (transitionList.length == 0) { - logDebug('No unprocessed trips. yay!'); - return []; - } else { - logDebug(`Found ${transitionList.length} transitions. yay!`); - const tripsList = transitions2Trips(transitionList); - logDebug(`Mapped into ${tripsList.length} trips. yay!`); - tripsList.forEach(function (trip) { - logDebug(JSON.stringify(trip, null, 2)); - }); - const tripFillPromises = tripsList.map(transitionTrip2TripObj); - return Promise.all(tripFillPromises).then((rawTripObjs: (UnprocessedTrip | undefined)[]) => { - // Now we need to link up the trips. linking unprocessed trips - // to one another is fairly simple, but we need to link the - // first unprocessed trip to the last processed trip. - // This might be challenging if we don't have any processed - // trips for the day. I don't want to go back forever until - // I find a trip. So if this is the first trip, we will start a - // new chain for now, since this is with unprocessed data - // anyway. - - logDebug(`mapping trips to tripObjs of size ${rawTripObjs.length}`); - /* Filtering: we will keep trips that are 1) defined and 2) have a distance >= 100m, + return getUnifiedDataForInterval('statemachine/transition', tq, getMessageMethod).then( + (transitionList: Array>) => { + if (transitionList.length == 0) { + logDebug('No unprocessed trips. yay!'); + return []; + } else { + logDebug(`Found ${transitionList.length} transitions. yay!`); + const tripsList = transitions2Trips(transitionList); + logDebug(`Mapped into ${tripsList.length} trips. yay!`); + tripsList.forEach((trip) => { + logDebug(JSON.stringify(trip, null, 2)); + }); + const tripFillPromises = tripsList.map(transitionTrip2TripObj); + return Promise.all(tripFillPromises).then( + (rawTripObjs: (UnprocessedTrip | undefined)[]) => { + // Now we need to link up the trips. linking unprocessed trips + // to one another is fairly simple, but we need to link the + // first unprocessed trip to the last processed trip. + // This might be challenging if we don't have any processed + // trips for the day. I don't want to go back forever until + // I find a trip. So if this is the first trip, we will start a + // new chain for now, since this is with unprocessed data + // anyway. + + logDebug(`mapping trips to tripObjs of size ${rawTripObjs.length}`); + /* Filtering: we will keep trips that are 1) defined and 2) have a distance >= 100m, or duration >= 5 minutes https://github.com/e-mission/e-mission-docs/issues/966#issuecomment-1709112578 */ - const tripObjs = rawTripObjs.filter( - (trip) => trip && (trip.distance >= 100 || trip.duration >= 300), - ); - logDebug(`after filtering undefined and distance < 100m, + const tripObjs = rawTripObjs.filter( + (trip) => trip && (trip.distance >= 100 || trip.duration >= 300), + ); + logDebug(`after filtering undefined and distance < 100m, tripObjs size = ${tripObjs.length}`); - // Link 0th trip to first, first to second, ... - for (let i = 0; i < tripObjs.length - 1; i++) { - linkTrips(tripObjs[i], tripObjs[i + 1]); - } - logDebug(`finished linking trips for list of size ${tripObjs.length}`); - if (lastProcessedTrip && tripObjs.length != 0) { - // Need to link the entire chain above to the processed data - logDebug('linking unprocessed and processed trip chains'); - linkTrips(lastProcessedTrip, tripObjs[0]); - } - logDebug(`Returning final list of size ${tripObjs.length}`); - return tripObjs; - }); - } - }); + // Link 0th trip to first, first to second, ... + for (let i = 0; i < tripObjs.length - 1; i++) { + linkTrips(tripObjs[i], tripObjs[i + 1]); + } + logDebug(`finished linking trips for list of size ${tripObjs.length}`); + if (lastProcessedTrip && tripObjs.length != 0) { + // Need to link the entire chain above to the processed data + logDebug('linking unprocessed and processed trip chains'); + linkTrips(lastProcessedTrip, tripObjs[0]); + } + logDebug(`Returning final list of size ${tripObjs.length}`); + return tripObjs; + }, + ); + } + }, + ); } diff --git a/www/js/i18nextInit.ts b/www/js/i18nextInit.ts index 9d5aab146..3137d78e6 100644 --- a/www/js/i18nextInit.ts +++ b/www/js/i18nextInit.ts @@ -15,7 +15,7 @@ On dev builds, the fallback translation is prefixed with a globe emoji so it's e and we can fix it. On prod builds, we'll just show the English string. */ /* any strings defined in fallbackLang but not in lang will be merged into lang, recursively */ -const mergeInTranslations = (lang, fallbackLang) => { +function mergeInTranslations(lang, fallbackLang) { Object.entries(fallbackLang).forEach(([key, value]) => { if (lang[key] === undefined) { console.warn(`Missing translation for key '${key}'`); @@ -34,7 +34,7 @@ const mergeInTranslations = (lang, fallbackLang) => { } }); return lang; -}; +} import enJson from '../i18n/en.json'; import esJson from '../../locales/es/i18n/es.json'; diff --git a/www/js/metrics/CarbonFootprintCard.tsx b/www/js/metrics/CarbonFootprintCard.tsx index 047ab6aeb..ff06e8632 100644 --- a/www/js/metrics/CarbonFootprintCard.tsx +++ b/www/js/metrics/CarbonFootprintCard.tsx @@ -19,7 +19,7 @@ import { } from './metricsHelper'; import { useTranslation } from 'react-i18next'; import BarChart from '../components/BarChart'; -import ChangeIndicator from './ChangeIndicator'; +import ChangeIndicator, { CarbonChange } from './ChangeIndicator'; import color from 'color'; import { useAppTheme } from '../appTheme'; @@ -28,7 +28,7 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { const { colors } = useAppTheme(); const { t } = useTranslation(); - const [emissionsChange, setEmissionsChange] = useState({}); + const [emissionsChange, setEmissionsChange] = useState(undefined); const userCarbonRecords = useMemo(() => { if (userMetrics?.distance?.length) { diff --git a/www/js/metrics/ChangeIndicator.tsx b/www/js/metrics/ChangeIndicator.tsx index 869ec684f..137113ac1 100644 --- a/www/js/metrics/ChangeIndicator.tsx +++ b/www/js/metrics/ChangeIndicator.tsx @@ -5,41 +5,33 @@ import { useTranslation } from 'react-i18next'; import colorLib from 'color'; import { useAppTheme } from '../appTheme'; -type Props = { - change: { low: number; high: number }; -}; +export type CarbonChange = { low: number; high: number } | undefined; +type Props = { change: CarbonChange }; -const ChangeIndicator = ({ change }) => { +const ChangeIndicator = ({ change }: Props) => { const { colors } = useAppTheme(); const { t } = useTranslation(); - const changeSign = function (changeNum) { - if (changeNum > 0) { - return '+'; - } else { - return '-'; - } - }; + const changeSign = (changeNum) => (changeNum > 0 ? '+' : '-'); const changeText = useMemo(() => { - if (change) { - let low = isFinite(change.low) ? Math.round(Math.abs(change.low)) : '∞'; - let high = isFinite(change.high) ? Math.round(Math.abs(change.high)) : '∞'; + if (!change) return; + let low = isFinite(change.low) ? Math.round(Math.abs(change.low)) : '∞'; + let high = isFinite(change.high) ? Math.round(Math.abs(change.high)) : '∞'; - if (Math.round(change.low) == Math.round(change.high)) { - let text = changeSign(change.low) + low + '%'; - return text; - } else if (!(isFinite(change.low) || isFinite(change.high))) { - return ''; //if both are not finite, no information is really conveyed, so don't show - } else { - let text = `${changeSign(change.low) + low}% / ${changeSign(change.high) + high}%`; - return text; - } + if (Math.round(change.low) == Math.round(change.high)) { + let text = changeSign(change.low) + low + '%'; + return text; + } else if (!(isFinite(change.low) || isFinite(change.high))) { + return ''; //if both are not finite, no information is really conveyed, so don't show + } else { + let text = `${changeSign(change.low) + low}% / ${changeSign(change.high) + high}%`; + return text; } }, [change]); return changeText != '' ? ( - 0 ? colors.danger : colors.success)}> + 0 ? colors.danger : colors.success)}> {changeText + '\n'} {`${t('metrics.this-week')}`} diff --git a/www/js/metrics/metricsHelper.ts b/www/js/metrics/metricsHelper.ts index 3c709fca2..7ed348b75 100644 --- a/www/js/metrics/metricsHelper.ts +++ b/www/js/metrics/metricsHelper.ts @@ -62,24 +62,19 @@ const ON_FOOT_MODES = ['WALKING', 'RUNNING', 'ON_FOOT'] as const; * for regular data (user-specific), this will return the field value * for avg data (aggregate), this will return the field value/nUsers */ -const metricToValue = function (population: 'user' | 'aggreagte', metric, field) { - if (population == 'user') { - return metric[field]; - } else { - return metric[field] / metric.nUsers; - } -}; +const metricToValue = (population: 'user' | 'aggregate', metric, field) => + population == 'user' ? metric[field] : metric[field] / metric.nUsers; //testing agains global list of what is "on foot" //returns true | false -const isOnFoot = function (mode: string) { +function isOnFoot(mode: string) { for (let ped_mode in ON_FOOT_MODES) { if (mode === ped_mode) { return true; } } return false; -}; +} //from two weeks fo low and high values, calculates low and high change export function calculatePercentChange(pastWeekRange, previousWeekRange) { @@ -93,7 +88,7 @@ export function calculatePercentChange(pastWeekRange, previousWeekRange) { export function parseDataFromMetrics(metrics, population) { console.log('Called parseDataFromMetrics on ', metrics); let mode_bins: { [k: string]: [number, number, string][] } = {}; - metrics?.forEach(function (metric) { + metrics?.forEach((metric) => { let onFootVal = 0; for (let field in metric) { @@ -171,7 +166,7 @@ export function generateSummaryFromData(modeMap, metric) { * the label_prefix stripped out before this. Results should have either all * sensed labels or all custom labels. */ -export const isCustomLabels = function (modeMap) { +export function isCustomLabels(modeMap) { const isSensed = (mode) => mode == mode.toUpperCase(); const isCustom = (mode) => mode == mode.toLowerCase(); const metricSummaryChecksCustom: boolean[] = []; @@ -194,9 +189,9 @@ export const isCustomLabels = function (modeMap) { console.log('overall custom/not results for each metric = ', metricSummaryChecksCustom); return isAllCustom(metricSummaryChecksSensed, metricSummaryChecksCustom); -}; +} -const isAllCustom = function (isSensedKeys, isCustomKeys) { +function isAllCustom(isSensedKeys, isCustomKeys) { const allSensed = isSensedKeys.reduce((a, b) => a && b, true); const anySensed = isSensedKeys.reduce((a, b) => a || b, false); const allCustom = isCustomKeys.reduce((a, b) => a && b, true); @@ -210,4 +205,4 @@ const isAllCustom = function (isSensedKeys, isCustomKeys) { // Logger.displayError("Mixed entries that combine sensed and custom labels", // "Please report to your program admin"); return undefined; -}; +} diff --git a/www/js/onboarding/SurveyPage.tsx b/www/js/onboarding/SurveyPage.tsx index cdbb71521..a7880e8e6 100644 --- a/www/js/onboarding/SurveyPage.tsx +++ b/www/js/onboarding/SurveyPage.tsx @@ -16,7 +16,7 @@ import { displayErrorMsg } from '../plugin/logger'; import i18next from 'i18next'; let preloadedResponsePromise: Promise; -export const preloadDemoSurveyResponse = () => { +export function preloadDemoSurveyResponse() { if (!preloadedResponsePromise) { if (!registerUserDone) { displayErrorMsg(i18next.t('errors.not-registered-cant-contact')); @@ -25,7 +25,7 @@ export const preloadDemoSurveyResponse = () => { preloadedResponsePromise = loadPreviousResponseForSurvey(DEMOGRAPHIC_SURVEY_DATAKEY); } return preloadedResponsePromise; -}; +} const SurveyPage = () => { const { t } = useTranslation(); diff --git a/www/js/onboarding/WelcomePage.tsx b/www/js/onboarding/WelcomePage.tsx index 7c09a21d3..2cdd809f7 100644 --- a/www/js/onboarding/WelcomePage.tsx +++ b/www/js/onboarding/WelcomePage.tsx @@ -37,7 +37,7 @@ const WelcomePage = () => { const [infoPopupVis, setInfoPopupVis] = useState(false); const [existingToken, setExistingToken] = useState(''); - const getCode = function (result) { + function getCode(result) { let url = new window.URL(result.text); let notCancelled = result.cancelled == false; let isQR = result.format == 'QR_CODE'; @@ -45,27 +45,20 @@ const WelcomePage = () => { let hasToken = url.searchParams.has('token'); let code = url.searchParams.get('token'); - logDebug( - 'QR code ' + - result.text + - ' checks: cancel, format, prefix, params, code ' + - notCancelled + - isQR + - hasPrefix + - hasToken + - code, - ); + logDebug(`QR code ${result.text} checks: + cancel, format, prefix, params, code: + ${notCancelled}, ${isQR}, ${hasPrefix}, ${hasToken}, ${code}`); if (notCancelled && isQR && hasPrefix && hasToken) { return code; } else { return false; } - }; + } - const scanCode = function () { + function scanCode() { window['cordova'].plugins.barcodeScanner.scan( - function (result) { + (result) => { console.debug('scanned code', result); let code = getCode(result); if (code != false) { @@ -75,11 +68,11 @@ const WelcomePage = () => { displayError(result.text, 'invalid study reference'); } }, - function (error) { + (error) => { displayError(error, 'Scanning failed: '); }, ); - }; + } function loginWithToken(token) { initByUser({ token }) diff --git a/www/js/plugin/clientStats.ts b/www/js/plugin/clientStats.ts index 6735ef5ff..f342439c1 100644 --- a/www/js/plugin/clientStats.ts +++ b/www/js/plugin/clientStats.ts @@ -22,38 +22,38 @@ export const statKeys = { }; let appVersion; -export const getAppVersion = () => { +export function getAppVersion() { if (appVersion) return Promise.resolve(appVersion); return window['cordova']?.getAppVersion.getVersionNumber().then((version) => { appVersion = version; return version; }); -}; +} -const getStatsEvent = async (name: string, reading: any) => { +async function getStatsEvent(name: string, reading: any) { const ts = Date.now() / 1000; const client_app_version = await getAppVersion(); const client_os_version = window['device'].version; return { name, ts, reading, client_app_version, client_os_version }; -}; +} -export const addStatReading = async (name: string, reading: any) => { +export async function addStatReading(name: string, reading: any) { const db = window['cordova']?.plugins?.BEMUserCache; const event = await getStatsEvent(name, reading); if (db) return db.putMessage(CLIENT_TIME, event); displayErrorMsg('addStatReading: db is not defined'); -}; +} -export const addStatEvent = async (name: string) => { +export async function addStatEvent(name: string) { const db = window['cordova']?.plugins?.BEMUserCache; const event = await getStatsEvent(name, null); if (db) return db.putMessage(CLIENT_NAV_EVENT, event); displayErrorMsg('addStatEvent: db is not defined'); -}; +} -export const addStatError = async (name: string, errorStr: string) => { +export async function addStatError(name: string, errorStr: string) { const db = window['cordova']?.plugins?.BEMUserCache; const event = await getStatsEvent(name, errorStr); if (db) return db.putMessage(CLIENT_ERROR, event); displayErrorMsg('addStatError: db is not defined'); -}; +} diff --git a/www/js/plugin/storage.ts b/www/js/plugin/storage.ts index 7142991d8..deed606b8 100644 --- a/www/js/plugin/storage.ts +++ b/www/js/plugin/storage.ts @@ -1,19 +1,19 @@ import { addStatReading, statKeys } from './clientStats'; import { logDebug, logWarn } from './logger'; -const mungeValue = (key, value) => { +function mungeValue(key, value) { let store_val = value; if (typeof value != 'object') { store_val = {}; store_val[key] = value; } return store_val; -}; +} /* * If a non-JSON object was munged for storage, unwrap it. */ -const unmungeValue = (key, retData) => { +function unmungeValue(key, retData) { if (retData?.[key]) { // it must have been a simple data type that we munged upfront return retData[key]; @@ -21,25 +21,25 @@ const unmungeValue = (key, retData) => { // it must have been an object return retData; } -}; +} -const localStorageSet = (key: string, value: { [k: string]: any }) => { +function localStorageSet(key: string, value: { [k: string]: any }) { //checking for a value to prevent storing undefined //case where local was null and native was undefined stored "undefined" //see discussion: https://github.com/e-mission/e-mission-phone/pull/1072#discussion_r1373753945 if (value) { localStorage.setItem(key, JSON.stringify(value)); } -}; +} -const localStorageGet = (key: string) => { +function localStorageGet(key: string) { const value = localStorage.getItem(key); if (value) { return JSON.parse(value); } else { return null; } -}; +} /* We redundantly store data in both local and native storage. This function checks both for a value. If a value is present in only one, it copies it to the other and returns it. diff --git a/www/js/services/controlHelper.ts b/www/js/services/controlHelper.ts index 9969327be..1a9016557 100644 --- a/www/js/services/controlHelper.ts +++ b/www/js/services/controlHelper.ts @@ -8,52 +8,44 @@ import i18next from '../i18nextInit'; declare let window: FsWindow; -export const getMyDataHelpers = function ( - fileName: string, - startTimeString: string, - endTimeString: string, -) { - const localWriteFile = function (result: ServerResponse) { +export function getMyDataHelpers(fileName: string, startTimeString: string, endTimeString: string) { + function localWriteFile(result: ServerResponse) { const resultList = result.phone_data; - return new Promise(function (resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.cacheDirectory, function (fs) { - fs.filesystem.root.getFile( - fileName, - { create: true, exclusive: false }, - function (fileEntry) { - logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); - fileEntry.createWriter(function (fileWriter) { - fileWriter.onwriteend = function () { - logDebug('Successful file write...'); - resolve(); - }; - fileWriter.onerror = function (e) { - logDebug(`Failed file write: ${e.toString()}`); - reject(); - }; - logDebug(`fileWriter is: ${JSON.stringify(fileWriter.onwriteend, null, 2)}`); - // if data object is not passed in, create a new blob instead. - const dataObj = new Blob([JSON.stringify(resultList, null, 2)], { - type: 'application/json', - }); - fileWriter.write(dataObj); + return new Promise((resolve, reject) => { + window['resolveLocalFileSystemURL'](window['cordova'].file.cacheDirectory, (fs) => { + fs.filesystem.root.getFile(fileName, { create: true, exclusive: false }, (fileEntry) => { + logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); + fileEntry.createWriter((fileWriter) => { + fileWriter.onwriteend = () => { + logDebug('Successful file write...'); + resolve(); + }; + fileWriter.onerror = (e) => { + logDebug(`Failed file write: ${e.toString()}`); + reject(); + }; + logDebug(`fileWriter is: ${JSON.stringify(fileWriter.onwriteend, null, 2)}`); + // if data object is not passed in, create a new blob instead. + const dataObj = new Blob([JSON.stringify(resultList, null, 2)], { + type: 'application/json', }); - }, - ); + fileWriter.write(dataObj); + }); + }); }); }); - }; + } - const localShareData = function () { - return new Promise(function (resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.cacheDirectory, function (fs) { - fs.filesystem.root.getFile(fileName, null, function (fileEntry) { + function localShareData() { + return new Promise((resolve, reject) => { + window['resolveLocalFileSystemURL'](window['cordova'].file.cacheDirectory, (fs) => { + fs.filesystem.root.getFile(fileName, null, (fileEntry) => { logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file( - function (file) { + (file) => { const reader = new FileReader(); - reader.onloadend = function () { + reader.onloadend = () => { const readResult = this.result as string; logDebug(`Successfull file read with ${readResult.length} characters`); const dataArray = JSON.parse(readResult); @@ -71,19 +63,19 @@ export const getMyDataHelpers = function ( }; window['plugins'].socialsharing.shareWithOptions( shareObj, - function (result) { + (result) => { logDebug(`Share Completed? ${result.completed}`); // On Android, most likely returns false logDebug(`Shared to app: ${result.app}`); resolve(); }, - function (msg) { + (msg) => { logDebug(`Sharing failed with message ${msg}`); }, ); }; reader.readAsText(file); }, - function (error) { + (error) => { displayError(error, 'Error while downloading JSON dump'); reject(error); }, @@ -91,14 +83,14 @@ export const getMyDataHelpers = function ( }); }); }); - }; + } // window['cordova'].file.cacheDirectory is not guaranteed to free up memory, // so it's good practice to remove the file right after it's used! - const localClearData = function () { - return new Promise(function (resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.cacheDirectory, function (fs) { - fs.filesystem.root.getFile(fileName, null, function (fileEntry) { + function localClearData() { + return new Promise((resolve, reject) => { + window['resolveLocalFileSystemURL'](window['cordova'].file.cacheDirectory, (fs) => { + fs.filesystem.root.getFile(fileName, null, (fileEntry) => { fileEntry.remove( () => { logDebug(`Successfully cleaned up file ${fileName}`); @@ -112,20 +104,20 @@ export const getMyDataHelpers = function ( }); }); }); - }; + } return { writeFile: localWriteFile, shareData: localShareData, clearData: localClearData, }; -}; +} /** * getMyData fetches timeline data for a given day, and then gives the user a prompt to share the data * @param timeStamp initial timestamp of the timeline to be fetched. */ -export const getMyData = function (timeStamp: Date) { +export function getMyData(timeStamp: Date) { // We are only retrieving data for a single day to avoid // running out of memory on the phone const endTime = DateTime.fromJSDate(timeStamp); @@ -142,18 +134,13 @@ export const getMyData = function (timeStamp: Date) { .then(getDataMethods.writeFile) .then(getDataMethods.shareData) .then(getDataMethods.clearData) - .then(function () { + .then(() => { logInfo('Share queued successfully'); }) - .catch(function (error) { + .catch((error) => { displayError(error, 'Error sharing JSON dump'); }); -}; - -export const fetchOPCode = () => { - return window['cordova'].plugins.OPCodeAuth.getOPCode(); -}; +} -export const getSettings = () => { - return window['cordova'].plugins.BEMConnectionSettings.getSettings(); -}; +export const fetchOPCode = () => window['cordova'].plugins.OPCodeAuth.getOPCode(); +export const getSettings = () => window['cordova'].plugins.BEMConnectionSettings.getSettings(); diff --git a/www/js/services/unifiedDataLoader.ts b/www/js/services/unifiedDataLoader.ts index 540d3e479..97f20f8bf 100644 --- a/www/js/services/unifiedDataLoader.ts +++ b/www/js/services/unifiedDataLoader.ts @@ -6,23 +6,23 @@ import { ServerResponse, BEMData, TimeQuery } from '../types/serverData'; * @param list An array of values from a BEMUserCache promise * @returns an array with duplicate values removed */ -export const removeDup = function (list: Array>) { - return list.filter(function (value, i, array) { - const firstIndexOfValue = array.findIndex(function (element) { - return element.metadata.write_ts == value.metadata.write_ts; - }); +export function removeDup(list: Array>) { + return list.filter((value, i, array) => { + const firstIndexOfValue = array.findIndex( + (element) => element.metadata.write_ts == value.metadata.write_ts, + ); return firstIndexOfValue == i; }); -}; +} -export const combinedPromises = function ( +export function combinedPromises( promiseList: Array>, filter: (list: Array) => Array, ) { if (promiseList.length === 0) { throw new RangeError('combinedPromises needs input array.length >= 1'); } - return new Promise(function (resolve, reject) { + return new Promise((resolve, reject) => { Promise.allSettled(promiseList).then( (results) => { let allRej = true; @@ -42,7 +42,7 @@ export const combinedPromises = function ( }, ); }); -}; +} /** * getUnifiedDataForInterval is a generalized method to fetch data by its timestamps @@ -51,18 +51,18 @@ export const combinedPromises = function ( * @param localGetMethod a BEMUserCache method that fetches certain data via a promise * @returns A promise that evaluates to the all values found within the queried data */ -export const getUnifiedDataForInterval = function ( +export function getUnifiedDataForInterval( key: string, tq: TimeQuery, localGetMethod: (key: string, tq: TimeQuery, flag: boolean) => Promise, ) { const test = true; const getPromise = localGetMethod(key, tq, test); - const remotePromise = getRawEntries([key], tq.startTs, tq.endTs).then(function ( - serverResponse: ServerResponse, - ) { - return serverResponse.phone_data; - }); + const remotePromise = getRawEntries([key], tq.startTs, tq.endTs).then( + (serverResponse: ServerResponse) => { + return serverResponse.phone_data; + }, + ); const promiseList = [getPromise, remotePromise]; return combinedPromises(promiseList, removeDup); -}; +} diff --git a/www/js/splash/customURL.ts b/www/js/splash/customURL.ts index d351fcc0b..ec3da45bd 100644 --- a/www/js/splash/customURL.ts +++ b/www/js/splash/customURL.ts @@ -2,12 +2,10 @@ type UrlComponents = { [key: string]: string; }; -type OnLaunchCustomURL = ( +export function onLaunchCustomURL( rawUrl: string, - callback: (url: string, urlComponents: UrlComponents) => void, -) => void; - -export const onLaunchCustomURL: OnLaunchCustomURL = (rawUrl, handler) => { + handler: (url: string, urlComponents: UrlComponents) => void, +) { try { const url = rawUrl.split('//')[1]; const [route, paramString] = url.split('?'); @@ -21,4 +19,4 @@ export const onLaunchCustomURL: OnLaunchCustomURL = (rawUrl, handler) => { } catch { console.log('not a valid url'); } -}; +} diff --git a/www/js/splash/notifScheduler.ts b/www/js/splash/notifScheduler.ts index b059aa3cd..f82d7b214 100644 --- a/www/js/splash/notifScheduler.ts +++ b/www/js/splash/notifScheduler.ts @@ -14,7 +14,7 @@ function range(start, stop, step) { } // returns an array of DateTime objects, for all times that notifications should be sent -const calcNotifTimes = (scheme, dayZeroDate, timeOfDay): DateTime[] => { +function calcNotifTimes(scheme, dayZeroDate, timeOfDay): DateTime[] { const notifTimes: DateTime[] = []; for (const s of scheme.schedule) { // the days to send notifications, as integers, relative to day zero @@ -28,17 +28,17 @@ const calcNotifTimes = (scheme, dayZeroDate, timeOfDay): DateTime[] => { } } return notifTimes; -}; +} // returns true if all expected times are already scheduled -const areAlreadyScheduled = (notifs: any[], expectedTimes: DateTime[]) => { +function areAlreadyScheduled(notifs: any[], expectedTimes: DateTime[]) { for (const t of expectedTimes) { if (!notifs.some((n) => DateTime.fromJSDate(n.trigger.at).equals(t))) { return false; } } return true; -}; +} /* remove notif actions as they do not work, can restore post routing migration */ // const setUpActions = () => { @@ -73,7 +73,7 @@ function debugGetScheduled(prefix) { } //new method to fetch notifications -export const getScheduledNotifs = function (isScheduling: boolean, scheduledPromise: Promise) { +export function getScheduledNotifs(isScheduling: boolean, scheduledPromise: Promise) { return new Promise((resolve, reject) => { /* if the notifications are still in active scheduling it causes problems anywhere from 0-n of the scheduled notifs are displayed @@ -98,11 +98,11 @@ export const getScheduledNotifs = function (isScheduling: boolean, scheduledProm }); } }); -}; +} type ScheduledNotif = { key: string; val: string }; //get scheduled notifications from cordova plugin and format them -const getNotifs = function () { +function getNotifs() { return new Promise((resolve, reject) => { window['cordova'].plugins.notification.local.getScheduled((notifs: any[]) => { if (!notifs?.length) { @@ -126,10 +126,10 @@ const getNotifs = function () { resolve(scheduledNotifs); }); }); -}; +} // schedules the notifications using the cordova plugin -const scheduleNotifs = (scheme, notifTimes: DateTime[], setIsScheduling: Function) => { +function scheduleNotifs(scheme, notifTimes: DateTime[], setIsScheduling: Function) { return new Promise((rs) => { setIsScheduling(true); const localeCode = i18next.language; @@ -162,19 +162,17 @@ const scheduleNotifs = (scheme, notifTimes: DateTime[], setIsScheduling: Functio }); }); }); -}; +} -const removeEmptyObjects = (list: any[]): any[] => { - return list.filter((n) => Object.keys(n).length !== 0); -}; +const removeEmptyObjects = (list: any[]): any[] => list.filter((n) => Object.keys(n).length !== 0); // determines when notifications are needed, and schedules them if not already scheduled -export const updateScheduledNotifs = async ( +export async function updateScheduledNotifs( reminderSchemes: ReminderSchemesConfig, isScheduling: boolean, setIsScheduling: Function, scheduledPromise: Promise, -): Promise => { +): Promise { const { reminder_assignment, reminder_join_date, reminder_time_of_day } = await getReminderPrefs( reminderSchemes, isScheduling, @@ -212,13 +210,13 @@ export const updateScheduledNotifs = async ( } }); }); -}; +} /* Randomly assign a scheme, set the join date to today, and use the default time of day from config (or noon if not specified) This is only called once when the user first joins the study */ -const initReminderPrefs = (reminderSchemes: object): object => { +function initReminderPrefs(reminderSchemes: object): object { // randomly assign from the schemes listed in config const schemes = Object.keys(reminderSchemes); const randAssignment: string = schemes[Math.floor(Math.random() * schemes.length)]; @@ -229,7 +227,7 @@ const initReminderPrefs = (reminderSchemes: object): object => { reminder_join_date: todayDate, reminder_time_of_day: defaultTime, }; -}; +} /* EXAMPLE VALUES - present in user profile object reminder_assignment: 'passive', @@ -243,12 +241,12 @@ interface User { reminder_time_of_day: string; } -export const getReminderPrefs = async ( +export async function getReminderPrefs( reminderSchemes: ReminderSchemesConfig, isScheduling: boolean, setIsScheduling: Function, scheduledPromise: Promise, -): Promise => { +): Promise { const userPromise = getUser(); const user = (await userPromise) as User; if (user?.reminder_assignment && user?.reminder_join_date && user?.reminder_time_of_day) { @@ -267,14 +265,15 @@ export const getReminderPrefs = async ( scheduledPromise, ); return { ...user, ...initPrefs }; // user profile + the new prefs -}; -export const setReminderPrefs = async ( +} + +export async function setReminderPrefs( newPrefs: object, reminderSchemes: ReminderSchemesConfig, isScheduling: boolean, setIsScheduling: Function, scheduledPromise: Promise, -): Promise => { +): Promise { await updateUser(newPrefs); const updatePromise = new Promise((resolve, reject) => { //enforcing update before moving on @@ -298,4 +297,4 @@ export const setReminderPrefs = async ( }, ); return updatePromise; -}; +} diff --git a/www/js/splash/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts index cbc920ced..9b6f80f01 100644 --- a/www/js/splash/remoteNotifyHandler.ts +++ b/www/js/splash/remoteNotifyHandler.ts @@ -23,16 +23,13 @@ TODO: Potentially unify with the survey URL loading * @function launches a webpage * @param url to open in the browser */ -const launchWebpage = function (url) { - // THIS LINE FOR inAppBrowser - let iab = window['cordova'].InAppBrowser.open(url, '_blank', options); -}; +const launchWebpage = (url) => window['cordova'].InAppBrowser.open(url, '_blank', options); /** - * @callback for cloud notification event + * @description callback for cloud notification event * @param event that triggered this call */ -const onCloudNotifEvent = (event) => { +function onCloudNotifEvent(event) { const data = event.detail; addStatEvent(statKeys.NOTIFICATION_OPEN).then(() => { console.log('Added ' + statKeys.NOTIFICATION_OPEN + ' event. Data = ' + JSON.stringify(data)); @@ -64,12 +61,12 @@ const onCloudNotifEvent = (event) => { } } } -}; +} /** * @function initializes the remote notification handling * subscribes to cloud notification event */ -export const initRemoteNotifyHandler = function () { +export function initRemoteNotifyHandler() { subscribe(EVENTS.CLOUD_NOTIFICATION_EVENT, onCloudNotifEvent); -}; +} diff --git a/www/js/splash/startprefs.ts b/www/js/splash/startprefs.ts index 6d5761aae..7700f315a 100644 --- a/www/js/splash/startprefs.ts +++ b/www/js/splash/startprefs.ts @@ -29,7 +29,7 @@ export function markConsented() { // mark in native storage return readConsentState() .then(writeConsentToNative) - .then(function (response) { + .then((response) => { // mark in local storage storageSet(DATA_COLLECTION_CONSENTED_PROTOCOL, _req_consent); // mark in local variable as well @@ -68,13 +68,13 @@ export function isConsented() { export function readConsentState() { return fetch('json/startupConfig.json') .then((response) => response.json()) - .then(function (startupConfigResult) { + .then((startupConfigResult) => { console.log(startupConfigResult); _req_consent = startupConfigResult.emSensorDataCollectionProtocol; logDebug('required consent version = ' + JSON.stringify(_req_consent)); return storageGet(DATA_COLLECTION_CONSENTED_PROTOCOL); }) - .then(function (kv_store_consent) { + .then((kv_store_consent) => { _curr_consented = kv_store_consent; console.assert( _req_consent != undefined && _req_consent != null, @@ -93,13 +93,8 @@ export function readConsentState() { //used in ProfileSettings export function getConsentDocument() { return window['cordova'].plugins.BEMUserCache.getDocument('config/consent', false).then( - function (resultDoc) { - if (window['cordova'].plugins.BEMUserCache.isEmptyDoc(resultDoc)) { - return null; - } else { - return resultDoc; - } - }, + (resultDoc) => + window['cordova'].plugins.BEMUserCache.isEmptyDoc(resultDoc) ? null : resultDoc, ); } @@ -108,7 +103,7 @@ export function getConsentDocument() { * @returns if doc not stored in native, a promise to write it there */ function checkNativeConsent() { - getConsentDocument().then(function (resultDoc) { + getConsentDocument().then((resultDoc) => { if (resultDoc == null) { if (isConsented()) { logDebug('Local consent found, native consent missing, writing consent to native'); diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts index 2a4a646b9..4e389e749 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -6,16 +6,16 @@ import { readIntroDone } from '../onboarding/onboardingHelper'; import { subscribe, EVENTS, unsubscribe } from '../customEventHandler'; /** - * @function Gathers information about the user's device and stores it + * @description Gathers information about the user's device and stores it * @returns promise to updateUser in comm settings with device info */ -const storeDeviceSettings = function () { +function storeDeviceSettings() { var lang = i18next.resolvedLanguage; var manufacturer = window['device'].manufacturer; var osver = window['device'].version; return window['cordova'].getAppVersion .getVersionNumber() - .then(function (appver) { + .then((appver) => { var updateJSON = { phone_lang: lang, curr_platform: window['cordova'].platformId, @@ -26,19 +26,19 @@ const storeDeviceSettings = function () { logDebug('About to update profile with settings = ' + JSON.stringify(updateJSON)); return updateUser(updateJSON); }) - .then(function (updateJSON) { + .then((updateJSON) => { // alert("Finished saving token = "+JSON.stringify(t.token)); }) - .catch(function (error) { + .catch((error) => { displayError(error, 'Error in updating profile to store device settings'); }); -}; +} /** * @function stores device settings on reconsent * @param event that called this function */ -const onConsentEvent = (event) => { +function onConsentEvent(event) { console.log( 'got consented event ' + JSON.stringify(event['name']) + @@ -53,27 +53,26 @@ const onConsentEvent = (event) => { await storeDeviceSettings(); } }); -}; +} /** * @function stores device settings after intro received * @param event that called this function */ -const onIntroEvent = async (event) => { - logDebug( - 'intro is done -> original consent situation, we should have a token by now -> store device settings', - ); +async function onIntroEvent(event) { + logDebug(`intro is done -> original consent situation, + we should have a token by now -> store device settings`); await storeDeviceSettings(); -}; +} /** * @function initializes store device: subscribes to events * stores settings if already consented */ -export const initStoreDeviceSettings = function () { +export function initStoreDeviceSettings() { readConsentState() .then(isConsented) - .then(async function (consentState) { + .then(async (consentState) => { console.log('found consent', consentState); if (consentState == true) { await storeDeviceSettings(); @@ -84,9 +83,9 @@ export const initStoreDeviceSettings = function () { subscribe(EVENTS.INTRO_DONE_EVENT, onIntroEvent); }); logDebug('storedevicesettings startup done'); -}; +} -export const teardownDeviceSettings = function () { +export function teardownDeviceSettings() { unsubscribe(EVENTS.CONSENTED_EVENT, onConsentEvent); unsubscribe(EVENTS.INTRO_DONE_EVENT, onIntroEvent); -}; +} diff --git a/www/js/survey/enketo/enketoHelper.ts b/www/js/survey/enketo/enketoHelper.ts index 76b80112b..7bae2c06f 100644 --- a/www/js/survey/enketo/enketoHelper.ts +++ b/www/js/survey/enketo/enketoHelper.ts @@ -292,12 +292,12 @@ export function saveResponse( .then((data) => data); } -const _getMostRecent = (responses) => { +function _getMostRecent(responses) { responses.sort((a, b) => a.metadata.write_ts < b.metadata.write_ts); logDebug(`_getMostRecent: first response is ${responses[0]}; last response is ${responses.slice(-1)[0]}`); return responses[0]; -}; +} /* * We retrieve all the records every time instead of caching because of the diff --git a/www/js/survey/enketo/infinite_scroll_filters.ts b/www/js/survey/enketo/infinite_scroll_filters.ts index 5d17b600e..d4b281713 100644 --- a/www/js/survey/enketo/infinite_scroll_filters.ts +++ b/www/js/survey/enketo/infinite_scroll_filters.ts @@ -8,9 +8,7 @@ import i18next from 'i18next'; -const unlabeledCheck = (trip, userInputForTrip) => { - return !userInputForTrip?.['SURVEY']; -}; +const unlabeledCheck = (trip, userInputForTrip) => !userInputForTrip?.['SURVEY']; const TO_LABEL = { key: 'to_label', diff --git a/www/js/survey/inputMatcher.ts b/www/js/survey/inputMatcher.ts index bf6395ee1..b597400b8 100644 --- a/www/js/survey/inputMatcher.ts +++ b/www/js/survey/inputMatcher.ts @@ -12,19 +12,16 @@ const EPOCH_MAXIMUM = 2 ** 31 - 1; export const fmtTs = (ts_in_secs: number, tz: string): false | string | null => ts_in_secs && tz ? DateTime.fromSeconds(ts_in_secs, { zone: tz }).toISO() : null; -export const printUserInput = (ui: UserInputEntry): string => `${fmtTs( - ui.data.start_ts, - ui.metadata.time_zone, -)} (${ui.data.start_ts}) -> -${fmtTs(ui.data.end_ts, ui.metadata.time_zone)} (${ui.data.end_ts}) ${ui.data.label} logged at ${ - ui.metadata.write_ts -}`; - -export const validUserInputForDraftTrip = ( +export const printUserInput = (ui: UserInputEntry): string => + `${fmtTs(ui.data.start_ts, ui.metadata.time_zone)} (${ui.data.start_ts}) -> + ${fmtTs(ui.data.end_ts, ui.metadata.time_zone)} (${ui.data.end_ts}), + ${ui.data.label}, logged at ${ui.metadata.write_ts}`; + +export function validUserInputForDraftTrip( trip: CompositeTrip, userInput: UserInputEntry, logsEnabled: boolean, -): boolean => { +): boolean { if (logsEnabled) { logDebug(`Draft trip: comparing user = ${fmtTs(userInput.data.start_ts, userInput.metadata.time_zone)} @@ -43,14 +40,14 @@ export const validUserInputForDraftTrip = ( -(userInput.data.start_ts - trip.start_ts) <= 15 * 60) && userInput.data.end_ts <= trip.end_ts ); -}; +} -export const validUserInputForTimelineEntry = ( +export function validUserInputForTimelineEntry( tlEntry: TimelineEntry, nextEntry: TimelineEntry | null, userInput: UserInputEntry, logsEnabled: boolean, -): boolean => { +): boolean { if (!tlEntry.origin_key) return false; if (tlEntry.origin_key.includes('UNPROCESSED')) return validUserInputForDraftTrip(tlEntry as CompositeTrip, userInput, logsEnabled); @@ -130,10 +127,10 @@ export const validUserInputForTimelineEntry = ( } } return startChecks && endChecks; -}; +} // parallels get_not_deleted_candidates() in trip_queries.py -export const getNotDeletedCandidates = (candidates: UserInputEntry[]): UserInputEntry[] => { +export function getNotDeletedCandidates(candidates: UserInputEntry[]): UserInputEntry[] { console.log('getNotDeletedCandidates called with ' + candidates.length + ' candidates'); // We want to retain all ACTIVE entries that have not been DELETED @@ -147,13 +144,13 @@ export const getNotDeletedCandidates = (candidates: UserInputEntry[]): UserInput ${notDeletedActive.length} non deleted active entries`); return notDeletedActive; -}; +} -export const getUserInputForTimelineEntry = ( +export function getUserInputForTimelineEntry( entry: TimelineEntry, nextEntry: TimelineEntry | null, userInputList: UserInputEntry[], -): undefined | UserInputEntry => { +): undefined | UserInputEntry { const logsEnabled = userInputList?.length < 20; if (userInputList === undefined) { logDebug('In getUserInputForTimelineEntry, no user input, returning undefined'); @@ -191,14 +188,14 @@ export const getUserInputForTimelineEntry = ( logDebug('Returning mostRecentEntry ' + printUserInput(mostRecentEntry)); return mostRecentEntry; -}; +} // return array of matching additions for a trip or place -export const getAdditionsForTimelineEntry = ( +export function getAdditionsForTimelineEntry( entry: TimelineEntry, nextEntry: TimelineEntry | null, additionsList: EnketoUserInputEntry[], -): UserInputEntry[] => { +): UserInputEntry[] { const logsEnabled = additionsList?.length < 20; if (additionsList === undefined) { @@ -215,9 +212,9 @@ export const getAdditionsForTimelineEntry = ( if (logsEnabled) console.log(`Matching Addition list ${matchingAdditions.map(printUserInput)}`); return matchingAdditions; -}; +} -export const getUniqueEntries = (combinedList) => { +export function getUniqueEntries(combinedList) { /* we should not get any non-ACTIVE entries here since we have run filtering algorithms on both the phone and the server */ const allDeleted = combinedList.filter((c) => c.data.status && c.data.status == 'DELETED'); @@ -254,7 +251,7 @@ export const getUniqueEntries = (combinedList) => { } }); return Array.from(uniqueMap.values()); -}; +} /** * @param allEntries the array of timeline entries to map inputs to diff --git a/www/js/survey/multilabel/confirmHelper.ts b/www/js/survey/multilabel/confirmHelper.ts index d21e34857..b2951ab98 100644 --- a/www/js/survey/multilabel/confirmHelper.ts +++ b/www/js/survey/multilabel/confirmHelper.ts @@ -116,23 +116,23 @@ export const getLabelInputs = () => Object.keys(getLabelInputDetails()) as Multi export const getBaseLabelInputs = () => Object.keys(baseLabelInputDetails) as MultilabelKey[]; /** @description replace all underscores with spaces, and capitalizes the first letter of each word */ -export const labelKeyToReadable = (otherValue: string) => { +export function labelKeyToReadable(otherValue: string) { const words = otherValue.replace(/_/g, ' ').trim().split(' '); if (words.length == 0) return ''; return words.map((word) => word[0].toUpperCase() + word.slice(1)).join(' '); -}; +} /** @description replaces all spaces with underscores, and lowercases the string */ export const readableLabelToKey = (otherText: string) => otherText.trim().replace(/ /g, '_').toLowerCase(); -export const getFakeEntry = (otherValue): Partial | undefined => { +export function getFakeEntry(otherValue): Partial | undefined { if (!otherValue) return undefined; return { text: labelKeyToReadable(otherValue), value: otherValue, }; -}; +} export const labelKeyToRichMode = (labelKey: string) => labelOptionByValue(labelKey, 'MODE')?.text || labelKeyToReadable(labelKey); diff --git a/www/js/survey/multilabel/infinite_scroll_filters.ts b/www/js/survey/multilabel/infinite_scroll_filters.ts index a13d0e48d..187b56b2c 100644 --- a/www/js/survey/multilabel/infinite_scroll_filters.ts +++ b/www/js/survey/multilabel/infinite_scroll_filters.ts @@ -10,18 +10,18 @@ import i18next from 'i18next'; import { labelInputDetailsForTrip } from './confirmHelper'; import { logDebug } from '../../plugin/logger'; -const unlabeledCheck = (trip, userInputForTrip) => { +function unlabeledCheck(trip, userInputForTrip) { const tripInputDetails = labelInputDetailsForTrip(userInputForTrip); return Object.keys(tripInputDetails) .map((inputType) => !userInputForTrip?.[inputType]) .reduce((acc, val) => acc || val, false); -}; +} -const toLabelCheck = (trip, userInputForTrip) => { +function toLabelCheck(trip, userInputForTrip) { logDebug('Expectation: ' + trip.expectation); if (!trip.expectation) return true; return trip.expectation.to_label && unlabeledCheck(trip, userInputForTrip); -}; +} const UNLABELED = { key: 'unlabeled', diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index 0654f3cf8..c7fdacae4 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -53,7 +53,7 @@ const usePermissionStatus = () => { //using this function to update checks rather than mutate //this cues React to update UI function updateCheck(newObject) { - var tempList = [...checkList]; //make a copy rather than mutate + 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); @@ -92,7 +92,7 @@ const usePermissionStatus = () => { } function setupAndroidLocChecks() { - let fixSettings = function () { + let fixSettings = () => { console.log('Fix and refresh location settings'); return checkOrFix( locSettingsCheck, @@ -100,7 +100,7 @@ const usePermissionStatus = () => { true, ); }; - let checkSettings = function () { + let checkSettings = () => { console.log('Refresh location settings'); return checkOrFix( locSettingsCheck, @@ -108,7 +108,7 @@ const usePermissionStatus = () => { false, ); }; - let fixPerms = function () { + let fixPerms = () => { console.log('fix and refresh location permissions'); return checkOrFix( locPermissionsCheck, @@ -120,7 +120,7 @@ const usePermissionStatus = () => { } }); }; - let checkPerms = function () { + let checkPerms = () => { console.log('fix and refresh location permissions'); return checkOrFix( locPermissionsCheck, @@ -163,7 +163,7 @@ const usePermissionStatus = () => { } function setupIOSLocChecks() { - let fixSettings = function () { + let fixSettings = () => { console.log('Fix and refresh location settings'); return checkOrFix( locSettingsCheck, @@ -171,7 +171,7 @@ const usePermissionStatus = () => { true, ); }; - let checkSettings = function () { + let checkSettings = () => { console.log('Refresh location settings'); return checkOrFix( locSettingsCheck, @@ -179,7 +179,7 @@ const usePermissionStatus = () => { false, ); }; - let fixPerms = function () { + let fixPerms = () => { console.log('fix and refresh location permissions'); return checkOrFix( locPermissionsCheck, @@ -191,7 +191,7 @@ const usePermissionStatus = () => { } }); }; - let checkPerms = function () { + let checkPerms = () => { console.log('fix and refresh location permissions'); return checkOrFix( locPermissionsCheck, @@ -226,7 +226,7 @@ const usePermissionStatus = () => { function setupAndroidFitnessChecks() { if (window['device'].version.split('.')[0] >= 10) { - let fixPerms = function () { + let fixPerms = () => { console.log('fix and refresh fitness permissions'); return checkOrFix( fitnessPermissionsCheck, @@ -238,7 +238,7 @@ const usePermissionStatus = () => { } }); }; - let checkPerms = function () { + let checkPerms = () => { console.log('fix and refresh fitness permissions'); return checkOrFix( fitnessPermissionsCheck, @@ -260,7 +260,7 @@ const usePermissionStatus = () => { } function setupIOSFitnessChecks() { - let fixPerms = function () { + let fixPerms = () => { console.log('fix and refresh fitness permissions'); return checkOrFix( fitnessPermissionsCheck, @@ -272,7 +272,7 @@ const usePermissionStatus = () => { } }); }; - let checkPerms = function () { + let checkPerms = () => { console.log('fix and refresh fitness permissions'); return checkOrFix( fitnessPermissionsCheck, @@ -293,7 +293,7 @@ const usePermissionStatus = () => { } function setupAndroidNotificationChecks() { - let fixPerms = function () { + let fixPerms = () => { console.log('fix and refresh notification permissions'); return checkOrFix( appAndChannelNotificationsCheck, @@ -301,7 +301,7 @@ const usePermissionStatus = () => { true, ); }; - let checkPerms = function () { + let checkPerms = () => { console.log('fix and refresh notification permissions'); return checkOrFix( appAndChannelNotificationsCheck, @@ -321,7 +321,7 @@ const usePermissionStatus = () => { } function setupAndroidBackgroundRestrictionChecks() { - let fixPerms = function () { + let fixPerms = () => { console.log('fix and refresh backgroundRestriction permissions'); return checkOrFix( unusedAppsUnrestrictedCheck, @@ -329,7 +329,7 @@ const usePermissionStatus = () => { true, ); }; - let checkPerms = function () { + let checkPerms = () => { console.log('fix and refresh backgroundRestriction permissions'); return checkOrFix( unusedAppsUnrestrictedCheck, @@ -337,7 +337,7 @@ const usePermissionStatus = () => { false, ); }; - let fixBatteryOpt = function () { + let fixBatteryOpt = () => { console.log('fix and refresh battery optimization permissions'); return checkOrFix( ignoreBatteryOptCheck, @@ -345,7 +345,7 @@ const usePermissionStatus = () => { true, ); }; - let checkBatteryOpt = function () { + let checkBatteryOpt = () => { console.log('fix and refresh battery optimization permissions'); return checkOrFix( ignoreBatteryOptCheck, @@ -425,7 +425,7 @@ const usePermissionStatus = () => { refreshAllChecks(checkList); } - useAppStateChange(function () { + useAppStateChange(() => { console.log('PERMISSION CHECK: app has resumed, should refresh'); refreshAllChecks(checkList); }); From adc662012c961ffaa47f085621dc36db0083d836 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 26 Jan 2024 00:13:02 -0500 Subject: [PATCH 02/36] use 'const' and 'let' instead of 'var' in ES6, 'const' and 'let' are preferred over 'var', which is sort of outdated now. In a few places 'var' was still used. For best practice we replace these. --- www/js/components/QrCode.tsx | 2 +- www/js/control/ControlCollectionHelper.tsx | 8 ++++---- www/js/control/ControlSyncHelper.tsx | 6 +++--- www/js/control/LogPage.tsx | 2 +- www/js/control/uploadService.ts | 4 ++-- www/js/diary/timelineHelper.ts | 2 +- www/js/splash/remoteNotifyHandler.ts | 16 ++++++++-------- www/js/splash/storeDeviceSettings.ts | 11 ++++------- 8 files changed, 24 insertions(+), 27 deletions(-) diff --git a/www/js/components/QrCode.tsx b/www/js/components/QrCode.tsx index fb9315c17..195e124a9 100644 --- a/www/js/components/QrCode.tsx +++ b/www/js/components/QrCode.tsx @@ -21,7 +21,7 @@ export function shareQR(message) { ctx.drawImage(img, 0, 0); const pngFile = canvas.toDataURL('image/png'); - var prepopulateQRMessage = {}; + const prepopulateQRMessage = {}; prepopulateQRMessage['files'] = [pngFile]; prepopulateQRMessage['url'] = message; prepopulateQRMessage['message'] = message; //text saved to files with image! diff --git a/www/js/control/ControlCollectionHelper.tsx b/www/js/control/ControlCollectionHelper.tsx index 1b6caf749..b8fb160bc 100644 --- a/www/js/control/ControlCollectionHelper.tsx +++ b/www/js/control/ControlCollectionHelper.tsx @@ -32,9 +32,9 @@ export async function forceTransition(transition) { } async function accuracy2String(config) { - var accuracy = config.accuracy; + const accuracy = config.accuracy; let accuracyOptions = await getAccuracyOptions(); - for (var k in accuracyOptions) { + for (let k in accuracyOptions) { if (accuracyOptions[k] == accuracy) { return k; } @@ -47,7 +47,7 @@ export async function isMediumAccuracy() { if (!config || config == null) { return undefined; // config not loaded when loading ui, set default as false } else { - var v = await accuracy2String(config); + const v = await accuracy2String(config); console.log('window platform is', window['cordova'].platformId); if (window['cordova'].platformId == 'ios') { return ( @@ -148,7 +148,7 @@ const ControlCollectionHelper = ({ editVis, setEditVis }) => { function formatAccuracyForActions(accuracyOptions) { let tempAccuracyActions: AccuracyAction[] = []; - for (var name in accuracyOptions) { + for (let name in accuracyOptions) { tempAccuracyActions.push({ text: name, value: accuracyOptions[name] }); } return tempAccuracyActions; diff --git a/www/js/control/ControlSyncHelper.tsx b/www/js/control/ControlSyncHelper.tsx index 8f33d2aaa..576f71909 100644 --- a/www/js/control/ControlSyncHelper.tsx +++ b/www/js/control/ControlSyncHelper.tsx @@ -52,7 +52,7 @@ export const ForceSyncRow = ({ getState }) => { * with getLastSensorData and getLastMessages in the usercache * See https://github.com/e-mission/e-mission-phone/issues/279 for details */ - var sensorKey = 'statemachine/transition'; + const sensorKey = 'statemachine/transition'; let sensorDataList = await window['cordova'].plugins.BEMUserCache.getAllMessages( sensorKey, true, @@ -103,7 +103,7 @@ export const ForceSyncRow = ({ getState }) => { } async function getTransition(transKey) { - var entry_data = {}; + const entry_data = {}; const curr_state = await getState(); entry_data['curr_state'] = curr_state; if (transKey == getEndTransitionKey()) { @@ -117,7 +117,7 @@ export const ForceSyncRow = ({ getState }) => { async function endForceSync() { /* First, quickly start and end the trip. Let's listen to the promise * result for start so that we ensure ordering */ - var sensorKey = 'statemachine/transition'; + const sensorKey = 'statemachine/transition'; let entry_data = await getTransition(getStartTransitionKey()); let messagePut = await window['cordova'].plugins.BEMUserCache.putMessage(sensorKey, entry_data); entry_data = await getTransition(getEndTransitionKey()); diff --git a/www/js/control/LogPage.tsx b/www/js/control/LogPage.tsx index e71b17c78..1ac0b3557 100644 --- a/www/js/control/LogPage.tsx +++ b/www/js/control/LogPage.tsx @@ -21,7 +21,7 @@ const LogPage = ({ pageVis, setPageVis }) => { const [logMessage, setLogMessage] = useState(''); const [isFetching, setIsFetching] = useState(false); - var RETRIEVE_COUNT = 100; + const RETRIEVE_COUNT = 100; //when opening the modal, load the entries useEffect(() => { diff --git a/www/js/control/uploadService.ts b/www/js/control/uploadService.ts index 87c4f5673..163b65b2f 100644 --- a/www/js/control/uploadService.ts +++ b/www/js/control/uploadService.ts @@ -49,7 +49,7 @@ function readDBFile(parentDir, database, callbackFn) { console.log(fileEntry); fileEntry.file((file) => { console.log(file); - var reader = new FileReader(); + const reader = new FileReader(); reader.onprogress = (report) => { console.log('Current progress is ' + JSON.stringify(report)); @@ -93,7 +93,7 @@ const sendToServer = function upload(url, binArray, params) { export async function uploadFile(database, reason) { try { let uploadConfig = await getUploadConfig(); - var parentDir = 'unknown'; + let parentDir = 'unknown'; if (window['cordova'].platformId.toLowerCase() == 'android') { parentDir = window['cordova'].file.applicationStorageDirectory + '/databases'; diff --git a/www/js/diary/timelineHelper.ts b/www/js/diary/timelineHelper.ts index c8337c4fb..d22c4f956 100644 --- a/www/js/diary/timelineHelper.ts +++ b/www/js/diary/timelineHelper.ts @@ -41,7 +41,7 @@ export function useGeojsonForTrip( (labeledMode && getBaseModeByValue(labeledMode, labelOptions)?.color) || undefined; logDebug("Reading trip's " + trip.locations.length + ' location points at ' + new Date()); - var features = [ + const features = [ location2GeojsonPoint(trip.start_loc, 'start_place'), location2GeojsonPoint(trip.end_loc, 'end_place'), ...locations2GeojsonTrajectory(trip, trip.locations, trajectoryColor), diff --git a/www/js/splash/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts index 9b6f80f01..414f9f3f8 100644 --- a/www/js/splash/remoteNotifyHandler.ts +++ b/www/js/splash/remoteNotifyHandler.ts @@ -41,23 +41,23 @@ function onCloudNotifEvent(event) { data.additionalData.payload.alert_type ) { if (data.additionalData.payload.alert_type == 'website') { - var webpage_spec = data.additionalData.payload.spec; - if (webpage_spec && webpage_spec.url && webpage_spec.url.startsWith('https://')) { - launchWebpage(webpage_spec.url); + const webpageSpec = data.additionalData.payload.spec; + if (webpageSpec?.url?.startsWith('https://')) { + launchWebpage(webpageSpec.url); } else { displayErrorMsg( - JSON.stringify(webpage_spec), + JSON.stringify(webpageSpec), 'webpage was not specified correctly. spec is ', ); } } if (data.additionalData.payload.alert_type == 'popup') { - var popup_spec = data.additionalData.payload.spec; - if (popup_spec && popup_spec.title && popup_spec.text) { + const popupSpec = data.additionalData.payload.spec; + if (popupSpec?.title && popupSpec?.text) { /* TODO: replace popup with something with better UI */ - window.alert(popup_spec.title + ' ' + popup_spec.text); + window.alert(popupSpec.title + ' ' + popupSpec.text); } else { - displayErrorMsg(JSON.stringify(popup_spec), 'popup was not specified correctly. spec is '); + displayErrorMsg(JSON.stringify(popupSpec), 'popup was not specified correctly. spec is '); } } } diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts index 4e389e749..8d0426ebb 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -10,17 +10,14 @@ import { subscribe, EVENTS, unsubscribe } from '../customEventHandler'; * @returns promise to updateUser in comm settings with device info */ function storeDeviceSettings() { - var lang = i18next.resolvedLanguage; - var manufacturer = window['device'].manufacturer; - var osver = window['device'].version; return window['cordova'].getAppVersion .getVersionNumber() .then((appver) => { - var updateJSON = { - phone_lang: lang, + const updateJSON = { + phone_lang: i18next.language, curr_platform: window['cordova'].platformId, - manufacturer: manufacturer, - client_os_version: osver, + manufacturer: window['device'].manufacturer, + client_os_version: window['device'].version, client_app_version: appver, }; logDebug('About to update profile with settings = ' + JSON.stringify(updateJSON)); From 0a8ed767b63d83c666bc64a3c76dd66bf85b0cd7 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 26 Jan 2024 15:21:45 -0500 Subject: [PATCH 03/36] cleanup all console/log statements -- Replace all uses of console.log or console.debug with the functions from logger.ts (logDebug, logWarn, displayError). This ensures that all log statements are recorded by both the Javascript console AND the Cordova Logger plugin -- Format all these log statements concisely and in a unified way (longer statements make use of `template strings` with interpolation, and the statements are put on as few lines as possible to mimimize clutter) -- Remove log statements that are deemed redundant or excessive (we want to be thorough but not spammy - avoid log statements that run on every single re-render) --- www/js/App.tsx | 2 - www/js/components/QrCode.tsx | 10 ++-- www/js/components/charting.ts | 12 +---- www/js/config/dynamicConfig.ts | 12 ++--- www/js/control/ControlCollectionHelper.tsx | 11 ++-- www/js/control/ControlDataTable.tsx | 1 - www/js/control/ControlSyncHelper.tsx | 5 +- www/js/control/LogPage.tsx | 14 ++--- www/js/control/ProfileSettings.tsx | 48 ++++++----------- www/js/control/uploadService.ts | 17 +++--- www/js/diary/addressNamesHelper.ts | 12 ++--- www/js/diary/timelineHelper.ts | 53 ++++++------------ www/js/i18nextInit.ts | 3 +- www/js/metrics/CarbonFootprintCard.tsx | 9 ++-- www/js/metrics/CarbonTextCard.tsx | 4 +- www/js/metrics/customMetricsHelper.ts | 1 - www/js/metrics/metricsHelper.ts | 21 +++----- www/js/onboarding/OnboardingStack.tsx | 2 - www/js/onboarding/WelcomePage.tsx | 6 +-- www/js/plugin/clientStats.ts | 5 +- www/js/plugin/logger.ts | 6 +-- www/js/plugin/storage.ts | 22 +++----- www/js/splash/customURL.ts | 6 ++- www/js/splash/notifScheduler.ts | 11 +--- www/js/splash/remoteNotifyHandler.ts | 4 +- www/js/splash/startprefs.ts | 1 - www/js/splash/storeDeviceSettings.ts | 15 ++---- www/js/survey/enketo/AddNoteButton.tsx | 2 +- www/js/survey/enketo/AddedNotesList.tsx | 2 +- www/js/survey/enketo/EnketoModal.tsx | 8 +-- www/js/survey/inputMatcher.ts | 39 ++++++-------- www/js/survey/multilabel/confirmHelper.ts | 14 ++--- www/js/useAppStateChange.ts | 3 +- www/js/usePermissionStatus.ts | 63 +++++++++++----------- 34 files changed, 177 insertions(+), 267 deletions(-) diff --git a/www/js/App.tsx b/www/js/App.tsx index f43f006d9..ca0cdd406 100644 --- a/www/js/App.tsx +++ b/www/js/App.tsx @@ -92,8 +92,6 @@ const App = () => { setPermissionsPopupVis, }; - console.debug('onboardingState in App', onboardingState); - let appContent; if (onboardingState == null) { // if onboarding state is not yet determined, show a loading spinner diff --git a/www/js/components/QrCode.tsx b/www/js/components/QrCode.tsx index 195e124a9..c8547eaf8 100644 --- a/www/js/components/QrCode.tsx +++ b/www/js/components/QrCode.tsx @@ -4,7 +4,7 @@ we can remove this wrapper and just use the QRCode component directly */ import React from 'react'; import QRCode from 'react-qr-code'; -import { logWarn } from '../plugin/logger'; +import { logDebug, logWarn } from '../plugin/logger'; export function shareQR(message) { /*code adapted from demo of react-qr-code*/ @@ -29,11 +29,13 @@ export function shareQR(message) { window['plugins'].socialsharing.shareWithOptions( prepopulateQRMessage, (result) => { - console.log('Share completed? ' + result.completed); // On Android apps mostly return false even while it's true - console.log('Shared to app: ' + result.app); // On Android result.app is currently empty. On iOS it's empty when sharing is cancelled (result.completed=false) + // On Android apps mostly return completed=false even while it's true + // On Android result.app is currently empty. On iOS it's empty when sharing is cancelled (result.completed=false) + logDebug(`socialsharing: share completed? ' + ${result.completed}; + shared to app: ${result.app}`); }, (msg) => { - console.log('Sharing failed with message: ' + msg); + logWarn('socialsharing: failed with message: ' + msg); }, ); }; diff --git a/www/js/components/charting.ts b/www/js/components/charting.ts index 85b98bbbe..f536fc04f 100644 --- a/www/js/components/charting.ts +++ b/www/js/components/charting.ts @@ -1,6 +1,7 @@ import color from 'color'; import { getBaseModeByKey } from '../diary/diaryHelper'; import { readableLabelToKey } from '../survey/multilabel/confirmHelper'; +import { logDebug } from '../plugin/logger'; export const defaultPalette = [ '#c95465', // red oklch(60% 0.15 14) @@ -49,11 +50,9 @@ export function getChartHeight( function getBarHeight(stacks) { let totalHeight = 0; - console.log('ctx stacks', stacks.x); for (let val in stacks.x) { if (!val.startsWith('_')) { totalHeight += stacks.x[val]; - console.log('ctx added ', val); } } return totalHeight; @@ -82,14 +81,7 @@ function createDiagonalPattern(color = 'black') { export function getMeteredBackgroundColor(meter, currDataset, barCtx, colors, darken = 0) { if (!barCtx || !currDataset) return; let bar_height = getBarHeight(barCtx.parsed._stacks); - console.debug( - 'bar height for', - barCtx.raw.y, - ' is ', - bar_height, - 'which in chart is', - currDataset, - ); + logDebug(`bar height for ${barCtx.raw.y} is ${bar_height} which in chart is ${currDataset}`); let meteredColor; if (bar_height > meter.high) meteredColor = colors.danger; else if (bar_height > meter.middle) meteredColor = colors.warn; diff --git a/www/js/config/dynamicConfig.ts b/www/js/config/dynamicConfig.ts index 1b7176674..9773a1ead 100644 --- a/www/js/config/dynamicConfig.ts +++ b/www/js/config/dynamicConfig.ts @@ -197,7 +197,7 @@ function extractSubgroup(token: string, config: AppConfig): string | undefined { }), ); } else { - console.log('subgroup ' + tokenParts[2] + ' found in list ' + config.opcode.subgroups); + logDebug('subgroup ' + tokenParts[2] + ' found in list ' + config.opcode.subgroups); return tokenParts[2]; } } else { @@ -205,7 +205,7 @@ function extractSubgroup(token: string, config: AppConfig): string | undefined { // subpart not in config list throw new Error(i18next.t('config.invalid-subgroup-no-default', { token: token })); } else { - console.log("no subgroups in config, 'default' subgroup found in token "); + logDebug("no subgroups in config, 'default' subgroup found in token "); return tokenParts[2]; } } @@ -216,7 +216,7 @@ function extractSubgroup(token: string, config: AppConfig): string | undefined { * first is already handled in extractStudyName, second is handled * by default since download will fail if it is invalid */ - console.log('Old-style study, expecting token without a subgroup...'); + logDebug('Old-style study, expecting token without a subgroup...'); return undefined; } } @@ -251,10 +251,8 @@ function loadNewConfig(newToken: string, existingVersion?: number): Promise { - logDebug( - 'UI_CONFIG: Stored dynamic config in KVStore successfully, result = ' + - JSON.stringify(kvStoreResult), - ); + logDebug(`UI_CONFIG: Stored dynamic config in KVStore successfully, + result = ${JSON.stringify(kvStoreResult)}`); storedConfig = toSaveConfig; configChanged = true; return true; diff --git a/www/js/control/ControlCollectionHelper.tsx b/www/js/control/ControlCollectionHelper.tsx index b8fb160bc..993f3724f 100644 --- a/www/js/control/ControlCollectionHelper.tsx +++ b/www/js/control/ControlCollectionHelper.tsx @@ -4,7 +4,7 @@ import { Dialog, Button, Switch, Text, useTheme, TextInput } from 'react-native- import { useTranslation } from 'react-i18next'; import ActionMenu from '../components/ActionMenu'; import { settingStyles } from './ProfileSettings'; -import { displayError } from '../plugin/logger'; +import { displayError, displayErrorMsg, logDebug } from '../plugin/logger'; type collectionConfig = { is_duty_cycling: boolean; @@ -27,7 +27,7 @@ export async function forceTransition(transition) { window.alert('success -> ' + result); } catch (err) { window.alert('error -> ' + err); - console.log('error forcing state', err); + displayError(err, 'error forcing state'); } } @@ -48,7 +48,6 @@ export async function isMediumAccuracy() { return undefined; // config not loaded when loading ui, set default as false } else { const v = await accuracy2String(config); - console.log('window platform is', window['cordova'].platformId); if (window['cordova'].platformId == 'ios') { return ( v != 'kCLLocationAccuracyBestForNavigation' && @@ -58,7 +57,7 @@ export async function isMediumAccuracy() { } else if (window['cordova'].platformId == 'android') { return v != 'PRIORITY_HIGH_ACCURACY'; } else { - window.alert('Emission does not support this platform'); + displayErrorMsg('Emission does not support this platform: ' + window['cordova'].platformId); } } } @@ -82,7 +81,7 @@ export async function helperToggleLowAccuracy() { } try { let set = await setConfig(tempConfig); - console.log('setConfig Sucess'); + logDebug('setConfig Sucess'); } catch (err) { displayError(err, 'Error while setting collection config'); } @@ -159,7 +158,7 @@ const ControlCollectionHelper = ({ editVis, setEditVis }) => { */ async function saveAndReload() { - console.log('new config = ', localConfig); + logDebug('new config = ' + JSON.stringify(localConfig)); try { let set = await setConfig(localConfig); setEditVis(false); diff --git a/www/js/control/ControlDataTable.tsx b/www/js/control/ControlDataTable.tsx index 932762400..ea53bbd52 100644 --- a/www/js/control/ControlDataTable.tsx +++ b/www/js/control/ControlDataTable.tsx @@ -3,7 +3,6 @@ import { DataTable } from 'react-native-paper'; // val with explicit call toString() to resolve bool values not showing const ControlDataTable = ({ controlData }) => { - console.log('Printing data trying to tabulate', controlData); return ( //rows require unique keys! diff --git a/www/js/control/ControlSyncHelper.tsx b/www/js/control/ControlSyncHelper.tsx index 576f71909..cdc1a8e2a 100644 --- a/www/js/control/ControlSyncHelper.tsx +++ b/www/js/control/ControlSyncHelper.tsx @@ -44,8 +44,6 @@ export const ForceSyncRow = ({ getState }) => { async function forceSync() { try { let addedEvent = await addStatEvent(statKeys.BUTTON_FORCE_SYNC); - console.log('Added ' + statKeys.BUTTON_FORCE_SYNC + ' event'); - let sync = await forcePluginSync(); /* * Change to sensorKey to "background/location" after fixing issues @@ -198,7 +196,7 @@ const ControlSyncHelper = ({ editVis, setEditVis }) => { * Functions to edit and save values */ async function saveAndReload() { - console.log('new config = ' + localConfig); + logDebug('saveAndReload: new config = ' + JSON.stringify(localConfig)); try { let set = setConfig(localConfig); //NOTE -- we need to make sure we update these settings in ProfileSettings :) -- getting rid of broadcast handling for migration!! @@ -212,7 +210,6 @@ const ControlSyncHelper = ({ editVis, setEditVis }) => { curr_sync_interval: (localConfig as syncConfig).sync_interval, }); } catch (err) { - console.log('error with setting sync config', err); displayError(err, 'Error while setting sync config'); } } diff --git a/www/js/control/LogPage.tsx b/www/js/control/LogPage.tsx index 1ac0b3557..098592dee 100644 --- a/www/js/control/LogPage.tsx +++ b/www/js/control/LogPage.tsx @@ -6,6 +6,7 @@ import { FlashList } from '@shopify/flash-list'; import { DateTime } from 'luxon'; import AlertBar from './AlertBar'; import { sendEmail } from './emailService'; +import { displayError, logDebug } from '../plugin/logger'; type loadStats = { currentStart: number; gotMaxIndex: boolean; reachedEnd: boolean }; @@ -31,7 +32,7 @@ const LogPage = ({ pageVis, setPageVis }) => { async function refreshEntries() { try { let maxIndex = await window['Logger'].getMaxIndex(); - console.log('maxIndex = ' + maxIndex); + logDebug('Logger maxIndex = ' + maxIndex); let tempStats = {} as loadStats; tempStats.currentStart = maxIndex; tempStats.gotMaxIndex = true; @@ -40,7 +41,7 @@ const LogPage = ({ pageVis, setPageVis }) => { setEntries([]); } catch (error) { let errorString = t('errors.while-max-index') + JSON.stringify(error, null, 2); - console.log(errorString); + displayError(error, errorString); setMaxMessage(errorString); setMaxErrorVis(true); } finally { @@ -62,17 +63,16 @@ const LogPage = ({ pageVis, setPageVis }) => { } async function addEntries() { - console.log('calling addEntries'); setIsFetching(true); let start = loadStats?.currentStart ? loadStats.currentStart : 0; //set a default start to prevent initial fetch error try { let entryList = await window['Logger'].getMessagesFromIndex(start, RETRIEVE_COUNT); processEntries(entryList); - console.log('entry list size = ' + entries.length); + logDebug('addEntries: entry list size = ' + entries.length); setIsFetching(false); } catch (error) { let errStr = t('errors.while-log-messages') + JSON.stringify(error, null, 2); - console.log(errStr); + displayError(error, errStr); setLogMessage(errStr); setLogErrorVis(true); setIsFetching(false); @@ -87,11 +87,11 @@ const LogPage = ({ pageVis, setPageVis }) => { tempEntries.push(e); }); if (entryList.length == 0) { - console.log('Reached the end of the scrolling'); + logDebug('LogPage reached the end of the scrolling'); tempLoadStats.reachedEnd = true; } else { tempLoadStats.currentStart = entryList[entryList.length - 1].ID; - console.log('new start index = ' + loadStats?.currentStart); + logDebug('LogPage new start index = ' + loadStats?.currentStart); } setEntries([...entries].concat(tempEntries)); //push the new entries onto the list setLoadStats(tempLoadStats); diff --git a/www/js/control/ProfileSettings.tsx b/www/js/control/ProfileSettings.tsx index 6951e889b..3af63b2c4 100644 --- a/www/js/control/ProfileSettings.tsx +++ b/www/js/control/ProfileSettings.tsx @@ -158,14 +158,12 @@ const ProfileSettings = () => { }); } - // setTemplateText(tempUiConfig.intro.translated_text); - // console.log("translated text is??", templateText); setUiConfig(tempUiConfig); refreshScreen(); } async function refreshCollectSettings() { - console.debug('about to refreshCollectSettings, collectSettings = ', collectSettings); + logDebug('refreshCollectSettings: collectSettings = ' + JSON.stringify(collectSettings)); const newCollectSettings: any = {}; // // refresh collect plugin configuration @@ -190,17 +188,15 @@ const ProfileSettings = () => { useEffect(() => { if (editCollectionVis == false) { setTimeout(() => { - console.log('closed editor, time to refresh collect'); + logDebug('closed editor, time to refreshCollectSettings'); refreshCollectSettings(); }, 1000); } }, [editCollectionVis]); async function refreshNotificationSettings() { - logDebug( - 'about to refreshNotificationSettings, notificationSettings = ' + - JSON.stringify(notificationSettings), - ); + logDebug(`about to refreshNotificationSettings, + notificationSettings = ${JSON.stringify(notificationSettings)}`); const newNotificationSettings: any = {}; if (uiConfig?.reminderSchemes) { @@ -212,12 +208,8 @@ const ProfileSettings = () => { let resultList = await Promise.all(promiseList); const prefs = resultList[0]; const scheduledNotifs = resultList[1]; - logDebug( - 'prefs and scheduled notifs\n' + - JSON.stringify(prefs) + - '\n-\n' + - JSON.stringify(scheduledNotifs), - ); + logDebug(`prefs - scheduled notifs: + ${JSON.stringify(prefs)}\n - \n${JSON.stringify(scheduledNotifs)}`); const m = DateTime.fromFormat(prefs.reminder_time_of_day, 'HH:mm'); newNotificationSettings.prefReminderTimeVal = m.toJSDate(); @@ -226,22 +218,17 @@ const ProfileSettings = () => { newNotificationSettings.scheduledNotifs = scheduledNotifs; } - logDebug( - 'notification settings before and after\n' + - JSON.stringify(notificationSettings) + - '\n-\n' + - JSON.stringify(newNotificationSettings), - ); + logDebug(`notification settings before - after: + ${JSON.stringify(notificationSettings)} - ${JSON.stringify(newNotificationSettings)}`); setNotificationSettings(newNotificationSettings); } async function getSyncSettings() { - console.log('getting sync settings'); const newSyncSettings: any = {}; getHelperSyncSettings().then((showConfig) => { newSyncSettings.show_config = showConfig; setSyncSettings(newSyncSettings); - console.log('sync settings are ', syncSettings); + logDebug('sync settings are: ' + JSON.stringify(syncSettings)); }); } @@ -254,8 +241,8 @@ const ProfileSettings = () => { getSettings().then( (response) => { const newConnectSettings: any = {}; + logDebug('getConnectURL: got response.connectUrl = ' + response.connectUrl); newConnectSettings.url = response.connectUrl; - console.log(response); setConnectSettings(newConnectSettings); }, (error) => { @@ -333,14 +320,14 @@ const ProfileSettings = () => { //for now, use window.cordova.platformId function parseState(state) { - console.log('state in parse state is', state); + logDebug(`parseState: state = ${state}; + platformId = ${window['cordova'].platformId}`); if (state) { - console.log('state in parse state exists', window['cordova'].platformId); if (window['cordova'].platformId == 'android') { - console.log('ANDROID state in parse state is', state.substring(12)); + logDebug('platform ANDROID; parsed state will be ' + state.substring(12)); return state.substring(12); } else if (window['cordova'].platformId == 'ios') { - console.log('IOS state in parse state is', state.substring(6)); + logDebug('platform IOS; parsed state will be ' + state.substring(6)); return state.substring(6); } } @@ -349,7 +336,7 @@ const ProfileSettings = () => { async function invalidateCache() { window['cordova'].plugins.BEMUserCache.invalidateAllCache().then( (result) => { - console.log('invalidate result', result); + logDebug('invalidateCache: result = ' + JSON.stringify(result)); setCacheResult(result); setInvalidateSuccessVis(true); }, @@ -384,7 +371,6 @@ const ProfileSettings = () => { //conditional creation of setting sections let logUploadSection; - console.debug('appConfg: support_upload:', appConfig?.profile_controls?.support_upload); if (appConfig?.profile_controls?.support_upload) { logUploadSection = ( { console.log('')}> + action={() => {}}> ); @@ -507,7 +493,7 @@ const ProfileSettings = () => { console.log('')} + action={() => {}} desc={appVersion.current}> diff --git a/www/js/control/uploadService.ts b/www/js/control/uploadService.ts index 163b65b2f..c0b0213ea 100644 --- a/www/js/control/uploadService.ts +++ b/www/js/control/uploadService.ts @@ -19,7 +19,6 @@ async function getUploadConfig() { let response = await fetch('json/uploadConfig.json.sample'); let uploadConfig = await response.json(); logDebug('default uploadConfigString = ' + JSON.stringify(uploadConfig['url'])); - console.log('default uploadConfigString = ' + JSON.stringify(uploadConfig['url'])); url.push(uploadConfig['url']); resolve(url); } catch (err) { @@ -41,32 +40,30 @@ function onUploadError(err) { function readDBFile(parentDir, database, callbackFn) { return new Promise((resolve, reject) => { window['resolveLocalFileSystemURL'](parentDir, (fs) => { - console.log('resolving file system as ', fs); + logDebug('resolving file system as ' + JSON.stringify(fs)); fs.filesystem.root.getFile( fs.fullPath + database, null, (fileEntry) => { - console.log(fileEntry); + logDebug('fileEntry = ' + JSON.stringify(fileEntry)); fileEntry.file((file) => { - console.log(file); + logDebug('file = ' + JSON.stringify(file)); const reader = new FileReader(); reader.onprogress = (report) => { - console.log('Current progress is ' + JSON.stringify(report)); + logDebug('Current progress is ' + JSON.stringify(report)); if (callbackFn != undefined) { callbackFn((report.loaded * 100) / report.total); } }; reader.onerror = (error) => { - console.log(this.error); + logDebug('Error while reading file ' + JSON.stringify(this.error)); reject({ error: { message: this.error } }); }; reader.onload = () => { - console.log( - 'Successful file read with ' + this.result?.['byteLength'] + ' characters', - ); + logDebug('Successful file read with ' + this.result?.['byteLength'] + ' characters'); resolve(new DataView(this.result as ArrayBuffer)); }; @@ -106,7 +103,7 @@ export async function uploadFile(database, reason) { logInfo('Going to upload ' + database); try { let binString: any = await readDBFile(parentDir, database, undefined); - console.log('Uploading file of size ' + binString['byteLength']); + logDebug('Uploading file of size ' + binString['byteLength']); const params = { reason: reason, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, diff --git a/www/js/diary/addressNamesHelper.ts b/www/js/diary/addressNamesHelper.ts index b66eee8e8..30740ad19 100644 --- a/www/js/diary/addressNamesHelper.ts +++ b/www/js/diary/addressNamesHelper.ts @@ -67,7 +67,7 @@ export function useLocalStorage(key: string, initialValue: T) { window.localStorage.setItem(key, JSON.stringify(valueToStore)); } } catch (error) { - console.error(error); + displayError(error); } } return [storedValue, setValue]; @@ -110,15 +110,11 @@ async function fetchNominatimLocName(loc_geojson) { const coordsStr = loc_geojson.coordinates.toString(); const cachedResponse = localStorage.getItem(coordsStr); if (cachedResponse) { - console.log( - 'fetchNominatimLocName: found cached response for ', - coordsStr, - cachedResponse, - 'skipping fetch', - ); + logDebug(`fetchNominatimLocName: found cached response for ${coordsStr} = + ${cachedResponse}, skipping fetch`); return; } - console.log('Getting location name for ', coordsStr); + logDebug('Getting location name for ' + JSON.stringify(coordsStr)); const url = 'https://nominatim.openstreetmap.org/reverse?format=json&lat=' + loc_geojson.coordinates[1] + diff --git a/www/js/diary/timelineHelper.ts b/www/js/diary/timelineHelper.ts index d22c4f956..f140f1750 100644 --- a/www/js/diary/timelineHelper.ts +++ b/www/js/diary/timelineHelper.ts @@ -351,12 +351,10 @@ function transitionTrip2TripObj(trip: Array): Promise>) => { @@ -375,12 +373,8 @@ function transitionTrip2TripObj(trip: Array): Promise): Promise>) { .slice(processedUntil) .findIndex(isEndingTransition); if (foundEndTransitionIndex == -1) { - logDebug( - "Can't find end for trip starting at " + - JSON.stringify(transitionList[currStartTransitionIndex]) + - ' dropping it', - ); + logDebug(`Can't find end for trip starting at: + ${JSON.stringify(transitionList[currStartTransitionIndex])} - dropping it`); processedUntil = transitionList.length; } else { currEndTransitionIndex = processedUntil + foundEndTransitionIndex; processedUntil = currEndTransitionIndex; logDebug(`currEndTransitionIndex ${currEndTransitionIndex}`); - logDebug( - 'Unprocessed trip starting at ' + - JSON.stringify(transitionList[currStartTransitionIndex]) + - ' ends at ' + - JSON.stringify(transitionList[currEndTransitionIndex]), - ); + logDebug(`Unprocessed trip, + starting at: ${JSON.stringify(transitionList[currStartTransitionIndex])}; + ends at: ${JSON.stringify(transitionList[currEndTransitionIndex])}`); tripList.push([ transitionList[currStartTransitionIndex], transitionList[currEndTransitionIndex], @@ -528,11 +511,7 @@ export function readUnprocessedTrips( lastProcessedTrip?: CompositeTrip, ) { const tq = { key: 'write_ts', startTs, endTs }; - logDebug( - 'about to query for unprocessed trips from ' + - DateTime.fromSeconds(tq.startTs).toLocaleString(DateTime.DATETIME_MED) + - DateTime.fromSeconds(tq.endTs).toLocaleString(DateTime.DATETIME_MED), - ); + logDebug(`about to query for unprocessed trips from ${tq.startTs} to ${tq.endTs}`); const getMessageMethod = window['cordova'].plugins.BEMUserCache.getMessagesForInterval; return getUnifiedDataForInterval('statemachine/transition', tq, getMessageMethod).then( (transitionList: Array>) => { diff --git a/www/js/i18nextInit.ts b/www/js/i18nextInit.ts index 3137d78e6..e42a130ac 100644 --- a/www/js/i18nextInit.ts +++ b/www/js/i18nextInit.ts @@ -18,7 +18,7 @@ On dev builds, the fallback translation is prefixed with a globe emoji so it's e function mergeInTranslations(lang, fallbackLang) { Object.entries(fallbackLang).forEach(([key, value]) => { if (lang[key] === undefined) { - console.warn(`Missing translation for key '${key}'`); + logWarn(`Missing translation for key '${key}'`); if (__DEV__) { if (typeof value === 'string') { lang[key] = `🌐${value}`; @@ -66,6 +66,7 @@ export default i18next; // Next, register the translations for react-native-paper-dates import { en, es, fr, it, registerTranslation } from 'react-native-paper-dates'; +import { logWarn } from './plugin/logger'; const rnpDatesLangs = { en, es, diff --git a/www/js/metrics/CarbonFootprintCard.tsx b/www/js/metrics/CarbonFootprintCard.tsx index ff06e8632..56e955f60 100644 --- a/www/js/metrics/CarbonFootprintCard.tsx +++ b/www/js/metrics/CarbonFootprintCard.tsx @@ -22,6 +22,7 @@ import BarChart from '../components/BarChart'; import ChangeIndicator, { CarbonChange } from './ChangeIndicator'; import color from 'color'; import { useAppTheme } from '../appTheme'; +import { logDebug, logWarn } from '../plugin/logger'; type Props = { userMetrics?: MetricsData; aggMetrics?: MetricsData }; const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { @@ -109,7 +110,8 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { if (aggMetrics?.distance?.length) { //separate data into weeks const thisWeekDistance = segmentDaysByWeeks(aggMetrics?.distance, 1)[0]; - console.log('testing agg metrics', aggMetrics, thisWeekDistance); + logDebug(`groupCarbonRecords: aggMetrics = ${JSON.stringify(aggMetrics)}; + thisWeekDistance = ${JSON.stringify(thisWeekDistance)}`); let aggThisWeekModeMap = parseDataFromMetrics(thisWeekDistance, 'aggregate'); let aggThisWeekSummary = generateSummaryFromData(aggThisWeekModeMap, 'distance'); @@ -120,7 +122,7 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { for (let i in aggThisWeekSummary) { aggCarbonData.push(aggThisWeekSummary[i]); if (isNaN(aggCarbonData[i].values)) { - console.warn(`WARNING in calculating groupCarbonRecords: value is NaN for mode + logWarn(`WARNING in calculating groupCarbonRecords: value is NaN for mode ${aggCarbonData[i].key}, changing to 0`); aggCarbonData[i].values = 0; } @@ -132,7 +134,7 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { low: getFootprintForMetrics(aggCarbonData, 0), high: getFootprintForMetrics(aggCarbonData, getHighestFootprint()), }; - console.log('testing group past week', aggCarbon); + logDebug(`groupCarbonRecords: aggCarbon = ${JSON.stringify(aggCarbon)}`); groupRecords.push({ label: t('main-metrics.unlabeled'), x: aggCarbon.high - aggCarbon.low, @@ -157,7 +159,6 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { tempChartData = tempChartData.concat(groupCarbonRecords); } tempChartData = tempChartData.reverse(); - console.log('testing chart data', tempChartData); return tempChartData; }, [userCarbonRecords, groupCarbonRecords]); diff --git a/www/js/metrics/CarbonTextCard.tsx b/www/js/metrics/CarbonTextCard.tsx index fe26e7a22..bf89bdb49 100644 --- a/www/js/metrics/CarbonTextCard.tsx +++ b/www/js/metrics/CarbonTextCard.tsx @@ -17,7 +17,7 @@ import { segmentDaysByWeeks, MetricsSummary, } from './metricsHelper'; -import { logWarn } from '../plugin/logger'; +import { logDebug, logWarn } from '../plugin/logger'; type Props = { userMetrics?: MetricsData; aggMetrics?: MetricsData }; const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => { @@ -112,7 +112,7 @@ const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => { low: getFootprintForMetrics(aggCarbonData, 0), high: getFootprintForMetrics(aggCarbonData, getHighestFootprint()), }; - console.log('testing group past week', aggCarbon); + logDebug(`groupText: aggCarbon = ${JSON.stringify(aggCarbon)}`); const label = t('main-metrics.average'); if (aggCarbon.low == aggCarbon.high) groupText.push({ label: label, value: `${Math.round(aggCarbon.low)}` }); diff --git a/www/js/metrics/customMetricsHelper.ts b/www/js/metrics/customMetricsHelper.ts index 1831f51c6..c354d1f58 100644 --- a/www/js/metrics/customMetricsHelper.ts +++ b/www/js/metrics/customMetricsHelper.ts @@ -45,7 +45,6 @@ function populateCustomMETs() { // we assume that they specify -1 instead, and we will // map -1 to Number.MAX_VALUE here by iterating over all the ranges for (const rangeName in currMET) { - // console.log("Handling range ", rangeName); currMET[rangeName].range = currMET[rangeName].range.map((i) => i == -1 ? Number.MAX_VALUE : i, ); diff --git a/www/js/metrics/metricsHelper.ts b/www/js/metrics/metricsHelper.ts index 7ed348b75..9dbc23c82 100644 --- a/www/js/metrics/metricsHelper.ts +++ b/www/js/metrics/metricsHelper.ts @@ -1,6 +1,7 @@ import { DateTime } from 'luxon'; import { formatForDisplay } from '../config/useImperialConfig'; import { DayOfMetricData } from './metricsTypes'; +import { logDebug } from '../plugin/logger'; export function getUniqueLabelsForDays(metricDataDays: DayOfMetricData[]) { const uniqueLabels: string[] = []; @@ -86,7 +87,8 @@ export function calculatePercentChange(pastWeekRange, previousWeekRange) { } export function parseDataFromMetrics(metrics, population) { - console.log('Called parseDataFromMetrics on ', metrics); + logDebug(`parseDataFromMetrics: metrics = ${JSON.stringify(metrics)}; + population = ${population}`); let mode_bins: { [k: string]: [number, number, string][] } = {}; metrics?.forEach((metric) => { let onFootVal = 0; @@ -115,7 +117,7 @@ export function parseDataFromMetrics(metrics, population) { //this section handles user lables, assuming 'label_' prefix if (field.startsWith('label_')) { let actualMode = field.slice(6, field.length); //remove prefix - console.log('Mapped field ' + field + ' to mode ' + actualMode); + logDebug('Mapped field ' + field + ' to mode ' + actualMode); if (!(actualMode in mode_bins)) { mode_bins[actualMode] = []; } @@ -137,7 +139,7 @@ export function parseDataFromMetrics(metrics, population) { export type MetricsSummary = { key: string; values: number }; export function generateSummaryFromData(modeMap, metric) { - console.log('Invoked getSummaryDataRaw on ', modeMap, 'with', metric); + logDebug(`Invoked getSummaryDataRaw on ${JSON.stringify(modeMap)} with ${metric}`); let summaryMap: MetricsSummary[] = []; @@ -175,19 +177,12 @@ export function isCustomLabels(modeMap) { const distanceKeys = modeMap.map((e) => e.key); const isSensedKeys = distanceKeys.map(isSensed); const isCustomKeys = distanceKeys.map(isCustom); - console.log( - 'Checking metric keys', - distanceKeys, - ' sensed ', - isSensedKeys, - ' custom ', - isCustomKeys, - ); + logDebug(`Checking metric keys ${distanceKeys}; sensed ${isSensedKeys}; custom ${isCustomKeys}`); const isAllCustomForMetric = isAllCustom(isSensedKeys, isCustomKeys); metricSummaryChecksSensed.push(!isAllCustomForMetric); metricSummaryChecksCustom.push(!!isAllCustomForMetric); - - console.log('overall custom/not results for each metric = ', metricSummaryChecksCustom); + logDebug(`overall custom/not results for each metric + is ${JSON.stringify(metricSummaryChecksCustom)}`); return isAllCustom(metricSummaryChecksSensed, metricSummaryChecksCustom); } diff --git a/www/js/onboarding/OnboardingStack.tsx b/www/js/onboarding/OnboardingStack.tsx index 30b725ea9..9682156ae 100644 --- a/www/js/onboarding/OnboardingStack.tsx +++ b/www/js/onboarding/OnboardingStack.tsx @@ -12,8 +12,6 @@ import { displayErrorMsg } from '../plugin/logger'; const OnboardingStack = () => { const { onboardingState } = useContext(AppContext); - console.debug('onboardingState in OnboardingStack', onboardingState); - if (onboardingState.route == OnboardingRoute.WELCOME) { return ; } else if (onboardingState.route == OnboardingRoute.SUMMARY) { diff --git a/www/js/onboarding/WelcomePage.tsx b/www/js/onboarding/WelcomePage.tsx index 2cdd809f7..5699a47a3 100644 --- a/www/js/onboarding/WelcomePage.tsx +++ b/www/js/onboarding/WelcomePage.tsx @@ -59,10 +59,10 @@ const WelcomePage = () => { function scanCode() { window['cordova'].plugins.barcodeScanner.scan( (result) => { - console.debug('scanned code', result); + logDebug('scanCode: scanned ' + JSON.stringify(result)); let code = getCode(result); if (code != false) { - console.log('found code', code); + logDebug('scanCode: found code ' + code); loginWithToken(code); } else { displayError(result.text, 'invalid study reference'); @@ -83,7 +83,7 @@ const WelcomePage = () => { } }) .catch((err) => { - console.error('Error logging in with token', err); + displayError(err, 'Error logging in with token'); setExistingToken(''); }); } diff --git a/www/js/plugin/clientStats.ts b/www/js/plugin/clientStats.ts index f342439c1..bdf4c1888 100644 --- a/www/js/plugin/clientStats.ts +++ b/www/js/plugin/clientStats.ts @@ -1,4 +1,4 @@ -import { displayErrorMsg } from './logger'; +import { displayErrorMsg, logDebug } from './logger'; const CLIENT_TIME = 'stats/client_time'; const CLIENT_ERROR = 'stats/client_error'; @@ -40,6 +40,7 @@ async function getStatsEvent(name: string, reading: any) { export async function addStatReading(name: string, reading: any) { const db = window['cordova']?.plugins?.BEMUserCache; const event = await getStatsEvent(name, reading); + logDebug('addStatReading: adding CLIENT_TIME event: ' + JSON.stringify(event)); if (db) return db.putMessage(CLIENT_TIME, event); displayErrorMsg('addStatReading: db is not defined'); } @@ -47,6 +48,7 @@ export async function addStatReading(name: string, reading: any) { export async function addStatEvent(name: string) { const db = window['cordova']?.plugins?.BEMUserCache; const event = await getStatsEvent(name, null); + logDebug('addStatEvent: adding CLIENT_NAV_EVENT event: ' + JSON.stringify(event)); if (db) return db.putMessage(CLIENT_NAV_EVENT, event); displayErrorMsg('addStatEvent: db is not defined'); } @@ -54,6 +56,7 @@ export async function addStatEvent(name: string) { export async function addStatError(name: string, errorStr: string) { const db = window['cordova']?.plugins?.BEMUserCache; const event = await getStatsEvent(name, errorStr); + logDebug('addStatError: adding CLIENT_ERROR event: ' + JSON.stringify(event)); if (db) return db.putMessage(CLIENT_ERROR, event); displayErrorMsg('addStatError: db is not defined'); } diff --git a/www/js/plugin/logger.ts b/www/js/plugin/logger.ts index c2e678d40..98e852978 100644 --- a/www/js/plugin/logger.ts +++ b/www/js/plugin/logger.ts @@ -1,11 +1,11 @@ export const logDebug = (message: string) => - window['Logger'].log(window['Logger'].LEVEL_DEBUG, message); + window['Logger']?.log(window['Logger'].LEVEL_DEBUG, message); export const logInfo = (message: string) => - window['Logger'].log(window['Logger'].LEVEL_INFO, message); + window['Logger']?.log(window['Logger'].LEVEL_INFO, message); export const logWarn = (message: string) => - window['Logger'].log(window['Logger'].LEVEL_WARN, message); + window['Logger']?.log(window['Logger'].LEVEL_WARN, message); export function displayError(error: Error, title?: string) { const errorMsg = error.message ? error.message + '\n' + error.stack : JSON.stringify(error); diff --git a/www/js/plugin/storage.ts b/www/js/plugin/storage.ts index deed606b8..e22bb4669 100644 --- a/www/js/plugin/storage.ts +++ b/www/js/plugin/storage.ts @@ -83,10 +83,8 @@ function getUnifiedValue(key) { // both values are present, but they are different console.assert( ls_stored_val != null && uc_stored_val != null, - 'ls_stored_val =' + - JSON.stringify(ls_stored_val) + - 'uc_stored_val =' + - JSON.stringify(uc_stored_val), + `ls_stored_val = ${JSON.stringify(ls_stored_val)}; + uc_stored_val = ${JSON.stringify(uc_stored_val)}`, ); logWarn(`for key ${key}, uc_stored_val = ${JSON.stringify(uc_stored_val)}, ls_stored_val = ${JSON.stringify(ls_stored_val)}. @@ -145,25 +143,21 @@ function findMissing(fromKeys: any[], toKeys: any[]) { } export function storageSyncLocalAndNative() { - console.log('STORAGE_PLUGIN: Called syncAllWebAndNativeValues '); + logDebug('STORAGE_PLUGIN: Called syncAllWebAndNativeValues'); const syncKeys = window['cordova'].plugins.BEMUserCache.listAllLocalStorageKeys().then( (nativeKeys) => { - console.log('STORAGE_PLUGIN: native plugin returned'); const webKeys = Object.keys(localStorage); // I thought about iterating through the lists and copying over // only missing values, etc but `getUnifiedValue` already does // that, and we don't need to copy it // so let's just find all the missing values and read them - logDebug('STORAGE_PLUGIN: Comparing web keys ' + webKeys + ' with ' + nativeKeys); + logDebug(`STORAGE_PLUGIN: native keys returned = ${JSON.stringify(nativeKeys)}; + comparing against webKeys = ${JSON.stringify(webKeys)}`); let [foundNative, missingNative] = findMissing(webKeys, nativeKeys); let [foundWeb, missingWeb] = findMissing(nativeKeys, webKeys); - logDebug( - 'STORAGE_PLUGIN: Found native keys ' + - foundNative + - ' missing native keys ' + - missingNative, - ); - logDebug('STORAGE_PLUGIN: Found web keys ' + foundWeb + ' missing web keys ' + missingWeb); + logDebug(`STORAGE_PLUGIN: + Found native keys = ${foundNative}; Missing native keys = ${missingNative}; + Found web keys = ${foundWeb}; Missing web keys = ${missingWeb}`); const allMissing = missingNative.concat(missingWeb); logDebug('STORAGE_PLUGIN: Syncing all missing keys ' + allMissing); allMissing.forEach(getUnifiedValue); diff --git a/www/js/splash/customURL.ts b/www/js/splash/customURL.ts index ec3da45bd..21625ac29 100644 --- a/www/js/splash/customURL.ts +++ b/www/js/splash/customURL.ts @@ -1,3 +1,5 @@ +import { displayError } from '../plugin/logger'; + type UrlComponents = { [key: string]: string; }; @@ -16,7 +18,7 @@ export function onLaunchCustomURL( urlComponents[key] = value; } handler(url, urlComponents); - } catch { - console.log('not a valid url'); + } catch (err) { + displayError(err, 'onLaunchCustomURL: not a valid URL'); } } diff --git a/www/js/splash/notifScheduler.ts b/www/js/splash/notifScheduler.ts index f82d7b214..b6bc21778 100644 --- a/www/js/splash/notifScheduler.ts +++ b/www/js/splash/notifScheduler.ts @@ -66,9 +66,8 @@ function debugGetScheduled(prefix) { }; }); //have the list of scheduled show up in this log - logDebug( - `${prefix}, there are ${notifs.length} scheduled notifications at ${time} first is ${scheduledNotifs[0].key} at ${scheduledNotifs[0].val}`, - ); + logDebug(`${prefix}, there are ${notifs.length} scheduled notifications at ${time}; + first is ${scheduledNotifs[0].key} at ${scheduledNotifs[0].val}`); }); } @@ -79,12 +78,7 @@ export function getScheduledNotifs(isScheduling: boolean, scheduledPromise: Prom anywhere from 0-n of the scheduled notifs are displayed if actively scheduling, wait for the scheduledPromise to resolve before fetching prevents such errors */ - console.log('test log: isScheduling during getScheduledNotifs', isScheduling); - console.log('test log: scheduledPromise during getScheduledNotifs', scheduledPromise); if (isScheduling) { - console.log( - 'test log: requesting fetch while still actively scheduling, waiting on scheduledPromise', - ); logDebug('requesting fetch while still actively scheduling, waiting on scheduledPromise'); scheduledPromise.then(() => { getNotifs().then((notifs) => { @@ -92,7 +86,6 @@ export function getScheduledNotifs(isScheduling: boolean, scheduledPromise: Prom }); }); } else { - console.log('test log: not actively scheduling, fetching'); getNotifs().then((notifs) => { resolve(notifs); }); diff --git a/www/js/splash/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts index 414f9f3f8..098625ee2 100644 --- a/www/js/splash/remoteNotifyHandler.ts +++ b/www/js/splash/remoteNotifyHandler.ts @@ -31,9 +31,7 @@ const launchWebpage = (url) => window['cordova'].InAppBrowser.open(url, '_blank' */ function onCloudNotifEvent(event) { const data = event.detail; - addStatEvent(statKeys.NOTIFICATION_OPEN).then(() => { - console.log('Added ' + statKeys.NOTIFICATION_OPEN + ' event. Data = ' + JSON.stringify(data)); - }); + addStatEvent(statKeys.NOTIFICATION_OPEN); logDebug('data = ' + JSON.stringify(data)); if ( data.additionalData && diff --git a/www/js/splash/startprefs.ts b/www/js/splash/startprefs.ts index 7700f315a..5e1edd188 100644 --- a/www/js/splash/startprefs.ts +++ b/www/js/splash/startprefs.ts @@ -69,7 +69,6 @@ export function readConsentState() { return fetch('json/startupConfig.json') .then((response) => response.json()) .then((startupConfigResult) => { - console.log(startupConfigResult); _req_consent = startupConfigResult.emSensorDataCollectionProtocol; logDebug('required consent version = ' + JSON.stringify(_req_consent)); return storageGet(DATA_COLLECTION_CONSENTED_PROTOCOL); diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts index 8d0426ebb..a2a319ef5 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -36,17 +36,12 @@ function storeDeviceSettings() { * @param event that called this function */ function onConsentEvent(event) { - console.log( - 'got consented event ' + - JSON.stringify(event['name']) + - ' with data ' + - JSON.stringify(event.detail), - ); + logDebug(`got consented event ${JSON.stringify(event['name'])} + with data ${JSON.stringify(event.detail)}`); readIntroDone().then(async (isIntroDone) => { if (isIntroDone) { - logDebug( - 'intro is done -> reconsent situation, we already have a token -> store device settings', - ); + logDebug(`intro is done -> reconsent situation, + we already have a token -> store device settings`); await storeDeviceSettings(); } }); @@ -70,7 +65,7 @@ export function initStoreDeviceSettings() { readConsentState() .then(isConsented) .then(async (consentState) => { - console.log('found consent', consentState); + logDebug(`found consent: ${consentState}`); if (consentState == true) { await storeDeviceSettings(); } else { diff --git a/www/js/survey/enketo/AddNoteButton.tsx b/www/js/survey/enketo/AddNoteButton.tsx index f125f8185..93b3103b4 100644 --- a/www/js/survey/enketo/AddNoteButton.tsx +++ b/www/js/survey/enketo/AddNoteButton.tsx @@ -83,7 +83,7 @@ const AddNoteButton = ({ timelineEntry, notesConfig, storeKey }: Props) => { function launchAddNoteSurvey() { const surveyName = notesConfig.surveyName; - console.log('About to launch survey ', surveyName); + logDebug(`AddNoteButton: about to launch survey ${surveyName}`); setPrefillTimes(getPrefillTimes()); setModalVisible(true); } diff --git a/www/js/survey/enketo/AddedNotesList.tsx b/www/js/survey/enketo/AddedNotesList.tsx index e64783fe0..838300621 100644 --- a/www/js/survey/enketo/AddedNotesList.tsx +++ b/www/js/survey/enketo/AddedNotesList.tsx @@ -90,7 +90,7 @@ const AddedNotesList = ({ timelineEntry, additionEntries }: Props) => { function editEntry(entry) { setEditingEntry(entry); - console.debug('Editing entry is now ', entry); + logDebug('editingEntry = ' + JSON.stringify(entry)); setSurveyModalVisible(true); } diff --git a/www/js/survey/enketo/EnketoModal.tsx b/www/js/survey/enketo/EnketoModal.tsx index d1f11a83a..54a10ed7d 100644 --- a/www/js/survey/enketo/EnketoModal.tsx +++ b/www/js/survey/enketo/EnketoModal.tsx @@ -5,7 +5,7 @@ import { ModalProps } from 'react-native-paper'; import useAppConfig from '../../useAppConfig'; import { useTranslation } from 'react-i18next'; import { SurveyOptions, fetchSurvey, getInstanceStr, saveResponse } from './enketoHelper'; -import { displayError, displayErrorMsg } from '../../plugin/logger'; +import { displayError, displayErrorMsg, logDebug } from '../../plugin/logger'; type Props = Omit & { surveyName: string; @@ -40,9 +40,9 @@ const EnketoModal = ({ surveyName, onResponseSaved, opts, ...rest }: Props) => { // init logic: retrieve form -> inject into DOM -> initialize Enketo -> show modal function initSurvey() { - console.debug('Loading survey', surveyName); + logDebug('EnketoModal: loading survey ' + surveyName); const formPath = appConfig.survey_info?.surveys?.[surveyName]?.formPath; - if (!formPath) return console.error('No form path found for survey', surveyName); + if (!formPath) return displayErrorMsg('No form path found for survey ' + surveyName); fetchSurvey(formPath).then(({ form, model }) => { surveyJson.current = { form, model }; @@ -62,7 +62,7 @@ const EnketoModal = ({ surveyName, onResponseSaved, opts, ...rest }: Props) => { useEffect(() => { if (!rest.visible) return; - if (!appConfig) return console.error('App config not loaded yet'); + if (!appConfig) return displayErrorMsg('App config not loaded yet'); initSurvey(); }, [appConfig, rest.visible]); diff --git a/www/js/survey/inputMatcher.ts b/www/js/survey/inputMatcher.ts index b597400b8..f29926525 100644 --- a/www/js/survey/inputMatcher.ts +++ b/www/js/survey/inputMatcher.ts @@ -104,25 +104,23 @@ export function validUserInputForTimelineEntry( endChecks = true; // so we will just skip the end check } else { endChecks = userInput.data.end_ts <= nextEntryEnd; - logDebug( - `Second level of end checks when the next trip is defined(${userInput.data.end_ts} <= ${nextEntryEnd}) ${endChecks}`, - ); + logDebug(`Second level of end checks when the next trip is defined, + (${userInput.data.end_ts} <= ${nextEntryEnd}), + endChecks = ${endChecks}`); } } else { // next trip is not defined, last trip endChecks = userInput.data.end_local_dt?.day == userInput.data.start_local_dt?.day; logDebug('Second level of end checks for the last trip of the day'); - logDebug( - `compare ${userInput.data.end_local_dt?.day} with ${userInput.data.start_local_dt?.day} ${endChecks}`, - ); + logDebug(`compare ${userInput.data.end_local_dt?.day} with ${userInput.data.start_local_dt?.day}; + endChecks = ${endChecks}`); } if (endChecks) { // If we have flipped the values, check to see that there is sufficient overlap const overlapDuration = Math.min(userInput.data.end_ts, entryEnd) - Math.max(userInput.data.start_ts, entryStart); - logDebug( - `Flipped endCheck, overlap(${overlapDuration})/trip(${tlEntry.duration} (${overlapDuration} / ${tlEntry.duration})`, - ); + logDebug(`Flipped endCheck, overlapDuration / tlEntry.duration is + ${overlapDuration} / ${tlEntry.duration} = ${overlapDuration / tlEntry.duration}`); endChecks = overlapDuration / tlEntry.duration > 0.5; } } @@ -131,7 +129,7 @@ export function validUserInputForTimelineEntry( // parallels get_not_deleted_candidates() in trip_queries.py export function getNotDeletedCandidates(candidates: UserInputEntry[]): UserInputEntry[] { - console.log('getNotDeletedCandidates called with ' + candidates.length + ' candidates'); + logDebug('getNotDeletedCandidates called with ' + candidates.length + ' candidates'); // We want to retain all ACTIVE entries that have not been DELETED const allActiveList = candidates.filter((c) => !c.data.status || c.data.status == 'ACTIVE'); @@ -140,8 +138,9 @@ export function getNotDeletedCandidates(candidates: UserInputEntry[]): UserInput .map((c) => c.data['match_id']); const notDeletedActive = allActiveList.filter((c) => !allDeletedIds.includes(c.data['match_id'])); - console.log(`Found ${allActiveList.length} active entries, ${allDeletedIds.length} deleted entries -> - ${notDeletedActive.length} non deleted active entries`); + logDebug(`Found ${allActiveList.length} active entries; + ${allDeletedIds.length} deleted entries -> + ${notDeletedActive.length} non-deleted active entries`); return notDeletedActive; } @@ -157,7 +156,7 @@ export function getUserInputForTimelineEntry( return undefined; } - if (logsEnabled) console.log(`Input list = ${userInputList.map(printUserInput)}`); + if (logsEnabled) logDebug(`Input list = ${userInputList.map(printUserInput)}`); // undefined !== true, so this covers the label view case as well const potentialCandidates = userInputList.filter((ui) => @@ -171,11 +170,8 @@ export function getUserInputForTimelineEntry( } if (potentialCandidates.length === 1) { - logDebug( - `In getUserInputForTimelineEntry, one potential candidate, returning ${printUserInput( - potentialCandidates[0], - )}`, - ); + logDebug(`In getUserInputForTimelineEntry, one potential candidate, + returning ${printUserInput(potentialCandidates[0])}`); return potentialCandidates[0]; } @@ -209,7 +205,7 @@ export function getAdditionsForTimelineEntry( validUserInputForTimelineEntry(entry, nextEntry, ui, logsEnabled), ); - if (logsEnabled) console.log(`Matching Addition list ${matchingAdditions.map(printUserInput)}`); + if (logsEnabled) logDebug(`Matching Addition list ${matchingAdditions.map(printUserInput)}`); return matchingAdditions; } @@ -242,9 +238,8 @@ export function getUniqueEntries(combinedList) { `${JSON.stringify(existingVal)} vs ${JSON.stringify(e)}`, ); } else { - console.log( - `Found two entries with match_id ${existingVal.data.match_id} but they are identical`, - ); + logDebug(`Found two entries with match_id ${existingVal.data.match_id}, + but they are identical`); } } else { uniqueMap.set(e.data.match_id, e); diff --git a/www/js/survey/multilabel/confirmHelper.ts b/www/js/survey/multilabel/confirmHelper.ts index b2951ab98..cf61faa81 100644 --- a/www/js/survey/multilabel/confirmHelper.ts +++ b/www/js/survey/multilabel/confirmHelper.ts @@ -92,18 +92,12 @@ export function labelInputDetailsForTrip(userInputForTrip, appConfigParam?) { if (appConfigParam) appConfig = appConfigParam; if (appConfig.intro.mode_studied) { if (userInputForTrip?.['MODE']?.value == appConfig.intro.mode_studied) { - logDebug( - 'Found trip labeled with mode of study ' + - appConfig.intro.mode_studied + - '. Needs REPLACED_MODE', - ); + logDebug(`Found trip labeled with mode of study, ${appConfig.intro.mode_studied}. + Needs REPLACED_MODE`); return getLabelInputDetails(); } else { - logDebug( - 'Found trip not labeled with mode of study ' + - appConfig.intro.mode_studied + - ". Doesn't need REPLACED_MODE", - ); + logDebug(`Found trip not labeled with mode of study, ${appConfig.intro.mode_studied}. + Doesn't need REPLACED_MODE`); return baseLabelInputDetails; } } else { diff --git a/www/js/useAppStateChange.ts b/www/js/useAppStateChange.ts index 470eb67a6..9a77909a0 100644 --- a/www/js/useAppStateChange.ts +++ b/www/js/useAppStateChange.ts @@ -5,6 +5,7 @@ import { useEffect, useRef } from 'react'; import { AppState } from 'react-native'; +import { logDebug } from './plugin/logger'; const useAppStateChange = (onResume) => { const appState = useRef(AppState.currentState); @@ -16,7 +17,7 @@ const useAppStateChange = (onResume) => { } appState.current = nextAppState; - console.log('AppState', appState.current); + logDebug('new AppState: ' + appState.current); }); }, []); diff --git a/www/js/usePermissionStatus.ts b/www/js/usePermissionStatus.ts index c7fdacae4..8c3128bea 100644 --- a/www/js/usePermissionStatus.ts +++ b/www/js/usePermissionStatus.ts @@ -3,6 +3,7 @@ import useAppStateChange from './useAppStateChange'; import useAppConfig from './useAppConfig'; import { useTranslation } from 'react-i18next'; import { useAppTheme } from './appTheme'; +import { logDebug, logWarn } from './plugin/logger'; //refreshing checks with the plugins to update the check's statusState export function refreshAllChecks(checkList) { @@ -10,7 +11,7 @@ export function refreshAllChecks(checkList) { checkList.forEach((lc) => { lc.refresh(); }); - console.log('setting checks are', checkList); + logDebug('After refreshing all checks, checks are ' + JSON.stringify(checkList)); } type Check = { @@ -42,7 +43,7 @@ const usePermissionStatus = () => { let status = true; if (!checkList?.length) return undefined; // if checks not loaded yet, status is undetermined checkList.forEach((lc) => { - console.debug('check in permission status for ' + lc.name + ':', lc.statusState); + logDebug('check in permission status for ' + lc.name + ':' + lc.statusState); if (lc.statusState === false) { status = false; } @@ -67,33 +68,31 @@ const usePermissionStatus = () => { } async function checkOrFix(checkObj, nativeFn, showError = true) { - console.log('checking object', checkObj.name, checkObj); + logDebug('checking object ' + checkObj.name + ' ' + JSON.stringify(checkObj)); let newCheck = checkObj; return nativeFn() .then((status) => { - console.log('availability ', status); + logDebug('availability = ' + status); newCheck.statusState = true; updateCheck(newCheck); - console.log('after checking object', checkObj.name, checkList); + logDebug(`after checking obj ${checkObj.name}, checkList is ${JSON.stringify(checkList)}`); return status; }) .catch((error) => { - console.log('Error', error); if (showError) { - console.log('please fix again'); setError(error); setErrorVis(true); } newCheck.statusState = false; updateCheck(newCheck); - console.log('after checking object', checkObj.name, checkList); + logDebug(`after checking obj ${checkObj.name}, checkList is ${JSON.stringify(checkList)}`); return error; }); } function setupAndroidLocChecks() { let fixSettings = () => { - console.log('Fix and refresh location settings'); + logDebug('Fix and refresh location settings'); return checkOrFix( locSettingsCheck, window['cordova'].plugins.BEMDataCollection.fixLocationSettings, @@ -101,7 +100,7 @@ const usePermissionStatus = () => { ); }; let checkSettings = () => { - console.log('Refresh location settings'); + logDebug('Refresh location settings'); return checkOrFix( locSettingsCheck, window['cordova'].plugins.BEMDataCollection.isValidLocationSettings, @@ -109,7 +108,7 @@ const usePermissionStatus = () => { ); }; let fixPerms = () => { - console.log('fix and refresh location permissions'); + logDebug('fix and refresh location permissions'); return checkOrFix( locPermissionsCheck, window['cordova'].plugins.BEMDataCollection.fixLocationPermissions, @@ -121,7 +120,7 @@ const usePermissionStatus = () => { }); }; let checkPerms = () => { - console.log('fix and refresh location permissions'); + logDebug('fix and refresh location permissions'); return checkOrFix( locPermissionsCheck, window['cordova'].plugins.BEMDataCollection.isValidLocationPermissions, @@ -143,7 +142,7 @@ const usePermissionStatus = () => { : androidVersion < 12 ? 'intro.appstatus.locperms.description.android-11' : 'intro.appstatus.locperms.description.android-gte-12'; - console.log('description tags are ' + androidSettingsDescTag + ' ' + androidPermDescTag); + logDebug('description tags are ' + androidSettingsDescTag + ' ' + androidPermDescTag); // location settings let locSettingsCheck = { name: t('intro.appstatus.locsettings.name'), @@ -164,7 +163,7 @@ const usePermissionStatus = () => { function setupIOSLocChecks() { let fixSettings = () => { - console.log('Fix and refresh location settings'); + logDebug('Fix and refresh location settings'); return checkOrFix( locSettingsCheck, window['cordova'].plugins.BEMDataCollection.fixLocationSettings, @@ -172,7 +171,7 @@ const usePermissionStatus = () => { ); }; let checkSettings = () => { - console.log('Refresh location settings'); + logDebug('Refresh location settings'); return checkOrFix( locSettingsCheck, window['cordova'].plugins.BEMDataCollection.isValidLocationSettings, @@ -180,7 +179,7 @@ const usePermissionStatus = () => { ); }; let fixPerms = () => { - console.log('fix and refresh location permissions'); + logDebug('fix and refresh location permissions'); return checkOrFix( locPermissionsCheck, window['cordova'].plugins.BEMDataCollection.fixLocationPermissions, @@ -192,7 +191,7 @@ const usePermissionStatus = () => { }); }; let checkPerms = () => { - console.log('fix and refresh location permissions'); + logDebug('fix and refresh location permissions'); return checkOrFix( locPermissionsCheck, window['cordova'].plugins.BEMDataCollection.isValidLocationPermissions, @@ -205,7 +204,7 @@ const usePermissionStatus = () => { iOSVersion < 13 ? 'intro.appstatus.locperms.description.ios-lt-13' : 'intro.appstatus.locperms.description.ios-gte-13'; - console.log('description tags are ' + iOSSettingsDescTag + ' ' + iOSPermDescTag); + logDebug('description tags are ' + iOSSettingsDescTag + ' ' + iOSPermDescTag); const locSettingsCheck = { name: t('intro.appstatus.locsettings.name'), @@ -227,7 +226,7 @@ const usePermissionStatus = () => { function setupAndroidFitnessChecks() { if (window['device'].version.split('.')[0] >= 10) { let fixPerms = () => { - console.log('fix and refresh fitness permissions'); + logDebug('fix and refresh fitness permissions'); return checkOrFix( fitnessPermissionsCheck, window['cordova'].plugins.BEMDataCollection.fixFitnessPermissions, @@ -239,7 +238,7 @@ const usePermissionStatus = () => { }); }; let checkPerms = () => { - console.log('fix and refresh fitness permissions'); + logDebug('fix and refresh fitness permissions'); return checkOrFix( fitnessPermissionsCheck, window['cordova'].plugins.BEMDataCollection.isValidFitnessPermissions, @@ -261,7 +260,7 @@ const usePermissionStatus = () => { function setupIOSFitnessChecks() { let fixPerms = () => { - console.log('fix and refresh fitness permissions'); + logDebug('fix and refresh fitness permissions'); return checkOrFix( fitnessPermissionsCheck, window['cordova'].plugins.BEMDataCollection.fixFitnessPermissions, @@ -273,7 +272,7 @@ const usePermissionStatus = () => { }); }; let checkPerms = () => { - console.log('fix and refresh fitness permissions'); + logDebug('fix and refresh fitness permissions'); return checkOrFix( fitnessPermissionsCheck, window['cordova'].plugins.BEMDataCollection.isValidFitnessPermissions, @@ -294,7 +293,7 @@ const usePermissionStatus = () => { function setupAndroidNotificationChecks() { let fixPerms = () => { - console.log('fix and refresh notification permissions'); + logDebug('fix and refresh notification permissions'); return checkOrFix( appAndChannelNotificationsCheck, window['cordova'].plugins.BEMDataCollection.fixShowNotifications, @@ -302,7 +301,7 @@ const usePermissionStatus = () => { ); }; let checkPerms = () => { - console.log('fix and refresh notification permissions'); + logDebug('fix and refresh notification permissions'); return checkOrFix( appAndChannelNotificationsCheck, window['cordova'].plugins.BEMDataCollection.isValidShowNotifications, @@ -322,7 +321,7 @@ const usePermissionStatus = () => { function setupAndroidBackgroundRestrictionChecks() { let fixPerms = () => { - console.log('fix and refresh backgroundRestriction permissions'); + logDebug('fix and refresh backgroundRestriction permissions'); return checkOrFix( unusedAppsUnrestrictedCheck, window['cordova'].plugins.BEMDataCollection.fixUnusedAppRestrictions, @@ -330,7 +329,7 @@ const usePermissionStatus = () => { ); }; let checkPerms = () => { - console.log('fix and refresh backgroundRestriction permissions'); + logDebug('fix and refresh backgroundRestriction permissions'); return checkOrFix( unusedAppsUnrestrictedCheck, window['cordova'].plugins.BEMDataCollection.isUnusedAppUnrestricted, @@ -338,7 +337,7 @@ const usePermissionStatus = () => { ); }; let fixBatteryOpt = () => { - console.log('fix and refresh battery optimization permissions'); + logDebug('fix and refresh battery optimization permissions'); return checkOrFix( ignoreBatteryOptCheck, window['cordova'].plugins.BEMDataCollection.fixIgnoreBatteryOptimizations, @@ -346,7 +345,7 @@ const usePermissionStatus = () => { ); }; let checkBatteryOpt = () => { - console.log('fix and refresh battery optimization permissions'); + logDebug('fix and refresh battery optimization permissions'); return checkOrFix( ignoreBatteryOptCheck, window['cordova'].plugins.BEMDataCollection.isIgnoreBatteryOptimizations, @@ -403,7 +402,7 @@ const usePermissionStatus = () => { //TODO - update samsung handling based on feedback - console.log('Explanation = ' + explanationList); + logDebug('Explanation = ' + explanationList); } function createChecklist() { @@ -419,14 +418,14 @@ const usePermissionStatus = () => { } else { setError('Alert! unknownplatform, no tracking'); setErrorVis(true); - console.log('Alert! unknownplatform, no tracking'); //need an alert, can use AlertBar? + logWarn('Alert! unknownplatform, no tracking'); //need an alert, can use AlertBar? } refreshAllChecks(checkList); } useAppStateChange(() => { - console.log('PERMISSION CHECK: app has resumed, should refresh'); + logDebug('PERMISSION CHECK: app has resumed, should refresh'); refreshAllChecks(checkList); }); @@ -435,7 +434,7 @@ const usePermissionStatus = () => { if (appConfig && window['device']?.platform) { setupPermissionText(); setHaveSetText(true); - console.log('setting up permissions'); + logDebug('setting up permissions'); createChecklist(); } }, [appConfig]); From a26d03935f1d01e26a72266e07bd56cf0aae3039 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 12 Feb 2024 11:19:15 -0500 Subject: [PATCH 04/36] use PascalCase for all type defs --- www/js/control/ControlCollectionHelper.tsx | 12 ++++++------ www/js/control/ControlSyncHelper.tsx | 12 ++++++------ www/js/control/LogPage.tsx | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/www/js/control/ControlCollectionHelper.tsx b/www/js/control/ControlCollectionHelper.tsx index 993f3724f..9ee0efc6c 100644 --- a/www/js/control/ControlCollectionHelper.tsx +++ b/www/js/control/ControlCollectionHelper.tsx @@ -6,7 +6,7 @@ import ActionMenu from '../components/ActionMenu'; import { settingStyles } from './ProfileSettings'; import { displayError, displayErrorMsg, logDebug } from '../plugin/logger'; -type collectionConfig = { +type CollectionConfig = { is_duty_cycling: boolean; simulate_user_interaction: boolean; accuracy: number; @@ -127,7 +127,7 @@ function formatConfigForDisplay(config, accuracyOptions) { const ControlCollectionHelper = ({ editVis, setEditVis }) => { const { colors } = useTheme(); - const [localConfig, setLocalConfig] = useState(); + const [localConfig, setLocalConfig] = useState(); const [accuracyActions, setAccuracyActions] = useState([]); const [accuracyVis, setAccuracyVis] = useState(false); @@ -168,19 +168,19 @@ const ControlCollectionHelper = ({ editVis, setEditVis }) => { } function onToggle(config_key) { - let tempConfig = { ...localConfig } as collectionConfig; - tempConfig[config_key] = !(localConfig as collectionConfig)[config_key]; + let tempConfig = { ...localConfig } as CollectionConfig; + tempConfig[config_key] = !(localConfig as CollectionConfig)[config_key]; setLocalConfig(tempConfig); } function onChooseAccuracy(accuracyOption) { - let tempConfig = { ...localConfig } as collectionConfig; + let tempConfig = { ...localConfig } as CollectionConfig; tempConfig.accuracy = accuracyOption.value; setLocalConfig(tempConfig); } function onChangeText(newText, config_key) { - let tempConfig = { ...localConfig } as collectionConfig; + let tempConfig = { ...localConfig } as CollectionConfig; tempConfig[config_key] = parseInt(newText); setLocalConfig(tempConfig); } diff --git a/www/js/control/ControlSyncHelper.tsx b/www/js/control/ControlSyncHelper.tsx index cdc1a8e2a..4dc63f2de 100644 --- a/www/js/control/ControlSyncHelper.tsx +++ b/www/js/control/ControlSyncHelper.tsx @@ -31,7 +31,7 @@ export async function getHelperSyncSettings() { return formatConfigForDisplay(tempConfig); } -type syncConfig = { sync_interval: number; ios_use_remote_push: boolean }; +type SyncConfig = { sync_interval: number; ios_use_remote_push: boolean }; //forceSync and endForceSync SettingRows & their actions export const ForceSyncRow = ({ getState }) => { @@ -170,7 +170,7 @@ const ControlSyncHelper = ({ editVis, setEditVis }) => { const { t } = useTranslation(); const { colors } = useTheme(); - const [localConfig, setLocalConfig] = useState(); + const [localConfig, setLocalConfig] = useState(); const [intervalVis, setIntervalVis] = useState(false); /* @@ -207,21 +207,21 @@ const ControlSyncHelper = ({ editVis, setEditVis }) => { // or continue to store from native // this is easier for people to see, but means that calls to // native, even through the javascript interface are not complete - curr_sync_interval: (localConfig as syncConfig).sync_interval, + curr_sync_interval: (localConfig as SyncConfig).sync_interval, }); } catch (err) { displayError(err, 'Error while setting sync config'); } } function onChooseInterval(interval) { - let tempConfig = { ...localConfig } as syncConfig; + let tempConfig = { ...localConfig } as SyncConfig; tempConfig.sync_interval = interval.value; setLocalConfig(tempConfig); } function onTogglePush() { - let tempConfig = { ...localConfig } as syncConfig; - tempConfig.ios_use_remote_push = !(localConfig as syncConfig).ios_use_remote_push; + let tempConfig = { ...localConfig } as SyncConfig; + tempConfig.ios_use_remote_push = !(localConfig as SyncConfig).ios_use_remote_push; setLocalConfig(tempConfig); } diff --git a/www/js/control/LogPage.tsx b/www/js/control/LogPage.tsx index 098592dee..e1af11fdf 100644 --- a/www/js/control/LogPage.tsx +++ b/www/js/control/LogPage.tsx @@ -8,13 +8,13 @@ import AlertBar from './AlertBar'; import { sendEmail } from './emailService'; import { displayError, logDebug } from '../plugin/logger'; -type loadStats = { currentStart: number; gotMaxIndex: boolean; reachedEnd: boolean }; +type LoadStats = { currentStart: number; gotMaxIndex: boolean; reachedEnd: boolean }; const LogPage = ({ pageVis, setPageVis }) => { const { t } = useTranslation(); const { colors } = useTheme(); - const [loadStats, setLoadStats] = useState(); + const [loadStats, setLoadStats] = useState(); const [entries, setEntries] = useState([]); const [maxErrorVis, setMaxErrorVis] = useState(false); const [logErrorVis, setLogErrorVis] = useState(false); @@ -33,7 +33,7 @@ const LogPage = ({ pageVis, setPageVis }) => { try { let maxIndex = await window['Logger'].getMaxIndex(); logDebug('Logger maxIndex = ' + maxIndex); - let tempStats = {} as loadStats; + let tempStats = {} as LoadStats; tempStats.currentStart = maxIndex; tempStats.gotMaxIndex = true; tempStats.reachedEnd = false; @@ -81,7 +81,7 @@ const LogPage = ({ pageVis, setPageVis }) => { function processEntries(entryList) { let tempEntries: any[] = []; - let tempLoadStats: loadStats = { ...loadStats } as loadStats; + let tempLoadStats: LoadStats = { ...loadStats } as LoadStats; entryList.forEach((e) => { e.fmt_time = DateTime.fromSeconds(e.ts).toLocaleString(DateTime.DATETIME_MED); tempEntries.push(e); From 069a72eba1f51af1b11489cb010fececf560e1ee Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 12 Feb 2024 11:19:32 -0500 Subject: [PATCH 05/36] use LocalDt in MetricsTypes --- www/js/metrics/metricsTypes.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/js/metrics/metricsTypes.ts b/www/js/metrics/metricsTypes.ts index cfe4444a3..cce1cd243 100644 --- a/www/js/metrics/metricsTypes.ts +++ b/www/js/metrics/metricsTypes.ts @@ -1,3 +1,4 @@ +import { LocalDt } from '../types/serverData'; import { METRIC_LIST } from './MetricsTab'; type MetricName = (typeof METRIC_LIST)[number]; @@ -6,7 +7,7 @@ export type DayOfMetricData = LabelProps & { ts: number; fmt_time: string; nUsers: number; - local_dt: { [k: string]: any }; // TODO type datetime obj + local_dt: LocalDt; }; export type MetricsData = { From 633db15183286d9ef189fd0852321ff87d81da20 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 12 Feb 2024 11:32:57 -0500 Subject: [PATCH 06/36] use 'i18n.resolvedLanguage', not 'language' This was added in a relatively recent version of i18next; some online resources haven't updated to reflect it https://www.i18next.com/overview/api#resolvedlanguage `language` is what i18next detected or we have set it to. It could be either a locale code like 'en-US' or a plain language like 'en'. `resolvedLanguage` is what language is actually being used for translation, after considering what is detected, what langauges we provided, and what fallbacks we specified. In our case this should always be either 'en', 'es', or 'lo' - the 3 languages we support at this time. So `resolvedLanguage` is the safe property to use and we should avoid `language`. --- www/js/config/useImperialConfig.ts | 2 +- www/js/control/DataDatePicker.tsx | 2 +- www/js/diary/diaryHelper.ts | 2 +- www/js/onboarding/PrivacyPolicy.tsx | 5 ++++- www/js/onboarding/StudySummary.tsx | 5 ++++- www/js/splash/notifScheduler.ts | 2 +- www/js/splash/storeDeviceSettings.ts | 2 +- www/js/survey/enketo/AddNoteButton.tsx | 2 +- www/js/survey/enketo/enketoHelper.ts | 2 +- www/js/survey/multilabel/confirmHelper.ts | 2 +- 10 files changed, 16 insertions(+), 10 deletions(-) diff --git a/www/js/config/useImperialConfig.ts b/www/js/config/useImperialConfig.ts index 04125aaf4..aa87ed1c6 100644 --- a/www/js/config/useImperialConfig.ts +++ b/www/js/config/useImperialConfig.ts @@ -25,7 +25,7 @@ export function formatForDisplay(value: number): string { if (value >= 100) opts.maximumFractionDigits = 0; else if (value >= 1) opts.maximumSignificantDigits = 3; else opts.maximumFractionDigits = 2; - return Intl.NumberFormat(i18next.language, opts).format(value); + return Intl.NumberFormat(i18next.resolvedLanguage, opts).format(value); } export function convertDistance(distMeters: number, imperial: boolean): number { diff --git a/www/js/control/DataDatePicker.tsx b/www/js/control/DataDatePicker.tsx index c2a149fc5..308d940e3 100644 --- a/www/js/control/DataDatePicker.tsx +++ b/www/js/control/DataDatePicker.tsx @@ -26,7 +26,7 @@ const DataDatePicker = ({ date, setDate, open, setOpen, minDate }) => { return ( <> { ); } - const templateText = useMemo(() => getTemplateText(appConfig, i18n.language), [appConfig]); + const templateText = useMemo( + () => getTemplateText(appConfig, i18n.resolvedLanguage), + [appConfig], + ); return ( <> diff --git a/www/js/onboarding/StudySummary.tsx b/www/js/onboarding/StudySummary.tsx index 9913c6d81..7ecb14fd5 100644 --- a/www/js/onboarding/StudySummary.tsx +++ b/www/js/onboarding/StudySummary.tsx @@ -14,7 +14,10 @@ const StudySummary = () => { const { i18n } = useTranslation(); const appConfig = useAppConfig(); - const templateText = useMemo(() => getTemplateText(appConfig, i18n.language), [appConfig]); + const templateText = useMemo( + () => getTemplateText(appConfig, i18n.resolvedLanguage), + [appConfig], + ); return ( <> diff --git a/www/js/splash/notifScheduler.ts b/www/js/splash/notifScheduler.ts index b6bc21778..a52e69f9e 100644 --- a/www/js/splash/notifScheduler.ts +++ b/www/js/splash/notifScheduler.ts @@ -125,7 +125,7 @@ function getNotifs() { function scheduleNotifs(scheme, notifTimes: DateTime[], setIsScheduling: Function) { return new Promise((rs) => { setIsScheduling(true); - const localeCode = i18next.language; + const localeCode = i18next.resolvedLanguage || 'en'; const nots = notifTimes.map((n) => { const nDate = n.toJSDate(); const seconds = nDate.getTime() / 1000; // the id must be in seconds, otherwise the sorting won't work diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts index a2a319ef5..7492b35b8 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -14,7 +14,7 @@ function storeDeviceSettings() { .getVersionNumber() .then((appver) => { const updateJSON = { - phone_lang: i18next.language, + phone_lang: i18next.resolvedLanguage, curr_platform: window['cordova'].platformId, manufacturer: window['device'].manufacturer, client_os_version: window['device'].version, diff --git a/www/js/survey/enketo/AddNoteButton.tsx b/www/js/survey/enketo/AddNoteButton.tsx index 93b3103b4..8f2b11726 100644 --- a/www/js/survey/enketo/AddNoteButton.tsx +++ b/www/js/survey/enketo/AddNoteButton.tsx @@ -28,7 +28,7 @@ const AddNoteButton = ({ timelineEntry, notesConfig, storeKey }: Props) => { useEffect(() => { let newLabel: string; - const localeCode = i18n.language; + const localeCode = i18n.resolvedLanguage || 'en'; if (notesConfig?.['filled-in-label'] && notesFor(timelineEntry)?.length) { newLabel = notesConfig?.['filled-in-label']?.[localeCode]; setDisplayLabel(newLabel); diff --git a/www/js/survey/enketo/enketoHelper.ts b/www/js/survey/enketo/enketoHelper.ts index 7bae2c06f..2df2d3b2d 100644 --- a/www/js/survey/enketo/enketoHelper.ts +++ b/www/js/survey/enketo/enketoHelper.ts @@ -61,7 +61,7 @@ const LABEL_FUNCTIONS = { const configSurveys = appConfig.survey_info.surveys; const config = configSurveys[name]; // config for this survey - const lang = i18next.language; + const lang = i18next.resolvedLanguage || 'en'; const labelTemplate = config.labelTemplate?.[lang]; if (!labelTemplate) return 'Answered'; // no template given in config diff --git a/www/js/survey/multilabel/confirmHelper.ts b/www/js/survey/multilabel/confirmHelper.ts index 085a82c13..58980f3c0 100644 --- a/www/js/survey/multilabel/confirmHelper.ts +++ b/www/js/survey/multilabel/confirmHelper.ts @@ -31,7 +31,7 @@ export async function getLabelOptions(appConfigParam?) { } /* fill in the translations to the 'text' fields of the labelOptions, according to the current language */ - const lang = i18next.language; + const lang = i18next.resolvedLanguage || 'en'; for (const opt in labelOptions) { labelOptions[opt]?.forEach?.((o, i) => { const translationKey = o.value; From 125d00926f36211e4140b2b94c5befc86a716005 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 12 Feb 2024 11:39:12 -0500 Subject: [PATCH 07/36] LabelTab: guard clause for pipelineRange, both start_ts and end_ts --- www/js/diary/LabelTab.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/www/js/diary/LabelTab.tsx b/www/js/diary/LabelTab.tsx index 8cbe7e24b..c740a9afc 100644 --- a/www/js/diary/LabelTab.tsx +++ b/www/js/diary/LabelTab.tsx @@ -186,7 +186,8 @@ const LabelTab = () => { async function loadAnotherWeek(when: 'past' | 'future') { try { logDebug('LabelTab: loadAnotherWeek into the ' + when); - if (!pipelineRange?.start_ts) return logWarn('No pipelineRange yet - early return'); + if (!pipelineRange?.start_ts || !pipelineRange?.end_ts) + return logWarn('No pipelineRange yet - early return'); const reachedPipelineStart = queriedRange?.start_ts && queriedRange.start_ts <= pipelineRange.start_ts; @@ -265,7 +266,8 @@ const LabelTab = () => { } async function fetchTripsInRange(startTs: number, endTs: number) { - if (!pipelineRange?.start_ts) return logWarn('No pipelineRange yet - early return'); + if (!pipelineRange?.start_ts || !pipelineRange?.end_ts) + return logWarn('No pipelineRange yet - early return'); logDebug('LabelTab: fetchTripsInRange from ' + startTs + ' to ' + endTs); const readCompositePromise = readAllCompositeTrips(startTs, endTs); let readUnprocessedPromise; From 2453e9a0313cb83b8c5e24e6494dd2d78cd681fa Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 12 Feb 2024 11:42:47 -0500 Subject: [PATCH 08/36] use Boolean() instead of !! https://github.com/e-mission/e-mission-phone/pull/1106#discussion_r1473849212 --- www/js/metrics/metricsHelper.ts | 2 +- www/js/onboarding/onboardingHelper.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/www/js/metrics/metricsHelper.ts b/www/js/metrics/metricsHelper.ts index 9dbc23c82..ca3846806 100644 --- a/www/js/metrics/metricsHelper.ts +++ b/www/js/metrics/metricsHelper.ts @@ -180,7 +180,7 @@ export function isCustomLabels(modeMap) { logDebug(`Checking metric keys ${distanceKeys}; sensed ${isSensedKeys}; custom ${isCustomKeys}`); const isAllCustomForMetric = isAllCustom(isSensedKeys, isCustomKeys); metricSummaryChecksSensed.push(!isAllCustomForMetric); - metricSummaryChecksCustom.push(!!isAllCustomForMetric); + metricSummaryChecksCustom.push(Boolean(isAllCustomForMetric)); logDebug(`overall custom/not results for each metric is ${JSON.stringify(metricSummaryChecksCustom)}`); return isAllCustom(metricSummaryChecksSensed, metricSummaryChecksCustom); diff --git a/www/js/onboarding/onboardingHelper.ts b/www/js/onboarding/onboardingHelper.ts index 72020382b..89e05d9e7 100644 --- a/www/js/onboarding/onboardingHelper.ts +++ b/www/js/onboarding/onboardingHelper.ts @@ -78,7 +78,7 @@ async function readConsented() { } export async function readIntroDone() { - return storageGet(INTRO_DONE_KEY).then((read_val) => !!read_val) as Promise; + return storageGet(INTRO_DONE_KEY).then((read_val) => Boolean(read_val)) as Promise; } export async function markIntroDone() { From c483322f6ffc06f73a84a17391876300a1408705 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 12 Feb 2024 14:07:00 -0500 Subject: [PATCH 09/36] use AppConfig type def on the useAppConfig hook and in inputMatcher --- www/js/survey/inputMatcher.ts | 3 ++- www/js/useAppConfig.ts | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/www/js/survey/inputMatcher.ts b/www/js/survey/inputMatcher.ts index ec0761387..da802d2e8 100644 --- a/www/js/survey/inputMatcher.ts +++ b/www/js/survey/inputMatcher.ts @@ -10,6 +10,7 @@ import { import { TimelineLabelMap, TimelineNotesMap } from '../diary/LabelTabContext'; import { MultilabelKey } from '../types/labelTypes'; import { EnketoUserInputEntry } from './enketo/enketoHelper'; +import { AppConfig } from '../types/appConfigTypes'; const EPOCH_MAXIMUM = 2 ** 31 - 1; @@ -259,7 +260,7 @@ export function getUniqueEntries(combinedList) { */ export function mapInputsToTimelineEntries( allEntries: TimelineEntry[], - appConfig, + appConfig: AppConfig, ): [TimelineLabelMap, TimelineNotesMap] { const timelineLabelMap: TimelineLabelMap = {}; const timelineNotesMap: TimelineNotesMap = {}; diff --git a/www/js/useAppConfig.ts b/www/js/useAppConfig.ts index 0c57b7b2c..8432c8ce1 100644 --- a/www/js/useAppConfig.ts +++ b/www/js/useAppConfig.ts @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { configChanged, getConfig, setConfigChanged } from './config/dynamicConfig'; import { logDebug } from './plugin/logger'; +import { AppConfig } from './types/appConfigTypes'; /* For Cordova, 'deviceready' means that Cordova plugins are loaded and ready to access. https://cordova.apache.org/docs/en/5.0.0/cordova/events/events.deviceready.html @@ -11,7 +12,7 @@ const deviceReady = new Promise((resolve) => { }); const useAppConfig = () => { - const [appConfig, setAppConfig] = useState(null); + const [appConfig, setAppConfig] = useState(null as any); useEffect(() => { deviceReady.then(updateConfig); @@ -23,7 +24,7 @@ const useAppConfig = () => { setAppConfig(config); } else { logDebug('Config was empty, treating as null'); - setAppConfig(null); + setAppConfig(null as any); } }); } From a4eb833ea5603422991e8fa97595184f1e6d5559 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 12 Feb 2024 14:08:36 -0500 Subject: [PATCH 10/36] remove 'ch' unit from MetricsDateSelect Although the 'ch' unit works here, is not officially a valid type for React Native Web Changing to `25` to match the Label screen DateSelect --- www/js/metrics/MetricsDateSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/metrics/MetricsDateSelect.tsx b/www/js/metrics/MetricsDateSelect.tsx index 014b5a00c..92cfbe1cc 100644 --- a/www/js/metrics/MetricsDateSelect.tsx +++ b/www/js/metrics/MetricsDateSelect.tsx @@ -88,7 +88,7 @@ const MetricsDateSelect = ({ dateRange, setDateRange }: Props) => { export const s = StyleSheet.create({ divider: { - width: '3ch', + width: 25, marginHorizontal: 'auto', }, }); From 260f213f0cdf5c5d42462d4a311c51b93fb0fd51 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 9 Feb 2024 13:47:16 -0500 Subject: [PATCH 11/36] bump 'react-native-paper' to ^5.11.0 The `Icon` component was added in 5.11.0 and is referenced in CustomLabelSettingRow. So we must use 5.11.0 at minimum --- package.cordovabuild.json | 2 +- package.serve.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.cordovabuild.json b/package.cordovabuild.json index 0fc1e31b2..77da54bdb 100644 --- a/package.cordovabuild.json +++ b/package.cordovabuild.json @@ -150,7 +150,7 @@ "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.*", "react-i18next": "^13.5.0", - "react-native-paper": "^5.8.0", + "react-native-paper": "^5.11.0", "react-native-paper-dates": "^0.18.12", "react-native-safe-area-context": "^4.6.3", "react-native-screens": "^3.22.0", diff --git a/package.serve.json b/package.serve.json index 64cc65ca2..dceeb2267 100644 --- a/package.serve.json +++ b/package.serve.json @@ -80,7 +80,7 @@ "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.*", "react-i18next": "^13.5.0", - "react-native-paper": "^5.8.0", + "react-native-paper": "^5.11.0", "react-native-paper-dates": "^0.18.12", "react-native-safe-area-context": "^4.6.3", "react-native-screens": "^3.22.0", From 1ed9e57feb914bf8aa7c892eb8d360de1ac7f846 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 13 Feb 2024 01:27:17 -0500 Subject: [PATCH 12/36] use the new Icon component from RN Paper 5.11 Reach Native Paper didn't have a standalone component until recently. Now that it does, we can get rid of the component we had made and just use theirs. --- www/js/components/DiaryButton.tsx | 12 +++---- www/js/components/Icon.tsx | 31 ------------------- www/js/components/NavBarButton.tsx | 12 ++----- www/js/diary/cards/ModesIndicator.tsx | 7 ++--- www/js/diary/components/StartEndLocations.tsx | 7 ++--- .../diary/details/OverallTripDescriptives.tsx | 5 ++- .../details/TripSectionsDescriptives.tsx | 27 +++++++++------- www/js/diary/list/TimelineScrollList.tsx | 7 ++--- www/js/onboarding/WelcomePage.tsx | 4 +-- www/js/plugin/ErrorBoundary.tsx | 9 ++++-- www/js/survey/enketo/AddedNotesList.tsx | 7 ++--- 11 files changed, 46 insertions(+), 82 deletions(-) delete mode 100644 www/js/components/Icon.tsx diff --git a/www/js/components/DiaryButton.tsx b/www/js/components/DiaryButton.tsx index 6a04cb079..fef584398 100644 --- a/www/js/components/DiaryButton.tsx +++ b/www/js/components/DiaryButton.tsx @@ -1,8 +1,7 @@ import React from 'react'; import { StyleSheet } from 'react-native'; -import { Button, ButtonProps, useTheme } from 'react-native-paper'; +import { Button, ButtonProps, Icon, useTheme } from 'react-native-paper'; import color from 'color'; -import { Icon } from './Icon'; type Props = ButtonProps & { fillColor?: string; borderColor?: string }; const DiaryButton = ({ children, fillColor, borderColor, icon, ...rest }: Props) => { @@ -19,7 +18,7 @@ const DiaryButton = ({ children, fillColor, borderColor, icon, ...rest }: Props) contentStyle={s.buttonContent} {...rest}> <> - {icon && } + {icon && } {children} @@ -39,15 +38,14 @@ const s = StyleSheet.create({ height: 25, }, label: { + display: 'flex', + alignItems: 'center', marginHorizontal: 5, marginVertical: 0, fontSize: 13, fontWeight: '500', whiteSpace: 'nowrap', - }, - icon: { - marginRight: 4, - verticalAlign: 'middle', + gap: 4, }, }); diff --git a/www/js/components/Icon.tsx b/www/js/components/Icon.tsx deleted file mode 100644 index c680dacef..000000000 --- a/www/js/components/Icon.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* React Native Paper provides an IconButton component, but it doesn't provide a plain Icon. - We want a plain Icon that is 'presentational' - not seen as interactive to the user or screen readers, and - it should not have any extra padding or margins around it. */ -/* Using the underlying Icon from React Native Paper doesn't bundle correctly, so the easiest thing to do - for now is wrap an IconButton and remove its interactivity and padding. */ - -import React from 'react'; -import { StyleSheet } from 'react-native'; -import { IconButton } from 'react-native-paper'; -import { Props as IconButtonProps } from 'react-native-paper/lib/typescript/src/components/IconButton/IconButton'; - -export const Icon = ({ style, ...rest }: IconButtonProps) => { - return ( - - ); -}; - -const s = StyleSheet.create({ - icon: { - width: 'unset', - height: 'unset', - padding: 0, - margin: 0, - }, -}); diff --git a/www/js/components/NavBarButton.tsx b/www/js/components/NavBarButton.tsx index a86d305b8..8e8c8db0c 100644 --- a/www/js/components/NavBarButton.tsx +++ b/www/js/components/NavBarButton.tsx @@ -1,8 +1,7 @@ import React from 'react'; import { View, StyleSheet } from 'react-native'; import color from 'color'; -import { Button, useTheme } from 'react-native-paper'; -import { Icon } from './Icon'; +import { Button, Icon, useTheme } from 'react-native-paper'; const NavBarButton = ({ children, icon, onPressAction, ...otherProps }) => { const { colors } = useTheme(); @@ -22,13 +21,8 @@ const NavBarButton = ({ children, icon, onPressAction, ...otherProps }) => { {...otherProps}> {children} {icon && ( - - + + )} diff --git a/www/js/diary/cards/ModesIndicator.tsx b/www/js/diary/cards/ModesIndicator.tsx index 8b1a7fa65..bba65c107 100644 --- a/www/js/diary/cards/ModesIndicator.tsx +++ b/www/js/diary/cards/ModesIndicator.tsx @@ -4,8 +4,7 @@ import color from 'color'; import LabelTabContext from '../LabelTabContext'; import { logDebug } from '../../plugin/logger'; import { getBaseModeByValue } from '../diaryHelper'; -import { Icon } from '../../components/Icon'; -import { Text, useTheme } from 'react-native-paper'; +import { Text, Icon, useTheme } from 'react-native-paper'; import { useTranslation } from 'react-i18next'; const ModesIndicator = ({ trip, detectedModes }) => { @@ -24,7 +23,7 @@ const ModesIndicator = ({ trip, detectedModes }) => { logDebug(`TripCard: got baseMode = ${JSON.stringify(baseMode)}`); modeViews = ( - + { {t('diary.detected')} {detectedModes?.map?.((pct, i) => ( - + {/* show percents if there are more than one detected modes */} {detectedModes?.length > 1 && ( { {props.displayStartTime} )} - + {props.displayStartName} @@ -37,7 +36,7 @@ const StartEndLocations = (props: Props) => { {props.displayEndTime} )} - + {props.displayEndName} diff --git a/www/js/diary/details/OverallTripDescriptives.tsx b/www/js/diary/details/OverallTripDescriptives.tsx index 8030842df..33c00fcfc 100644 --- a/www/js/diary/details/OverallTripDescriptives.tsx +++ b/www/js/diary/details/OverallTripDescriptives.tsx @@ -1,8 +1,7 @@ import React from 'react'; import { View } from 'react-native'; -import { Text } from 'react-native-paper'; +import { Text, Icon } from 'react-native-paper'; import useDerivedProperties from '../useDerivedProperties'; -import { Icon } from '../../components/Icon'; import { useTranslation } from 'react-i18next'; const OverallTripDescriptives = ({ trip }) => { @@ -32,7 +31,7 @@ const OverallTripDescriptives = ({ trip }) => { {detectedModes?.map?.((pct, i) => ( - + {pct.pct}% ))} diff --git a/www/js/diary/details/TripSectionsDescriptives.tsx b/www/js/diary/details/TripSectionsDescriptives.tsx index 1e8488621..fdab61eb3 100644 --- a/www/js/diary/details/TripSectionsDescriptives.tsx +++ b/www/js/diary/details/TripSectionsDescriptives.tsx @@ -1,7 +1,6 @@ import React, { useContext } from 'react'; -import { View } from 'react-native'; -import { Text, useTheme } from 'react-native-paper'; -import { Icon } from '../../components/Icon'; +import { View, StyleSheet } from 'react-native'; +import { Icon, Text, useTheme } from 'react-native-paper'; import useDerivedProperties from '../useDerivedProperties'; import { getBaseModeByKey, getBaseModeByValue } from '../diaryHelper'; import LabelTabContext from '../LabelTabContext'; @@ -60,14 +59,9 @@ const TripSectionsDescriptives = ({ trip, showLabeledMode = false }) => { {`${section.distance} ${distanceSuffix}`} - + + + {showLabeledMode && labeledModeForTrip && ( {labeledModeForTrip.text} @@ -80,4 +74,15 @@ const TripSectionsDescriptives = ({ trip, showLabeledMode = false }) => { ); }; +const s = StyleSheet.create({ + modeIconContainer: (bgColor) => ({ + backgroundColor: bgColor, + height: 32, + width: 32, + borderRadius: 16, + justifyContent: 'center', + alignItems: 'center', + }), +}); + export default TripSectionsDescriptives; diff --git a/www/js/diary/list/TimelineScrollList.tsx b/www/js/diary/list/TimelineScrollList.tsx index 33d71d288..3cb651d56 100644 --- a/www/js/diary/list/TimelineScrollList.tsx +++ b/www/js/diary/list/TimelineScrollList.tsx @@ -4,10 +4,9 @@ import TripCard from '../cards/TripCard'; import PlaceCard from '../cards/PlaceCard'; import UntrackedTimeCard from '../cards/UntrackedTimeCard'; import { View } from 'react-native'; -import { ActivityIndicator, Banner, Text } from 'react-native-paper'; +import { ActivityIndicator, Banner, Icon, Text } from 'react-native-paper'; import LoadMoreButton from './LoadMoreButton'; import { useTranslation } from 'react-i18next'; -import { Icon } from '../../components/Icon'; function renderCard({ item: listEntry }) { if (listEntry.origin_key.includes('trip')) { @@ -59,9 +58,7 @@ const TimelineScrollList = ({ ); const noTravelBanner = ( - }> + }> {t('diary.no-travel')} {t('diary.no-travel-hint')} diff --git a/www/js/onboarding/WelcomePage.tsx b/www/js/onboarding/WelcomePage.tsx index d89a002fc..7ded3a208 100644 --- a/www/js/onboarding/WelcomePage.tsx +++ b/www/js/onboarding/WelcomePage.tsx @@ -13,6 +13,7 @@ import { Button, Dialog, Divider, + Icon, IconButton, Surface, Text, @@ -25,7 +26,6 @@ import { initByUser } from '../config/dynamicConfig'; import { AppContext } from '../App'; import { displayError, logDebug } from '../plugin/logger'; import { onboardingStyles } from './OnboardingStack'; -import { Icon } from '../components/Icon'; const WelcomePage = () => { const { t } = useTranslation(); @@ -235,7 +235,7 @@ const WelcomePageButton = ({ onPress, icon, children }) => { return ( - + {children} diff --git a/www/js/plugin/ErrorBoundary.tsx b/www/js/plugin/ErrorBoundary.tsx index 61fdf023e..45902b787 100644 --- a/www/js/plugin/ErrorBoundary.tsx +++ b/www/js/plugin/ErrorBoundary.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { displayError } from './logger'; -import { Icon } from '../components/Icon'; +import { View } from 'react-native'; +import { Icon } from 'react-native-paper'; class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean }> { constructor(props) { @@ -20,7 +21,11 @@ class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { has render() { if (this.state.hasError) { - return ; + return ( + + + + ); } return this.props.children; diff --git a/www/js/survey/enketo/AddedNotesList.tsx b/www/js/survey/enketo/AddedNotesList.tsx index 838300621..755798e24 100644 --- a/www/js/survey/enketo/AddedNotesList.tsx +++ b/www/js/survey/enketo/AddedNotesList.tsx @@ -5,10 +5,9 @@ import React, { useContext, useState } from 'react'; import { DateTime } from 'luxon'; import { Modal } from 'react-native'; -import { Text, Button, DataTable, Dialog } from 'react-native-paper'; +import { Text, Button, DataTable, Dialog, Icon } from 'react-native-paper'; import LabelTabContext from '../../diary/LabelTabContext'; import { getFormattedDateAbbr, isMultiDay } from '../../diary/diaryHelper'; -import { Icon } from '../../components/Icon'; import EnketoModal from './EnketoModal'; import { useTranslation } from 'react-i18next'; import { EnketoUserInputEntry } from './enketoHelper'; @@ -129,8 +128,8 @@ const AddedNotesList = ({ timelineEntry, additionEntries }: Props) => { confirmDeleteEntry(entry)} - style={[styles.cell, { flex: 1 }]}> - + style={[styles.cell, { flex: 1, justifyContent: 'center' }]}> + ); From a717560d7c6e38c831a855fc198f7b922ae87eee Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 13 Feb 2024 01:30:39 -0500 Subject: [PATCH 13/36] in useAppConfig, check config contents safely If `getConfig` resolves to null (meaning there is no stored config & we are on the Welcome page), then we should not call Object.keys on it. (Object.keys expects a non-null input) (There was causing an error in the console, but not a crash) --- www/js/useAppConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/useAppConfig.ts b/www/js/useAppConfig.ts index 8432c8ce1..baeb41358 100644 --- a/www/js/useAppConfig.ts +++ b/www/js/useAppConfig.ts @@ -20,7 +20,7 @@ const useAppConfig = () => { function updateConfig() { return getConfig().then((config) => { - if (Object.keys(config).length) { + if (config && Object.keys(config).length) { setAppConfig(config); } else { logDebug('Config was empty, treating as null'); From ed49146b6d5531b95ed68bf981623fd36d89322e Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 13 Feb 2024 01:32:56 -0500 Subject: [PATCH 14/36] AddedNotesList: adjust UI the textStyle of the datatable cell was not getting applied to the underlying Text element. With style applied on the Text component directly, the label is 12pt and bold --- www/js/survey/enketo/AddedNotesList.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/www/js/survey/enketo/AddedNotesList.tsx b/www/js/survey/enketo/AddedNotesList.tsx index 755798e24..78abf2320 100644 --- a/www/js/survey/enketo/AddedNotesList.tsx +++ b/www/js/survey/enketo/AddedNotesList.tsx @@ -115,9 +115,10 @@ const AddedNotesList = ({ timelineEntry, additionEntries }: Props) => { editEntry(entry)} - style={[styles.cell, { flex: 5, pointerEvents: 'auto' }]} - textStyle={{ fontSize: 12, fontWeight: 'bold' }}> - {entry.data.label} + style={[styles.cell, { flex: 5, pointerEvents: 'auto' }]}> + + {entry.data.label} + editEntry(entry)} From b076910fb1ed841a5a689f0163fc422b497393e1 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 13 Feb 2024 01:37:34 -0500 Subject: [PATCH 15/36] EnketoModal: allow appConfig to be initially null It is actually expected for appConfig to be initially null here, and we don't need to show an error message for it. EnketoModal is not rendered until it is needed; it hasn't called useAppConfig() in advance. So until useAppConfig() resolves, appConfig is null. Once it does resolve and appConfig is set, this useEffect runs again and initSurvey() gets called. --- www/js/survey/enketo/EnketoModal.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/www/js/survey/enketo/EnketoModal.tsx b/www/js/survey/enketo/EnketoModal.tsx index 54a10ed7d..e2e4b5baa 100644 --- a/www/js/survey/enketo/EnketoModal.tsx +++ b/www/js/survey/enketo/EnketoModal.tsx @@ -61,8 +61,7 @@ const EnketoModal = ({ surveyName, onResponseSaved, opts, ...rest }: Props) => { } useEffect(() => { - if (!rest.visible) return; - if (!appConfig) return displayErrorMsg('App config not loaded yet'); + if (!rest.visible || !appConfig) return; initSurvey(); }, [appConfig, rest.visible]); From 8101388435646677f82536a0ce7af069415e8fb4 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 13 Feb 2024 01:38:57 -0500 Subject: [PATCH 16/36] use theme colors for logout button We avoid hardcoding colors to make dynamic theming easier --- www/js/control/ProfileSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/control/ProfileSettings.tsx b/www/js/control/ProfileSettings.tsx index 7306dee31..a02d2f8e9 100644 --- a/www/js/control/ProfileSettings.tsx +++ b/www/js/control/ProfileSettings.tsx @@ -421,7 +421,7 @@ const ProfileSettings = () => { Date: Tue, 13 Feb 2024 01:42:09 -0500 Subject: [PATCH 17/36] fix OverallTripDescriptives not showing for multimodal unlabeled trips The initial state of modesShown should be 'labeled' only if the trip has a label; otherwise it should be 'detected' (since there will be no toggling) With the initial state being 'detected', if there are multiple modes then the OverallTripDescriptives will show --- www/js/diary/details/LabelDetailsScreen.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/js/diary/details/LabelDetailsScreen.tsx b/www/js/diary/details/LabelDetailsScreen.tsx index 17fcb0584..b580d2699 100644 --- a/www/js/diary/details/LabelDetailsScreen.tsx +++ b/www/js/diary/details/LabelDetailsScreen.tsx @@ -40,7 +40,10 @@ const LabelScreenDetails = ({ route, navigation }) => { const { displayDate, displayStartTime, displayEndTime } = useDerivedProperties(trip); const [tripStartDisplayName, tripEndDisplayName] = useAddressNames(trip); - const [modesShown, setModesShown] = useState<'labeled' | 'detected'>('labeled'); + const [modesShown, setModesShown] = useState<'labeled' | 'detected'>(() => + // if trip has a labeled mode, initial state shows that; otherwise, show detected modes + trip && labelFor(trip, 'MODE')?.value ? 'labeled' : 'detected', + ); const tripGeojson = trip && labelOptions && From 72608de427c2c0879e74f51612dd2b879832e9ae Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 13 Feb 2024 14:59:41 -0500 Subject: [PATCH 18/36] make a reusable component The declaration of the Appbar.Header is repeated across every screen in the UI. Let's unify all these declarations into one component called . We already had a component called . It makes sense to put both in the same file since they will always be used together. So the new filename will be NavBar.tsx and it will export both and . --- .../{NavBarButton.tsx => NavBar.tsx} | 24 +++++++++++++++---- www/js/control/LogPage.tsx | 8 +++---- www/js/control/ProfileSettings.tsx | 9 +++---- www/js/control/SensedPage.tsx | 8 +++---- www/js/diary/details/LabelDetailsScreen.tsx | 8 +++---- www/js/diary/list/DateSelect.tsx | 2 +- www/js/diary/list/FilterSelect.tsx | 2 +- www/js/diary/list/LabelListScreen.tsx | 8 +++---- www/js/metrics/MetricsDateSelect.tsx | 2 +- www/js/metrics/MetricsTab.tsx | 9 +++---- 10 files changed, 40 insertions(+), 40 deletions(-) rename www/js/components/{NavBarButton.tsx => NavBar.tsx} (71%) diff --git a/www/js/components/NavBarButton.tsx b/www/js/components/NavBar.tsx similarity index 71% rename from www/js/components/NavBarButton.tsx rename to www/js/components/NavBar.tsx index 8e8c8db0c..65096ebaa 100644 --- a/www/js/components/NavBarButton.tsx +++ b/www/js/components/NavBar.tsx @@ -1,9 +1,25 @@ import React from 'react'; import { View, StyleSheet } from 'react-native'; import color from 'color'; -import { Button, Icon, useTheme } from 'react-native-paper'; +import { Appbar, Button, Icon, useTheme } from 'react-native-paper'; -const NavBarButton = ({ children, icon, onPressAction, ...otherProps }) => { +const NavBar = ({ children }) => { + const { colors } = useTheme(); + return ( + + {children} + + ); +}; + +export default NavBar; + +// NavBarButton, a greyish button with outline, to be used inside a NavBar + +export const NavBarButton = ({ children, icon, onPressAction, ...otherProps }) => { const { colors } = useTheme(); const buttonColor = color(colors.onBackground).alpha(0.07).rgb().string(); const outlineColor = color(colors.onBackground).alpha(0.2).rgb().string(); @@ -30,7 +46,7 @@ const NavBarButton = ({ children, icon, onPressAction, ...otherProps }) => { ); }; -export const s = StyleSheet.create({ +const s = StyleSheet.create({ btn: { borderRadius: 10, marginLeft: 5, @@ -55,5 +71,3 @@ export const s = StyleSheet.create({ alignItems: 'center', }, }); - -export default NavBarButton; diff --git a/www/js/control/LogPage.tsx b/www/js/control/LogPage.tsx index e1af11fdf..4df218e80 100644 --- a/www/js/control/LogPage.tsx +++ b/www/js/control/LogPage.tsx @@ -7,6 +7,7 @@ import { DateTime } from 'luxon'; import AlertBar from './AlertBar'; import { sendEmail } from './emailService'; import { displayError, logDebug } from '../plugin/logger'; +import NavBar from '../components/NavBar'; type LoadStats = { currentStart: number; gotMaxIndex: boolean; reachedEnd: boolean }; @@ -116,17 +117,14 @@ const LogPage = ({ pageVis, setPageVis }) => { return ( setPageVis(false)}> - + { setPageVis(false); }} /> - + diff --git a/www/js/control/ProfileSettings.tsx b/www/js/control/ProfileSettings.tsx index a02d2f8e9..f6c55d3d5 100644 --- a/www/js/control/ProfileSettings.tsx +++ b/www/js/control/ProfileSettings.tsx @@ -50,6 +50,7 @@ import { } from '../splash/notifScheduler'; import { DateTime } from 'luxon'; import { AppConfig } from '../types/appConfigTypes'; +import NavBar from '../components/NavBar'; //any pure functions can go outside const ProfileSettings = () => { @@ -412,11 +413,7 @@ const ProfileSettings = () => { return ( <> - + { onPress={() => setLogoutVis(true)} right={() => } /> - + { const { t } = useTranslation(); @@ -51,13 +52,10 @@ const SensedPage = ({ pageVis, setPageVis }) => { return ( setPageVis(false)}> - + setPageVis(false)} /> - + diff --git a/www/js/diary/details/LabelDetailsScreen.tsx b/www/js/diary/details/LabelDetailsScreen.tsx index b580d2699..4985300cb 100644 --- a/www/js/diary/details/LabelDetailsScreen.tsx +++ b/www/js/diary/details/LabelDetailsScreen.tsx @@ -28,6 +28,7 @@ import OverallTripDescriptives from './OverallTripDescriptives'; import ToggleSwitch from '../../components/ToggleSwitch'; import useAppConfig from '../../useAppConfig'; import { CompositeTrip } from '../../types/diaryTypes'; +import NavBar from '../../components/NavBar'; const LabelScreenDetails = ({ route, navigation }) => { const { timelineMap, labelOptions, labelFor } = useContext(LabelTabContext); @@ -57,17 +58,14 @@ const LabelScreenDetails = ({ route, navigation }) => { const modal = ( - + { navigation.goBack(); }} /> - + { const { pipelineRange } = useContext(LabelTabContext); diff --git a/www/js/diary/list/FilterSelect.tsx b/www/js/diary/list/FilterSelect.tsx index 0018c1bc5..36410486b 100644 --- a/www/js/diary/list/FilterSelect.tsx +++ b/www/js/diary/list/FilterSelect.tsx @@ -10,8 +10,8 @@ import React, { useState, useMemo } from 'react'; import { Modal } from 'react-native'; import { useTranslation } from 'react-i18next'; -import NavBarButton from '../../components/NavBarButton'; import { RadioButton, Text, Dialog } from 'react-native-paper'; +import { NavBarButton } from '../../components/NavBar'; const FilterSelect = ({ filters, setFilters, numListDisplayed, numListTotal }) => { const { t } = useTranslation(); diff --git a/www/js/diary/list/LabelListScreen.tsx b/www/js/diary/list/LabelListScreen.tsx index f1c167e20..7dfcb676d 100644 --- a/www/js/diary/list/LabelListScreen.tsx +++ b/www/js/diary/list/LabelListScreen.tsx @@ -5,6 +5,7 @@ import DateSelect from './DateSelect'; import FilterSelect from './FilterSelect'; import TimelineScrollList from './TimelineScrollList'; import LabelTabContext from '../LabelTabContext'; +import NavBar from '../../components/NavBar'; const LabelListScreen = () => { const { @@ -23,10 +24,7 @@ const LabelListScreen = () => { return ( <> - + { accessibilityLabel="Refresh" style={{ marginLeft: 'auto' }} /> - + { return ( <> - + - + From 09a0408123b29aff4d54ce5538cf166c32fcea80 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 13 Feb 2024 15:04:41 -0500 Subject: [PATCH 19/36] aria: make NavBar + NavBarButton taller We had the NavBar height set at 46px to match the old AngularJS + Ionic UI. It was a bit cramped. The migration is done and we no longer have to worry about that, so what's the optimal height? Well for accessibility reasons, click zones of the action items in the navbar should ideally be at least 44x44. If the NavBarButtons are going to be 44px tall, the NavBar should be a bit taller than that. 56px looks comfortable. --- www/js/components/NavBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/js/components/NavBar.tsx b/www/js/components/NavBar.tsx index 65096ebaa..8d00f2d24 100644 --- a/www/js/components/NavBar.tsx +++ b/www/js/components/NavBar.tsx @@ -9,7 +9,7 @@ const NavBar = ({ children }) => { + style={{ height: 56, backgroundColor: colors.surface }}> {children} ); @@ -30,7 +30,7 @@ export const NavBarButton = ({ children, icon, onPressAction, ...otherProps }) = mode="outlined" buttonColor={buttonColor} textColor={colors.onBackground} - contentStyle={{ flexDirection: 'row', height: 36 }} + contentStyle={{ height: 44, flexDirection: 'row' }} style={[s.btn, { borderColor: outlineColor }]} labelStyle={s.label} onPress={() => onPressAction()} From d647f3356bb2f8ee6cfd3c3b2b0bc0a9354f077a Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Thu, 15 Feb 2024 14:13:33 -0500 Subject: [PATCH 20/36] add AlertManager for global alerts There's a bunch of places in the app where we need to show a SnackBar or "toast"-style message. But to do this, we've been having to keep each SnackBar's visibility as state so it can show/hide depeding on its "visible" value. It would be simpler to be able to do this from anywhere in the codebase, though, not just by setting local state. So I refactored AlertBar to behave as a global stack of alert messages. AlertBar is instantiated once at the top level in App.tsx. A static class called AlertManager can be used to add messages to this stack from anywhere in the codebase. As a result, components no longer need to have their own instance of AlertBar and they don't have to control the visibility themselves. They just call AlertManager.addMessage when they want to show a message. --- www/js/App.tsx | 2 + www/js/appstatus/PermissionsControls.tsx | 20 +++---- www/js/control/AlertBar.tsx | 76 ++++++++++++++---------- www/js/control/ControlSyncHelper.tsx | 10 +--- www/js/control/LogPage.tsx | 23 +------ www/js/control/PopOpCode.tsx | 54 +++++++---------- www/js/control/ProfileSettings.tsx | 20 ++----- 7 files changed, 90 insertions(+), 115 deletions(-) diff --git a/www/js/App.tsx b/www/js/App.tsx index 900ae2a9e..40b7d9055 100644 --- a/www/js/App.tsx +++ b/www/js/App.tsx @@ -19,6 +19,7 @@ import { initStoreDeviceSettings } from './splash/storeDeviceSettings'; import { initRemoteNotifyHandler } from './splash/remoteNotifyHandler'; import { withErrorBoundary } from './plugin/ErrorBoundary'; import { initCustomDatasetHelper } from './metrics/customMetricsHelper'; +import AlertBar from './control/AlertBar'; const defaultRoutes = (t) => [ { @@ -131,6 +132,7 @@ const App = () => { )} + ); }; diff --git a/www/js/appstatus/PermissionsControls.tsx b/www/js/appstatus/PermissionsControls.tsx index 39d5386d3..f126837db 100644 --- a/www/js/appstatus/PermissionsControls.tsx +++ b/www/js/appstatus/PermissionsControls.tsx @@ -1,20 +1,26 @@ //component to view and manage permission settings -import React, { useContext, useState } from 'react'; +import React, { useContext, useEffect, 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 { refreshAllChecks } from '../usePermissionStatus'; import ExplainPermissions from './ExplainPermissions'; -import AlertBar from '../control/AlertBar'; +import { AlertManager } from '../control/AlertBar'; import { AppContext } from '../App'; const PermissionsControls = ({ onAccept }) => { const { t } = useTranslation(); const [explainVis, setExplainVis] = useState(false); const { permissionStatus } = useContext(AppContext); - const { checkList, overallStatus, error, errorVis, setErrorVis, explanationList } = - permissionStatus; + const { checkList, overallStatus, error, explanationList } = permissionStatus; + + useEffect(() => { + if (!error) return; + AlertManager.addMessage({ + text: error, + }); + }, [error]); return ( <> @@ -36,12 +42,6 @@ const PermissionsControls = ({ onAccept }) => { {t('control.button-accept')} - - ); }; diff --git a/www/js/control/AlertBar.tsx b/www/js/control/AlertBar.tsx index c132f4104..6bdd8d157 100644 --- a/www/js/control/AlertBar.tsx +++ b/www/js/control/AlertBar.tsx @@ -1,41 +1,57 @@ -import React from 'react'; -import { Modal } from 'react-native'; +/* Provides a global context for alerts to show as SnackBars ('toasts') at the bottom of the screen. + Alerts can be added to the queue from anywhere by calling AlertManager.addMessage. */ + +import React, { useState, useEffect } from 'react'; import { Snackbar } from 'react-native-paper'; +import { Modal } from 'react-native'; import { useTranslation } from 'react-i18next'; -import { SafeAreaView } from 'react-native-safe-area-context'; +import { ParseKeys } from 'i18next'; -type Props = { - visible: boolean; - setVisible: any; - messageKey: any; - messageAddition?: string; +type AlertMessage = { + msgKey?: ParseKeys<'translation'>; + text?: string; + duration?: number; }; -const AlertBar = ({ visible, setVisible, messageKey, messageAddition }: Props) => { - const { t } = useTranslation(); - const onDismissSnackBar = () => setVisible(false); - let text = ''; - if (messageAddition) { - text = t(messageKey) + messageAddition; - } else { - text = t(messageKey); +// public static AlertManager that can add messages from a global context +export class AlertManager { + private static listener?: (msg: AlertMessage) => void; + static setListener(listener?: (msg: AlertMessage) => void) { + AlertManager.listener = listener; } + static addMessage(msg: AlertMessage) { + AlertManager.listener?.(msg); + } +} + +const AlertBar = () => { + const { t } = useTranslation(); + const [messages, setMessages] = useState([]); + const onDismissSnackBar = () => setMessages(messages.slice(1)); + + // on init, attach a listener to AlertManager so messages can be added from a global context + useEffect(() => { + AlertManager.setListener((msg) => { + setMessages([...messages, msg]); + }); + return () => AlertManager.setListener(undefined); + }, []); + if (!messages.length) return null; + const { msgKey, text } = messages[0]; + const alertText = [msgKey && t(msgKey), text].filter((x) => x).join(' '); return ( - setVisible(false)} transparent={true}> - - { - onDismissSnackBar(); - }, - }}> - {text} - - + + + {alertText} + ); }; diff --git a/www/js/control/ControlSyncHelper.tsx b/www/js/control/ControlSyncHelper.tsx index 4dc63f2de..da0623a4a 100644 --- a/www/js/control/ControlSyncHelper.tsx +++ b/www/js/control/ControlSyncHelper.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { settingStyles } from './ProfileSettings'; import ActionMenu from '../components/ActionMenu'; import SettingRow from './SettingRow'; -import AlertBar from './AlertBar'; +import { AlertManager } from './AlertBar'; import { addStatEvent, statKeys } from '../plugin/clientStats'; import { updateUser } from '../services/commHelper'; import { displayError, logDebug, logWarn } from '../plugin/logger'; @@ -39,7 +39,6 @@ export const ForceSyncRow = ({ getState }) => { const { colors } = useTheme(); const [dataPendingVis, setDataPendingVis] = useState(false); - const [dataPushedVis, setDataPushedVis] = useState(false); async function forceSync() { try { @@ -69,7 +68,7 @@ export const ForceSyncRow = ({ getState }) => { logDebug('data is pending, showing confirm dialog'); setDataPendingVis(true); //consent handling in modal } else { - setDataPushedVis(true); + AlertManager.addMessage({ text: 'all data pushed!' }); } } catch (error) { displayError(error, 'Error while forcing sync'); @@ -156,11 +155,6 @@ export const ForceSyncRow = ({ getState }) => { - - ); }; diff --git a/www/js/control/LogPage.tsx b/www/js/control/LogPage.tsx index 4df218e80..c918ce7f9 100644 --- a/www/js/control/LogPage.tsx +++ b/www/js/control/LogPage.tsx @@ -4,7 +4,7 @@ import { useTheme, Text, Appbar, IconButton } from 'react-native-paper'; import { useTranslation } from 'react-i18next'; import { FlashList } from '@shopify/flash-list'; import { DateTime } from 'luxon'; -import AlertBar from './AlertBar'; +import { AlertManager } from './AlertBar'; import { sendEmail } from './emailService'; import { displayError, logDebug } from '../plugin/logger'; import NavBar from '../components/NavBar'; @@ -17,10 +17,6 @@ const LogPage = ({ pageVis, setPageVis }) => { const [loadStats, setLoadStats] = useState(); const [entries, setEntries] = useState([]); - const [maxErrorVis, setMaxErrorVis] = useState(false); - const [logErrorVis, setLogErrorVis] = useState(false); - const [maxMessage, setMaxMessage] = useState(''); - const [logMessage, setLogMessage] = useState(''); const [isFetching, setIsFetching] = useState(false); const RETRIEVE_COUNT = 100; @@ -43,8 +39,7 @@ const LogPage = ({ pageVis, setPageVis }) => { } catch (error) { let errorString = t('errors.while-max-index') + JSON.stringify(error, null, 2); displayError(error, errorString); - setMaxMessage(errorString); - setMaxErrorVis(true); + AlertManager.addMessage({ text: errorString }); } finally { addEntries(); } @@ -74,8 +69,7 @@ const LogPage = ({ pageVis, setPageVis }) => { } catch (error) { let errStr = t('errors.while-log-messages') + JSON.stringify(error, null, 2); displayError(error, errStr); - setLogMessage(errStr); - setLogErrorVis(true); + AlertManager.addMessage({ text: errStr }); setIsFetching(false); } } @@ -153,17 +147,6 @@ const LogPage = ({ pageVis, setPageVis }) => { }} /> - - - ); }; diff --git a/www/js/control/PopOpCode.tsx b/www/js/control/PopOpCode.tsx index 6b6220eec..4537b1edf 100644 --- a/www/js/control/PopOpCode.tsx +++ b/www/js/control/PopOpCode.tsx @@ -3,7 +3,7 @@ import { Modal, StyleSheet } from 'react-native'; import { Button, Text, IconButton, Dialog, useTheme } from 'react-native-paper'; import { useTranslation } from 'react-i18next'; import QrCode from '../components/QrCode'; -import AlertBar from './AlertBar'; +import { AlertManager } from './AlertBar'; import { settingStyles } from './ProfileSettings'; const PopOpCode = ({ visibilityValue, tokenURL, action, setVis }) => { @@ -13,11 +13,9 @@ const PopOpCode = ({ visibilityValue, tokenURL, action, setVis }) => { const opcodeList = tokenURL.split('='); const opcode = opcodeList[opcodeList.length - 1]; - const [copyAlertVis, setCopyAlertVis] = useState(false); - function copyText(textToCopy) { navigator.clipboard.writeText(textToCopy).then(() => { - setCopyAlertVis(true); + AlertManager.addMessage({ msgKey: 'Copied to clipboard!' }); }); } @@ -28,7 +26,6 @@ const PopOpCode = ({ visibilityValue, tokenURL, action, setVis }) => { icon="content-copy" onPress={() => { copyText(opcode); - setCopyAlertVis(true); }} style={styles.button} /> @@ -36,33 +33,26 @@ const PopOpCode = ({ visibilityValue, tokenURL, action, setVis }) => { } return ( - <> - setVis(false)} transparent={true}> - setVis(false)} - style={settingStyles.dialog(colors.elevation.level3)}> - {t('general-settings.qrcode')} - - {t('general-settings.qrcode-share-title')} - - {opcode} - - - action()} style={styles.button} /> - {copyButton} - - - - - - - + setVis(false)} transparent={true}> + setVis(false)} + style={settingStyles.dialog(colors.elevation.level3)}> + {t('general-settings.qrcode')} + + {t('general-settings.qrcode-share-title')} + + {opcode} + + + action()} style={styles.button} /> + {copyButton} + + + + ); }; const styles = StyleSheet.create({ diff --git a/www/js/control/ProfileSettings.tsx b/www/js/control/ProfileSettings.tsx index f6c55d3d5..f0d4d3298 100644 --- a/www/js/control/ProfileSettings.tsx +++ b/www/js/control/ProfileSettings.tsx @@ -18,7 +18,7 @@ import DemographicsSettingRow from './DemographicsSettingRow'; import PopOpCode from './PopOpCode'; import ReminderTime from './ReminderTime'; import useAppConfig from '../useAppConfig'; -import AlertBar from './AlertBar'; +import { AlertManager } from './AlertBar'; import DataDatePicker from './DataDatePicker'; import PrivacyPolicyModal from './PrivacyPolicyModal'; import { sendEmail } from './emailService'; @@ -68,9 +68,7 @@ const ProfileSettings = () => { const [nukeSetVis, setNukeVis] = useState(false); const [forceStateVis, setForceStateVis] = useState(false); const [logoutVis, setLogoutVis] = useState(false); - const [invalidateSuccessVis, setInvalidateSuccessVis] = useState(false); const [noConsentVis, setNoConsentVis] = useState(false); - const [noConsentMessageVis, setNoConsentMessageVis] = useState(false); const [consentVis, setConsentVis] = useState(false); const [dateDumpVis, setDateDumpVis] = useState(false); const [privacyVis, setPrivacyVis] = useState(false); @@ -347,8 +345,10 @@ const ProfileSettings = () => { window['cordova'].plugins.BEMUserCache.invalidateAllCache().then( (result) => { logDebug('invalidateCache: result = ' + JSON.stringify(result)); - setCacheResult(result); - setInvalidateSuccessVis(true); + AlertManager.addMessage({ + msgKey: 'success -> ', + text: result, + }); }, (error) => { displayError(error, 'while invalidating cache, error->'); @@ -663,16 +663,6 @@ const ProfileSettings = () => { new Date(appConfig?.intro?.start_year, appConfig?.intro?.start_month - 1, 1) }> - - - From 76f4d5d8f707a91698eed418559aae0b3bece803 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Thu, 15 Feb 2024 14:14:05 -0500 Subject: [PATCH 21/36] remove weird comma This comma didn't seem to have any adverse effects, but it doesn't belong here --- www/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/index.js b/www/index.js index f81e710fc..98ea54086 100644 --- a/www/index.js +++ b/www/index.js @@ -45,6 +45,6 @@ deviceReady.then(() => { - , + ); }); From c6a97777cde452d3ad7def9fd2824c481b965755 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Thu, 15 Feb 2024 14:18:13 -0500 Subject: [PATCH 22/36] move AlertBar to /components Since AlertBar / AlertManager can now be used from anywhere in the codebase, they are not specific to the Profile screen and can be moved to a more general location --- www/js/App.tsx | 2 +- www/js/appstatus/PermissionsControls.tsx | 2 +- www/js/{control => components}/AlertBar.tsx | 0 www/js/control/ControlSyncHelper.tsx | 2 +- www/js/control/LogPage.tsx | 2 +- www/js/control/PopOpCode.tsx | 2 +- www/js/control/ProfileSettings.tsx | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename www/js/{control => components}/AlertBar.tsx (100%) diff --git a/www/js/App.tsx b/www/js/App.tsx index 40b7d9055..648b93d86 100644 --- a/www/js/App.tsx +++ b/www/js/App.tsx @@ -19,7 +19,7 @@ import { initStoreDeviceSettings } from './splash/storeDeviceSettings'; import { initRemoteNotifyHandler } from './splash/remoteNotifyHandler'; import { withErrorBoundary } from './plugin/ErrorBoundary'; import { initCustomDatasetHelper } from './metrics/customMetricsHelper'; -import AlertBar from './control/AlertBar'; +import AlertBar from './components/AlertBar'; const defaultRoutes = (t) => [ { diff --git a/www/js/appstatus/PermissionsControls.tsx b/www/js/appstatus/PermissionsControls.tsx index f126837db..0eb9fdd60 100644 --- a/www/js/appstatus/PermissionsControls.tsx +++ b/www/js/appstatus/PermissionsControls.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import PermissionItem from './PermissionItem'; import { refreshAllChecks } from '../usePermissionStatus'; import ExplainPermissions from './ExplainPermissions'; -import { AlertManager } from '../control/AlertBar'; +import { AlertManager } from '../components/AlertBar'; import { AppContext } from '../App'; const PermissionsControls = ({ onAccept }) => { diff --git a/www/js/control/AlertBar.tsx b/www/js/components/AlertBar.tsx similarity index 100% rename from www/js/control/AlertBar.tsx rename to www/js/components/AlertBar.tsx diff --git a/www/js/control/ControlSyncHelper.tsx b/www/js/control/ControlSyncHelper.tsx index da0623a4a..bdb565f61 100644 --- a/www/js/control/ControlSyncHelper.tsx +++ b/www/js/control/ControlSyncHelper.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { settingStyles } from './ProfileSettings'; import ActionMenu from '../components/ActionMenu'; import SettingRow from './SettingRow'; -import { AlertManager } from './AlertBar'; +import { AlertManager } from '../components/AlertBar'; import { addStatEvent, statKeys } from '../plugin/clientStats'; import { updateUser } from '../services/commHelper'; import { displayError, logDebug, logWarn } from '../plugin/logger'; diff --git a/www/js/control/LogPage.tsx b/www/js/control/LogPage.tsx index c918ce7f9..96ef290b3 100644 --- a/www/js/control/LogPage.tsx +++ b/www/js/control/LogPage.tsx @@ -4,7 +4,7 @@ import { useTheme, Text, Appbar, IconButton } from 'react-native-paper'; import { useTranslation } from 'react-i18next'; import { FlashList } from '@shopify/flash-list'; import { DateTime } from 'luxon'; -import { AlertManager } from './AlertBar'; +import { AlertManager } from '../components/AlertBar'; import { sendEmail } from './emailService'; import { displayError, logDebug } from '../plugin/logger'; import NavBar from '../components/NavBar'; diff --git a/www/js/control/PopOpCode.tsx b/www/js/control/PopOpCode.tsx index 4537b1edf..3687d513b 100644 --- a/www/js/control/PopOpCode.tsx +++ b/www/js/control/PopOpCode.tsx @@ -3,7 +3,7 @@ import { Modal, StyleSheet } from 'react-native'; import { Button, Text, IconButton, Dialog, useTheme } from 'react-native-paper'; import { useTranslation } from 'react-i18next'; import QrCode from '../components/QrCode'; -import { AlertManager } from './AlertBar'; +import { AlertManager } from '../components/AlertBar'; import { settingStyles } from './ProfileSettings'; const PopOpCode = ({ visibilityValue, tokenURL, action, setVis }) => { diff --git a/www/js/control/ProfileSettings.tsx b/www/js/control/ProfileSettings.tsx index f0d4d3298..cfa539115 100644 --- a/www/js/control/ProfileSettings.tsx +++ b/www/js/control/ProfileSettings.tsx @@ -18,7 +18,7 @@ import DemographicsSettingRow from './DemographicsSettingRow'; import PopOpCode from './PopOpCode'; import ReminderTime from './ReminderTime'; import useAppConfig from '../useAppConfig'; -import { AlertManager } from './AlertBar'; +import { AlertManager } from '../components/AlertBar'; import DataDatePicker from './DataDatePicker'; import PrivacyPolicyModal from './PrivacyPolicyModal'; import { sendEmail } from './emailService'; From 9592107cd805dbc438e825ed149cd00c32c7e412 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Thu, 15 Feb 2024 14:38:19 -0500 Subject: [PATCH 23/36] revert "remove weird comma" Apparently, Prettier actually does want a comma here because this is an argument to a function, and we have trailing commas enabled. It just looks strange because it's following a JSX element --- www/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/index.js b/www/index.js index 98ea54086..f81e710fc 100644 --- a/www/index.js +++ b/www/index.js @@ -45,6 +45,6 @@ deviceReady.then(() => { - + , ); }); From b573e062cfe36ac0be03ed0c1238dd8b78ad1c04 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 16 Feb 2024 11:46:15 -0500 Subject: [PATCH 24/36] make push notifications warning an AlertBar 'remote notifications are not supported in the simulator' - we expect this error in the Simulator, but don't want to completely silence it so that people don't expect it to work. An Alert works for this --- www/js/splash/pushNotifySettings.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index 76b7d899f..5613867fb 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -18,6 +18,7 @@ import { logDebug, displayError, logWarn } from '../plugin/logger'; import { publish, subscribe, EVENTS } from '../customEventHandler'; import { isConsented, readConsentState } from './startprefs'; import { readIntroDone } from '../onboarding/onboardingHelper'; +import { AlertManager } from '../components/AlertBar'; let push; @@ -115,7 +116,9 @@ function registerPush() { }) .catch((error) => { if (error.message.includes('remote notifications are not supported in the simulator')) { - logWarn(error.message); // this is to be expected in the Simulator, so no need to popup + AlertManager.addMessage({ + text: 'Error in registering push notifications: ' + error.message, + }); } else { displayError(error, 'Error in registering push notifications'); } From 524eb71d61c1faee96d29ea3d394b99a2af2d20f Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 16 Feb 2024 11:57:13 -0500 Subject: [PATCH 25/36] make Log Out button a NavBarButton; tweak NavBarButton On the other screens of the UI, we use a NavBarButton for clickable buttons with text. I realized the Log Out button is inconsistent with this, so I changed it to a NavBarButton. I tested this with a screen reader and it is still accessible (since it uses the Button component it is grouped as and recognized as 1 element with role 'button') Using a NavBarButton made the log out icon smaller than before, so while making this change I revised the NavBarButton props. I added an optional "iconSize" that can be used to override the default 20. I removed "onPressAction" because the Button component already has "onPress" which we can just use. I organized the styles for both NavBar and NavBarButton, and made them able to be extended on by props. --- www/js/components/NavBar.tsx | 38 +++++++++++++++++----------- www/js/control/ProfileSettings.tsx | 26 ++++--------------- www/js/diary/list/DateSelect.tsx | 2 +- www/js/diary/list/FilterSelect.tsx | 4 +-- www/js/metrics/MetricsDateSelect.tsx | 2 +- 5 files changed, 31 insertions(+), 41 deletions(-) diff --git a/www/js/components/NavBar.tsx b/www/js/components/NavBar.tsx index 8d00f2d24..291f0b9e9 100644 --- a/www/js/components/NavBar.tsx +++ b/www/js/components/NavBar.tsx @@ -1,15 +1,12 @@ import React from 'react'; import { View, StyleSheet } from 'react-native'; import color from 'color'; -import { Appbar, Button, Icon, useTheme } from 'react-native-paper'; +import { Appbar, Button, ButtonProps, Icon, useTheme } from 'react-native-paper'; const NavBar = ({ children }) => { const { colors } = useTheme(); return ( - + {children} ); @@ -19,7 +16,8 @@ export default NavBar; // NavBarButton, a greyish button with outline, to be used inside a NavBar -export const NavBarButton = ({ children, icon, onPressAction, ...otherProps }) => { +type Props = ButtonProps & { icon?: string; iconSize?: number }; +export const NavBarButton = ({ children, icon, iconSize, ...rest }: Props) => { const { colors } = useTheme(); const buttonColor = color(colors.onBackground).alpha(0.07).rgb().string(); const outlineColor = color(colors.onBackground).alpha(0.2).rgb().string(); @@ -30,15 +28,14 @@ export const NavBarButton = ({ children, icon, onPressAction, ...otherProps }) = mode="outlined" buttonColor={buttonColor} textColor={colors.onBackground} - contentStyle={{ height: 44, flexDirection: 'row' }} - style={[s.btn, { borderColor: outlineColor }]} - labelStyle={s.label} - onPress={() => onPressAction()} - {...otherProps}> + contentStyle={[s.btnContent, rest.contentStyle]} + style={[s.btn(outlineColor), rest.style]} + labelStyle={[s.btnLabel, rest.labelStyle]} + {...rest}> {children} {icon && ( - + )} @@ -47,11 +44,22 @@ export const NavBarButton = ({ children, icon, onPressAction, ...otherProps }) = }; const s = StyleSheet.create({ - btn: { + navBar: (backgroundColor) => ({ + backgroundColor, + height: 56, + paddingHorizontal: 8, + gap: 5, + }), + btn: (borderColor) => ({ + borderColor, borderRadius: 10, - marginLeft: 5, + }), + btnContent: { + height: 44, + flexDirection: 'row', + paddingHorizontal: 2, }, - label: { + btnLabel: { fontSize: 12.5, fontWeight: '400', height: '100%', diff --git a/www/js/control/ProfileSettings.tsx b/www/js/control/ProfileSettings.tsx index cfa539115..98fa6d7f9 100644 --- a/www/js/control/ProfileSettings.tsx +++ b/www/js/control/ProfileSettings.tsx @@ -1,15 +1,6 @@ import React, { useState, useEffect, useContext, useRef } from 'react'; import { Modal, StyleSheet, ScrollView } from 'react-native'; -import { - Dialog, - Button, - useTheme, - Text, - Appbar, - IconButton, - TextInput, - List, -} from 'react-native-paper'; +import { Dialog, Button, useTheme, Text, Appbar, TextInput } from 'react-native-paper'; import { useTranslation } from 'react-i18next'; import ExpansionSection from './ExpandMenu'; import SettingRow from './SettingRow'; @@ -50,7 +41,7 @@ import { } from '../splash/notifScheduler'; import { DateTime } from 'luxon'; import { AppConfig } from '../types/appConfigTypes'; -import NavBar from '../components/NavBar'; +import NavBar, { NavBarButton } from '../components/NavBar'; //any pure functions can go outside const ProfileSettings = () => { @@ -415,16 +406,9 @@ const ProfileSettings = () => { <> - setLogoutVis(true)} - right={() => } - /> + setLogoutVis(true)}> + {t('control.log-out')} + diff --git a/www/js/diary/list/DateSelect.tsx b/www/js/diary/list/DateSelect.tsx index 73ac34638..a4131c1f0 100644 --- a/www/js/diary/list/DateSelect.tsx +++ b/www/js/diary/list/DateSelect.tsx @@ -68,7 +68,7 @@ const DateSelect = ({ tsRange, loadSpecificWeekFn }) => { accessibilityLabel={ 'Date range: ' + (dateRange[0] ? dateRange[0] + ' to ' : '') + dateRangeEnd } - onPressAction={() => setOpen(true)}> + onPress={() => setOpen(true)}> {dateRange[0] && ( <> {dateRange[0]} diff --git a/www/js/diary/list/FilterSelect.tsx b/www/js/diary/list/FilterSelect.tsx index 36410486b..d971c98ab 100644 --- a/www/js/diary/list/FilterSelect.tsx +++ b/www/js/diary/list/FilterSelect.tsx @@ -46,9 +46,7 @@ const FilterSelect = ({ filters, setFilters, numListDisplayed, numListTotal }) = return ( <> - setModalVisible(true)}> + setModalVisible(true)}> {labelDisplayText} setModalVisible(false)}> diff --git a/www/js/metrics/MetricsDateSelect.tsx b/www/js/metrics/MetricsDateSelect.tsx index 381ef2170..07656ec25 100644 --- a/www/js/metrics/MetricsDateSelect.tsx +++ b/www/js/metrics/MetricsDateSelect.tsx @@ -59,7 +59,7 @@ const MetricsDateSelect = ({ dateRange, setDateRange }: Props) => { return ( <> - setOpen(true)}> + setOpen(true)}> {dateRange[0] && ( <> {dateRange[0].toLocaleString()} From 5f8bb7783b7235d229b3aef59c06123c58fd1ce2 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 19 Feb 2024 00:34:23 -0500 Subject: [PATCH 26/36] explanation comment in dynamicStyleSheet.d.ts --- www/js/types/dynamicStyleSheet.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/js/types/dynamicStyleSheet.d.ts b/www/js/types/dynamicStyleSheet.d.ts index 4fd7c05e3..68be967ef 100644 --- a/www/js/types/dynamicStyleSheet.d.ts +++ b/www/js/types/dynamicStyleSheet.d.ts @@ -1,3 +1,8 @@ +/* + Enables "dynamic" styles in StyleSheet.create. + Explanation: https://github.com/e-mission/e-mission-phone/pull/1106/files#r1475128446 +*/ + import 'react-native'; import { TextStyle, ViewStyle, ImageStyle } from 'react-native'; From f9c3c57ea48885d93b1885714d21c5b4de1f48c7 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 20 Feb 2024 11:55:28 -0500 Subject: [PATCH 27/36] AddedNotesList: safer guard clauses in deleteEntry https://github.com/e-mission/e-mission-phone/pull/1106/files#r1473736741 --- www/js/survey/enketo/AddedNotesList.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/www/js/survey/enketo/AddedNotesList.tsx b/www/js/survey/enketo/AddedNotesList.tsx index 78abf2320..91cea8536 100644 --- a/www/js/survey/enketo/AddedNotesList.tsx +++ b/www/js/survey/enketo/AddedNotesList.tsx @@ -11,7 +11,7 @@ import { getFormattedDateAbbr, isMultiDay } from '../../diary/diaryHelper'; import EnketoModal from './EnketoModal'; import { useTranslation } from 'react-i18next'; import { EnketoUserInputEntry } from './enketoHelper'; -import { logDebug } from '../../plugin/logger'; +import { displayErrorMsg, logDebug } from '../../plugin/logger'; type Props = { timelineEntry: any; @@ -59,10 +59,13 @@ const AddedNotesList = ({ timelineEntry, additionEntries }: Props) => { } function deleteEntry(entry?: EnketoUserInputEntry) { - if (!entry) return; + const dataKey = entry?.data?.key || entry?.metadata?.key; + const data = entry?.data; + + if (!dataKey || !data) { + return displayErrorMsg(`Error in deleteEntry, entry was: ${JSON.stringify(entry)}`); + } - const dataKey = entry.data.key || entry.metadata.key; - const data = entry.data; const index = additionEntries.indexOf(entry); data.status = 'DELETED'; @@ -71,7 +74,10 @@ const AddedNotesList = ({ timelineEntry, additionEntries }: Props) => { index = ${index}`); return window['cordova'].plugins.BEMUserCache.putMessage(dataKey, data).then(() => { - additionEntries.splice(index, 1); + // if entry was found in additionEntries, remove it + if (index > -1) { + additionEntries.splice(index, 1); + } setConfirmDeleteModalVisible(false); setEditingEntry(undefined); }); From 9178685b2c6dfdfb3ac8123a48b921b79268ad23 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 20 Feb 2024 12:10:05 -0500 Subject: [PATCH 28/36] aria: use 'touch' style for Enketo forms As it turns out, Enketo supports a more mobile-friendly forms interface than we've been using. I noticed the mobile-friendly interface whle using Kobotoolbox, and I wondered why it didn't show up the same in e-mission-phone. I dug into Enketo source code and found that it automatically detects mobile devices by inspecting the user agent and platform info. (e.g. if it contains "iPhone", then mobile=true). It applies "touch" class to the element to flag this. This is indeed observed in e-mission-phone, but because we only inject Enketo styles into the views they open in (i.e. we scope those styles under .enketo-plugin in styles), the element's "touch" class has no effect. An easy solution is to just add the "touch" class at the root of our enketo content (for our purposes, we can assume e-mission-phone will always be on a phone / tablet a since it's a mobile app. We don't really need the 'mobile detection' that Enketo does) The result of this is that the enketo forms have larger click areas on multiple-choice / multi-select questions, which is better for accessibility. --- www/js/survey/enketo/EnketoModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/survey/enketo/EnketoModal.tsx b/www/js/survey/enketo/EnketoModal.tsx index e2e4b5baa..c7e8bfc95 100644 --- a/www/js/survey/enketo/EnketoModal.tsx +++ b/www/js/survey/enketo/EnketoModal.tsx @@ -68,7 +68,7 @@ const EnketoModal = ({ surveyName, onResponseSaved, opts, ...rest }: Props) => { /* adapted from the template given by enketo-core: https://github.com/enketo/enketo-core/blob/master/src/index.html */ const enketoContent = ( -
+
{/* This form header (markup/css) can be changed in the application. Just make sure to keep a .form-language-selector element into which the form language selector (