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",
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/__tests__/inputMatcher.test.ts b/www/__tests__/inputMatcher.test.ts
index 5c0b248d9..062951b35 100644
--- a/www/__tests__/inputMatcher.test.ts
+++ b/www/__tests__/inputMatcher.test.ts
@@ -1,6 +1,7 @@
import { mockBEMUserCache } from '../__mocks__/cordovaMocks';
import { mockLogger } from '../__mocks__/globalMocks';
import { unprocessedLabels, updateLocalUnprocessedInputs } from '../js/diary/timelineHelper';
+import * as logger from '../js/plugin/logger';
import { EnketoUserInputEntry } from '../js/survey/enketo/enketoHelper';
import {
fmtTs,
@@ -148,11 +149,13 @@ describe('input-matcher', () => {
});
it('tests getNotDeletedCandidates called with 0 candidates', () => {
- jest.spyOn(console, 'log');
+ jest.spyOn(logger, 'logDebug');
const candidates = getNotDeletedCandidates([]);
// check if the log printed collectly with
- expect(console.log).toHaveBeenCalledWith('getNotDeletedCandidates called with 0 candidates');
+ expect(logger.logDebug).toHaveBeenCalledWith(
+ 'getNotDeletedCandidates called with 0 candidates',
+ );
expect(candidates).toStrictEqual([]);
});
diff --git a/www/css/style.css b/www/css/style.scss
similarity index 61%
rename from www/css/style.css
rename to www/css/style.scss
index 7d7707fdc..e2172a632 100644
--- a/www/css/style.css
+++ b/www/css/style.scss
@@ -1,3 +1,6 @@
+/* This file is in Sassy CSS (SCSS) because Enketo uses SCSS and we want to extend on their styles.
+ Webpack will just compile this to plain CSS anyway */
+
@import 'leaflet/dist/leaflet.css';
html {
@@ -7,6 +10,9 @@ html {
/* Scoped styles for Enketo */
/* if we don't contain them here, they will leak into the rest of the app */
.enketo-plugin {
+ // Enketo's default theme uses orange; we can override with our own blue
+ $primary: #0080b9; // matches 'primary' in appTheme.ts
+ $brand-primary-color: darken($primary, 5%); // make it a bit darker for legibility in Enketo UI
@import 'enketo-core/src/sass/formhub/formhub.scss';
flex: 1;
.question.non-select {
diff --git a/www/index.js b/www/index.js
index f81e710fc..cd4757f3f 100644
--- a/www/index.js
+++ b/www/index.js
@@ -2,7 +2,7 @@ import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider as PaperProvider } from 'react-native-paper';
-import './css/style.css';
+import './css/style.scss';
import 'chartjs-adapter-luxon';
import initializedI18next from './js/i18nextInit';
diff --git a/www/js/App.tsx b/www/js/App.tsx
index c3854dbf0..648b93d86 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 './components/AlertBar';
const defaultRoutes = (t) => [
{
@@ -95,8 +96,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
@@ -133,6 +132,7 @@ const App = () => {
)}
+
>
);
};
diff --git a/www/js/appTheme.ts b/www/js/appTheme.ts
index 615a48c7e..b66f493e6 100644
--- a/www/js/appTheme.ts
+++ b/www/js/appTheme.ts
@@ -18,6 +18,10 @@ const AppTheme = {
surfaceVariant: '#e0f0ff', // lch(94% 50 250) - background of DataTable
surfaceDisabled: '#c7e0f7', // lch(88% 15 250)
onSurfaceDisabled: '#3a4955', // lch(30% 10 250)
+ // "inverse" colors - used for SnackBars / AlertBars
+ inversePrimary: '#90ceff', // lch(80% 35 250) - SnackBar colored text
+ inverseSurface: '#2e3133', // lch(20% 2 250) - SnackBar background
+ inverseOnSurface: '#edf1f6', // lch(95% 3 250) - SnackBar text
elevation: {
level0: 'transparent',
level1: '#fafdff', // lch(99% 30 250)
@@ -85,7 +89,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 +98,4 @@ export const getTheme = (flavor?: keyof typeof flavorOverrides) => {
...{ ...typeStyle.colors, elevation: scopedElevation },
};
return { ...AppTheme, colors: scopedColors };
-};
+}
diff --git a/www/js/appstatus/PermissionsControls.tsx b/www/js/appstatus/PermissionsControls.tsx
index 39d5386d3..0eb9fdd60 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 '../components/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/components/AlertBar.tsx b/www/js/components/AlertBar.tsx
new file mode 100644
index 000000000..6bdd8d157
--- /dev/null
+++ b/www/js/components/AlertBar.tsx
@@ -0,0 +1,59 @@
+/* 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 { ParseKeys } from 'i18next';
+
+type AlertMessage = {
+ msgKey?: ParseKeys<'translation'>;
+ text?: string;
+ duration?: number;
+};
+
+// 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 (
+
+
+ {alertText}
+
+
+ );
+};
+
+export default AlertBar;
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/LeafletView.tsx b/www/js/components/LeafletView.tsx
index b5d6b8dbe..ce719272f 100644
--- a/www/js/components/LeafletView.tsx
+++ b/www/js/components/LeafletView.tsx
@@ -164,7 +164,7 @@ const LeafletView = ({ geojson, opts, downscaleTiles, cacheHtml, ...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 });
@@ -175,6 +175,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/NavBar.tsx b/www/js/components/NavBar.tsx
new file mode 100644
index 000000000..291f0b9e9
--- /dev/null
+++ b/www/js/components/NavBar.tsx
@@ -0,0 +1,81 @@
+import React from 'react';
+import { View, StyleSheet } from 'react-native';
+import color from 'color';
+import { Appbar, Button, ButtonProps, Icon, useTheme } from 'react-native-paper';
+
+const NavBar = ({ children }) => {
+ const { colors } = useTheme();
+ return (
+
+ {children}
+
+ );
+};
+
+export default NavBar;
+
+// NavBarButton, a greyish button with outline, to be used inside a NavBar
+
+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();
+
+ return (
+ <>
+
+ >
+ );
+};
+
+const s = StyleSheet.create({
+ navBar: (backgroundColor) => ({
+ backgroundColor,
+ height: 56,
+ paddingHorizontal: 8,
+ gap: 5,
+ }),
+ btn: (borderColor) => ({
+ borderColor,
+ borderRadius: 10,
+ }),
+ btnContent: {
+ height: 44,
+ flexDirection: 'row',
+ paddingHorizontal: 2,
+ },
+ btnLabel: {
+ fontSize: 12.5,
+ fontWeight: '400',
+ height: '100%',
+ marginHorizontal: 'auto',
+ marginVertical: 'auto',
+ display: 'flex',
+ },
+ icon: {
+ margin: 'auto',
+ width: 'auto',
+ height: 'auto',
+ },
+ textWrapper: {
+ lineHeight: '100%',
+ marginHorizontal: 5,
+ justifyContent: 'space-evenly',
+ alignItems: 'center',
+ },
+});
diff --git a/www/js/components/NavBarButton.tsx b/www/js/components/NavBarButton.tsx
deleted file mode 100644
index a86d305b8..000000000
--- a/www/js/components/NavBarButton.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-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';
-
-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();
-
- return (
- <>
-
- >
- );
-};
-
-export const s = StyleSheet.create({
- btn: {
- borderRadius: 10,
- marginLeft: 5,
- },
- label: {
- fontSize: 12.5,
- fontWeight: '400',
- height: '100%',
- marginHorizontal: 'auto',
- marginVertical: 'auto',
- display: 'flex',
- },
- icon: {
- margin: 'auto',
- width: 'auto',
- height: 'auto',
- },
- textWrapper: {
- lineHeight: '100%',
- marginHorizontal: 5,
- justifyContent: 'space-evenly',
- alignItems: 'center',
- },
-});
-
-export default NavBarButton;
diff --git a/www/js/components/QrCode.tsx b/www/js/components/QrCode.tsx
index 7e16ac28e..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*/
@@ -21,19 +21,21 @@ 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!
window['plugins'].socialsharing.shareWithOptions(
prepopulateQRMessage,
- function (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)
+ (result) => {
+ // 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}`);
},
- function (msg) {
- console.log('Sharing failed with message: ' + msg);
+ (msg) => {
+ logWarn('socialsharing: failed with message: ' + msg);
},
);
};
diff --git a/www/js/components/charting.ts b/www/js/components/charting.ts
index 6d0f75ed5..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;
@@ -170,7 +162,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 +179,4 @@ export const dedupColors = (colors: string[][]) => {
}
}
return dedupedColors;
-};
+}
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/config/useImperialConfig.ts b/www/js/config/useImperialConfig.ts
index d55fdffb0..aa87ed1c6 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);
-};
+ return Intl.NumberFormat(i18next.resolvedLanguage, 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/AlertBar.tsx b/www/js/control/AlertBar.tsx
deleted file mode 100644
index c132f4104..000000000
--- a/www/js/control/AlertBar.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react';
-import { Modal } from 'react-native';
-import { Snackbar } from 'react-native-paper';
-import { useTranslation } from 'react-i18next';
-import { SafeAreaView } from 'react-native-safe-area-context';
-
-type Props = {
- visible: boolean;
- setVisible: any;
- messageKey: any;
- messageAddition?: string;
-};
-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);
- }
-
- return (
- setVisible(false)} transparent={true}>
-
- {
- onDismissSnackBar();
- },
- }}>
- {text}
-
-
-
- );
-};
-
-export default AlertBar;
diff --git a/www/js/control/ControlCollectionHelper.tsx b/www/js/control/ControlCollectionHelper.tsx
index 4c6a21eaa..9ee0efc6c 100644
--- a/www/js/control/ControlCollectionHelper.tsx
+++ b/www/js/control/ControlCollectionHelper.tsx
@@ -4,9 +4,9 @@ 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 = {
+type CollectionConfig = {
is_duty_cycling: boolean;
simulate_user_interaction: boolean;
accuracy: number;
@@ -27,14 +27,14 @@ 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');
}
}
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,8 +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);
- console.log('window platform is', window['cordova'].platformId);
+ const v = await accuracy2String(config);
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');
}
@@ -92,9 +91,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 +108,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,12 +122,12 @@ const formatConfigForDisplay = function (config, accuracyOptions) {
}
}
return retVal;
-};
+}
const ControlCollectionHelper = ({ editVis, setEditVis }) => {
const { colors } = useTheme();
- const [localConfig, setLocalConfig] = useState();
+ const [localConfig, setLocalConfig] = useState();
const [accuracyActions, setAccuracyActions] = useState([]);
const [accuracyVis, setAccuracyVis] = useState(false);
@@ -158,20 +145,20 @@ const ControlCollectionHelper = ({ editVis, setEditVis }) => {
getCollectionSettings();
}, [editVis]);
- const formatAccuracyForActions = function (accuracyOptions) {
+ 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;
- };
+ }
/*
* Functions to edit and save values
*/
async function saveAndReload() {
- console.log('new config = ', localConfig);
+ logDebug('new config = ' + JSON.stringify(localConfig));
try {
let set = await setConfig(localConfig);
setEditVis(false);
@@ -180,23 +167,23 @@ const ControlCollectionHelper = ({ editVis, setEditVis }) => {
}
}
- const onToggle = function (config_key) {
- let tempConfig = { ...localConfig } as collectionConfig;
- tempConfig[config_key] = !(localConfig as collectionConfig)[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) {
- let tempConfig = { ...localConfig } as collectionConfig;
+ function onChooseAccuracy(accuracyOption) {
+ let tempConfig = { ...localConfig } as CollectionConfig;
tempConfig.accuracy = accuracyOption.value;
setLocalConfig(tempConfig);
- };
+ }
- const onChangeText = function (newText, config_key) {
- let tempConfig = { ...localConfig } as collectionConfig;
+ 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/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 e46c38af6..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 AlertBar 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';
@@ -14,32 +14,24 @@ 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();
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 }) => {
@@ -47,20 +39,17 @@ export const ForceSyncRow = ({ getState }) => {
const { colors } = useTheme();
const [dataPendingVis, setDataPendingVis] = useState(false);
- const [dataPushedVis, setDataPushedVis] = useState(false);
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
* 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,
@@ -68,9 +57,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},
@@ -81,39 +68,39 @@ 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');
}
}
- 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 = {};
+ const entry_data = {};
const curr_state = await getState();
entry_data['curr_state'] = curr_state;
if (transKey == getEndTransitionKey()) {
@@ -127,7 +114,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());
@@ -168,11 +155,6 @@ export const ForceSyncRow = ({ getState }) => {
-
-
>
);
};
@@ -182,7 +164,7 @@ const ControlSyncHelper = ({ editVis, setEditVis }) => {
const { t } = useTranslation();
const { colors } = useTheme();
- const [localConfig, setLocalConfig] = useState();
+ const [localConfig, setLocalConfig] = useState();
const [intervalVis, setIntervalVis] = useState(false);
/*
@@ -208,7 +190,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!!
@@ -219,24 +201,23 @@ 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) {
- console.log('error with setting sync config', err);
displayError(err, 'Error while setting sync config');
}
}
- const onChooseInterval = function (interval) {
- let tempConfig = { ...localConfig } as syncConfig;
+ function onChooseInterval(interval) {
+ let tempConfig = { ...localConfig } as SyncConfig;
tempConfig.sync_interval = interval.value;
setLocalConfig(tempConfig);
- };
+ }
- const onTogglePush = function () {
- let tempConfig = { ...localConfig } as syncConfig;
- tempConfig.ios_use_remote_push = !(localConfig as syncConfig).ios_use_remote_push;
+ 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/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 { 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);
- const [maxMessage, setMaxMessage] = useState('');
- 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(() => {
@@ -31,8 +29,8 @@ const LogPage = ({ pageVis, setPageVis }) => {
async function refreshEntries() {
try {
let maxIndex = await window['Logger'].getMaxIndex();
- console.log('maxIndex = ' + maxIndex);
- let tempStats = {} as loadStats;
+ logDebug('Logger maxIndex = ' + maxIndex);
+ let tempStats = {} as LoadStats;
tempStats.currentStart = maxIndex;
tempStats.gotMaxIndex = true;
tempStats.reachedEnd = false;
@@ -40,9 +38,8 @@ const LogPage = ({ pageVis, setPageVis }) => {
setEntries([]);
} catch (error) {
let errorString = t('errors.while-max-index') + JSON.stringify(error, null, 2);
- console.log(errorString);
- setMaxMessage(errorString);
- setMaxErrorVis(true);
+ displayError(error, errorString);
+ AlertManager.addMessage({ text: errorString });
} finally {
addEntries();
}
@@ -52,54 +49,52 @@ 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');
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);
- setLogMessage(errStr);
- setLogErrorVis(true);
+ displayError(error, errStr);
+ AlertManager.addMessage({ text: errStr });
setIsFetching(false);
}
}
- const processEntries = function (entryList) {
+ 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);
});
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);
- };
+ }
- const emailLog = function () {
+ function emailLog() {
sendEmail('loggerDB');
- };
+ }
const separator = () => ;
const logItem = ({ item: logItem }) => (
@@ -116,17 +111,14 @@ const LogPage = ({ pageVis, setPageVis }) => {
return (
setPageVis(false)}>
-
+ {
setPageVis(false);
}}
/>
-
+
@@ -155,17 +147,6 @@ const LogPage = ({ pageVis, setPageVis }) => {
}}
/>
-
-
-
);
};
diff --git a/www/js/control/PopOpCode.tsx b/www/js/control/PopOpCode.tsx
index 451294867..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 AlertBar from './AlertBar';
+import { AlertManager } from '../components/AlertBar';
import { settingStyles } from './ProfileSettings';
const PopOpCode = ({ visibilityValue, tokenURL, action, setVis }) => {
@@ -13,13 +13,11 @@ const PopOpCode = ({ visibilityValue, tokenURL, action, setVis }) => {
const opcodeList = tokenURL.split('=');
const opcode = opcodeList[opcodeList.length - 1];
- const [copyAlertVis, setCopyAlertVis] = useState(false);
-
- const copyText = function (textToCopy) {
+ function copyText(textToCopy) {
navigator.clipboard.writeText(textToCopy).then(() => {
- setCopyAlertVis(true);
+ AlertManager.addMessage({ msgKey: 'Copied to clipboard!' });
});
- };
+ }
let copyButton;
if (window['cordova'].platformId == 'ios') {
@@ -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)} transparent={true}>
+
+
);
};
const styles = StyleSheet.create({
diff --git a/www/js/control/ProfileSettings.tsx b/www/js/control/ProfileSettings.tsx
index 9a639dd20..c6ca8b848 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';
@@ -18,7 +9,7 @@ import DemographicsSettingRow from './DemographicsSettingRow';
import PopOpCode from './PopOpCode';
import ReminderTime from './ReminderTime';
import useAppConfig from '../useAppConfig';
-import AlertBar from './AlertBar';
+import { AlertManager } from '../components/AlertBar';
import DataDatePicker from './DataDatePicker';
import PrivacyPolicyModal from './PrivacyPolicyModal';
import { sendEmail } from './emailService';
@@ -50,6 +41,7 @@ import {
} from '../splash/notifScheduler';
import { DateTime } from 'luxon';
import { AppConfig } from '../types/appConfigTypes';
+import NavBar, { NavBarButton } from '../components/NavBar';
//any pure functions can go outside
const ProfileSettings = () => {
@@ -67,9 +59,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);
@@ -158,14 +148,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
@@ -189,18 +177,16 @@ const ProfileSettings = () => {
//ensure ui table updated when editor closes
useEffect(() => {
if (editCollectionVis == false) {
- setTimeout(function () {
- console.log('closed editor, time to refresh collect');
+ setTimeout(() => {
+ 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 +198,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 +208,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(function (showConfig) {
+ getHelperSyncSettings().then((showConfig) => {
newSyncSettings.show_config = showConfig;
setSyncSettings(newSyncSettings);
- console.log('sync settings are ', syncSettings);
+ logDebug('sync settings are: ' + JSON.stringify(syncSettings));
});
}
@@ -252,13 +229,13 @@ const ProfileSettings = () => {
async function getConnectURL() {
getSettings().then(
- function (response) {
+ (response) => {
const newConnectSettings: any = {};
+ logDebug('getConnectURL: got response.connectUrl = ' + response.connectUrl);
newConnectSettings.url = response.connectUrl;
- console.log(response);
setConnectSettings(newConnectSettings);
},
- function (error) {
+ (error) => {
displayError(error, 'While getting connect url');
},
);
@@ -324,7 +301,7 @@ const ProfileSettings = () => {
async function toggleLowAccuracy() {
let toggle = await helperToggleLowAccuracy();
- setTimeout(function () {
+ setTimeout(() => {
refreshCollectSettings();
}, 1500);
}
@@ -333,14 +310,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);
}
}
@@ -348,12 +325,11 @@ const ProfileSettings = () => {
async function invalidateCache() {
window['cordova'].plugins.BEMUserCache.invalidateAllCache().then(
- function (result) {
- console.log('invalidate result', result);
- setCacheResult(result);
- setInvalidateSuccessVis(true);
+ (result) => {
+ logDebug('invalidateCache: result = ' + JSON.stringify(result));
+ AlertManager.addMessage({ text: `success -> ${result}` });
},
- function (error) {
+ (error) => {
displayError(error, 'while invalidating cache, error->');
},
);
@@ -362,7 +338,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 +347,7 @@ const ProfileSettings = () => {
setConsentVis(true);
}
},
- function (error) {
+ (error) => {
displayError(error, 'Error reading consent document from cache');
},
);
@@ -384,7 +360,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={() => {}}>
>
);
@@ -417,23 +392,12 @@ const ProfileSettings = () => {
return (
<>
-
+
- setLogoutVis(true)}
- right={() => }
- />
-
+ setLogoutVis(true)}>
+ {t('control.log-out')}
+
+
{
console.log('')}
+ action={() => {}}
desc={appVersion.current}>
@@ -671,16 +635,6 @@ const ProfileSettings = () => {
new Date(appConfig?.intro?.start_year, appConfig?.intro?.start_month - 1, 1)
}>
-
-
-
diff --git a/www/js/control/SensedPage.tsx b/www/js/control/SensedPage.tsx
index d4fe12451..78971e9d0 100644
--- a/www/js/control/SensedPage.tsx
+++ b/www/js/control/SensedPage.tsx
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import { FlashList } from '@shopify/flash-list';
import { DateTime } from 'luxon';
import { sendEmail } from './emailService';
+import NavBar from '../components/NavBar';
const SensedPage = ({ pageVis, setPageVis }) => {
const { t } = useTranslation();
@@ -12,10 +13,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;
@@ -55,18 +52,15 @@ const SensedPage = ({ pageVis, setPageVis }) => {
return (
setPageVis(false)}>
-
+ setPageVis(false)} />
-
+
updateEntries()} />
- emailCache()} />
+ sendEmail('userCacheDB')} />
);
}
- let descriptionText;
- if (desc) {
- descriptionText = { desc };
- } else {
- descriptionText = '';
- }
return (
(async function (resolve, reject) {
+ return new Promise(async (resolve, reject) => {
logInfo('About to get email config');
let url: string[] = [];
try {
@@ -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) {
@@ -39,35 +38,33 @@ function onUploadError(err) {
}
function readDBFile(parentDir, database, callbackFn) {
- return new Promise(function (resolve, reject) {
- window['resolveLocalFileSystemURL'](parentDir, function (fs) {
- console.log('resolving file system as ', fs);
+ return new Promise((resolve, reject) => {
+ window['resolveLocalFileSystemURL'](parentDir, (fs) => {
+ logDebug('resolving file system as ' + JSON.stringify(fs));
fs.filesystem.root.getFile(
fs.fullPath + database,
null,
(fileEntry) => {
- console.log(fileEntry);
- fileEntry.file(function (file) {
- console.log(file);
- var reader = new FileReader();
+ logDebug('fileEntry = ' + JSON.stringify(fileEntry));
+ fileEntry.file((file) => {
+ logDebug('file = ' + JSON.stringify(file));
+ const reader = new FileReader();
- reader.onprogress = function (report) {
- console.log('Current progress is ' + JSON.stringify(report));
+ reader.onprogress = (report) => {
+ logDebug('Current progress is ' + JSON.stringify(report));
if (callbackFn != undefined) {
callbackFn((report.loaded * 100) / report.total);
}
};
- reader.onerror = function (error) {
- console.log(this.error);
- reject({ error: { message: this.error } });
+ reader.onerror = (error) => {
+ logDebug('Error while reading file ' + JSON.stringify(reader.error));
+ reject({ error: { message: reader.error } });
};
- reader.onload = function () {
- console.log(
- 'Successful file read with ' + this.result?.['byteLength'] + ' characters',
- );
- resolve(new DataView(this.result as ArrayBuffer));
+ reader.onload = () => {
+ logDebug(`Successful file read with ${reader.result?.['byteLength']} characters`);
+ resolve(new DataView(reader.result as ArrayBuffer));
};
reader.readAsArrayBuffer(file);
@@ -93,7 +90,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';
@@ -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/LabelTab.tsx b/www/js/diary/LabelTab.tsx
index 946ccb91c..0ceaf0505 100644
--- a/www/js/diary/LabelTab.tsx
+++ b/www/js/diary/LabelTab.tsx
@@ -31,6 +31,7 @@ import { getNotDeletedCandidates, mapInputsToTimelineEntries } from '../survey/i
import { configuredFilters as multilabelConfiguredFilters } from '../survey/multilabel/infinite_scroll_filters';
import { configuredFilters as enketoConfiguredFilters } from '../survey/enketo/infinite_scroll_filters';
import LabelTabContext, {
+ LabelTabFilter,
TimelineLabelMap,
TimelineMap,
TimelineNotesMap,
@@ -49,7 +50,7 @@ const LabelTab = () => {
const { colors } = useTheme();
const [labelOptions, setLabelOptions] = useState | null>(null);
- const [filterInputs, setFilterInputs] = useState([]);
+ const [filterInputs, setFilterInputs] = useState([]);
const [lastFilteredTs, setLastFilteredTs] = useState(null);
const [pipelineRange, setPipelineRange] = useState(null);
const [queriedRange, setQueriedRange] = useState(null);
@@ -186,7 +187,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;
@@ -249,9 +251,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()]}`);
@@ -267,7 +267,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;
diff --git a/www/js/diary/LabelTabContext.ts b/www/js/diary/LabelTabContext.ts
index 89448082b..9e80cccae 100644
--- a/www/js/diary/LabelTabContext.ts
+++ b/www/js/diary/LabelTabContext.ts
@@ -1,5 +1,5 @@
import { createContext } from 'react';
-import { TimelineEntry, UserInputEntry } from '../types/diaryTypes';
+import { TimelineEntry, TimestampRange, UserInputEntry } from '../types/diaryTypes';
import { LabelOption, LabelOptions, MultilabelKey } from '../types/labelTypes';
import { EnketoUserInputEntry } from '../survey/enketo/enketoHelper';
@@ -21,6 +21,13 @@ export type TimelineNotesMap = {
[k: string]: UserInputEntry[];
};
+export type LabelTabFilter = {
+ key: string;
+ text: string;
+ filter: (trip: TimelineEntry, userInputForTrip: UserInputMap) => boolean;
+ state?: boolean;
+};
+
type ContextProps = {
labelOptions: LabelOptions | null;
timelineMap: TimelineMap | null;
@@ -29,14 +36,14 @@ type ContextProps = {
labelFor: (tlEntry: TimelineEntry, labelType: MultilabelKey) => LabelOption | undefined;
addUserInputToEntry: (oid: string, userInput: any, inputType: 'label' | 'note') => void;
displayedEntries: TimelineEntry[] | null;
- filterInputs: any; // TODO
- setFilterInputs: any; // TODO
- queriedRange: any; // TODO
- pipelineRange: any; // TODO
+ filterInputs: LabelTabFilter[];
+ setFilterInputs: (filters: LabelTabFilter[]) => void;
+ queriedRange: TimestampRange | null;
+ pipelineRange: TimestampRange | null;
isLoading: string | false;
- loadAnotherWeek: any; // TODO
- loadSpecificWeek: any; // TODO
- refresh: any; // TODO
+ loadAnotherWeek: (when: 'past' | 'future') => void;
+ loadSpecificWeek: (d: Date) => void;
+ refresh: () => void;
};
export default createContext({} as ContextProps);
diff --git a/www/js/diary/addressNamesHelper.ts b/www/js/diary/addressNamesHelper.ts
index 0364bffaa..30740ad19 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);
@@ -67,9 +67,9 @@ 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/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/LabelDetailsScreen.tsx b/www/js/diary/details/LabelDetailsScreen.tsx
index 17fcb0584..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);
@@ -40,7 +41,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 &&
@@ -54,17 +58,14 @@ const LabelScreenDetails = ({ route, navigation }) => {
const modal = (
-
+ {
navigation.goBack();
}}
/>
-
+
{
@@ -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/diaryHelper.ts b/www/js/diary/diaryHelper.ts
index 271686c73..f02797fff 100644
--- a/www/js/diary/diaryHelper.ts
+++ b/www/js/diary/diaryHelper.ts
@@ -156,7 +156,7 @@ export function getFormattedTimeRange(beginFmtTime: string, endFmtTime: string)
const endTime = DateTime.fromISO(endFmtTime, { setZone: true });
const range = endTime.diff(beginTime, ['hours', 'minutes']);
return humanizeDuration(range.as('milliseconds'), {
- language: i18next.language,
+ language: i18next.resolvedLanguage,
largest: 1,
round: true,
});
diff --git a/www/js/diary/list/DateSelect.tsx b/www/js/diary/list/DateSelect.tsx
index 5bda95fbb..02b8d1ca1 100644
--- a/www/js/diary/list/DateSelect.tsx
+++ b/www/js/diary/list/DateSelect.tsx
@@ -14,7 +14,7 @@ import { DatePickerModal } from 'react-native-paper-dates';
import { Text, Divider, useTheme } from 'react-native-paper';
import i18next from 'i18next';
import { useTranslation } from 'react-i18next';
-import NavBarButton from '../../components/NavBarButton';
+import { NavBarButton } from '../../components/NavBar';
const DateSelect = ({ tsRange, loadSpecificWeekFn }) => {
const { pipelineRange } = useContext(LabelTabContext);
@@ -32,7 +32,7 @@ const DateSelect = ({ tsRange, loadSpecificWeekFn }) => {
}, [pipelineRange]);
useEffect(() => {
- if (!tsRange.oldestTs) return;
+ if (!pipelineRange || !tsRange.oldestTs) return;
const displayStartTs = Math.max(tsRange.oldestTs, pipelineRange.start_ts);
const displayStartDate = DateTime.fromSeconds(displayStartTs).toLocaleString(
DateTime.DATE_SHORT,
@@ -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 0018c1bc5..039d76be0 100644
--- a/www/js/diary/list/FilterSelect.tsx
+++ b/www/js/diary/list/FilterSelect.tsx
@@ -10,10 +10,17 @@
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';
+import { LabelTabFilter } from '../LabelTabContext';
-const FilterSelect = ({ filters, setFilters, numListDisplayed, numListTotal }) => {
+type Props = {
+ filters: LabelTabFilter[];
+ setFilters: (filters: LabelTabFilter[]) => void;
+ numListDisplayed?: number;
+ numListTotal?: number;
+};
+const FilterSelect = ({ filters, setFilters, numListDisplayed, numListTotal }: Props) => {
const { t } = useTranslation();
const [modalVisible, setModalVisible] = useState(false);
const selectedFilter = useMemo(() => filters?.find((f) => f.state)?.key || 'show-all', [filters]);
@@ -46,9 +53,7 @@ const FilterSelect = ({ filters, setFilters, numListDisplayed, numListTotal }) =
return (
<>
- setModalVisible(true)}>
+ setModalVisible(true)}>
{labelDisplayText} setModalVisible(false)}>
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' }}
/>
-
+
{
+function renderCard({ item: listEntry, index }) {
if (listEntry.origin_key.includes('trip')) {
return ;
} else if (listEntry.origin_key.includes('place')) {
@@ -18,7 +17,7 @@ const renderCard = ({ item: listEntry, index }) => {
} else {
throw new Error(`Unknown listEntry type: ${JSON.stringify(listEntry)}`);
}
-};
+}
const separator = () => ;
const bigSpinner = ;
@@ -59,9 +58,7 @@ const TimelineScrollList = ({
);
const noTravelBanner = (
- }>
+ }>
{t('diary.no-travel')}{t('diary.no-travel-hint')}
diff --git a/www/js/diary/timelineHelper.ts b/www/js/diary/timelineHelper.ts
index 01f40fa99..f140f1750 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),
@@ -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];
@@ -354,25 +351,19 @@ 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
@@ -382,12 +373,8 @@ function transitionTrip2TripObj(trip: Array): Promise): Promise) {
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 +426,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 +442,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;
@@ -477,9 +461,8 @@ 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..e42a130ac 100644
--- a/www/js/i18nextInit.ts
+++ b/www/js/i18nextInit.ts
@@ -15,10 +15,10 @@ 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}'`);
+ logWarn(`Missing translation for key '${key}'`);
if (__DEV__) {
if (typeof value === 'string') {
lang[key] = `🌐${value}`;
@@ -34,7 +34,7 @@ const mergeInTranslations = (lang, fallbackLang) => {
}
});
return lang;
-};
+}
import enJson from '../i18n/en.json';
import esJson from '../../locales/es/i18n/es.json';
@@ -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 047ab6aeb..56e955f60 100644
--- a/www/js/metrics/CarbonFootprintCard.tsx
+++ b/www/js/metrics/CarbonFootprintCard.tsx
@@ -19,16 +19,17 @@ 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';
+import { logDebug, logWarn } from '../plugin/logger';
type Props = { userMetrics?: MetricsData; aggMetrics?: MetricsData };
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) {
@@ -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/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/MetricsDateSelect.tsx b/www/js/metrics/MetricsDateSelect.tsx
index 014b5a00c..07656ec25 100644
--- a/www/js/metrics/MetricsDateSelect.tsx
+++ b/www/js/metrics/MetricsDateSelect.tsx
@@ -12,8 +12,8 @@ import { DatePickerModal } from 'react-native-paper-dates';
import { Divider, useTheme } from 'react-native-paper';
import i18next from 'i18next';
import { useTranslation } from 'react-i18next';
-import NavBarButton from '../components/NavBarButton';
import { DateTime } from 'luxon';
+import { NavBarButton } from '../components/NavBar';
type Props = {
dateRange: DateTime[];
@@ -59,7 +59,7 @@ const MetricsDateSelect = ({ dateRange, setDateRange }: Props) => {
return (
<>
- setOpen(true)}>
+ setOpen(true)}>
{dateRange[0] && (
<>
{dateRange[0].toLocaleString()}
@@ -88,7 +88,7 @@ const MetricsDateSelect = ({ dateRange, setDateRange }: Props) => {
export const s = StyleSheet.create({
divider: {
- width: '3ch',
+ width: 25,
marginHorizontal: 'auto',
},
});
diff --git a/www/js/metrics/MetricsTab.tsx b/www/js/metrics/MetricsTab.tsx
index de59b7354..7533022a5 100644
--- a/www/js/metrics/MetricsTab.tsx
+++ b/www/js/metrics/MetricsTab.tsx
@@ -1,9 +1,9 @@
import React, { useEffect, useState, useMemo } from 'react';
import { View, ScrollView, useWindowDimensions } from 'react-native';
import { Appbar, useTheme } from 'react-native-paper';
-import NavBarButton from '../components/NavBarButton';
import { useTranslation } from 'react-i18next';
import { DateTime } from 'luxon';
+import NavBar from '../components/NavBar';
import { MetricsData } from './metricsTypes';
import MetricsCard from './MetricsCard';
import { formatForDisplay, useImperialConfig } from '../config/useImperialConfig';
@@ -97,14 +97,11 @@ const MetricsTab = () => {
return (
<>
-
+
-
+
diff --git a/www/js/metrics/customMetricsHelper.ts b/www/js/metrics/customMetricsHelper.ts
index 8b16436d2..096a62cb4 100644
--- a/www/js/metrics/customMetricsHelper.ts
+++ b/www/js/metrics/customMetricsHelper.ts
@@ -54,7 +54,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 3c709fca2..ca3846806 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[] = [];
@@ -62,24 +63,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) {
@@ -91,9 +87,10 @@ 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(function (metric) {
+ metrics?.forEach((metric) => {
let onFootVal = 0;
for (let field in metric) {
@@ -120,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] = [];
}
@@ -142,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[] = [];
@@ -171,7 +168,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[] = [];
@@ -180,23 +177,16 @@ export const isCustomLabels = function (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);
+ metricSummaryChecksCustom.push(Boolean(isAllCustomForMetric));
+ logDebug(`overall custom/not results for each metric
+ is ${JSON.stringify(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 +200,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/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 = {
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/PrivacyPolicy.tsx b/www/js/onboarding/PrivacyPolicy.tsx
index 8fe12961d..a24449045 100644
--- a/www/js/onboarding/PrivacyPolicy.tsx
+++ b/www/js/onboarding/PrivacyPolicy.tsx
@@ -47,7 +47,10 @@ const PrivacyPolicy = () => {
});
}
- 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/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 f552a56a7..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();
@@ -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,41 +45,34 @@ 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) {
- console.debug('scanned code', result);
+ (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');
}
},
- function (error) {
+ (error) => {
displayError(error, 'Scanning failed: ');
},
);
- };
+ }
function loginWithToken(token) {
initByUser({ token })
@@ -90,7 +83,7 @@ const WelcomePage = () => {
}
})
.catch((err) => {
- console.error('Error logging in with token', err);
+ displayError(err, 'Error logging in with token');
setExistingToken('');
});
}
@@ -242,7 +235,7 @@ const WelcomePageButton = ({ onPress, icon, children }) => {
return (
-
+
{children}
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() {
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/plugin/clientStats.ts b/www/js/plugin/clientStats.ts
index 6735ef5ff..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';
@@ -22,38 +22,41 @@ 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);
+ logDebug('addStatReading: adding CLIENT_TIME event: ' + JSON.stringify(event));
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);
+ 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');
-};
+}
-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);
+ 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 7142991d8..e22bb4669 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.
@@ -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/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..21625ac29 100644
--- a/www/js/splash/customURL.ts
+++ b/www/js/splash/customURL.ts
@@ -1,13 +1,13 @@
+import { displayError } from '../plugin/logger';
+
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('?');
@@ -18,7 +18,7 @@ export const onLaunchCustomURL: OnLaunchCustomURL = (rawUrl, handler) => {
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 00f24571d..10d20b18b 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
@@ -32,17 +32,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 = () => {
@@ -70,25 +70,19 @@ 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}`);
});
}
//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
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) => {
@@ -96,17 +90,16 @@ export const getScheduledNotifs = function (isScheduling: boolean, scheduledProm
});
});
} else {
- console.log('test log: not actively scheduling, fetching');
getNotifs().then((notifs) => {
resolve(notifs);
});
}
});
-};
+}
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) {
@@ -130,13 +123,13 @@ 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;
+ 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
@@ -166,19 +159,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,
@@ -216,13 +207,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)];
@@ -233,7 +224,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',
@@ -247,12 +238,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) {
@@ -271,14 +262,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
@@ -302,4 +294,4 @@ export const setReminderPrefs = async (
},
);
return updatePromise;
-};
+}
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');
}
diff --git a/www/js/splash/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts
index cbc920ced..098625ee2 100644
--- a/www/js/splash/remoteNotifyHandler.ts
+++ b/www/js/splash/remoteNotifyHandler.ts
@@ -23,20 +23,15 @@ 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));
- });
+ addStatEvent(statKeys.NOTIFICATION_OPEN);
logDebug('data = ' + JSON.stringify(data));
if (
data.additionalData &&
@@ -44,32 +39,32 @@ const 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 ');
}
}
}
-};
+}
/**
* @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..5e1edd188 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,12 @@ export function isConsented() {
export function readConsentState() {
return fetch('json/startupConfig.json')
.then((response) => response.json())
- .then(function (startupConfigResult) {
- console.log(startupConfigResult);
+ .then((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 +92,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 +102,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..7492b35b8 100644
--- a/www/js/splash/storeDeviceSettings.ts
+++ b/www/js/splash/storeDeviceSettings.ts
@@ -6,75 +6,66 @@ 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 () {
- var lang = i18next.resolvedLanguage;
- var manufacturer = window['device'].manufacturer;
- var osver = window['device'].version;
+function storeDeviceSettings() {
return window['cordova'].getAppVersion
.getVersionNumber()
- .then(function (appver) {
- var updateJSON = {
- phone_lang: lang,
+ .then((appver) => {
+ const updateJSON = {
+ phone_lang: i18next.resolvedLanguage,
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));
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) => {
- console.log(
- 'got consented event ' +
- JSON.stringify(event['name']) +
- ' with data ' +
- JSON.stringify(event.detail),
- );
+function onConsentEvent(event) {
+ 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();
}
});
-};
+}
/**
* @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) {
- console.log('found consent', consentState);
+ .then(async (consentState) => {
+ logDebug(`found consent: ${consentState}`);
if (consentState == true) {
await storeDeviceSettings();
} else {
@@ -84,9 +75,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/AddNoteButton.tsx b/www/js/survey/enketo/AddNoteButton.tsx
index f125f8185..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);
@@ -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..91cea8536 100644
--- a/www/js/survey/enketo/AddedNotesList.tsx
+++ b/www/js/survey/enketo/AddedNotesList.tsx
@@ -5,14 +5,13 @@
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';
-import { logDebug } from '../../plugin/logger';
+import { displayErrorMsg, logDebug } from '../../plugin/logger';
type Props = {
timelineEntry: any;
@@ -60,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';
@@ -72,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);
});
@@ -90,7 +95,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);
}
@@ -116,9 +121,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)}
@@ -129,8 +135,8 @@ const AddedNotesList = ({ timelineEntry, additionEntries }: Props) => {
confirmDeleteEntry(entry)}
- style={[styles.cell, { flex: 1 }]}>
-
+ style={[styles.cell, { flex: 1, justifyContent: 'center' }]}>
+
);
diff --git a/www/js/survey/enketo/EnketoModal.tsx b/www/js/survey/enketo/EnketoModal.tsx
index d1f11a83a..768fcd0d1 100644
--- a/www/js/survey/enketo/EnketoModal.tsx
+++ b/www/js/survey/enketo/EnketoModal.tsx
@@ -1,11 +1,11 @@
import React, { useRef, useEffect } from 'react';
import { Form } from 'enketo-core';
import { StyleSheet, Modal, ScrollView, SafeAreaView, Pressable } from 'react-native';
-import { ModalProps } from 'react-native-paper';
+import { Button, Icon, 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 };
@@ -61,15 +61,14 @@ const EnketoModal = ({ surveyName, onResponseSaved, opts, ...rest }: Props) => {
}
useEffect(() => {
- if (!rest.visible) return;
- if (!appConfig) return console.error('App config not loaded yet');
+ if (!rest.visible || !appConfig) return;
initSurvey();
}, [appConfig, rest.visible]);
/* 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 (