From 2cab1ed9588f8688bcdee7c836b8fe7dea1b929a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 13 Jun 2023 14:12:59 -0600 Subject: [PATCH 001/154] switched collect and sync lists over to react --- www/js/control/ControlDataTable.jsx | 28 +++++++++++++++++++++++++ www/js/control/general-settings.js | 4 +++- www/templates/control/main-control.html | 22 +++++++++++++------ 3 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 www/js/control/ControlDataTable.jsx diff --git a/www/js/control/ControlDataTable.jsx b/www/js/control/ControlDataTable.jsx new file mode 100644 index 000000000..d640cb3e1 --- /dev/null +++ b/www/js/control/ControlDataTable.jsx @@ -0,0 +1,28 @@ +import React from "react"; +import { DataTable } from 'react-native-paper'; +import { angularize } from "../angular-react-helper"; +import { array } from "prop-types"; + +// Note the camelCase to dash-case conventions when translating to .html files! +// val with explicit call toString() to resolve bool values not showing +const ControlDataTable = ({ controlData }) => { + console.log("Printing data trying to tabulate", controlData); + return ( + + {controlData.map((e) => + + {e.key} + {e.val.toString()} + + )} + + ); + }; +ControlDataTable.propTypes = { + controlData: array + } + +// need call to angularize to let the React and Angular co-mingle + //second argument is "module path" - unsure of exact usage, could require update later? +angularize(ControlDataTable, 'emission.main.control.dataTable'); +export default ControlDataTable; \ No newline at end of file diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 1b768efd0..208f20014 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -2,6 +2,7 @@ import angular from 'angular'; import QrCode from './QrCode'; +import ControlDataTable from './ControlDataTable'; angular.module('emission.main.control',['emission.services', 'emission.i18n.utils', @@ -19,7 +20,8 @@ angular.module('emission.main.control',['emission.services', 'emission.survey.enketo.demographics', 'emission.plugin.logger', 'emission.config.dynamic', - QrCode.module]) + QrCode.module, + ControlDataTable.module]) .controller('ControlCtrl', function($scope, $window, $ionicScrollDelegate, $ionicPlatform, diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 9475bb861..e979f2844 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -140,23 +140,32 @@
{{'control.check-sensed-data'}}
+ + +
{{'control.collection'}}
- - + + + + + + +
{{'control.sync'}}
- + + + +
{{'control.app-version'}}
{{settings.clientAppVer}}
From ba5406e8b4bc34a6d47533a42c7ae4214c239ea7 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 14 Jun 2023 10:52:41 -0600 Subject: [PATCH 002/154] inserted a placeholding div control-list-item This is not yet complete, but is an idea of where the schedule table might eventually be --- www/templates/control/main-control.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index e979f2844..be83cf648 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -120,6 +120,13 @@
Dummy Notification in 5 Seconds
+ +
+
Upcoming Notifications
+ +
+ +
{{'control.invalidate-cached-docs'}}
From 2ad296cf6d5a1d996bc3dc1ae0231a948e62ed7d Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 14 Jun 2023 13:20:45 -0600 Subject: [PATCH 003/154] patch from Jack incorporating a patch from Jack with a solution to some of the issues with scheduled notifications, as well as cleaning up some of the style around the control-data-table (s) I converted earlier --- www/js/control/ControlDataTable.jsx | 19 ++++++++++++++----- www/js/control/general-settings.js | 6 +++++- www/js/splash/notifScheduler.js | 8 ++++++++ www/templates/control/main-control.html | 17 +++++++++++------ 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/www/js/control/ControlDataTable.jsx b/www/js/control/ControlDataTable.jsx index d640cb3e1..b22382df2 100644 --- a/www/js/control/ControlDataTable.jsx +++ b/www/js/control/ControlDataTable.jsx @@ -8,7 +8,7 @@ import { array } from "prop-types"; const ControlDataTable = ({ controlData }) => { console.log("Printing data trying to tabulate", controlData); return ( - + {controlData.map((e) => {e.key} @@ -16,13 +16,22 @@ const ControlDataTable = ({ controlData }) => { )} - ); - }; + ); +}; +const styles = { + table: { + marginLeft: 10, + borderWidth: 1, + borderColor: 'rgba(0,0,0,0.25)', + borderLeftWidth: 15, + borderLeftColor: 'rgba(0,0,0,0.25)', + } +} ControlDataTable.propTypes = { controlData: array } // need call to angularize to let the React and Angular co-mingle - //second argument is "module path" - unsure of exact usage, could require update later? + //second argument is "module path" - can access later as ControlDataTable.module angularize(ControlDataTable, 'emission.main.control.dataTable'); -export default ControlDataTable; \ No newline at end of file +export default ControlDataTable; diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 208f20014..0d088567b 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -3,6 +3,7 @@ import angular from 'angular'; import QrCode from './QrCode'; import ControlDataTable from './ControlDataTable'; +import ControlListItem from './ControlListItem'; angular.module('emission.main.control',['emission.services', 'emission.i18n.utils', @@ -21,7 +22,8 @@ angular.module('emission.main.control',['emission.services', 'emission.plugin.logger', 'emission.config.dynamic', QrCode.module, - ControlDataTable.module]) + ControlDataTable.module, + ControlListItem.module]) .controller('ControlCtrl', function($scope, $window, $ionicScrollDelegate, $ionicPlatform, @@ -133,6 +135,7 @@ angular.module('emission.main.control',['emission.services', $scope.settings.notification.prefReminderTime = m.format('LT'); // display in user's locale if (storeNewVal) NotificationScheduler.setReminderPrefs({ reminder_time_of_day: m.format('HH:mm') }); // store in HH:mm + $scope.settings.notification.scheduledNotifs = cordova.plugins.notification.local.scheduledNotifs; } $scope.fixAppStatus = function() { @@ -376,6 +379,7 @@ angular.module('emission.main.control',['emission.services', NotificationScheduler.getReminderPrefs().then((prefs) => { $scope.$apply(() => { const m = moment(prefs.reminder_time_of_day, 'HH:mm'); + $scope.settings.notification.scheduledNotifs = cordova.plugins.notification.local.scheduledNotifs; $scope.settings.notification.prefReminderTimeVal = m.toDate(); $scope.settings.notification.prefReminderTimeOnLoad = prefs.reminder_time_of_day; $scope.updatePrefReminderTime(false); // update the displayed time diff --git a/www/js/splash/notifScheduler.js b/www/js/splash/notifScheduler.js index 349248cf5..7108bd933 100644 --- a/www/js/splash/notifScheduler.js +++ b/www/js/splash/notifScheduler.js @@ -65,6 +65,14 @@ angular.module('emission.splash.notifscheduler', if (!notifs?.length) return Logger.log(`${prefix}, there are no scheduled notifications`); const time = moment(notifs?.[0].trigger.at).format('HH:mm'); + cordova.plugins.notification.local.scheduledNotifs = notifs.map((n) => { + const time = moment(n.trigger.at).format('LT'); + const date = moment(n.trigger.at).format('LL'); + return { + key: date, + val: time + } + }); Logger.log(`${prefix}, there are ${notifs.length} scheduled notifications at ${time}`); }); } diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index be83cf648..163751902 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -5,7 +5,13 @@
-
+ + +
{{'control.view-privacy'}}
@@ -122,10 +128,10 @@
-
Upcoming Notifications
+
{Upcoming Notifications}
- +
{{'control.invalidate-cached-docs'}}
@@ -147,7 +153,6 @@
{{'control.check-sensed-data'}}
-
@@ -156,7 +161,7 @@
- + - -
+
{{'control.view-privacy'}}
@@ -153,23 +150,14 @@
{{'control.check-sensed-data'}}
- +
{{'control.collection'}}
- - - - - +
{{'control.sync'}}
@@ -177,17 +165,6 @@
- -
{{'control.app-version'}}
{{settings.clientAppVer}}
From af44bf0af6ab58754e68bb08160e99ad45b8fe61 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 16 Jun 2023 11:27:29 -0600 Subject: [PATCH 006/154] replacing hard coded strings the "Upcoming Notifications" and "Dummy Notification in 5 Seconds statements were hard coded, I've added them as internationalized strings and updated the en.json file --- www/i18n/en.json | 4 +++- www/templates/control/main-control.html | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/www/i18n/en.json b/www/i18n/en.json index 0861613df..644f6d55c 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -87,7 +87,9 @@ "button-accept": "I accept", "view-qrc": "My OPcode", "app-version": "App Version", - "reminders-time-of-day": "Time of Day for Reminders ({{time}})" + "reminders-time-of-day": "Time of Day for Reminders ({{time}})", + "upcoming-notifications": "Upcoming Notifications", + "dummy-notification" : "Dummy Notification in 5 Seconds" }, "general-settings":{ diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 2ce60fdb5..1cbe82a18 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -120,13 +120,12 @@
-
Dummy Notification in 5 Seconds
+
{{'control.dummy-notification'}}
- +
-
{Upcoming Notifications}
- +
{{'control.upcoming-notifications'}}
From f558103689f0cea6b4c78efb11af1a2ec2461029 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 16 Jun 2023 15:01:59 -0600 Subject: [PATCH 007/154] start at storing data in better place I started to convert the data storage, data is fine in general-settings.js but is failing to make it through main-control.html to ControlDataTable.jsx --- www/js/control/ControlDataTable.jsx | 2 +- www/js/control/general-settings.js | 4 +++- www/js/splash/notifScheduler.js | 9 +++++++-- www/templates/control/main-control.html | 4 ++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/www/js/control/ControlDataTable.jsx b/www/js/control/ControlDataTable.jsx index 9c1119baf..45e4f2fd6 100644 --- a/www/js/control/ControlDataTable.jsx +++ b/www/js/control/ControlDataTable.jsx @@ -6,7 +6,7 @@ import { array } from "prop-types"; // Note the camelCase to dash-case conventions when translating to .html files! // val with explicit call toString() to resolve bool values not showing const ControlDataTable = ({ controlData }) => { - // console.log("Printing data trying to tabulate", controlData); + console.log("Printing data trying to tabulate", controlData); //notif data is not here! return ( //rows require unique keys! diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 6c43d8493..744519d3e 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -378,7 +378,9 @@ angular.module('emission.main.control',['emission.services', $scope.$apply(() => { const m = moment(prefs.reminder_time_of_day, 'HH:mm'); // defining data used to populate the upcoming display - $scope.settings.notification.scheduledNotifs = cordova.plugins.notification.local.scheduledNotifs; + console.log("data before setting", NotificationScheduler.scheduledNotifs); //data came through here! + $scope.settings.notification.scheduledNotifs = NotificationScheduler.scheduledNotifs; + console.log("data after setting", $scope.settings.notification.scheduledNotifs); //data came through here too! $scope.settings.notification.prefReminderTimeVal = m.toDate(); $scope.settings.notification.prefReminderTimeOnLoad = prefs.reminder_time_of_day; $scope.updatePrefReminderTime(false); // update the displayed time diff --git a/www/js/splash/notifScheduler.js b/www/js/splash/notifScheduler.js index 7108bd933..67daccb94 100644 --- a/www/js/splash/notifScheduler.js +++ b/www/js/splash/notifScheduler.js @@ -65,7 +65,10 @@ angular.module('emission.splash.notifscheduler', if (!notifs?.length) return Logger.log(`${prefix}, there are no scheduled notifications`); const time = moment(notifs?.[0].trigger.at).format('HH:mm'); - cordova.plugins.notification.local.scheduledNotifs = notifs.map((n) => { + //change where this is stored - plugin should be read-only + + // cordova.plugins.notification.local.scheduledNotifs = notifs.map((n) => { + scheduler.scheduledNotifs = notifs.map((n) => { const time = moment(n.trigger.at).format('LT'); const date = moment(n.trigger.at).format('LL'); return { @@ -73,7 +76,9 @@ angular.module('emission.splash.notifscheduler', val: time } }); - Logger.log(`${prefix}, there are ${notifs.length} scheduled notifications at ${time}`); + //have the list of scheduled show up in this log + Logger.log(`${prefix}, there are ${notifs.length} scheduled notifications at ${time} see list here:`); + console.log("notifications", scheduler.scheduledNotifs); }); } diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 1cbe82a18..0199f6b48 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -8,6 +8,10 @@ + + + +
{{'control.view-privacy'}}
From e8bb3d67bd58d72937e1f6accab103c0daf1cf31 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 16 Jun 2023 15:06:49 -0600 Subject: [PATCH 008/154] spelling --- www/templates/control/main-control.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 0199f6b48..cdb243c71 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -127,7 +127,7 @@
{{'control.dummy-notification'}}
- +
{{'control.upcoming-notifications'}}
From d8a12aaa74cd219c27716efc76ba65f2c8e82841 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 16 Jun 2023 16:22:17 -0600 Subject: [PATCH 009/154] starting to work with other rows of profile this is where I'm struggling with the parameterization of i18n keys --- www/js/control/SettingRow.jsx | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 www/js/control/SettingRow.jsx diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx new file mode 100644 index 000000000..496135222 --- /dev/null +++ b/www/js/control/SettingRow.jsx @@ -0,0 +1,49 @@ +import React from "react"; +import { angularize } from "../angular-react-helper"; +import { Text } from 'react-native-paper'; +import { useTranslation } from "react-i18next"; +import { string, func, object} from "prop-types"; + +/* +biggest struggle here is how to pass i18n keys as parameters (or strings in general) +*/ + +const SettingRow = ({textKey, iconName}) => { + const { t } = useTranslation(); //this accesses the translations + console.log("row created!!!!!", "translate hard-code" + t('general-settings.are-you-sure') + "translate via param" + t(textKey)+ " and the icon (plain string) " + iconName); + return ( + + {"translated text" + t(textKey)} + + ); +}; +SettingRow.propTypes = { + textKey: string, + iconName: string +} + +/* +tinkered with the idea of a mapped solution, but didn't get any further +*/ + +// const dummyData = [{textKey: "control.view-privacy", actionFcn:"viewPrivacyPolicy($event)", iconName: "eye"}] + +// const SettingList = ({settingParams}) => { +// +// {settingParams.map((setting) => +// { +// return ( +// ); +// }) +// } +// +// }; +// SettingList.propTypes = { +// settingParams: array +// } + +angularize(SettingRow, 'emission.main.control.settingRow'); +export default SettingRow; \ No newline at end of file From 5b20f9fc2ad50d84dcdfef5dfbae39948b7fff2b Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 16 Jun 2023 16:36:53 -0600 Subject: [PATCH 010/154] setting up issue in using key as parameter Added in code so the broken setting row is enabled, note the console.log statement demoing the different ways the info comes through --- www/js/control/SettingRow.jsx | 3 ++- www/js/control/general-settings.js | 4 +++- www/templates/control/main-control.html | 4 ---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 496135222..1410414be 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -11,9 +11,10 @@ biggest struggle here is how to pass i18n keys as parameters (or strings in gene const SettingRow = ({textKey, iconName}) => { const { t } = useTranslation(); //this accesses the translations console.log("row created!!!!!", "translate hard-code" + t('general-settings.are-you-sure') + "translate via param" + t(textKey)+ " and the icon (plain string) " + iconName); + //from the above print statement the textKey is "0" and iconName is "undefined" but the hard-coded translation key works great! return ( - {"translated text" + t(textKey)} + {"translated text" + t(textKey)} ); }; diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 744519d3e..4816f1b25 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -3,6 +3,7 @@ import angular from 'angular'; import QrCode from './QrCode'; import ControlDataTable from './ControlDataTable'; +import SettingRow from './SettingRow'; angular.module('emission.main.control',['emission.services', 'emission.i18n.utils', @@ -21,7 +22,8 @@ angular.module('emission.main.control',['emission.services', 'emission.plugin.logger', 'emission.config.dynamic', QrCode.module, - ControlDataTable.module]) + ControlDataTable.module, + SettingRow.module]) .controller('ControlCtrl', function($scope, $window, $ionicScrollDelegate, $ionicPlatform, diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index cdb243c71..6fe000b87 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -5,10 +5,6 @@
- - From 0a629fa1b38fe3712278b942f3b04f98eca70ab0 Mon Sep 17 00:00:00 2001 From: Abby Wheelis <54848919+Abby-Wheelis@users.noreply.github.com> Date: Sat, 17 Jun 2023 13:35:39 -0600 Subject: [PATCH 011/154] fixing strings in Angular, string parameters need to be created as "'string'" - this should resolve some of the issues that I have been experiencing here Co-authored-by: Jack Greenlee --- www/templates/control/main-control.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 6fe000b87..c8d02bdaa 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -6,7 +6,7 @@ - +
{{'control.view-privacy'}}
From 5db9dd8e0e08102d578aecf54154a9dff5900f95 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 20 Jun 2023 11:34:48 -0600 Subject: [PATCH 012/154] text working on sample list item after I learned how to pass strings, I was able to render the row as a list item, which will hopefully eventually be good for showing everything as a list, and can have icons built in Icons and passing through functionality are next --- www/js/control/SettingRow.jsx | 19 ++++++++----------- www/templates/control/main-control.html | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 1410414be..9e2d25edc 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -1,21 +1,18 @@ import React from "react"; import { angularize } from "../angular-react-helper"; -import { Text } from 'react-native-paper'; +import { List } from 'react-native-paper'; import { useTranslation } from "react-i18next"; -import { string, func, object} from "prop-types"; - -/* -biggest struggle here is how to pass i18n keys as parameters (or strings in general) -*/ +import { string, func} from "prop-types"; const SettingRow = ({textKey, iconName}) => { const { t } = useTranslation(); //this accesses the translations - console.log("row created!!!!!", "translate hard-code" + t('general-settings.are-you-sure') + "translate via param" + t(textKey)+ " and the icon (plain string) " + iconName); - //from the above print statement the textKey is "0" and iconName is "undefined" but the hard-coded translation key works great! + // console.log("row created!!!!!", "translate hard-code" + t('general-settings.are-you-sure') + "translate via param" + t(textKey)+ " and the icon (plain string) " + iconName); + return ( - - {"translated text" + t(textKey)} - + ); }; SettingRow.propTypes = { diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index c8d02bdaa..cc6327a48 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -5,7 +5,7 @@
- +
From 337816caff65226dd9f04b190a9d8cd7ba504e7c Mon Sep 17 00:00:00 2001 From: Abby Wheelis <54848919+Abby-Wheelis@users.noreply.github.com> Date: Thu, 22 Jun 2023 12:33:21 -0600 Subject: [PATCH 013/154] carry data storage change through switching from plugin storage to scheduler storage Co-authored-by: Jack Greenlee --- www/js/control/general-settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 744519d3e..51edc5445 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -133,7 +133,7 @@ angular.module('emission.main.control',['emission.services', $scope.settings.notification.prefReminderTime = m.format('LT'); // display in user's locale if (storeNewVal) NotificationScheduler.setReminderPrefs({ reminder_time_of_day: m.format('HH:mm') }); // store in HH:mm - $scope.settings.notification.scheduledNotifs = cordova.plugins.notification.local.scheduledNotifs; + $scope.settings.notification.scheduledNotifs = NotificationScheduler.scheduledNotifs; } $scope.fixAppStatus = function() { From aa547f89fb2d882f37f60efe24c4bc65f7a92a3f Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 22 Jun 2023 12:50:09 -0600 Subject: [PATCH 014/154] polishing up - removing print statements, altering log after fix from Jack (2nd place to update where notifs come from) added in optional chain to reduce errors in table, took out diagnostic log statements, updated Logger.log to show num, time, and first notif --- www/js/control/ControlDataTable.jsx | 4 ++-- www/js/control/general-settings.js | 2 -- www/js/splash/notifScheduler.js | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/www/js/control/ControlDataTable.jsx b/www/js/control/ControlDataTable.jsx index 45e4f2fd6..48ab5e4b3 100644 --- a/www/js/control/ControlDataTable.jsx +++ b/www/js/control/ControlDataTable.jsx @@ -6,11 +6,11 @@ import { array } from "prop-types"; // Note the camelCase to dash-case conventions when translating to .html files! // val with explicit call toString() to resolve bool values not showing const ControlDataTable = ({ controlData }) => { - console.log("Printing data trying to tabulate", controlData); //notif data is not here! + // console.log("Printing data trying to tabulate", controlData); return ( //rows require unique keys! - {controlData.map((e) => + {controlData?.map((e) => {e.key} {e.val.toString()} diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 51edc5445..80c852cc3 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -378,9 +378,7 @@ angular.module('emission.main.control',['emission.services', $scope.$apply(() => { const m = moment(prefs.reminder_time_of_day, 'HH:mm'); // defining data used to populate the upcoming display - console.log("data before setting", NotificationScheduler.scheduledNotifs); //data came through here! $scope.settings.notification.scheduledNotifs = NotificationScheduler.scheduledNotifs; - console.log("data after setting", $scope.settings.notification.scheduledNotifs); //data came through here too! $scope.settings.notification.prefReminderTimeVal = m.toDate(); $scope.settings.notification.prefReminderTimeOnLoad = prefs.reminder_time_of_day; $scope.updatePrefReminderTime(false); // update the displayed time diff --git a/www/js/splash/notifScheduler.js b/www/js/splash/notifScheduler.js index 67daccb94..125895232 100644 --- a/www/js/splash/notifScheduler.js +++ b/www/js/splash/notifScheduler.js @@ -67,7 +67,6 @@ angular.module('emission.splash.notifscheduler', const time = moment(notifs?.[0].trigger.at).format('HH:mm'); //change where this is stored - plugin should be read-only - // cordova.plugins.notification.local.scheduledNotifs = notifs.map((n) => { scheduler.scheduledNotifs = notifs.map((n) => { const time = moment(n.trigger.at).format('LT'); const date = moment(n.trigger.at).format('LL'); @@ -77,8 +76,7 @@ angular.module('emission.splash.notifscheduler', } }); //have the list of scheduled show up in this log - Logger.log(`${prefix}, there are ${notifs.length} scheduled notifications at ${time} see list here:`); - console.log("notifications", scheduler.scheduledNotifs); + Logger.log(`${prefix}, there are ${notifs.length} scheduled notifications at ${time} first is ${scheduler.scheduledNotifs[0].key} at ${scheduler.scheduledNotifs[0].val}`); }); } From 11de879c2a3920f3b8a1395e3b99ede9c28880c8 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 22 Jun 2023 12:57:03 -0600 Subject: [PATCH 015/154] update comments, eliminate changes not a part of this PR the setting-row process has been separated to More profile migrations #994 --- www/js/splash/notifScheduler.js | 3 +-- www/templates/control/main-control.html | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/www/js/splash/notifScheduler.js b/www/js/splash/notifScheduler.js index 125895232..df89d1bd8 100644 --- a/www/js/splash/notifScheduler.js +++ b/www/js/splash/notifScheduler.js @@ -65,8 +65,7 @@ angular.module('emission.splash.notifscheduler', if (!notifs?.length) return Logger.log(`${prefix}, there are no scheduled notifications`); const time = moment(notifs?.[0].trigger.at).format('HH:mm'); - //change where this is stored - plugin should be read-only - + //was in plugin, changed to scheduler scheduler.scheduledNotifs = notifs.map((n) => { const time = moment(n.trigger.at).format('LT'); const date = moment(n.trigger.at).format('LL'); diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index cdb243c71..52fce4e27 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -5,13 +5,6 @@
- - - - -
{{'control.view-privacy'}}
From 2ddfab54c2f97303d8c6852dcdf4ee636ec0f1a8 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 22 Jun 2023 14:36:25 -0600 Subject: [PATCH 016/154] action parameters for setting rows - some work have a rough outline going of some setting rows that say and do the right thing! More complex actions, some text, and icons still need lots of work --- www/js/control/ControlDataTable.jsx | 2 +- www/js/control/SettingRow.jsx | 44 +++++-------- www/js/control/general-settings.js | 4 +- www/templates/control/main-control.html | 83 +++++++++++++++---------- 4 files changed, 67 insertions(+), 66 deletions(-) diff --git a/www/js/control/ControlDataTable.jsx b/www/js/control/ControlDataTable.jsx index 45e4f2fd6..9a591e465 100644 --- a/www/js/control/ControlDataTable.jsx +++ b/www/js/control/ControlDataTable.jsx @@ -10,7 +10,7 @@ const ControlDataTable = ({ controlData }) => { return ( //rows require unique keys! - {controlData.map((e) => + {controlData?.map((e) => {e.key} {e.val.toString()} diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 9e2d25edc..68cd6b4c5 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -1,47 +1,31 @@ import React from "react"; -import { angularize } from "../angular-react-helper"; +import { angularize} from "../angular-react-helper"; import { List } from 'react-native-paper'; import { useTranslation } from "react-i18next"; -import { string, func} from "prop-types"; +import { string, func, object} from "prop-types"; -const SettingRow = ({textKey, iconName}) => { +const SettingRow = ({textKey, iconName, action}) => { const { t } = useTranslation(); //this accesses the translations - // console.log("row created!!!!!", "translate hard-code" + t('general-settings.are-you-sure') + "translate via param" + t(textKey)+ " and the icon (plain string) " + iconName); - + + function clickTest() + { + console.log("pressed"); + {action()}; + } return ( + onPress = {clickTest} + > + ); }; SettingRow.propTypes = { textKey: string, - iconName: string + iconName: string, + action: func } -/* -tinkered with the idea of a mapped solution, but didn't get any further -*/ - -// const dummyData = [{textKey: "control.view-privacy", actionFcn:"viewPrivacyPolicy($event)", iconName: "eye"}] - -// const SettingList = ({settingParams}) => { -// -// {settingParams.map((setting) => -// { -// return ( -// ); -// }) -// } -// -// }; -// SettingList.propTypes = { -// settingParams: array -// } - angularize(SettingRow, 'emission.main.control.settingRow'); export default SettingRow; \ No newline at end of file diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 4816f1b25..13bb0081a 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -135,7 +135,7 @@ angular.module('emission.main.control',['emission.services', $scope.settings.notification.prefReminderTime = m.format('LT'); // display in user's locale if (storeNewVal) NotificationScheduler.setReminderPrefs({ reminder_time_of_day: m.format('HH:mm') }); // store in HH:mm - $scope.settings.notification.scheduledNotifs = cordova.plugins.notification.local.scheduledNotifs; + $scope.settings.notification.scheduledNotifs = NotificationScheduler.scheduledNotifs; } $scope.fixAppStatus = function() { @@ -380,9 +380,7 @@ angular.module('emission.main.control',['emission.services', $scope.$apply(() => { const m = moment(prefs.reminder_time_of_day, 'HH:mm'); // defining data used to populate the upcoming display - console.log("data before setting", NotificationScheduler.scheduledNotifs); //data came through here! $scope.settings.notification.scheduledNotifs = NotificationScheduler.scheduledNotifs; - console.log("data after setting", $scope.settings.notification.scheduledNotifs); //data came through here too! $scope.settings.notification.prefReminderTimeVal = m.toDate(); $scope.settings.notification.prefReminderTimeOnLoad = prefs.reminder_time_of_day; $scope.updatePrefReminderTime(false); // update the displayed time diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index cc6327a48..a726ff2eb 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -6,8 +6,8 @@
- - + +
{{'control.view-privacy'}}
@@ -36,10 +36,11 @@
-
+ +
{{'control.medium-accuracy'}}
+
-
{{carbonDatasetString}}
+
{{carbonDatasetString}}
-
+ + + + + -
+ -
+
+
--> + -
+ + -
+ +
{{'control.user-data'}}
@@ -104,51 +112,62 @@
{{'control.dev-zone'}}
+
-
+ + + + + + + +
{{'control.upcoming-notifications'}}
-
+ + + +
{{parseState(settings.collect.state)}}
-
+ + + + + +
From 90eda2ba586247a3e0f8b41692d46b49b31d898a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 22 Jun 2023 14:56:28 -0600 Subject: [PATCH 017/154] comments and spacing I added a bunch of comments and whitespace to make it easier for myself to see what needs to be done -- I'll reformat once I have things working --- www/templates/control/main-control.html | 41 ++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index a726ff2eb..fe6a79122 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -1,21 +1,29 @@ + +
{{settings.auth.opcode}}
+ + - - - + +
{{'control.view-privacy'}}
+ + +
{{'control.view-qrc'}}
+ +
{{'control.reminders-time-of-day' | i18next: {time: settings.notification.prefReminderTime} }} @@ -27,6 +35,8 @@ style="position: absolute; height: 100%; inset: 0; display: flex; opacity: 0;">
+ +
{{'control.tracking'}}
+ +
{{'control.medium-accuracy'}}
+
{{carbonDatasetString}}
@@ -61,6 +74,7 @@
--> + +
{{'control.user-data'}}
@@ -107,35 +122,40 @@
- -
+ +
+ + + + +
{{'control.upcoming-notifications'}}
@@ -147,27 +167,32 @@
--> + +
{{parseState(settings.collect.state)}}
+ + +
@@ -182,10 +207,12 @@
- + +
{{'control.app-version'}}
{{settings.clientAppVer}}
+ From f1373ec76aafe5f5cf913715532478eeb851063c Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 22 Jun 2023 16:53:52 -0600 Subject: [PATCH 018/154] more row migrations most rows now migrated, still need to handle some with slightly unique functionality, styling with icons also needed! --- www/js/control/SettingRow.jsx | 7 +--- www/js/control/general-settings.js | 2 +- www/templates/control/main-control.html | 50 ++++++++++++++----------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 68cd6b4c5..83cce8c0f 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -7,16 +7,11 @@ import { string, func, object} from "prop-types"; const SettingRow = ({textKey, iconName, action}) => { const { t } = useTranslation(); //this accesses the translations - function clickTest() - { - console.log("pressed"); - {action()}; - } return ( action(e)} > ); diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 13bb0081a..125e0e29a 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -165,7 +165,7 @@ angular.module('emission.main.control',['emission.services', gender: userDataFromStorage.gender == 1? i18next.t('gender-male') : i18next.t('gender-female') } for (var i in temp) { - $scope.userData.push({key: i, value: temp[i]}); + $scope.userData.push({key: i, val: temp[i]}); //changed from value to val! watch for rammifications! } } }); diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index fe6a79122..6cfad01f9 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -11,17 +11,18 @@ -
+ +
--> + -
+ +
@@ -53,6 +54,7 @@
--> +
{{'control.medium-accuracy'}}
- +
{{'control.erase-data'}}
- - + + + +
@@ -157,9 +161,10 @@ -
+ + -
+ - +
--> + -
+ + -
+ + -
+ + + From 10f571cf84107fdf599d01b6db4eb4ed61b22fac Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 23 Jun 2023 11:13:32 -0600 Subject: [PATCH 019/154] rough draft - two rows unconverted all rows ugly but functional but need guidance on reminder time and demographics button --- www/js/control/general-settings.js | 7 +++++ www/templates/control/main-control.html | 35 ++++++++++++++++--------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 125e0e29a..b0f390a39 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -396,6 +396,13 @@ angular.module('emission.main.control',['emission.services', }); } + // needed a parameter-less function to maintain the flow with the react transition :) + $scope.copyOpToClipboard = function() { + navigator.clipboard.writeText($scope.settings.auth.opcode).then(() => { + ionicToast.show('{Copied to clipboard!}', 'bottom', false, 2000); + }); + } + $scope.logOut = function() { $ionicPopup.confirm({ title: i18next.t('general-settings.are-you-sure'), diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 6cfad01f9..75bd137af 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -2,10 +2,12 @@ -
+ + + @@ -36,9 +38,14 @@ style="position: absolute; height: 100%; inset: 0; display: flex; opacity: 0;">
+ -
+ + -
+ + -
+ + -
+ +
-
+ From 0f239b0f896e5b3ca4dce8ce3b18e50ebffe0632 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 23 Jun 2023 11:59:38 -0600 Subject: [PATCH 020/154] fixing strings - i18n --- www/i18n/en.json | 1 + www/templates/control/main-control.html | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/www/i18n/en.json b/www/i18n/en.json index 644f6d55c..39c493736 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -101,6 +101,7 @@ "nuke-everything" : "Everything", "clear-data": "Clear data", "are-you-sure": "Are you sure?", + "log-out": "Log Out", "log-out-warning": "You will be logged out and your credentials will not be saved. Unsynced data may be lost.", "cancel": "Cancel", "confirm": "Confirm", diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 75bd137af..b3b923b75 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -7,7 +7,7 @@
--> - + @@ -74,7 +74,7 @@
--> - + - - + + @@ -54,7 +54,7 @@
--> - + - + - + - + - +
- + - +
@@ -157,7 +157,7 @@
{{'control.end-trip-sync'}}
--> - + - + - + - + - + @@ -231,7 +231,8 @@
{{settings.clientAppVer}}
--> - - + + + From cad10341408807f93ce18592d7b5e6ddcb8de8cb Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 23 Jun 2023 17:00:21 -0600 Subject: [PATCH 023/154] tidy code - still ways to go --- www/js/control/SettingRow.jsx | 2 +- www/templates/control/main-control.html | 120 ++---------------------- 2 files changed, 11 insertions(+), 111 deletions(-) diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 0df5178e1..ca850711f 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -2,7 +2,7 @@ import React from "react"; import { angularize} from "../angular-react-helper"; import { List } from 'react-native-paper'; import { useTranslation } from "react-i18next"; -import { string, func, object} from "prop-types"; +import { string, func} from "prop-types"; const SettingRow = ({textKey, iconName, action}) => { const { t } = useTranslation(); //this accesses the translations diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 5c7a02d0c..73f5ccad7 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -9,24 +9,16 @@ - + - - + - - + - +
{{'control.reminders-time-of-day' | i18next: {time: settings.notification.prefReminderTime} }} @@ -38,11 +30,6 @@ style="position: absolute; height: 100%; inset: 0; display: flex; opacity: 0;">
- - @@ -74,23 +57,8 @@
--> - - - - - - - - - - + -
- - - + - - + - - + - - - - - - - + - - + - - - - - - From b6b1fc6a09dc88fbc156bc1ca351b72bbaa07dcd Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 23 Jun 2023 17:08:30 -0600 Subject: [PATCH 024/154] combine logout and copy (eliminate copy) --- www/i18n/en.json | 1 - www/js/control/general-settings.js | 9 +-------- www/templates/control/main-control.html | 3 +-- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/www/i18n/en.json b/www/i18n/en.json index 39c493736..644f6d55c 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -101,7 +101,6 @@ "nuke-everything" : "Everything", "clear-data": "Clear data", "are-you-sure": "Are you sure?", - "log-out": "Log Out", "log-out-warning": "You will be logged out and your credentials will not be saved. Unsynced data may be lost.", "cancel": "Cancel", "confirm": "Confirm", diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index b0f390a39..6ead71ea4 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -394,14 +394,7 @@ angular.module('emission.main.control',['emission.services', navigator.clipboard.writeText(textToCopy).then(() => { ionicToast.show('{Copied to clipboard!}', 'bottom', false, 2000); }); - } - - // needed a parameter-less function to maintain the flow with the react transition :) - $scope.copyOpToClipboard = function() { - navigator.clipboard.writeText($scope.settings.auth.opcode).then(() => { - ionicToast.show('{Copied to clipboard!}', 'bottom', false, 2000); - }); - } + } $scope.logOut = function() { $ionicPopup.confirm({ diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 73f5ccad7..46d5bb4ee 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -6,8 +6,7 @@
{{settings.auth.opcode}}
--> - - + From e8b7b19a3e90230206063b66b888ffc1032ced9c Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Mon, 26 Jun 2023 10:22:39 -0600 Subject: [PATCH 025/154] convert to use of IconButtons now the button is what has action, not the entire row - there's a bug requiring an empty onPress in the list Item for the one in IconButton to work see issue https://github.com/callstack/react-native-paper/issues/3898 and hopefully soon to be fix https://github.com/callstack/react-native-paper/pull/3908 --- www/js/control/SettingRow.jsx | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index ca850711f..97c58b0f5 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -1,6 +1,6 @@ import React from "react"; import { angularize} from "../angular-react-helper"; -import { List } from 'react-native-paper'; +import { List, IconButton} from 'react-native-paper'; import { useTranslation } from "react-i18next"; import { string, func} from "prop-types"; @@ -8,13 +8,17 @@ const SettingRow = ({textKey, iconName, action}) => { const { t } = useTranslation(); //this accesses the translations return ( - action(e)} - right={props => } - > - - ); + console.log("empty")} + right={() => ( + action(e)} + /> + )} + /> + ); }; SettingRow.propTypes = { textKey: string, From ebeca540dd0fab3529313910495a9d45b6349644 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Mon, 26 Jun 2023 12:27:01 -0600 Subject: [PATCH 026/154] introduce toggle switches the two items that had switches are now interactive again, there is work to be done with getting the switches to have the proper starting value toggle switch: https://callstack.github.io/react-native-paper/docs/components/Switch/ --- www/js/control/SettingRow.jsx | 56 ++++++++++++---- www/templates/control/main-control.html | 87 ++++++++++--------------- 2 files changed, 80 insertions(+), 63 deletions(-) diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 97c58b0f5..269b1cef6 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -1,29 +1,63 @@ import React from "react"; import { angularize} from "../angular-react-helper"; -import { List, IconButton} from 'react-native-paper'; +import { List, IconButton, Switch} from 'react-native-paper'; import { useTranslation } from "react-i18next"; -import { string, func} from "prop-types"; +import { string, func, bool} from "prop-types"; -const SettingRow = ({textKey, iconName, action}) => { - const { t } = useTranslation(); //this accesses the translations +const ToggleSwitch = ({onSwitch, getVal}) => { + // const startResult = getVal(); + const [isSwitchOn, setIsSwitchOn] = React.useState(true); + + const onToggleSwitch = function() { + onSwitch(); + setIsSwitchOn(!isSwitchOn); + }; + + return ; +}; +ToggleSwitch.propTypes = { + onSwitch: func, + startVal: func +} - return ( - { + const { t } = useTranslation(); //this accesses the translations + if(!isToggle) + { + return ( + console.log("empty")} right={() => ( - action(e)} - /> + /> + )} + /> + ); + } + else{ + return ( + console.log("empty")} + right={() => ( + )} - /> - ); + /> + ); + } }; SettingRow.propTypes = { textKey: string, iconName: string, - action: func + action: func, + isToggle: bool, + toggleVal: func } angularize(SettingRow, 'emission.main.control.settingRow'); diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 46d5bb4ee..cc8ced263 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -11,11 +11,9 @@ - - + - - +
@@ -40,9 +38,9 @@
--> - + - + - + - - - + + + - - + + + + - + - - +
- - - + -
- +
- + - + - + - + - - + + - + - + - + - + - + - + - + - - - - - + + + +
From 813c4f5da5673b4adc6d56ce83beacb1fb0de93e Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Mon, 26 Jun 2023 14:32:23 -0600 Subject: [PATCH 027/154] experiment with list accordian converted the user data section to a list accordian - works great! Unfortunately relies right now on a lot of hard-coding --- www/js/control/UserData.jsx | 31 +++++++++++++++++++++++++ www/js/control/general-settings.js | 4 +++- www/templates/control/main-control.html | 12 ++-------- 3 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 www/js/control/UserData.jsx diff --git a/www/js/control/UserData.jsx b/www/js/control/UserData.jsx new file mode 100644 index 000000000..bc93e918a --- /dev/null +++ b/www/js/control/UserData.jsx @@ -0,0 +1,31 @@ +import React from "react"; +import { angularize} from "../angular-react-helper"; +import { List } from 'react-native-paper'; +import { useTranslation } from "react-i18next"; +import { array, func } from "prop-types"; +import SettingRow from "./SettingRow"; +import ControlDataTable from "./ControlDataTable"; + +const UserData = ({data, buttonAction}) => { + const { t } = useTranslation(); //this accesses the translations + const [expanded, setExpanded] = React.useState(false); + + const handlePress = () => setExpanded(!expanded); + + return ( + + + + + ); +}; +UserData.propTypes = { + data: array, + buttonAction: func +} + +angularize(UserData, 'emission.main.control.userData'); +export default UserData; \ No newline at end of file diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 6ead71ea4..3d528ec0b 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -4,6 +4,7 @@ import angular from 'angular'; import QrCode from './QrCode'; import ControlDataTable from './ControlDataTable'; import SettingRow from './SettingRow'; +import UserData from './UserData'; angular.module('emission.main.control',['emission.services', 'emission.i18n.utils', @@ -23,7 +24,8 @@ angular.module('emission.main.control',['emission.services', 'emission.config.dynamic', QrCode.module, ControlDataTable.module, - SettingRow.module]) + SettingRow.module, + UserData.module]) .controller('ControlCtrl', function($scope, $window, $ionicScrollDelegate, $ionicPlatform, diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index cc8ced263..7ecdb5d14 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -68,16 +68,8 @@ - - - -
- - -
+ + From 4553eb7133007b029efc32e9633c5fdef1f8ad03 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Mon, 26 Jun 2023 15:21:08 -0600 Subject: [PATCH 028/154] use of .map for creating the collapsing list! first-run at creating a drop-down part of the menu with map and passing in a data structure --- www/js/control/MenuDropper.jsx | 57 +++++++++++++++++++++++++ www/js/control/general-settings.js | 4 +- www/templates/control/main-control.html | 6 ++- 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 www/js/control/MenuDropper.jsx diff --git a/www/js/control/MenuDropper.jsx b/www/js/control/MenuDropper.jsx new file mode 100644 index 000000000..935446a70 --- /dev/null +++ b/www/js/control/MenuDropper.jsx @@ -0,0 +1,57 @@ +import React from "react"; +import { angularize} from "../angular-react-helper"; +import { List } from 'react-native-paper'; +import { useTranslation } from "react-i18next"; +import { array, string } from "prop-types"; +import SettingRow from "./SettingRow"; +import ControlDataTable from "./ControlDataTable"; + +/* hoping for a dropdown that is parameterized +some sort of object passed in that dicates everything +each "thing" in the list will either be a dataTable +or a settingRow +and needs to be handled accordingly */ + +/* + {type: row/table + data: [] + titleKey: '' + action: function + iconName: '' + isToggle: bool} +*/ + + +const ExpansionSection = ({sectionTitle, sectionContents}) => { + const { t } = useTranslation(); //this accesses the translations + const [expanded, setExpanded] = React.useState(false); + + const handlePress = () => setExpanded(!expanded); + + return ( + + {sectionContents?.map((item) => + { + if(item.type == "row") + { + return ( ); + } + else + { + return () + } + } + )} + + ); +}; +ExpansionSection.propTypes = { + sectionContents: array, + sectionTitle: string +} + +angularize(ExpansionSection, 'emission.main.control.expansionSection'); +export default ExpansionSection; \ No newline at end of file diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 3d528ec0b..7b3c76b34 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -5,6 +5,7 @@ import QrCode from './QrCode'; import ControlDataTable from './ControlDataTable'; import SettingRow from './SettingRow'; import UserData from './UserData'; +import ExpansionSection from './MenuDropper'; angular.module('emission.main.control',['emission.services', 'emission.i18n.utils', @@ -25,7 +26,8 @@ angular.module('emission.main.control',['emission.services', QrCode.module, ControlDataTable.module, SettingRow.module, - UserData.module]) + UserData.module, + ExpansionSection.module]) .controller('ControlCtrl', function($scope, $window, $ionicScrollDelegate, $ionicPlatform, diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 7ecdb5d14..07150c846 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -68,8 +68,10 @@ - - + + + From 2934b516a7d39a8535c96d3944ae00f809ac0cff Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Mon, 26 Jun 2023 16:05:07 -0600 Subject: [PATCH 029/154] both drop-downs converted to ExpandMenu components fully converted the userData and devZone to the react element, creates an accordian element that has SettingRow and ControlDataTable elements --- .../{MenuDropper.jsx => ExpandMenu.jsx} | 0 www/js/control/UserData.jsx | 31 --------- www/js/control/general-settings.js | 4 +- www/templates/control/main-control.html | 65 +++++++------------ 4 files changed, 23 insertions(+), 77 deletions(-) rename www/js/control/{MenuDropper.jsx => ExpandMenu.jsx} (100%) delete mode 100644 www/js/control/UserData.jsx diff --git a/www/js/control/MenuDropper.jsx b/www/js/control/ExpandMenu.jsx similarity index 100% rename from www/js/control/MenuDropper.jsx rename to www/js/control/ExpandMenu.jsx diff --git a/www/js/control/UserData.jsx b/www/js/control/UserData.jsx deleted file mode 100644 index bc93e918a..000000000 --- a/www/js/control/UserData.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; -import { angularize} from "../angular-react-helper"; -import { List } from 'react-native-paper'; -import { useTranslation } from "react-i18next"; -import { array, func } from "prop-types"; -import SettingRow from "./SettingRow"; -import ControlDataTable from "./ControlDataTable"; - -const UserData = ({data, buttonAction}) => { - const { t } = useTranslation(); //this accesses the translations - const [expanded, setExpanded] = React.useState(false); - - const handlePress = () => setExpanded(!expanded); - - return ( - - - - - ); -}; -UserData.propTypes = { - data: array, - buttonAction: func -} - -angularize(UserData, 'emission.main.control.userData'); -export default UserData; \ No newline at end of file diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 7b3c76b34..8b06134d7 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -4,8 +4,7 @@ import angular from 'angular'; import QrCode from './QrCode'; import ControlDataTable from './ControlDataTable'; import SettingRow from './SettingRow'; -import UserData from './UserData'; -import ExpansionSection from './MenuDropper'; +import ExpansionSection from './ExpandMenu'; angular.module('emission.main.control',['emission.services', 'emission.i18n.utils', @@ -26,7 +25,6 @@ angular.module('emission.main.control',['emission.services', QrCode.module, ControlDataTable.module, SettingRow.module, - UserData.module, ExpansionSection.module]) .controller('ControlCtrl', function($scope, $window, diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 07150c846..7b6f4a130 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -1,18 +1,12 @@ - - - @@ -68,47 +62,32 @@ - + - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + -
From ddff05475e38a8ec48557795392b3dfd408d8bbc Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 27 Jun 2023 15:14:01 -0600 Subject: [PATCH 030/154] profile settings component draft --- www/js/control/ProfileSettings.jsx | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 www/js/control/ProfileSettings.jsx diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx new file mode 100644 index 000000000..b0b46a96e --- /dev/null +++ b/www/js/control/ProfileSettings.jsx @@ -0,0 +1,34 @@ +import React from "react"; +import { angularize} from "../angular-react-helper"; +import { List, IconButton, Switch} from 'react-native-paper'; +import { useTranslation } from "react-i18next"; +import { string, func, bool} from "prop-types"; +import ExpansionSection from "./ExpandMenu"; +import SettingRow from "./SettingRow"; +import ControlDataTable from "./ControlDataTable"; + + +const ProfileSettings = ({ }) => { + return ( + <> + {/*
+
+ + +
+
*/} + + + + ); + }; + + ProfileSettings.propTypes = { + + } + + angularize(ProfileSettings, 'emission.main.control.profileSettings'); + export default ProfileSettings; + \ No newline at end of file From 803f7827347672513b24b1d72edf176cd5873615 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 27 Jun 2023 15:14:52 -0600 Subject: [PATCH 031/154] profile settings in general settings --- www/js/control/general-settings.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 8b06134d7..aab483038 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -5,6 +5,7 @@ import QrCode from './QrCode'; import ControlDataTable from './ControlDataTable'; import SettingRow from './SettingRow'; import ExpansionSection from './ExpandMenu'; +import ProfileSettings from './ProfileSettings'; angular.module('emission.main.control',['emission.services', 'emission.i18n.utils', @@ -25,7 +26,8 @@ angular.module('emission.main.control',['emission.services', QrCode.module, ControlDataTable.module, SettingRow.module, - ExpansionSection.module]) + ExpansionSection.module, + ProfileSettings.module]) .controller('ControlCtrl', function($scope, $window, $ionicScrollDelegate, $ionicPlatform, @@ -219,6 +221,22 @@ angular.module('emission.main.control',['emission.services', } } $scope.toggleLowAccuracy = ControlCollectionHelper.toggleLowAccuracy; + + $scope.getTracking = function() { + console.log("tracking on or off?", $scope.settings.collect.trackingOn); + // return true: toggle on; return false: toggle off. + var isTracking = $scope.settings.collect.trackingOn; + if (!angular.isDefined(isTracking)) { + // config not loaded when loading ui, set default as false + // TODO: Read the value if it is not defined. + // Otherwise, don't we have a race with reading? + // we don't really $apply on this field... + return false; + } else { + console.log("tracking on or off?", $scope.settings.collect.trackingOn); + return $scope.settings.collect.trackingOn; + } + } $scope.getConnectURL = function() { ControlHelper.getSettings().then(function(response) { From 757936cbd7400c36528319137f8f0c43f97f2328 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 27 Jun 2023 16:48:34 -0600 Subject: [PATCH 032/154] first set settings now in ProfileSettingsComponent creating an over-arching component for the ProfileSettings -- still passing in functions, some things have bugs/functional differences noted in comments --- www/js/control/ProfileSettings.jsx | 35 ++++++++++++--------- www/templates/control/main-control.html | 42 ++++++++++++++----------- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index b0b46a96e..bd3fbb14d 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,32 +1,37 @@ import React from "react"; -import { angularize} from "../angular-react-helper"; +import { angularize, getAngularService, createScopeWithVars } from "../angular-react-helper"; import { List, IconButton, Switch} from 'react-native-paper'; import { useTranslation } from "react-i18next"; -import { string, func, bool} from "prop-types"; +import { object } from "prop-types"; import ExpansionSection from "./ExpandMenu"; import SettingRow from "./SettingRow"; import ControlDataTable from "./ControlDataTable"; +const ProfileSettings = ({tempFuncBundle}) => { -const ProfileSettings = ({ }) => { return ( <> - {/*
-
- - -
-
*/} - - + + + + {/* this toggle only kinda works */} + + + {/* this switch is also a troublemaker (it's just not fully implemented well yet) */} + + + + + + {/* this row missing condition!!! Should only show iff ui_config.profile_controls.support_upload == true */} + + ); - }; + }; ProfileSettings.propTypes = { - + tempFuncBundle: object } angularize(ProfileSettings, 'emission.main.control.profileSettings'); diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 7b6f4a130..f548b319a 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -1,14 +1,27 @@ - + + - - -
@@ -32,9 +45,7 @@
--> - - - + - - - - - - - + - - + - + From b4c70b318f4a8828408f51dee526bc4eba869883 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 27 Jun 2023 17:24:42 -0600 Subject: [PATCH 033/154] different method of passing info to Profile Settings --- www/js/control/ProfileSettings.jsx | 44 ++++++++++++++++--------- www/templates/control/main-control.html | 18 +--------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index bd3fbb14d..05c56f5f6 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,37 +1,49 @@ import React from "react"; import { angularize, getAngularService, createScopeWithVars } from "../angular-react-helper"; -import { List, IconButton, Switch} from 'react-native-paper'; -import { useTranslation } from "react-i18next"; import { object } from "prop-types"; import ExpansionSection from "./ExpandMenu"; import SettingRow from "./SettingRow"; import ControlDataTable from "./ControlDataTable"; +import DemographicsSettingRow from "./DemographicsSettingRow"; -const ProfileSettings = ({tempFuncBundle}) => { +const ProfileSettings = ({ settingsScope }) => { + + // settingsScope is the $scope of general-settings.js + // grab any variables or functions you need from it like this: + const { logOut, viewPrivacyPolicy, viewQRCode, userStartStopTracking, + fixAppStatus, toggleLowAccuracy, changeCarbonDataset, + forceSync, share, openDatePicker, uploadLog, emailLog, + eraseUserData, userData, carbonString, settings } = settingsScope; return ( <> - - - + {/* */} + + + {/* this toggle only kinda works */} - - + + {/* this switch is also a troublemaker (it's just not fully implemented well yet) */} - - - - - + + + + + {/* this row missing condition!!! Should only show iff ui_config.profile_controls.support_upload == true */} - - + + + + + + + ); }; ProfileSettings.propTypes = { - tempFuncBundle: object + settingsScope: object } angularize(ProfileSettings, 'emission.main.control.profileSettings'); diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index f548b319a..43077e909 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -1,23 +1,7 @@ - - + From df5da3567b76c8397fd60c83fb93dcf476eccc63 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 27 Jun 2023 17:24:58 -0600 Subject: [PATCH 034/154] introduce Demographics Setting Row --- www/js/control/DemographicsSettingRow.jsx | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 www/js/control/DemographicsSettingRow.jsx diff --git a/www/js/control/DemographicsSettingRow.jsx b/www/js/control/DemographicsSettingRow.jsx new file mode 100644 index 000000000..d30017598 --- /dev/null +++ b/www/js/control/DemographicsSettingRow.jsx @@ -0,0 +1,31 @@ +import React from "react"; +import { angularize } from "../angular-react-helper"; +import { getAngularService } from "../angular-react-helper"; +import SettingRow from "./SettingRow"; + +const DemographicsSettingRow = ({ }) => { + + const EnketoDemographicsService = getAngularService('EnketoDemographicsService'); + const EnketoSurveyLaunch = getAngularService('EnketoSurveyLaunch'); + const $rootScope = getAngularService('$rootScope'); + + // copied from /js/survey/enketo/enketo-demographics.js + function openPopover() { + return EnketoDemographicsService.loadPriorDemographicSurvey().then((lastSurvey) => { + return EnketoSurveyLaunch + .launch($rootScope, 'UserProfileSurvey', { + prefilledSurveyResponse: lastSurvey?.data?.xmlResponse, + showBackButton: true, showFormFooterJumpNav: true + }) + .then(result => { + console.log("demographic survey result ", result); + }); + }); + } + + return +}; + +angularize(DemographicsSettingRow, 'emission.main.control.demographicsSettingRow'); +export default DemographicsSettingRow; \ No newline at end of file From a9043a284b225c7991c90975f1dd7e789c1f3fab Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 28 Jun 2023 10:24:29 -0600 Subject: [PATCH 035/154] rough sketch everything in ProfileSettings working towards full migration and pulling everything into a single react Component. This is not working perfectly, there are a lot of things to handle that are currently commented out --- www/js/control/ProfileSettings.jsx | 63 ++++++++++++++++++--- www/js/control/SettingRow.jsx | 74 ++++++++++--------------- www/templates/control/main-control.html | 14 +---- 3 files changed, 88 insertions(+), 63 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 05c56f5f6..d1c37d95f 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { angularize, getAngularService, createScopeWithVars } from "../angular-react-helper"; +import { angularize } from "../angular-react-helper"; import { object } from "prop-types"; import ExpansionSection from "./ExpandMenu"; import SettingRow from "./SettingRow"; @@ -13,20 +13,41 @@ const ProfileSettings = ({ settingsScope }) => { const { logOut, viewPrivacyPolicy, viewQRCode, userStartStopTracking, fixAppStatus, toggleLowAccuracy, changeCarbonDataset, forceSync, share, openDatePicker, uploadLog, emailLog, - eraseUserData, userData, carbonString, settings } = settingsScope; + eraseUserData, userData, carbonDatasetString, settings, + refreshScreen, endForceSync, checkConsent, dummyNotification, + invalidateCache, nukeUserCache, showLog, showSensed, + editCollectionConfig, editSyncConfig } = settingsScope; + + // let opcode = settings.auth.opcode + function getTracking(){ + if(settings?.collect?.trackingOn){ + return settings.collect.trackingOn; + } + else{ + return false; + } + } + function getOpcode(){ + if(settings?.auth?.opcode){ + return settings.auth.opcode; + } + else{ + return "ERROR GETTING OPCODE"; + } + } return ( <> - {/* */} + {/* */} {/* this toggle only kinda works */} - + {/* */} {/* this switch is also a troublemaker (it's just not fully implemented well yet) */} - - + {/* */} + @@ -34,10 +55,38 @@ const ProfileSettings = ({ settingsScope }) => { - + + + {/* + {type: 'row', data: [], textKey: 'control.collection', action: editCollectionConfig, iconName: 'pencil', isToggle: false}, + {type: 'table', data: settings.collect.show_config}, + {type: 'row', data: [], textKey: 'control.sync', action: editSyncConfig, iconName: 'pencil', isToggle: false}, + {type: 'table', data: settings.sync.show_config}, + {type: 'row', data: [], textKey: 'control.app-version', action: '', iconName: 'application', isToggle: false}, + {type: 'row', data: [], textKey: settings.clientAppVer, action: '', iconName: '', isToggle: false} */} + + + + + + + + console.log("")}> + {/* */} + + + {/* */} + + + + {/* */} + + console.log("")}> + {/* console.log("")}> */} + ); }; diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 269b1cef6..f7cb5b4d7 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -4,60 +4,46 @@ import { List, IconButton, Switch} from 'react-native-paper'; import { useTranslation } from "react-i18next"; import { string, func, bool} from "prop-types"; -const ToggleSwitch = ({onSwitch, getVal}) => { - // const startResult = getVal(); - const [isSwitchOn, setIsSwitchOn] = React.useState(true); +// const ToggleSwitch = ({onSwitch, switchVal}) => { +// // const startResult = getVal(); +// const [isSwitchOn, setIsSwitchOn] = React.useState(true); - const onToggleSwitch = function() { - onSwitch(); - setIsSwitchOn(!isSwitchOn); - }; +// const onToggleSwitch = function() { +// onSwitch(); +// setIsSwitchOn(!isSwitchOn); +// }; - return ; -}; -ToggleSwitch.propTypes = { - onSwitch: func, - startVal: func -} +// return ; +// }; +// ToggleSwitch.propTypes = { +// onSwitch: func, +// startVal: func +// } -const SettingRow = ({textKey, iconName, action, isToggle, toggleVal}) => { +const SettingRow = ({textKey, iconName, action, switchValue}) => { const { t } = useTranslation(); //this accesses the translations - if(!isToggle) - { - return ( - console.log("empty")} - right={() => ( - action(e)} - /> - )} - /> - ); - } - else{ - return ( - console.log("empty")} - right={() => ( - - )} - /> - ); + + let rightComponent; + // will using switchValue here only render when the value is true? + if (switchValue) { + rightComponent = action(e)}/>; + } else { + rightComponent = action(e)} />; } + + return ( + console.log("empty")} + right={() => rightComponent} + /> + ); }; SettingRow.propTypes = { textKey: string, iconName: string, action: func, - isToggle: bool, - toggleVal: func + switchValue: bool } angularize(SettingRow, 'emission.main.control.settingRow'); diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 43077e909..c571a1835 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -3,9 +3,6 @@ - - -
@@ -50,15 +47,8 @@ - - - - - - + --> From e0f9983d76737050e288c333c12f0aafa8b7a72c Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 28 Jun 2023 10:29:02 -0600 Subject: [PATCH 036/154] more of migration to ProfileSettings component removing some commented out code in ExpandMenu.jsx, props is now the listed parameter, this allows to get the default parameter props.children to populate the inner components. the section Title is accessed with props.sectionTitle --- www/js/control/ExpandMenu.jsx | 20 ++++++-------------- www/js/control/ProfileSettings.jsx | 9 --------- www/templates/control/main-control.html | 24 ------------------------ 3 files changed, 6 insertions(+), 47 deletions(-) diff --git a/www/js/control/ExpandMenu.jsx b/www/js/control/ExpandMenu.jsx index 935446a70..795a613a1 100644 --- a/www/js/control/ExpandMenu.jsx +++ b/www/js/control/ExpandMenu.jsx @@ -21,30 +21,22 @@ and needs to be handled accordingly */ isToggle: bool} */ +//any pure functions can go outside -const ExpansionSection = ({sectionTitle, sectionContents}) => { +const ExpansionSection = (props) => { const { t } = useTranslation(); //this accesses the translations const [expanded, setExpanded] = React.useState(false); const handlePress = () => setExpanded(!expanded); + // anything that mutates must go in --- depend on props or state... + return ( - {sectionContents?.map((item) => - { - if(item.type == "row") - { - return ( ); - } - else - { - return () - } - } - )} + {props.children} ); }; diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index d1c37d95f..08b8ca1ac 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -60,15 +60,6 @@ const ProfileSettings = ({ settingsScope }) => { - {/* - {type: 'row', data: [], textKey: 'control.collection', action: editCollectionConfig, iconName: 'pencil', isToggle: false}, - {type: 'table', data: settings.collect.show_config}, - {type: 'row', data: [], textKey: 'control.sync', action: editSyncConfig, iconName: 'pencil', isToggle: false}, - {type: 'table', data: settings.sync.show_config}, - {type: 'row', data: [], textKey: 'control.app-version', action: '', iconName: 'application', isToggle: false}, - {type: 'row', data: [], textKey: settings.clientAppVer, action: '', iconName: '', isToggle: false} */} - - diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index c571a1835..ab4d37081 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -45,29 +45,5 @@ - - - - - From 7bc97b7a02396156caabb68150c5780651123156 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 28 Jun 2023 10:39:58 -0600 Subject: [PATCH 037/154] adding a value in the scope for the accuracy setting working on the switches, and trying to get their displayed value to match the value of the think they control --- www/js/control/general-settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index aab483038..ccfd9baea 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -217,6 +217,7 @@ angular.module('emission.main.control',['emission.services', // we don't really $apply on this field... return false; } else { + $scope.settings.collect.lowAccuracy = isMediumAccuracy; //adding to scope to use w/ switches return isMediumAccuracy; } } From a3097ac7f383ef408e326c1c3d5f8c2ef1e7f05f Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 28 Jun 2023 11:27:02 -0600 Subject: [PATCH 038/154] adding additional argument to angularize call this is needed from changes in #993 --- www/js/control/ControlDataTable.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/control/ControlDataTable.jsx b/www/js/control/ControlDataTable.jsx index 48ab5e4b3..84f6d724e 100644 --- a/www/js/control/ControlDataTable.jsx +++ b/www/js/control/ControlDataTable.jsx @@ -34,5 +34,5 @@ ControlDataTable.propTypes = { // need call to angularize to let the React and Angular co-mingle //second argument is "module path" - can access later as ControlDataTable.module -angularize(ControlDataTable, 'emission.main.control.dataTable'); +angularize(ControlDataTable, 'ControlDataTable', 'emission.main.control.dataTable'); export default ControlDataTable; From aa027ad415966a4f9fa540b8861c2c783db7da3e Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 28 Jun 2023 13:17:46 -0600 Subject: [PATCH 039/154] cleaned up irrelivant codes and comments --- www/js/control/ExpandMenu.jsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/www/js/control/ExpandMenu.jsx b/www/js/control/ExpandMenu.jsx index 795a613a1..27fde3baa 100644 --- a/www/js/control/ExpandMenu.jsx +++ b/www/js/control/ExpandMenu.jsx @@ -3,26 +3,8 @@ import { angularize} from "../angular-react-helper"; import { List } from 'react-native-paper'; import { useTranslation } from "react-i18next"; import { array, string } from "prop-types"; -import SettingRow from "./SettingRow"; -import ControlDataTable from "./ControlDataTable"; - -/* hoping for a dropdown that is parameterized -some sort of object passed in that dicates everything -each "thing" in the list will either be a dataTable -or a settingRow -and needs to be handled accordingly */ - -/* - {type: row/table - data: [] - titleKey: '' - action: function - iconName: '' - isToggle: bool} -*/ //any pure functions can go outside - const ExpansionSection = (props) => { const { t } = useTranslation(); //this accesses the translations const [expanded, setExpanded] = React.useState(false); @@ -41,7 +23,6 @@ const ExpansionSection = (props) => { ); }; ExpansionSection.propTypes = { - sectionContents: array, sectionTitle: string } From b612292aa4d933eb2356f75b8b5eddc4317d0479 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 28 Jun 2023 13:57:21 -0600 Subject: [PATCH 040/154] Revert "fixing merge conflicts" This reverts commit ffd2bc2bf0b1595566b7287f64d57d9b91a8ad3b, reversing changes made to a3097ac7f383ef408e326c1c3d5f8c2ef1e7f05f. --- www/js/control/DemographicsSettingRow.jsx | 31 ----- www/js/control/ExpandMenu.jsx | 30 ---- www/js/control/ProfileSettings.jsx | 91 ------------- www/js/control/SettingRow.jsx | 50 ------- www/js/control/general-settings.js | 29 +--- www/templates/control/main-control.html | 159 +++++++++++++++++++--- 6 files changed, 143 insertions(+), 247 deletions(-) delete mode 100644 www/js/control/DemographicsSettingRow.jsx delete mode 100644 www/js/control/ExpandMenu.jsx delete mode 100644 www/js/control/ProfileSettings.jsx delete mode 100644 www/js/control/SettingRow.jsx diff --git a/www/js/control/DemographicsSettingRow.jsx b/www/js/control/DemographicsSettingRow.jsx deleted file mode 100644 index d30017598..000000000 --- a/www/js/control/DemographicsSettingRow.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; -import { angularize } from "../angular-react-helper"; -import { getAngularService } from "../angular-react-helper"; -import SettingRow from "./SettingRow"; - -const DemographicsSettingRow = ({ }) => { - - const EnketoDemographicsService = getAngularService('EnketoDemographicsService'); - const EnketoSurveyLaunch = getAngularService('EnketoSurveyLaunch'); - const $rootScope = getAngularService('$rootScope'); - - // copied from /js/survey/enketo/enketo-demographics.js - function openPopover() { - return EnketoDemographicsService.loadPriorDemographicSurvey().then((lastSurvey) => { - return EnketoSurveyLaunch - .launch($rootScope, 'UserProfileSurvey', { - prefilledSurveyResponse: lastSurvey?.data?.xmlResponse, - showBackButton: true, showFormFooterJumpNav: true - }) - .then(result => { - console.log("demographic survey result ", result); - }); - }); - } - - return -}; - -angularize(DemographicsSettingRow, 'emission.main.control.demographicsSettingRow'); -export default DemographicsSettingRow; \ No newline at end of file diff --git a/www/js/control/ExpandMenu.jsx b/www/js/control/ExpandMenu.jsx deleted file mode 100644 index 27fde3baa..000000000 --- a/www/js/control/ExpandMenu.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import { angularize} from "../angular-react-helper"; -import { List } from 'react-native-paper'; -import { useTranslation } from "react-i18next"; -import { array, string } from "prop-types"; - -//any pure functions can go outside -const ExpansionSection = (props) => { - const { t } = useTranslation(); //this accesses the translations - const [expanded, setExpanded] = React.useState(false); - - const handlePress = () => setExpanded(!expanded); - - // anything that mutates must go in --- depend on props or state... - - return ( - - {props.children} - - ); -}; -ExpansionSection.propTypes = { - sectionTitle: string -} - -angularize(ExpansionSection, 'emission.main.control.expansionSection'); -export default ExpansionSection; \ No newline at end of file diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx deleted file mode 100644 index 08b8ca1ac..000000000 --- a/www/js/control/ProfileSettings.jsx +++ /dev/null @@ -1,91 +0,0 @@ -import React from "react"; -import { angularize } from "../angular-react-helper"; -import { object } from "prop-types"; -import ExpansionSection from "./ExpandMenu"; -import SettingRow from "./SettingRow"; -import ControlDataTable from "./ControlDataTable"; -import DemographicsSettingRow from "./DemographicsSettingRow"; - -const ProfileSettings = ({ settingsScope }) => { - - // settingsScope is the $scope of general-settings.js - // grab any variables or functions you need from it like this: - const { logOut, viewPrivacyPolicy, viewQRCode, userStartStopTracking, - fixAppStatus, toggleLowAccuracy, changeCarbonDataset, - forceSync, share, openDatePicker, uploadLog, emailLog, - eraseUserData, userData, carbonDatasetString, settings, - refreshScreen, endForceSync, checkConsent, dummyNotification, - invalidateCache, nukeUserCache, showLog, showSensed, - editCollectionConfig, editSyncConfig } = settingsScope; - - // let opcode = settings.auth.opcode - function getTracking(){ - if(settings?.collect?.trackingOn){ - return settings.collect.trackingOn; - } - else{ - return false; - } - } - function getOpcode(){ - if(settings?.auth?.opcode){ - return settings.auth.opcode; - } - else{ - return "ERROR GETTING OPCODE"; - } - } - - return ( - <> - {/* */} - - - - {/* this toggle only kinda works */} - {/* */} - - {/* this switch is also a troublemaker (it's just not fully implemented well yet) */} - {/* */} - - - - - {/* this row missing condition!!! Should only show iff ui_config.profile_controls.support_upload == true */} - - - - - - - - - - - - - - console.log("")}> - {/* */} - - - {/* */} - - - - {/* */} - - console.log("")}> - {/* console.log("")}> */} - - - ); - }; - - ProfileSettings.propTypes = { - settingsScope: object - } - - angularize(ProfileSettings, 'emission.main.control.profileSettings'); - export default ProfileSettings; - \ No newline at end of file diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx deleted file mode 100644 index f7cb5b4d7..000000000 --- a/www/js/control/SettingRow.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from "react"; -import { angularize} from "../angular-react-helper"; -import { List, IconButton, Switch} from 'react-native-paper'; -import { useTranslation } from "react-i18next"; -import { string, func, bool} from "prop-types"; - -// const ToggleSwitch = ({onSwitch, switchVal}) => { -// // const startResult = getVal(); -// const [isSwitchOn, setIsSwitchOn] = React.useState(true); - -// const onToggleSwitch = function() { -// onSwitch(); -// setIsSwitchOn(!isSwitchOn); -// }; - -// return ; -// }; -// ToggleSwitch.propTypes = { -// onSwitch: func, -// startVal: func -// } - -const SettingRow = ({textKey, iconName, action, switchValue}) => { - const { t } = useTranslation(); //this accesses the translations - - let rightComponent; - // will using switchValue here only render when the value is true? - if (switchValue) { - rightComponent = action(e)}/>; - } else { - rightComponent = action(e)} />; - } - - return ( - console.log("empty")} - right={() => rightComponent} - /> - ); -}; -SettingRow.propTypes = { - textKey: string, - iconName: string, - action: func, - switchValue: bool -} - -angularize(SettingRow, 'emission.main.control.settingRow'); -export default SettingRow; \ No newline at end of file diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index b28a2b1fd..2ea96bc1e 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -3,9 +3,6 @@ import angular from 'angular'; import ControlDataTable from './ControlDataTable'; import QrCode from '../components/QrCode'; -import SettingRow from './SettingRow'; -import ExpansionSection from './ExpandMenu'; -import ProfileSettings from './ProfileSettings'; angular.module('emission.main.control',['emission.services', 'emission.i18n.utils', @@ -24,10 +21,7 @@ angular.module('emission.main.control',['emission.services', 'emission.plugin.logger', 'emission.config.dynamic', QrCode.module, - ControlDataTable.module, - SettingRow.module, - ExpansionSection.module, - ProfileSettings.module]) + ControlDataTable.module]) .controller('ControlCtrl', function($scope, $window, $ionicScrollDelegate, $ionicPlatform, @@ -169,7 +163,7 @@ angular.module('emission.main.control',['emission.services', gender: userDataFromStorage.gender == 1? i18next.t('gender-male') : i18next.t('gender-female') } for (var i in temp) { - $scope.userData.push({key: i, val: temp[i]}); //changed from value to val! watch for rammifications! + $scope.userData.push({key: i, value: temp[i]}); } } }); @@ -221,27 +215,10 @@ angular.module('emission.main.control',['emission.services', // we don't really $apply on this field... return false; } else { - $scope.settings.collect.lowAccuracy = isMediumAccuracy; //adding to scope to use w/ switches return isMediumAccuracy; } } $scope.toggleLowAccuracy = ControlCollectionHelper.toggleLowAccuracy; - - $scope.getTracking = function() { - console.log("tracking on or off?", $scope.settings.collect.trackingOn); - // return true: toggle on; return false: toggle off. - var isTracking = $scope.settings.collect.trackingOn; - if (!angular.isDefined(isTracking)) { - // config not loaded when loading ui, set default as false - // TODO: Read the value if it is not defined. - // Otherwise, don't we have a race with reading? - // we don't really $apply on this field... - return false; - } else { - console.log("tracking on or off?", $scope.settings.collect.trackingOn); - return $scope.settings.collect.trackingOn; - } - } $scope.getConnectURL = function() { ControlHelper.getSettings().then(function(response) { @@ -419,7 +396,7 @@ angular.module('emission.main.control',['emission.services', navigator.clipboard.writeText(textToCopy).then(() => { ionicToast.show('{Copied to clipboard!}', 'bottom', false, 2000); }); - } + } $scope.logOut = function() { $ionicPopup.confirm({ diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index ab4d37081..eb98d3278 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -1,9 +1,19 @@ - - - - - + + +
+
{{settings.auth.opcode}}
+
+
+ +
+
{{'control.view-privacy'}}
+
+
+
+
{{'control.view-qrc'}}
+
+
{{'control.reminders-time-of-day' | i18next: {time: settings.notification.prefReminderTime} }} @@ -15,9 +25,7 @@ style="position: absolute; height: 100%; inset: 0; display: flex; opacity: 0;">
- - - - - - - - +
+
+
{{carbonDatasetString}}
+
+
+
+
{{'control.force-sync'}}
+
+
+
+
{{'control.share'}}
+
+
+ +
+
{{'control.download-json-dump'}}
+
+ +
+ +
+ +
+
{{'control.upload-log'}}
+
+
+ +
+
{{'control.email-log'}}
+
+
+ +
+
{{'control.user-data'}}
+
+
+ +
+
+
{{'control.erase-data'}}
+
+
+ + + +
{{entry.key}}
+
{{entry.value}}
+
+
+
+ + +
+
{{'control.dev-zone'}}
+
+
+ + +
+
+
{{'control.refresh'}}
+
+
+
+
{{'control.end-trip-sync'}}
+
+
+
+
{{'control.check-consent'}}
+
+
+
+
{{'control.dummy-notification'}}
+
+
+ +
+
{{'control.upcoming-notifications'}}
+
+ - - +
+
{{'control.invalidate-cached-docs'}}
+
+
+
+
{{'control.nuke-all'}}
+
+
+
+
{{parseState(settings.collect.state)}}
+
+
+
+
{{'control.check-log'}}
+
+
+
+
{{'control.check-sensed-data'}}
+
+
+ + +
+
{{'control.collection'}}
+
+
+ + +
+
{{'control.sync'}}
+
+
+ + +
+
{{'control.app-version'}}
+
{{settings.clientAppVer}}
+
From 51c0697bbd365e986388629dfab67413b07fe94e Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 28 Jun 2023 14:08:58 -0600 Subject: [PATCH 041/154] extra param in call to angularize func as a feature of #993 the calls to angularize need the name parameter, up to date and running smoothly! --- www/js/control/DemographicsSettingRow.jsx | 2 +- www/js/control/ExpandMenu.jsx | 2 +- www/js/control/ProfileSettings.jsx | 2 +- www/js/control/SettingRow.jsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/www/js/control/DemographicsSettingRow.jsx b/www/js/control/DemographicsSettingRow.jsx index d30017598..49e99c123 100644 --- a/www/js/control/DemographicsSettingRow.jsx +++ b/www/js/control/DemographicsSettingRow.jsx @@ -27,5 +27,5 @@ const DemographicsSettingRow = ({ }) => { textKey="control.edit-demographics" isToggle={false} /> }; -angularize(DemographicsSettingRow, 'emission.main.control.demographicsSettingRow'); +angularize(DemographicsSettingRow, 'DemographicsSettingRow','emission.main.control.demographicsSettingRow'); export default DemographicsSettingRow; \ No newline at end of file diff --git a/www/js/control/ExpandMenu.jsx b/www/js/control/ExpandMenu.jsx index 27fde3baa..d3f0c1337 100644 --- a/www/js/control/ExpandMenu.jsx +++ b/www/js/control/ExpandMenu.jsx @@ -26,5 +26,5 @@ ExpansionSection.propTypes = { sectionTitle: string } -angularize(ExpansionSection, 'emission.main.control.expansionSection'); +angularize(ExpansionSection, 'ExpansionSection', 'emission.main.control.expansionSection'); export default ExpansionSection; \ No newline at end of file diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 08b8ca1ac..1b3c2a417 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -86,6 +86,6 @@ const ProfileSettings = ({ settingsScope }) => { settingsScope: object } - angularize(ProfileSettings, 'emission.main.control.profileSettings'); + angularize(ProfileSettings, 'ProfileSettings', 'emission.main.control.profileSettings'); export default ProfileSettings; \ No newline at end of file diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index f7cb5b4d7..fdcc113d3 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -46,5 +46,5 @@ SettingRow.propTypes = { switchValue: bool } -angularize(SettingRow, 'emission.main.control.settingRow'); +angularize(SettingRow, 'SettingRow', 'emission.main.control.settingRow'); export default SettingRow; \ No newline at end of file From 60eb532634c7363d6aab2cbf2cafeb3b92ec75dc Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 28 Jun 2023 14:39:25 -0600 Subject: [PATCH 042/154] optional chaining with settings object lots of errors related to undefined settings object, using optional chaining - things don't show up, but I have faith that they will once settings is defined at the point in time that the information is asked for --- www/js/control/ExpandMenu.jsx | 3 --- www/js/control/ProfileSettings.jsx | 18 ++++++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/www/js/control/ExpandMenu.jsx b/www/js/control/ExpandMenu.jsx index d3f0c1337..e744a06b1 100644 --- a/www/js/control/ExpandMenu.jsx +++ b/www/js/control/ExpandMenu.jsx @@ -4,15 +4,12 @@ import { List } from 'react-native-paper'; import { useTranslation } from "react-i18next"; import { array, string } from "prop-types"; -//any pure functions can go outside const ExpansionSection = (props) => { const { t } = useTranslation(); //this accesses the translations const [expanded, setExpanded] = React.useState(false); const handlePress = () => setExpanded(!expanded); - // anything that mutates must go in --- depend on props or state... - return ( { + // anything that mutates must go in --- depend on props or state... // settingsScope is the $scope of general-settings.js // grab any variables or functions you need from it like this: @@ -16,7 +18,7 @@ const ProfileSettings = ({ settingsScope }) => { eraseUserData, userData, carbonDatasetString, settings, refreshScreen, endForceSync, checkConsent, dummyNotification, invalidateCache, nukeUserCache, showLog, showSensed, - editCollectionConfig, editSyncConfig } = settingsScope; + editCollectionConfig, editSyncConfig, parseState, forceState } = settingsScope; // let opcode = settings.auth.opcode function getTracking(){ @@ -38,15 +40,15 @@ const ProfileSettings = ({ settingsScope }) => { return ( <> - {/* */} + {/* this toggle only kinda works */} - {/* */} + {/* this switch is also a troublemaker (it's just not fully implemented well yet) */} - {/* */} + @@ -66,17 +68,17 @@ const ProfileSettings = ({ settingsScope }) => { console.log("")}> - {/* */} + - {/* */} + - {/* */} + console.log("")}> - {/* console.log("")}> */} + console.log("")}> ); From 12f462c046f6049c6c04a56c51abebc2a8bdce03 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 28 Jun 2023 17:01:04 -0600 Subject: [PATCH 043/154] styling for Setting Row adding some styling for setting row, referenced other stylings in code and https://reactnative.dev/docs/view-style-props and https://github.com/callstack/react-native-paper/blob/master/src/components/List/ListItem.tsx --- www/js/control/SettingRow.jsx | 50 ++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index fdcc113d3..29073eba3 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -1,26 +1,11 @@ import React from "react"; import { angularize} from "../angular-react-helper"; +import { StyleSheet } from 'react-native'; import { List, IconButton, Switch} from 'react-native-paper'; import { useTranslation } from "react-i18next"; import { string, func, bool} from "prop-types"; -// const ToggleSwitch = ({onSwitch, switchVal}) => { -// // const startResult = getVal(); -// const [isSwitchOn, setIsSwitchOn] = React.useState(true); - -// const onToggleSwitch = function() { -// onSwitch(); -// setIsSwitchOn(!isSwitchOn); -// }; - -// return ; -// }; -// ToggleSwitch.propTypes = { -// onSwitch: func, -// startVal: func -// } - -const SettingRow = ({textKey, iconName, action, switchValue}) => { +const SettingRow = ({textKey, iconName, action, desc, switchValue}) => { const { t } = useTranslation(); //this accesses the translations let rightComponent; @@ -28,21 +13,48 @@ const SettingRow = ({textKey, iconName, action, switchValue}) => { if (switchValue) { rightComponent = action(e)}/>; } else { - rightComponent = action(e)} />; + rightComponent = action(e)} style={styles.icon}/>; + } + let descriptionText; + if(desc) { + descriptionText = {desc}; + } else { + descriptionText = ""; } return ( - console.log("empty")} right={() => rightComponent} /> ); }; +const styles = StyleSheet.create({ + item:{ + flex: .75, + justifyContent: 'space-between', + backgroundColor: '#fff', + padding: 5, + margin: 5, + }, + title: { + fontSize: 16, + marginVertical: 2, + }, + icon: { + marginVertical: 2, + color: "#99FF66", + } + }); SettingRow.propTypes = { textKey: string, iconName: string, action: func, + desc: string, switchValue: bool } From af58485fea758d9d172e78243cbb205c04d8a1dd Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 28 Jun 2023 17:01:59 -0600 Subject: [PATCH 044/154] working with data flow - settings object settings object is undefined at the time the settings are getting evaluated - optional chaining used to prevent errors, but things aren't showing up --- www/js/control/ProfileSettings.jsx | 33 ++++++++----------------- www/templates/control/main-control.html | 4 +-- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 39bb8acb6..25e3a3db0 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -7,40 +7,29 @@ import ControlDataTable from "./ControlDataTable"; import DemographicsSettingRow from "./DemographicsSettingRow"; //any pure functions can go outside -const ProfileSettings = ({ settingsScope }) => { +const ProfileSettings = ({ settingsScope, settingsObject }) => { // anything that mutates must go in --- depend on props or state... // settingsScope is the $scope of general-settings.js // grab any variables or functions you need from it like this: + + + //why is settings not defined but everything else is fine? const { logOut, viewPrivacyPolicy, viewQRCode, userStartStopTracking, fixAppStatus, toggleLowAccuracy, changeCarbonDataset, forceSync, share, openDatePicker, uploadLog, emailLog, - eraseUserData, userData, carbonDatasetString, settings, + eraseUserData, userData, carbonDatasetString, refreshScreen, endForceSync, checkConsent, dummyNotification, invalidateCache, nukeUserCache, showLog, showSensed, editCollectionConfig, editSyncConfig, parseState, forceState } = settingsScope; - // let opcode = settings.auth.opcode - function getTracking(){ - if(settings?.collect?.trackingOn){ - return settings.collect.trackingOn; - } - else{ - return false; - } - } - function getOpcode(){ - if(settings?.auth?.opcode){ - return settings.auth.opcode; - } - else{ - return "ERROR GETTING OPCODE"; - } - } + console.log("settings?", settingsObject); + let settings = settingsObject; + console.log("settings", settings); return ( <> - + @@ -77,13 +66,11 @@ const ProfileSettings = ({ settingsScope }) => { - console.log("")}> - console.log("")}> + console.log("")} desc={settings?.clientAppVer}> ); }; - ProfileSettings.propTypes = { settingsScope: object } diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index ab4d37081..28bae5fbf 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -1,7 +1,7 @@ - +
@@ -26,7 +26,6 @@
--> - - From e106102152bb037aaede2faaa5d58b104a9c03a8 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 29 Jun 2023 09:10:53 -0600 Subject: [PATCH 045/154] styling ExpandMenu added styles to ExpandMenu and adjusted so they matched closely with the SettingRow, test row with description in ProfileSettings until settings var starts to work --- www/js/control/ExpandMenu.jsx | 23 +++++++++++++++++++---- www/js/control/ProfileSettings.jsx | 1 + www/js/control/SettingRow.jsx | 11 ++++------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/www/js/control/ExpandMenu.jsx b/www/js/control/ExpandMenu.jsx index e744a06b1..d5b4f1469 100644 --- a/www/js/control/ExpandMenu.jsx +++ b/www/js/control/ExpandMenu.jsx @@ -1,8 +1,9 @@ import React from "react"; import { angularize} from "../angular-react-helper"; +import { StyleSheet } from 'react-native'; import { List } from 'react-native-paper'; import { useTranslation } from "react-i18next"; -import { array, string } from "prop-types"; +import { string } from "prop-types"; const ExpansionSection = (props) => { const { t } = useTranslation(); //this accesses the translations @@ -12,13 +13,27 @@ const ExpansionSection = (props) => { return ( + style={styles.section} + title={t(props.sectionTitle)} + titleStyle={styles.title} + expanded={expanded} + onPress={handlePress}> {props.children} ); }; +const styles = StyleSheet.create({ + section:{ + justifyContent: 'space-between', + backgroundColor: '#fff', + height: 60, + margin: 5, + }, + title: { + fontSize: 16, + marginVertical: 2, + }, +}); ExpansionSection.propTypes = { sectionTitle: string } diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 25e3a3db0..e445c7596 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -30,6 +30,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { return ( <> + diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 29073eba3..33362fc5c 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -13,7 +13,7 @@ const SettingRow = ({textKey, iconName, action, desc, switchValue}) => { if (switchValue) { rightComponent = action(e)}/>; } else { - rightComponent = action(e)} style={styles.icon}/>; + rightComponent = action(e)}/>; } let descriptionText; if(desc) { @@ -35,20 +35,17 @@ const SettingRow = ({textKey, iconName, action, desc, switchValue}) => { }; const styles = StyleSheet.create({ item:{ - flex: .75, justifyContent: 'space-between', + alignContent: 'center', backgroundColor: '#fff', - padding: 5, + paddingTop: 0, + height: 60, margin: 5, }, title: { fontSize: 16, marginVertical: 2, }, - icon: { - marginVertical: 2, - color: "#99FF66", - } }); SettingRow.propTypes = { textKey: string, From 44bc1bee902bd237e6b4c48d90bf860a6b3a6d5c Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 29 Jun 2023 09:37:56 -0600 Subject: [PATCH 046/154] comment out old code Some code in general-setttings is no longer in use, particularly the code to handle expanding the menus that are now react accordian components in ExpansionMenu --- www/js/control/general-settings.js | 77 ++++++++++++++++-------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index b28a2b1fd..38e3c418f 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -415,11 +415,12 @@ angular.module('emission.main.control',['emission.services', $scope.getUserData(); }; - $scope.copyToClipboard = (textToCopy) => { - navigator.clipboard.writeText(textToCopy).then(() => { - ionicToast.show('{Copied to clipboard!}', 'bottom', false, 2000); - }); - } + //this feature has been eliminated (as of right now) + // $scope.copyToClipboard = (textToCopy) => { + // navigator.clipboard.writeText(textToCopy).then(() => { + // ionicToast.show('{Copied to clipboard!}', 'bottom', false, 2000); + // }); + // } $scope.logOut = function() { $ionicPopup.confirm({ @@ -574,12 +575,14 @@ angular.module('emission.main.control',['emission.services', } } - $scope.getExpandButtonClass = function() { - return ($scope.expanded)? "icon ion-ios-arrow-up" : "icon ion-ios-arrow-down"; - } - $scope.getUserDataExpandButtonClass = function() { - return ($scope.dataExpanded)? "icon ion-ios-arrow-up" : "icon ion-ios-arrow-down"; - } + //using the react accordians now! + // $scope.getExpandButtonClass = function() { + // return ($scope.expanded)? "icon ion-ios-arrow-up" : "icon ion-ios-arrow-down"; + // } + // $scope.getUserDataExpandButtonClass = function() { + // return ($scope.dataExpanded)? "icon ion-ios-arrow-up" : "icon ion-ios-arrow-down"; + // } + $scope.eraseUserData = function() { CalorieCal.delete().then(function() { $ionicPopup.alert({template: i18next.t('general-settings.user-data-erased')}); @@ -607,31 +610,33 @@ angular.module('emission.main.control',['emission.services', } }); }; - $scope.expandDeveloperZone = function() { - if ($scope.collectionExpanded()) { - $scope.expanded = false; - $ionicScrollDelegate.resize(); - $ionicScrollDelegate.scrollTo(0, 0, true); - - } else { - $scope.expanded = true; - $ionicScrollDelegate.resize(); - $ionicScrollDelegate.scrollTo(0, 1000, true); - } - } - $scope.toggleUserData = function() { - if ($scope.dataExpanded) { - $scope.dataExpanded = false; - } else { - $scope.dataExpanded = true; - } - } - $scope.collectionExpanded = function() { - return $scope.expanded; - } - $scope.userDataExpanded = function() { - return $scope.dataExpanded && $scope.userDataSaved(); - } + //this was eliminated in coversion because the React accordians handle their state + + // $scope.expandDeveloperZone = function() { + // if ($scope.collectionExpanded()) { + // $scope.expanded = false; + // $ionicScrollDelegate.resize(); + // $ionicScrollDelegate.scrollTo(0, 0, true); + + // } else { + // $scope.expanded = true; + // $ionicScrollDelegate.resize(); + // $ionicScrollDelegate.scrollTo(0, 1000, true); + // } + // } + // $scope.toggleUserData = function() { + // if ($scope.dataExpanded) { + // $scope.dataExpanded = false; + // } else { + // $scope.dataExpanded = true; + // } + // } + // $scope.collectionExpanded = function() { + // return $scope.expanded; + // } + // $scope.userDataExpanded = function() { + // return $scope.dataExpanded && $scope.userDataSaved(); + // } var handleNoConsent = function(resultDoc) { $ionicPopup.confirm({template: i18next.t('general-settings.consent-not-found')}) From 3eae6055de4f1d4c782e90f796333d95bfa812f2 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 29 Jun 2023 14:51:42 -0600 Subject: [PATCH 047/154] work with the toggle swapped so that it checked for an iconName, this way the toggle renders wether switchValue is true or false, toggle itself is still buggy - see comments --- www/js/control/SettingRow.jsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 33362fc5c..1f23a6b9c 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -9,11 +9,13 @@ const SettingRow = ({textKey, iconName, action, desc, switchValue}) => { const { t } = useTranslation(); //this accesses the translations let rightComponent; - // will using switchValue here only render when the value is true? - if (switchValue) { - rightComponent = action(e)}/>; - } else { + if (iconName) { rightComponent = action(e)}/>; + } else { + //when toggled from OFF to ON, the switch display does not update + //update takes when screen is "refreshed" - by tabbing btwn screens, showing policy... + //works just fine when going from ON to OFF + rightComponent = action(e)}/>; } let descriptionText; if(desc) { @@ -38,7 +40,6 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', alignContent: 'center', backgroundColor: '#fff', - paddingTop: 0, height: 60, margin: 5, }, From 83443ed22f428fb3fae5ee3e3a32d55cef144fbe Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 29 Jun 2023 14:52:55 -0600 Subject: [PATCH 048/154] begin general-settings to ProfileSettings move starting to transfer some of the more basic functionality out of general-settings.js and into ProfileSettings, cutting down on what is pulled from the angular scope --- www/js/control/ProfileSettings.jsx | 40 ++++++++++++++++++++---- www/js/control/general-settings.js | 50 +++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index e445c7596..4d746e37c 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,6 +1,7 @@ import React from "react"; -import { angularize } from "../angular-react-helper"; +import { angularize, getAngularService } from "../angular-react-helper"; import { object } from "prop-types"; +import { useTranslation } from "react-i18next"; import ExpansionSection from "./ExpandMenu"; import SettingRow from "./SettingRow"; import ControlDataTable from "./ControlDataTable"; @@ -9,16 +10,17 @@ import DemographicsSettingRow from "./DemographicsSettingRow"; //any pure functions can go outside const ProfileSettings = ({ settingsScope, settingsObject }) => { // anything that mutates must go in --- depend on props or state... + const { t } = useTranslation(); // settingsScope is the $scope of general-settings.js // grab any variables or functions you need from it like this: //why is settings not defined but everything else is fine? - const { logOut, viewPrivacyPolicy, viewQRCode, userStartStopTracking, + const { logOut, viewPrivacyPolicy, viewQRCode, fixAppStatus, toggleLowAccuracy, changeCarbonDataset, - forceSync, share, openDatePicker, uploadLog, emailLog, - eraseUserData, userData, carbonDatasetString, + forceSync, share, openDatePicker, + eraseUserData, userData, refreshScreen, endForceSync, checkConsent, dummyNotification, invalidateCache, nukeUserCache, showLog, showSensed, editCollectionConfig, editSyncConfig, parseState, forceState } = settingsScope; @@ -26,11 +28,35 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { console.log("settings?", settingsObject); let settings = settingsObject; console.log("settings", settings); + + const CarbonDatasetHelper = getAngularService('CarbonDatasetHelper'); + const UploadHelper = getAngularService('UploadHelper'); + const EmailHelper = getAngularService('EmailHelper'); + const ControlCollectionHelper = getAngularService('ControlCollectionHelper'); + + let carbonDatasetString = t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); + + const uploadLog = function () { + UploadHelper.uploadFile("loggerDB") + }; + + const emailLog = function () { + // Passing true, we want to send logs + EmailHelper.sendEmail("loggerDB") + }; + + const userStartStopTracking = function() { + //note the dependency on the settings object (still passed in) + if (settings.collect.trackingOn){ + return ControlCollectionHelper.forceTransition('STOP_TRACKING'); + } else { + return ControlCollectionHelper.forceTransition('START_TRACKING'); + } + } return ( <> - @@ -67,13 +93,15 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { + console.log("")} desc={settings?.clientAppVer}> ); }; ProfileSettings.propTypes = { - settingsScope: object + settingsScope: object, + settingsObject: object } angularize(ProfileSettings, 'ProfileSettings', 'emission.main.control.profileSettings'); diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 38e3c418f..47cd35fd8 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -81,17 +81,19 @@ angular.module('emission.main.control',['emission.services', ionicDatePicker.openDatePicker(datepickerObject); }; - $scope.carbonDatasetString = i18next.t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); + //these have been converted!! + // $scope.carbonDatasetString = i18next.t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); - $scope.uploadLog = function () { - UploadHelper.uploadFile("loggerDB") - }; + // $scope.uploadLog = function () { + // UploadHelper.uploadFile("loggerDB") + // }; - $scope.emailLog = function () { - // Passing true, we want to send logs - EmailHelper.sendEmail("loggerDB") - }; + // $scope.emailLog = function () { + // // Passing true, we want to send logs + // EmailHelper.sendEmail("loggerDB") + // }; + //this function used in ProfileSettings to viewPrivacyPolicy $scope.viewPrivacyPolicy = function($event) { // button -> list element -> scroll // const targetEl = $event.currentTarget.parentElement.parentElement; @@ -108,6 +110,7 @@ angular.module('emission.main.control',['emission.services', } } + //this function used in ProfileSettings to viewQRCode $scope.viewQRCode = function($event) { $scope.tokenURL = "emission://login_token?token="+$scope.settings.auth.opcode; if ($scope.qrp) { @@ -120,6 +123,7 @@ angular.module('emission.main.control',['emission.services', } } + //this function used in ProfileSettings to send DummyNotification $scope.dummyNotification = () => { cordova.plugins.notification.local.addActions('dummy-actions', [ { id: 'action', title: 'Yes' }, @@ -142,6 +146,7 @@ angular.module('emission.main.control',['emission.services', $scope.settings.notification.scheduledNotifs = NotificationScheduler.scheduledNotifs; } + //called in ProfileSettings on the AppStatus row $scope.fixAppStatus = function() { $scope.$broadcast("recomputeAppStatus"); $scope.appStatusModal.show(); @@ -225,6 +230,8 @@ angular.module('emission.main.control',['emission.services', return isMediumAccuracy; } } + + //this is the action called in ProfileSettings for the accuracy toggle $scope.toggleLowAccuracy = ControlCollectionHelper.toggleLowAccuracy; $scope.getTracking = function() { @@ -284,9 +291,11 @@ angular.module('emission.main.control',['emission.services', Logger.displayError("while getting opcode, ",error); }); }; + //in ProfileSettings in DevZone $scope.showLog = function() { $state.go("root.main.log"); } + //inProfileSettings in DevZone $scope.showSensed = function() { $state.go("root.main.sensed"); } @@ -317,6 +326,7 @@ angular.module('emission.main.control',['emission.services', }); } + //in ProfileSettings in DevZone $scope.nukeUserCache = function() { var nukeChoiceActions = [{text: i18next.t('general-settings.nuke-ui-state-only'), action: KVStore.clearOnlyLocal}, @@ -336,6 +346,7 @@ angular.module('emission.main.control',['emission.services', }); } + //in ProfileSettings in DevZone $scope.invalidateCache = function() { window.cordova.plugins.BEMUserCache.invalidateAllCache().then(function(result) { $scope.$apply(function() { @@ -370,6 +381,7 @@ angular.module('emission.main.control',['emission.services', $scope.refreshScreen(); }); + //in ProfileSettings in DevZone $scope.refreshScreen = function() { console.log("Refreshing screen"); $scope.settings = {}; @@ -422,6 +434,7 @@ angular.module('emission.main.control',['emission.services', // }); // } + //used in ProfileSettings at the profile/logout/opcode row $scope.logOut = function() { $ionicPopup.confirm({ title: i18next.t('general-settings.are-you-sure'), @@ -528,6 +541,7 @@ angular.module('emission.main.control',['emission.services', }) } + //in ProfileSettings in DevZone $scope.endForceSync = function() { /* First, quickly start and end the trip. Let's listen to the promise * result for start so that we ensure ordering */ @@ -567,13 +581,14 @@ angular.module('emission.main.control',['emission.services', } }); }; - $scope.userStartStopTracking = function() { - if ($scope.settings.collect.trackingOn){ - return ControlCollectionHelper.forceTransition('STOP_TRACKING'); - } else { - return ControlCollectionHelper.forceTransition('START_TRACKING'); - } - } + //moved this into ProfileSettings :) + // $scope.userStartStopTracking = function() { + // if ($scope.settings.collect.trackingOn){ + // return ControlCollectionHelper.forceTransition('STOP_TRACKING'); + // } else { + // return ControlCollectionHelper.forceTransition('START_TRACKING'); + // } + // } //using the react accordians now! // $scope.getExpandButtonClass = function() { @@ -583,11 +598,13 @@ angular.module('emission.main.control',['emission.services', // return ($scope.dataExpanded)? "icon ion-ios-arrow-up" : "icon ion-ios-arrow-down"; // } + //in ProfileSettings in UserData $scope.eraseUserData = function() { CalorieCal.delete().then(function() { $ionicPopup.alert({template: i18next.t('general-settings.user-data-erased')}); }); } + //in ProfileSettings in DevZone -- part of force/edit state $scope.parseState = function(state) { if (state) { if($scope.isAndroid()){ @@ -597,6 +614,7 @@ angular.module('emission.main.control',['emission.services', } } } + //in ProfileSettings change carbon set $scope.changeCarbonDataset = function() { $ionicActionSheet.show({ buttons: CarbonDatasetHelper.getCarbonDatasetOptions(), @@ -666,6 +684,7 @@ angular.module('emission.main.control',['emission.services', }); } + //in ProfileSettings in DevZone (above two functions are helpers) $scope.checkConsent = function() { StartPrefs.getConsentDocument().then(function(resultDoc){ if (resultDoc == null) { @@ -684,6 +703,7 @@ angular.module('emission.main.control',['emission.services', url: i18next.t('general-settings.share-url') } + //in ProfileSettings above is a helper var! $scope.share = function() { window.plugins.socialsharing.shareWithOptions(prepopulateMessage, function(result) { console.log("Share completed? " + result.completed); // On Android apps mostly return false even while it's true From ff4e4905caecb6291be2a1b149c0688bcee85427 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 29 Jun 2023 15:33:56 -0600 Subject: [PATCH 049/154] made rows taller - more white space the shorter rows were cramped and the opcode was not fitting in the allotted space --- www/js/control/ExpandMenu.jsx | 2 +- www/js/control/SettingRow.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/www/js/control/ExpandMenu.jsx b/www/js/control/ExpandMenu.jsx index d5b4f1469..8cf0a91a3 100644 --- a/www/js/control/ExpandMenu.jsx +++ b/www/js/control/ExpandMenu.jsx @@ -26,7 +26,7 @@ const styles = StyleSheet.create({ section:{ justifyContent: 'space-between', backgroundColor: '#fff', - height: 60, + height: 75, margin: 5, }, title: { diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 1f23a6b9c..d05b30c1e 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -40,7 +40,7 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', alignContent: 'center', backgroundColor: '#fff', - height: 60, + height: 75, margin: 5, }, title: { From 2bf64d2da51036810398d97ea28e771202b876e6 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 29 Jun 2023 15:34:58 -0600 Subject: [PATCH 050/154] low accuracy toggle functions not working I was trying to get the low accuracy toggle to work, and discovered the function is not quite working in this context - the value is not being changed (based on console statements) and toggle is not moving --- www/js/control/ProfileSettings.jsx | 18 +++++++++++++++--- www/js/control/general-settings.js | 9 +++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 4d746e37c..d9c0159bd 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -18,12 +18,12 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { //why is settings not defined but everything else is fine? const { logOut, viewPrivacyPolicy, viewQRCode, - fixAppStatus, toggleLowAccuracy, changeCarbonDataset, + fixAppStatus, changeCarbonDataset, forceSync, share, openDatePicker, eraseUserData, userData, refreshScreen, endForceSync, checkConsent, dummyNotification, invalidateCache, nukeUserCache, showLog, showSensed, - editCollectionConfig, editSyncConfig, parseState, forceState } = settingsScope; + parseState, } = settingsScope; console.log("settings?", settingsObject); let settings = settingsObject; @@ -33,6 +33,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { const UploadHelper = getAngularService('UploadHelper'); const EmailHelper = getAngularService('EmailHelper'); const ControlCollectionHelper = getAngularService('ControlCollectionHelper'); + const ControlSyncHelper = getAngularService('ControlSyncHelper'); let carbonDatasetString = t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); @@ -54,6 +55,16 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { } } + const forceState = ControlCollectionHelper.forceState; + const editCollectionConfig = ControlCollectionHelper.editConfig; + const editSyncConfig = ControlSyncHelper.editConfig; + const toggleLowAccuracy = function() { + console.log("change attempt in ProfileSettigns"); + //the function below is very broken!! + ControlCollectionHelper.toggleLowAccuracy(); + settings.collect.lowAccuracy = ControlCollectionHelper.isMediumAccuracy(); + } + return ( <> @@ -63,7 +74,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { {/* this toggle only kinda works */} - {/* this switch is also a troublemaker (it's just not fully implemented well yet) */} + {/* this switch is also fussy */} @@ -83,6 +94,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { + {/* upcoming notifications seem to be undefined at time of render :( */} console.log("")}> diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 47cd35fd8..e004b5570 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -232,7 +232,7 @@ angular.module('emission.main.control',['emission.services', } //this is the action called in ProfileSettings for the accuracy toggle - $scope.toggleLowAccuracy = ControlCollectionHelper.toggleLowAccuracy; + // $scope.toggleLowAccuracy = ControlCollectionHelper.toggleLowAccuracy; $scope.getTracking = function() { console.log("tracking on or off?", $scope.settings.collect.trackingOn); @@ -555,9 +555,10 @@ angular.module('emission.main.control',['emission.services', }).then($scope.forceSync); } - $scope.forceState = ControlCollectionHelper.forceState; - $scope.editCollectionConfig = ControlCollectionHelper.editConfig; - $scope.editSyncConfig = ControlSyncHelper.editConfig; + //migrated! + // $scope.forceState = ControlCollectionHelper.forceState; + // $scope.editCollectionConfig = ControlCollectionHelper.editConfig; + // $scope.editSyncConfig = ControlSyncHelper.editConfig; $scope.isAndroid = function() { return ionic.Platform.isAndroid(); From 20d29f40f4f915dad808ecadc476548b527d23ae Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 29 Jun 2023 15:49:03 -0600 Subject: [PATCH 051/154] make userData conditional setup so that userData section only shows if there is userData --- www/js/control/ProfileSettings.jsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index d9c0159bd..5d51a2230 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -18,7 +18,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { //why is settings not defined but everything else is fine? const { logOut, viewPrivacyPolicy, viewQRCode, - fixAppStatus, changeCarbonDataset, + fixAppStatus, changeCarbonDataset, userDataSaved, forceSync, share, openDatePicker, eraseUserData, userData, refreshScreen, endForceSync, checkConsent, dummyNotification, @@ -65,6 +65,15 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { settings.collect.lowAccuracy = ControlCollectionHelper.isMediumAccuracy(); } + let userDataSection; + if(userDataSaved()) + { + userDataSection = + + + ; + } + return ( <> @@ -84,11 +93,8 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { - - - - - + {userDataSection} + From bfc63552ba3b82ef232af825aeae3f7656fd4036 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 5 Jul 2023 14:11:47 -0600 Subject: [PATCH 052/154] dialog menus - replacing action sheets continuing to migrate, replacing the action sheet that comes up for nukeSettings menu item --- www/js/control/ProfileSettings.jsx | 50 +++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 5d51a2230..74868614a 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,4 +1,5 @@ import React from "react"; +import { Dialog, Button } from "react-native-paper"; import { angularize, getAngularService } from "../angular-react-helper"; import { object } from "prop-types"; import { useTranslation } from "react-i18next"; @@ -6,6 +7,7 @@ import ExpansionSection from "./ExpandMenu"; import SettingRow from "./SettingRow"; import ControlDataTable from "./ControlDataTable"; import DemographicsSettingRow from "./DemographicsSettingRow"; +import ActionSheet from "./DialogMenu"; //any pure functions can go outside const ProfileSettings = ({ settingsScope, settingsObject }) => { @@ -22,18 +24,23 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { forceSync, share, openDatePicker, eraseUserData, userData, refreshScreen, endForceSync, checkConsent, dummyNotification, - invalidateCache, nukeUserCache, showLog, showSensed, + invalidateCache, showLog, showSensed, parseState, } = settingsScope; console.log("settings?", settingsObject); let settings = settingsObject; console.log("settings", settings); + + var profileSettings = {}; + const [nukeSetVis, setNukeVis] = React.useState(false); const CarbonDatasetHelper = getAngularService('CarbonDatasetHelper'); const UploadHelper = getAngularService('UploadHelper'); const EmailHelper = getAngularService('EmailHelper'); const ControlCollectionHelper = getAngularService('ControlCollectionHelper'); const ControlSyncHelper = getAngularService('ControlSyncHelper'); + // const CalorieCal = getAngularService('CalorieCal'); + const KVStore = getAngularService('KVStore'); let carbonDatasetString = t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); @@ -55,6 +62,17 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { } } + const getLowAccuracy = function() { + var isMediumAccuracy = ControlCollectionHelper.isMediumAccuracy(); + if(isMediumAccuracy === undefined) { + return false; + } + else{ + profileSettings.collect.lowAccuracy = isMediumAccuracy; + return isMediumAccuracy; + } + } + const forceState = ControlCollectionHelper.forceState; const editCollectionConfig = ControlCollectionHelper.editConfig; const editSyncConfig = ControlSyncHelper.editConfig; @@ -62,7 +80,19 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { console.log("change attempt in ProfileSettigns"); //the function below is very broken!! ControlCollectionHelper.toggleLowAccuracy(); - settings.collect.lowAccuracy = ControlCollectionHelper.isMediumAccuracy(); + getLowAccuracy(); + } + + // const getCollectionSettings = function() { + // ControlCollectionHelper.getCollectionSettings().then(function(showConfig) { + // profileSettings.collect.show_config = showConfig; + // console.log("settings", showConfig); + // return showConfig; + // }); + // }; + + const nukeUserCache = function() { + setNukeSetVis(true); } let userDataSection; @@ -104,7 +134,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { console.log("")}> - + setNukeVis(true)}> @@ -114,6 +144,17 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { console.log("")} desc={settings?.clientAppVer}> + + setNukeVis(false)}> + {t('general-settings.clear-data')} + + + + + + + ); }; @@ -123,5 +164,4 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { } angularize(ProfileSettings, 'ProfileSettings', 'emission.main.control.profileSettings'); - export default ProfileSettings; - \ No newline at end of file + export default ProfileSettings; \ No newline at end of file From dfda0f1736135450c690b328cbb8fdecbe83818b Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 5 Jul 2023 14:56:42 -0600 Subject: [PATCH 053/154] migrating action sheets there's a small issue with setting the dataset? --- www/js/control/ProfileSettings.jsx | 39 +++++++++++++++++++++++------- www/js/control/general-settings.js | 36 +++++++++++++-------------- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 74868614a..69d0f1072 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -33,6 +33,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { var profileSettings = {}; const [nukeSetVis, setNukeVis] = React.useState(false); + const [carbonDataVis, setCarbonDataVis] = React.useState(false); const CarbonDatasetHelper = getAngularService('CarbonDatasetHelper'); const UploadHelper = getAngularService('UploadHelper'); @@ -42,7 +43,13 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { // const CalorieCal = getAngularService('CalorieCal'); const KVStore = getAngularService('KVStore'); + const forceState = ControlCollectionHelper.forceState; + const editCollectionConfig = ControlCollectionHelper.editConfig; + const editSyncConfig = ControlSyncHelper.editConfig; + let carbonDatasetString = t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); + const carbonOptions = CarbonDatasetHelper.getCarbonDatasetOptions(); + console.log(carbonOptions); const uploadLog = function () { UploadHelper.uploadFile("loggerDB") @@ -73,9 +80,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { } } - const forceState = ControlCollectionHelper.forceState; - const editCollectionConfig = ControlCollectionHelper.editConfig; - const editSyncConfig = ControlSyncHelper.editConfig; + const toggleLowAccuracy = function() { console.log("change attempt in ProfileSettigns"); //the function below is very broken!! @@ -91,10 +96,6 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { // }); // }; - const nukeUserCache = function() { - setNukeSetVis(true); - } - let userDataSection; if(userDataSaved()) { @@ -115,7 +116,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { {/* this switch is also fussy */} - + setCarbonDataVis(true)}> @@ -145,8 +146,9 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { console.log("")} desc={settings?.clientAppVer}> + {/* menu for "nuke data" */} setNukeVis(false)}> + onDismiss={() => setNukeVis(false)}> {t('general-settings.clear-data')} @@ -155,6 +157,25 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { + {/* menu for "set carbon dataset - only somewhat working" */} + setCarbonDataVis(false)}> + {t('general-settings.choose-dataset')} + + {carbonOptions.map((e) => + + )} + + + ); }; diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index e004b5570..0f68a19cc 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -327,24 +327,24 @@ angular.module('emission.main.control',['emission.services', } //in ProfileSettings in DevZone - $scope.nukeUserCache = function() { - var nukeChoiceActions = [{text: i18next.t('general-settings.nuke-ui-state-only'), - action: KVStore.clearOnlyLocal}, - {text: i18next.t('general-settings.nuke-native-cache-only'), - action: KVStore.clearOnlyNative}, - {text: i18next.t('general-settings.nuke-everything'), - action: KVStore.clearAll}]; - - $ionicActionSheet.show({ - titleText: i18next.t('general-settings.clear-data'), - cancelText: i18next.t('general-settings.cancel'), - buttons: nukeChoiceActions, - buttonClicked: function(index, button) { - button.action(); - return true; - } - }); - } + // $scope.nukeUserCache = function() { + // var nukeChoiceActions = [{text: i18next.t('general-settings.nuke-ui-state-only'), + // action: KVStore.clearOnlyLocal}, + // {text: i18next.t('general-settings.nuke-native-cache-only'), + // action: KVStore.clearOnlyNative}, + // {text: i18next.t('general-settings.nuke-everything'), + // action: KVStore.clearAll}]; + + // $ionicActionSheet.show({ + // titleText: i18next.t('general-settings.clear-data'), + // cancelText: i18next.t('general-settings.cancel'), + // buttons: nukeChoiceActions, + // buttonClicked: function(index, button) { + // button.action(); + // return true; + // } + // }); + // } //in ProfileSettings in DevZone $scope.invalidateCache = function() { From ac73e4360efefc9d47ba87298a2411ba08a503bd Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 5 Jul 2023 17:15:56 -0600 Subject: [PATCH 054/154] action sheet migrations -- unresolved error Migration of changeCarbon and nukeCache -- getting the error on changeCarbon --- www/js/control/ProfileSettings.jsx | 121 +++++++++++++++++++++-------- www/js/control/general-settings.js | 2 +- 2 files changed, 89 insertions(+), 34 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 69d0f1072..3d1b6223c 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { Dialog, Button } from "react-native-paper"; +import { Dialog, Button, Portal } from "react-native-paper"; import { angularize, getAngularService } from "../angular-react-helper"; import { object } from "prop-types"; import { useTranslation } from "react-i18next"; @@ -7,7 +7,6 @@ import ExpansionSection from "./ExpandMenu"; import SettingRow from "./SettingRow"; import ControlDataTable from "./ControlDataTable"; import DemographicsSettingRow from "./DemographicsSettingRow"; -import ActionSheet from "./DialogMenu"; //any pure functions can go outside const ProfileSettings = ({ settingsScope, settingsObject }) => { @@ -20,9 +19,9 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { //why is settings not defined but everything else is fine? const { logOut, viewPrivacyPolicy, viewQRCode, - fixAppStatus, changeCarbonDataset, userDataSaved, + fixAppStatus, changeCarbonDataset, forceSync, share, openDatePicker, - eraseUserData, userData, + eraseUserData, refreshScreen, endForceSync, checkConsent, dummyNotification, invalidateCache, showLog, showSensed, parseState, } = settingsScope; @@ -80,7 +79,6 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { } } - const toggleLowAccuracy = function() { console.log("change attempt in ProfileSettigns"); //the function below is very broken!! @@ -96,6 +94,43 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { // }); // }; + var userData = []; + var rawUserData; + const getUserData = function() { + return CalorieCal.get().then(function(userDataFromStorage) { + rawUserData = userDataFromStorage; + if(userDataSaved()) { + userData = [] + var height = userDataFromStorage.height.toString(); + var weight = userDataFromStorage.weight.toString(); + var temp = { + age: userDataFromStorage.age, + height: height + (userDataFromStorage.heightUnit == 1? ' cm' : ' ft'), + weight: weight + (userDataFromStorage.weightUnit == 1? ' kg' : ' lb'), + gender: userDataFromStorage.gender == 1? i18next.t('gender-male') : i18next.t('gender-female') + } + for (var i in temp) { + userData.push({key: i, val: temp[i]}); //changed from value to val! watch for rammifications! + } + } + }); + } + const userDataSaved = function() { + console.log(rawUserData); + var defined; + if(rawUserData){ + defined = true; + } + else{ + defined = false; + } + if (defined && rawUserData != null) { + return rawUserData.userDataSaved; + } else { + return false; + } + } + let userDataSection; if(userDataSaved()) { @@ -147,35 +182,55 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { {/* menu for "nuke data" */} - setNukeVis(false)}> - {t('general-settings.clear-data')} - - - - - - - - {/* menu for "set carbon dataset - only somewhat working" */} - setCarbonDataVis(false)}> - {t('general-settings.choose-dataset')} - - {carbonOptions.map((e) => - - )} - - - + + + + + + + + + {/* menu for "set carbon dataset - only somewhat working" */} + + setCarbonDataVis(false)}> + {t('general-settings.choose-dataset')} + + {carbonOptions.map((e) => + + )} + + + + + + ); }; diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 0f68a19cc..56cecacd9 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -159,7 +159,7 @@ angular.module('emission.main.control',['emission.services', $scope.appStatusModal.hide(); } - $scope.userData = [] + // $scope.userData = [] $scope.getUserData = function() { return CalorieCal.get().then(function(userDataFromStorage) { $scope.rawUserData = userDataFromStorage; From f2467faf9260b0c1cd965dcf60d3236218ccf7ab Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 6 Jul 2023 08:50:33 -0600 Subject: [PATCH 055/154] switched portal out for modal --- www/js/control/ProfileSettings.jsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 3d1b6223c..415b38014 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { Dialog, Button, Portal } from "react-native-paper"; +import { Dialog, Button, Modal } from "react-native-paper"; import { angularize, getAngularService } from "../angular-react-helper"; import { object } from "prop-types"; import { useTranslation } from "react-i18next"; @@ -182,9 +182,9 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { {/* menu for "nuke data" */} - + setNukeVis(false)} + transparent={true}> setNukeVis(false)}> {t('general-settings.clear-data')} @@ -205,11 +205,12 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { - + {/* menu for "set carbon dataset - only somewhat working" */} - + setCarbonDataVis(false)} + transparent={true}> setCarbonDataVis(false)}> + onDismiss={() => setCarbonDataVis(false)}> {t('general-settings.choose-dataset')} {carbonOptions.map((e) => @@ -230,7 +231,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { - + ); }; From f685472c344920bcc395192f8bd6ff0e63534911 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 6 Jul 2023 09:18:34 -0600 Subject: [PATCH 056/154] address errors in accuracy toggle was getting an "undefined is not an object" error, this seems to have resolved that --- www/js/control/ProfileSettings.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 415b38014..0e7166b77 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -70,11 +70,11 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { const getLowAccuracy = function() { var isMediumAccuracy = ControlCollectionHelper.isMediumAccuracy(); - if(isMediumAccuracy === undefined) { + if(typeof isMediumAccuracy == 'undefined') { return false; } else{ - profileSettings.collect.lowAccuracy = isMediumAccuracy; + settings.collect.lowAccuracy = isMediumAccuracy; return isMediumAccuracy; } } @@ -206,6 +206,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { + {/* menu for "set carbon dataset - only somewhat working" */} setCarbonDataVis(false)} transparent={true}> From 532f56af6808af77899c1f0f2583e95077790923 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 6 Jul 2023 09:37:57 -0600 Subject: [PATCH 057/154] organizing code commenting things into sections in ProfileSettings and commenting out old code in general-settings --- www/js/control/ProfileSettings.jsx | 29 +++++++-------- www/js/control/general-settings.js | 58 +++++++++++++++--------------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 0e7166b77..afbe10e87 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -19,37 +19,37 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { //why is settings not defined but everything else is fine? const { logOut, viewPrivacyPolicy, viewQRCode, - fixAppStatus, changeCarbonDataset, - forceSync, share, openDatePicker, - eraseUserData, - refreshScreen, endForceSync, checkConsent, dummyNotification, - invalidateCache, showLog, showSensed, - parseState, } = settingsScope; + fixAppStatus, forceSync, share, openDatePicker, + eraseUserData, refreshScreen, endForceSync, checkConsent, + dummyNotification, invalidateCache, showLog, showSensed, + parseState, } = settingsScope; console.log("settings?", settingsObject); let settings = settingsObject; console.log("settings", settings); - var profileSettings = {}; - const [nukeSetVis, setNukeVis] = React.useState(false); - const [carbonDataVis, setCarbonDataVis] = React.useState(false); - + //angular services needed const CarbonDatasetHelper = getAngularService('CarbonDatasetHelper'); const UploadHelper = getAngularService('UploadHelper'); const EmailHelper = getAngularService('EmailHelper'); const ControlCollectionHelper = getAngularService('ControlCollectionHelper'); const ControlSyncHelper = getAngularService('ControlSyncHelper'); - // const CalorieCal = getAngularService('CalorieCal'); + const CalorieCal = getAngularService('CalorieCal'); const KVStore = getAngularService('KVStore'); + //functions that come directly from an Angular service const forceState = ControlCollectionHelper.forceState; const editCollectionConfig = ControlCollectionHelper.editConfig; const editSyncConfig = ControlSyncHelper.editConfig; + //states and variables used to control/create the settings + var profileSettings = {}; + const [nukeSetVis, setNukeVis] = React.useState(false); + const [carbonDataVis, setCarbonDataVis] = React.useState(false); let carbonDatasetString = t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); const carbonOptions = CarbonDatasetHelper.getCarbonDatasetOptions(); - console.log(carbonOptions); - + + //methods that control the settings const uploadLog = function () { UploadHelper.uploadFile("loggerDB") }; @@ -81,7 +81,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { const toggleLowAccuracy = function() { console.log("change attempt in ProfileSettigns"); - //the function below is very broken!! + //the function below is broken? ControlCollectionHelper.toggleLowAccuracy(); getLowAccuracy(); } @@ -131,6 +131,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { } } + //conditional creation of the user dropdown let userDataSection; if(userDataSaved()) { diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 56cecacd9..fb70dfd47 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -216,39 +216,39 @@ angular.module('emission.main.control',['emission.services', $scope.refreshScreen(); }); }); - $scope.getLowAccuracy = function() { - // return true: toggle on; return false: toggle off. - var isMediumAccuracy = ControlCollectionHelper.isMediumAccuracy(); - if (!angular.isDefined(isMediumAccuracy)) { - // config not loaded when loading ui, set default as false - // TODO: Read the value if it is not defined. - // Otherwise, don't we have a race with reading? - // we don't really $apply on this field... - return false; - } else { - $scope.settings.collect.lowAccuracy = isMediumAccuracy; //adding to scope to use w/ switches - return isMediumAccuracy; - } - } + // $scope.getLowAccuracy = function() { + // // return true: toggle on; return false: toggle off. + // var isMediumAccuracy = ControlCollectionHelper.isMediumAccuracy(); + // if (!angular.isDefined(isMediumAccuracy)) { + // // config not loaded when loading ui, set default as false + // // TODO: Read the value if it is not defined. + // // Otherwise, don't we have a race with reading? + // // we don't really $apply on this field... + // return false; + // } else { + // $scope.settings.collect.lowAccuracy = isMediumAccuracy; //adding to scope to use w/ switches + // return isMediumAccuracy; + // } + // } //this is the action called in ProfileSettings for the accuracy toggle // $scope.toggleLowAccuracy = ControlCollectionHelper.toggleLowAccuracy; - $scope.getTracking = function() { - console.log("tracking on or off?", $scope.settings.collect.trackingOn); - // return true: toggle on; return false: toggle off. - var isTracking = $scope.settings.collect.trackingOn; - if (!angular.isDefined(isTracking)) { - // config not loaded when loading ui, set default as false - // TODO: Read the value if it is not defined. - // Otherwise, don't we have a race with reading? - // we don't really $apply on this field... - return false; - } else { - console.log("tracking on or off?", $scope.settings.collect.trackingOn); - return $scope.settings.collect.trackingOn; - } - } + // $scope.getTracking = function() { + // console.log("tracking on or off?", $scope.settings.collect.trackingOn); + // // return true: toggle on; return false: toggle off. + // var isTracking = $scope.settings.collect.trackingOn; + // if (!angular.isDefined(isTracking)) { + // // config not loaded when loading ui, set default as false + // // TODO: Read the value if it is not defined. + // // Otherwise, don't we have a race with reading? + // // we don't really $apply on this field... + // return false; + // } else { + // console.log("tracking on or off?", $scope.settings.collect.trackingOn); + // return $scope.settings.collect.trackingOn; + // } + // } $scope.getConnectURL = function() { ControlHelper.getSettings().then(function(response) { From 7237343898fcbc928e0ab67c587ad105ec847ba4 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 6 Jul 2023 09:46:40 -0600 Subject: [PATCH 058/154] make whole row clickable swapped the iconbuttons for list.icons and moved the onPress to the list.item in setting row, this keeps setting simple --- www/js/control/SettingRow.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index d05b30c1e..3ae52a4f0 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -10,7 +10,7 @@ const SettingRow = ({textKey, iconName, action, desc, switchValue}) => { let rightComponent; if (iconName) { - rightComponent = action(e)}/>; + rightComponent = ; } else { //when toggled from OFF to ON, the switch display does not update //update takes when screen is "refreshed" - by tabbing btwn screens, showing policy... @@ -30,7 +30,7 @@ const SettingRow = ({textKey, iconName, action, desc, switchValue}) => { title={t(textKey)} titleStyle={styles.title} description={desc} - onPress={() => console.log("empty")} + onPress={(e) => action(e)} right={() => rightComponent} /> ); From ca4bd2d3cc9911b4a55cbda5b8cb8c0664ed4ceb Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 6 Jul 2023 10:13:57 -0600 Subject: [PATCH 059/154] migrate share method --- www/js/control/ProfileSettings.jsx | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index afbe10e87..8cc3114e5 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -13,16 +13,14 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { // anything that mutates must go in --- depend on props or state... const { t } = useTranslation(); - // settingsScope is the $scope of general-settings.js - // grab any variables or functions you need from it like this: - - + //settingsScope is the $scope of general-settings.js + //grab any variables or functions you need from it like this: //why is settings not defined but everything else is fine? const { logOut, viewPrivacyPolicy, viewQRCode, - fixAppStatus, forceSync, share, openDatePicker, + fixAppStatus, forceSync, openDatePicker, eraseUserData, refreshScreen, endForceSync, checkConsent, dummyNotification, invalidateCache, showLog, showSensed, - parseState, } = settingsScope; + parseState } = settingsScope; console.log("settings?", settingsObject); let settings = settingsObject; @@ -107,7 +105,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { age: userDataFromStorage.age, height: height + (userDataFromStorage.heightUnit == 1? ' cm' : ' ft'), weight: weight + (userDataFromStorage.weightUnit == 1? ' kg' : ' lb'), - gender: userDataFromStorage.gender == 1? i18next.t('gender-male') : i18next.t('gender-female') + gender: userDataFromStorage.gender == 1? t('gender-male') : t('gender-female') } for (var i in temp) { userData.push({key: i, val: temp[i]}); //changed from value to val! watch for rammifications! @@ -126,11 +124,26 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { } if (defined && rawUserData != null) { return rawUserData.userDataSaved; - } else { + } else{ return false; } } + var prepopulateMessage = { + message: t('general-settings.share-message'), + subject: t('general-settings.share-subject'), + url: t('general-settings.share-url') + } + + const share = function() { + window.plugins.socialsharing.shareWithOptions(prepopulateMessage, 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) + }, function(msg) { + console.log("Sharing failed with message: " + msg); + }); + } + //conditional creation of the user dropdown let userDataSection; if(userDataSaved()) From e6ed67a125d6be73eaaddb26aaf179eaed57b9e3 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 6 Jul 2023 11:11:34 -0600 Subject: [PATCH 060/154] working on platform detection --- www/js/control/ProfileSettings.jsx | 9 ++++++ www/js/control/general-settings.js | 46 +++++++++++++++--------------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 8cc3114e5..1da014c45 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,4 +1,5 @@ import React from "react"; +import {Platform} from "react-native"; import { Dialog, Button, Modal } from "react-native-paper"; import { angularize, getAngularService } from "../angular-react-helper"; import { object } from "prop-types"; @@ -57,6 +58,14 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { EmailHelper.sendEmail("loggerDB") }; + const isAndroid = function() { + return Platform.OS == "android"; + } + + const isIOS = function() { + return Platform.OS == "ios"; + } + const userStartStopTracking = function() { //note the dependency on the settings object (still passed in) if (settings.collect.trackingOn){ diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index fb70dfd47..1e4700439 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -615,20 +615,20 @@ angular.module('emission.main.control',['emission.services', } } } - //in ProfileSettings change carbon set - $scope.changeCarbonDataset = function() { - $ionicActionSheet.show({ - buttons: CarbonDatasetHelper.getCarbonDatasetOptions(), - titleText: i18next.t('general-settings.choose-dataset'), - cancelText: i18next.t('general-settings.cancel'), - buttonClicked: function(index, button) { - console.log("changeCarbonDataset(): chose locale " + button.value); - CarbonDatasetHelper.saveCurrentCarbonDatasetLocale(button.value); - $scope.carbonDatasetString = i18next.t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); - return true; - } - }); - }; + // //in ProfileSettings change carbon set + // $scope.changeCarbonDataset = function() { + // $ionicActionSheet.show({ + // buttons: CarbonDatasetHelper.getCarbonDatasetOptions(), + // titleText: i18next.t('general-settings.choose-dataset'), + // cancelText: i18next.t('general-settings.cancel'), + // buttonClicked: function(index, button) { + // console.log("changeCarbonDataset(): chose locale " + button.value); + // CarbonDatasetHelper.saveCurrentCarbonDatasetLocale(button.value); + // $scope.carbonDatasetString = i18next.t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); + // return true; + // } + // }); + // }; //this was eliminated in coversion because the React accordians handle their state // $scope.expandDeveloperZone = function() { @@ -704,15 +704,15 @@ angular.module('emission.main.control',['emission.services', url: i18next.t('general-settings.share-url') } - //in ProfileSettings above is a helper var! - $scope.share = function() { - window.plugins.socialsharing.shareWithOptions(prepopulateMessage, 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) - }, function(msg) { - console.log("Sharing failed with message: " + msg); - }); - } + // //in ProfileSettings above is a helper var! + // $scope.share = function() { + // window.plugins.socialsharing.shareWithOptions(prepopulateMessage, 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) + // }, function(msg) { + // console.log("Sharing failed with message: " + msg); + // }); + // } $scope.shareQR = function() { var prepopulateQRMessage = {}; From eb3de5d4960ff2984c44e89286d554214f62984e Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 6 Jul 2023 13:32:21 -0600 Subject: [PATCH 061/154] introduction of useEffect for keeping up with switches this somewhat fixed the tracking switch! Now it just double notifies... Accuracy seems to have some problems of it's own --- www/js/control/ProfileSettings.jsx | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 1da014c45..9a833de59 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,6 +1,6 @@ -import React from "react"; -import {Platform} from "react-native"; -import { Dialog, Button, Modal } from "react-native-paper"; +import React, {useEffect} from "react"; +import { Platform, Modal } from "react-native"; +import { Dialog, Button } from "react-native-paper"; import { angularize, getAngularService } from "../angular-react-helper"; import { object } from "prop-types"; import { useTranslation } from "react-i18next"; @@ -45,8 +45,26 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { var profileSettings = {}; const [nukeSetVis, setNukeVis] = React.useState(false); const [carbonDataVis, setCarbonDataVis] = React.useState(false); + const [collectSettings, setCollectSettings] = React.useState({}); let carbonDatasetString = t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); const carbonOptions = CarbonDatasetHelper.getCarbonDatasetOptions(); + + //watch for changes settings workaround + useEffect(() => { + var defined; + if(settings.collect?.trackingOn){ + defined = true; + } + else{ + return; + } + //if empty or undefined, do nothing + if(!defined){ + return; + } + //else update + setCollectSettings(settings.collect); + }, [settings.collect]); //methods that control the settings const uploadLog = function () { @@ -68,7 +86,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { const userStartStopTracking = function() { //note the dependency on the settings object (still passed in) - if (settings.collect.trackingOn){ + if (collectSettings.trackingOn){ return ControlCollectionHelper.forceTransition('STOP_TRACKING'); } else { return ControlCollectionHelper.forceTransition('START_TRACKING'); @@ -170,10 +188,10 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { {/* this toggle only kinda works */} - + {/* this switch is also fussy */} - + setCarbonDataVis(true)}> From d2966a15fbb1996a9a8989c6c9e33a30106cfd88 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 12 Jul 2023 11:31:07 -0600 Subject: [PATCH 062/154] patch back over to reliance on ProfileSettings rather than html --- www/js/control/general-settings.js | 4 +- www/templates/control/main-control.html | 126 ------------------------ 2 files changed, 3 insertions(+), 127 deletions(-) diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index a819fbe37..4226b37de 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -3,6 +3,7 @@ import angular from 'angular'; import ControlDataTable from './ControlDataTable'; import QrCode from '../components/QrCode'; +import ProfileSettings from './ProfileSettings'; angular.module('emission.main.control',['emission.services', 'emission.i18n.utils', @@ -21,7 +22,8 @@ angular.module('emission.main.control',['emission.services', 'emission.plugin.logger', 'emission.config.dynamic', QrCode.module, - ControlDataTable.module]) + ControlDataTable.module, + ProfileSettings.module]) .controller('ControlCtrl', function($scope, $window, $ionicScrollDelegate, $ionicPlatform, diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index d02cd1a4c..dceb8f7b4 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -15,132 +15,6 @@ style="position: absolute; height: 100%; inset: 0; display: flex; opacity: 0;">
-
-
{{'control.tracking'}}
- -
--> - - - -
-
{{'control.download-json-dump'}}
-
- -
- -
- -
-
{{'control.upload-log'}}
-
-
- -
-
{{'control.email-log'}}
-
-
- -
-
{{'control.user-data'}}
-
-
- -
-
-
{{'control.erase-data'}}
-
-
- - - -
{{entry.key}}
-
{{entry.value}}
-
-
-
- - -
-
{{'control.dev-zone'}}
-
-
- - -
-
-
{{'control.refresh'}}
-
-
-
-
{{'control.end-trip-sync'}}
-
-
-
-
{{'control.check-consent'}}
-
-
-
-
{{'control.dummy-notification'}}
-
-
- -
-
{{'control.upcoming-notifications'}}
-
- - -
-
{{'control.invalidate-cached-docs'}}
-
-
-
-
{{'control.nuke-all'}}
-
-
-
-
{{parseState(settings.collect.state)}}
-
-
-
-
{{'control.check-log'}}
-
-
-
-
{{'control.check-sensed-data'}}
-
-
- - -
-
{{'control.collection'}}
-
-
- - - -
-
{{'control.sync'}}
-
-
- - -
-
{{'control.app-version'}}
-
{{settings.clientAppVer}}
-
From ab1e6a40d659b1840284adb424a9c3db3735606a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 12 Jul 2023 11:32:08 -0600 Subject: [PATCH 063/154] conditional showing of log uploads --- www/js/control/ProfileSettings.jsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 9a833de59..433c9a0b0 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -21,7 +21,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { fixAppStatus, forceSync, openDatePicker, eraseUserData, refreshScreen, endForceSync, checkConsent, dummyNotification, invalidateCache, showLog, showSensed, - parseState } = settingsScope; + parseState, ui_config } = settingsScope; console.log("settings?", settingsObject); let settings = settingsObject; @@ -181,6 +181,14 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { ; } + //show the upload log dependent on config -- still importing the ui_config the workaround way + let logUploadSection; + console.log("ui config thing", ui_config.profile_controls.support_upload); + if(ui_config.profile_controls.support_upload == true) + { + logUploadSection = ; + } + return ( <> @@ -197,7 +205,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { {/* this row missing condition!!! Should only show iff ui_config.profile_controls.support_upload == true */} - + {logUploadSection} {userDataSection} From 3b123e8f59a621af40ef78e039fc9dcb84e3efa3 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 12 Jul 2023 11:36:49 -0600 Subject: [PATCH 064/154] formatting modals with elevation I did not really note a difference here yet --- www/js/control/ProfileSettings.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 433c9a0b0..47e96e236 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -230,9 +230,11 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { console.log("")} desc={settings?.clientAppVer}> - {/* menu for "nuke data" */} + {/* menu for "nuke data" -- elevation not really working?? */} setNukeVis(false)} - transparent={true}> + elevated={true} + transparent={true} + style={{ elevation: 3 }}> setNukeVis(false)}> {t('general-settings.clear-data')} @@ -258,6 +260,8 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { {/* menu for "set carbon dataset - only somewhat working" */} setCarbonDataVis(false)} + elevated={true} + style={{ elevation: 3 }} transparent={true}> setCarbonDataVis(false)}> From 0bdd36ce9318482b9c21423f33001c7bdb68943d Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 12 Jul 2023 14:40:41 -0600 Subject: [PATCH 065/154] migrated show OpCode! the PopOpCode is now what is shown and hidden when clicking the opcode setting - uses a dialog inside a modal --- www/js/control/PopOpCode.jsx | 36 +++++++++++++++++++++ www/js/control/ProfileSettings.jsx | 25 ++++++++++++++- www/js/control/general-settings.js | 50 +++++++++++++++--------------- 3 files changed, 85 insertions(+), 26 deletions(-) create mode 100644 www/js/control/PopOpCode.jsx diff --git a/www/js/control/PopOpCode.jsx b/www/js/control/PopOpCode.jsx new file mode 100644 index 000000000..f124b04e9 --- /dev/null +++ b/www/js/control/PopOpCode.jsx @@ -0,0 +1,36 @@ +import React from "react"; +import { angularize} from "../angular-react-helper"; +import { Modal } from 'react-native'; +import { Text, IconButton, Dialog } from 'react-native-paper'; +import { useTranslation } from "react-i18next"; +import { bool, string, func } from "prop-types"; +import QrCode from "../components/QrCode"; + +const PopOpCode = ({visibilityValue, tokenURL, action, setVis}) => { + const { t } = useTranslation(); + + return ( + setVis(false)} + elevated={true} + style={{ elevation: 3 }} + transparent={true}> + setVis(false)}> + {t("general-settings.qrcode")} + + + {t("general-settings.qrcode-share-title")} + action()}/> + + + + ) +} +PopOpCode.prototypes = { + visibilityValue: bool, + tokenURL: string, + action: func, + setVis: func +} + +angularize(PopOpCode, '"popOpCode"', 'emission.main.control.popOpCode'); +export default PopOpCode; \ No newline at end of file diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 47e96e236..eb8d63b41 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -8,6 +8,7 @@ import ExpansionSection from "./ExpandMenu"; import SettingRow from "./SettingRow"; import ControlDataTable from "./ControlDataTable"; import DemographicsSettingRow from "./DemographicsSettingRow"; +import PopOpCode from "./popOpCode"; //any pure functions can go outside const ProfileSettings = ({ settingsScope, settingsObject }) => { @@ -17,7 +18,7 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { //settingsScope is the $scope of general-settings.js //grab any variables or functions you need from it like this: //why is settings not defined but everything else is fine? - const { logOut, viewPrivacyPolicy, viewQRCode, + const { logOut, viewPrivacyPolicy, fixAppStatus, forceSync, openDatePicker, eraseUserData, refreshScreen, endForceSync, checkConsent, dummyNotification, invalidateCache, showLog, showSensed, @@ -121,6 +122,26 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { var userData = []; var rawUserData; + const shareQR = function() { + var prepopulateQRMessage = {}; + const c = document.getElementsByClassName('qrcode-link'); + const cbase64 = c[0].getAttribute('href'); + prepopulateQRMessage.files = [cbase64]; + prepopulateQRMessage.url = $scope.settings.auth.opcode; + + 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) + }, function(msg) { + console.log("Sharing failed with message: " + msg); + }); + } + + const viewQRCode = function(e) { + // tokenURL = "emission://login_token?token="+settings.auth.opcode; + setOpCodeVis(true); + } + const getUserData = function() { return CalorieCal.get().then(function(userDataFromStorage) { rawUserData = userDataFromStorage; @@ -286,6 +307,8 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { + + ); }; diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 4226b37de..640fe9d10 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -107,17 +107,17 @@ angular.module('emission.main.control',['emission.services', } //this function used in ProfileSettings to viewQRCode - $scope.viewQRCode = function($event) { - $scope.tokenURL = "emission://login_token?token="+$scope.settings.auth.opcode; - if ($scope.qrp) { - $scope.qrp.show($event); - } else { - $ionicPopover.fromTemplateUrl("templates/control/qrc.html", {scope: $scope}).then((q) => { - $scope.qrp = q; - $scope.qrp.show($event); - }).catch((err) => Logger.displayError("Error while displaying QR Code", err)); - } - } + // $scope.viewQRCode = function($event) { + // $scope.tokenURL = "emission://login_token?token="+$scope.settings.auth.opcode; + // if ($scope.qrp) { + // $scope.qrp.show($event); + // } else { + // $ionicPopover.fromTemplateUrl("templates/control/qrc.html", {scope: $scope}).then((q) => { + // $scope.qrp = q; + // $scope.qrp.show($event); + // }).catch((err) => Logger.displayError("Error while displaying QR Code", err)); + // } + // } //this function used in ProfileSettings to send DummyNotification $scope.dummyNotification = () => { @@ -710,19 +710,19 @@ angular.module('emission.main.control',['emission.services', // }); // } - $scope.shareQR = function() { - var prepopulateQRMessage = {}; - const c = document.getElementsByClassName('qrcode-link'); - const cbase64 = c[0].getAttribute('href'); - prepopulateQRMessage.files = [cbase64]; - prepopulateQRMessage.url = $scope.settings.auth.opcode; - - 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) - }, function(msg) { - console.log("Sharing failed with message: " + msg); - }); - } + // $scope.shareQR = function() { + // var prepopulateQRMessage = {}; + // const c = document.getElementsByClassName('qrcode-link'); + // const cbase64 = c[0].getAttribute('href'); + // prepopulateQRMessage.files = [cbase64]; + // prepopulateQRMessage.url = $scope.settings.auth.opcode; + + // 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) + // }, function(msg) { + // console.log("Sharing failed with message: " + msg); + // }); + // } }); From e8faa6f486b6ec7f872cfdf7fd7fe55188915fd4 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 12 Jul 2023 16:56:53 -0600 Subject: [PATCH 066/154] create alertbar to use instead of ionic popups --- www/js/control/AlertBar.jsx | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 www/js/control/AlertBar.jsx diff --git a/www/js/control/AlertBar.jsx b/www/js/control/AlertBar.jsx new file mode 100644 index 000000000..809e217b8 --- /dev/null +++ b/www/js/control/AlertBar.jsx @@ -0,0 +1,34 @@ +import React from "react"; +import { angularize} from "../angular-react-helper"; +import { StyleSheet } from 'react-native'; +import { Modal, Snackbar} from 'react-native-paper'; +import { useTranslation } from "react-i18next"; +import { string, func, bool} from "prop-types"; + +const AlertBar = ({visible, setVisible, messageKey}) => { + const { t } = useTranslation(); + const onDismissSnackBar = () => setVisible(false); + + return ( + setVisible(false)}> + { + onDismissSnackBar() + }, + }}> + {t(messageKey)} + + + ); + }; +AlertBar.prototypes = { + visible: bool, + setVisible: func, + messageKey: string +} + +export default AlertBar; \ No newline at end of file From 810a293032011404d9d457d6d75cb9efe64043b1 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Thu, 13 Jul 2023 20:55:59 -0400 Subject: [PATCH 067/154] SettingRow: prevent duplicate action() call Since the entire SettingRow now handles the onPress for the action, we no longer need onValueChange in the switch. This would cause the action to be called twice. --- www/js/control/SettingRow.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 3ae52a4f0..80b4633b3 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -15,7 +15,7 @@ const SettingRow = ({textKey, iconName, action, desc, switchValue}) => { //when toggled from OFF to ON, the switch display does not update //update takes when screen is "refreshed" - by tabbing btwn screens, showing policy... //works just fine when going from ON to OFF - rightComponent = action(e)}/>; + rightComponent = ; } let descriptionText; if(desc) { From ba0c10158ab4fe7b4b5d7fa677a6936bcea4e677 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Thu, 13 Jul 2023 21:12:04 -0400 Subject: [PATCH 068/154] ProfileSettings: track collectSettings w/ useState In general-settings, the `$scope.settings` object is used to store all of the current values of options that are represeted by the Profile Tab. As we migrate to ProfileSettings, this should be stored as state in React. This change handles only `collect` settings; everything that was previously stored under `$scope.settings.collect`. We accomplish this with useState and a new function `refreshCollectSettings`, which is called 1) on config load and 2) whenever any setting relating to collection is changed `settingsScope` needed to be retrieved in a different way. Previously it was passed from AngularJS a prop; this caused the ProfileSettings component to be re-initialized every time any scope value changed. This made it impossible to persist state in ProfileSettings. Now we will retrieve the scope with a DOM lookup; we find the root element of the scope, then ask Angular for the scope attached to that element. --- www/js/control/ProfileSettings.jsx | 151 +++++++++++++---------------- www/templates/main.html | 2 +- 2 files changed, 69 insertions(+), 84 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index eb8d63b41..58a1c807d 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,4 +1,4 @@ -import React, {useEffect} from "react"; +import React, { useState, useEffect } from "react"; import { Platform, Modal } from "react-native"; import { Dialog, Button } from "react-native-paper"; import { angularize, getAngularService } from "../angular-react-helper"; @@ -8,25 +8,26 @@ import ExpansionSection from "./ExpandMenu"; import SettingRow from "./SettingRow"; import ControlDataTable from "./ControlDataTable"; import DemographicsSettingRow from "./DemographicsSettingRow"; -import PopOpCode from "./popOpCode"; +import PopOpCode from "./PopOpCode"; +import useAppConfig from "../useAppConfig"; + +let controlUpdateCompleteListenerRegistered = false; //any pure functions can go outside -const ProfileSettings = ({ settingsScope, settingsObject }) => { +const ProfileSettings = () => { // anything that mutates must go in --- depend on props or state... - const { t } = useTranslation(); + const { t } = useTranslation(); + const { appConfig, loading } = useAppConfig(); - //settingsScope is the $scope of general-settings.js - //grab any variables or functions you need from it like this: - //why is settings not defined but everything else is fine? - const { logOut, viewPrivacyPolicy, + // get the scope of the general-settings.js file + const mainMetricsEl = document.getElementById('main-control').querySelector('ion-view'); + const settingsScope = angular.element(mainMetricsEl).scope(); + // grab any variables or functions we need from it like this: + const { settings, logOut, viewPrivacyPolicy, fixAppStatus, forceSync, openDatePicker, eraseUserData, refreshScreen, endForceSync, checkConsent, dummyNotification, invalidateCache, showLog, showSensed, - parseState, ui_config } = settingsScope; - - console.log("settings?", settingsObject); - let settings = settingsObject; - console.log("settings", settings); + parseState } = settingsScope; //angular services needed const CarbonDatasetHelper = getAngularService('CarbonDatasetHelper'); @@ -37,35 +38,58 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { const CalorieCal = getAngularService('CalorieCal'); const KVStore = getAngularService('KVStore'); + if (!controlUpdateCompleteListenerRegistered) { + settingsScope.$on('control.update.complete', function() { + console.debug("Received control.update.complete event, refreshing screen"); + refreshScreen(); + refreshCollectSettings(); + }); + controlUpdateCompleteListenerRegistered = true; + } + //functions that come directly from an Angular service const forceState = ControlCollectionHelper.forceState; const editCollectionConfig = ControlCollectionHelper.editConfig; const editSyncConfig = ControlSyncHelper.editConfig; //states and variables used to control/create the settings - var profileSettings = {}; - const [nukeSetVis, setNukeVis] = React.useState(false); - const [carbonDataVis, setCarbonDataVis] = React.useState(false); - const [collectSettings, setCollectSettings] = React.useState({}); + const [opCodeVis, setOpCodeVis] = useState(false); + const [nukeSetVis, setNukeVis] = useState(false); + const [carbonDataVis, setCarbonDataVis] = useState(false); + const [collectSettings, setCollectSettings] = useState({}); let carbonDatasetString = t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); const carbonOptions = CarbonDatasetHelper.getCarbonDatasetOptions(); - //watch for changes settings workaround useEffect(() => { - var defined; - if(settings.collect?.trackingOn){ - defined = true; + if (appConfig) { + refreshCollectSettings(); } - else{ - return; - } - //if empty or undefined, do nothing - if(!defined){ - return; + }, [appConfig]); + + async function refreshCollectSettings() { + console.debug('about to refreshCollectSettings, collectSettings = ', collectSettings); + const newCollectSettings = {}; + + // refresh collect plugin configuration + const collectionPluginConfig = await ControlCollectionHelper.getCollectionSettings(); + newCollectSettings.config = collectionPluginConfig; + + const collectionPluginState = await ControlCollectionHelper.getState(); + newCollectSettings.state = collectionPluginState; + newCollectSettings.trackingOn = collectionPluginState != "local.state.tracking_stopped" + && collectionPluginState != "STATE_TRACKING_STOPPED"; + + // I am not sure that this is actually needed anymore since https://github.com/e-mission/e-mission-data-collection/commit/92f41145e58c49e3145a9222a78d1ccacd16d2a7 + const geofenceConfig = await KVStore.get("OP_GEOFENCE_CFG"); + newCollectSettings.experimentalGeofenceOn = geofenceConfig != null; + + const isLowAccuracy = ControlCollectionHelper.isMediumAccuracy(); + if (typeof isLowAccuracy != 'undefined') { + newCollectSettings.lowAccuracy = isLowAccuracy; } - //else update - setCollectSettings(settings.collect); - }, [settings.collect]); + + setCollectSettings(newCollectSettings); + } //methods that control the settings const uploadLog = function () { @@ -77,49 +101,19 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { EmailHelper.sendEmail("loggerDB") }; - const isAndroid = function() { - return Platform.OS == "android"; - } - - const isIOS = function() { - return Platform.OS == "ios"; - } - - const userStartStopTracking = function() { - //note the dependency on the settings object (still passed in) - if (collectSettings.trackingOn){ - return ControlCollectionHelper.forceTransition('STOP_TRACKING'); - } else { - return ControlCollectionHelper.forceTransition('START_TRACKING'); - } - } - - const getLowAccuracy = function() { - var isMediumAccuracy = ControlCollectionHelper.isMediumAccuracy(); - if(typeof isMediumAccuracy == 'undefined') { - return false; - } - else{ - settings.collect.lowAccuracy = isMediumAccuracy; - return isMediumAccuracy; - } + async function userStartStopTracking() { + const transitionToForce = collectSettings.trackingOn ? 'STOP_TRACKING' : 'START_TRACKING'; + ControlCollectionHelper.forceTransition(transitionToForce); + /* the ControlCollectionHelper.forceTransition call above will trigger a + 'control.update.complete' event when it's done, which will trigger refreshCollectSettings. + So we don't need to call refreshCollectSettings here. */ } const toggleLowAccuracy = function() { - console.log("change attempt in ProfileSettigns"); - //the function below is broken? ControlCollectionHelper.toggleLowAccuracy(); - getLowAccuracy(); + refreshCollectSettings(); } - // const getCollectionSettings = function() { - // ControlCollectionHelper.getCollectionSettings().then(function(showConfig) { - // profileSettings.collect.show_config = showConfig; - // console.log("settings", showConfig); - // return showConfig; - // }); - // }; - var userData = []; var rawUserData; const shareQR = function() { @@ -202,11 +196,9 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { ; } - //show the upload log dependent on config -- still importing the ui_config the workaround way let logUploadSection; - console.log("ui config thing", ui_config.profile_controls.support_upload); - if(ui_config.profile_controls.support_upload == true) - { + console.debug("appConfg: support_upload:", appConfig?.profile_controls?.support_upload); + if (appConfig?.profile_controls?.support_upload) { logUploadSection = ; } @@ -216,16 +208,13 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { - {/* this toggle only kinda works */} - {/* this switch is also fussy */} setCarbonDataVis(true)}> - {/* this row missing condition!!! Should only show iff ui_config.profile_controls.support_upload == true */} {logUploadSection} @@ -241,11 +230,11 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { setNukeVis(true)}> - + - + console.log("")} desc={settings?.clientAppVer}> @@ -308,14 +297,10 @@ const ProfileSettings = ({ settingsScope, settingsObject }) => { - + ); - }; - ProfileSettings.propTypes = { - settingsScope: object, - settingsObject: object - } +}; angularize(ProfileSettings, 'ProfileSettings', 'emission.main.control.profileSettings'); - export default ProfileSettings; \ No newline at end of file + export default ProfileSettings; diff --git a/www/templates/main.html b/www/templates/main.html index cdc66d406..c3de4adcb 100644 --- a/www/templates/main.html +++ b/www/templates/main.html @@ -18,7 +18,7 @@ - + From 8d76706ae55772efa883901768abefead5a364c8 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Thu, 13 Jul 2023 21:12:18 -0400 Subject: [PATCH 069/154] general-settings: remove $scope.settings.collect These settings are now stored in collectSettings of ProfileSettings. All references to $scope.settings.collect have been removed / commented out. --- www/js/control/general-settings.js | 80 ++++++++++++++++-------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 640fe9d10..781e3957e 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -257,13 +257,14 @@ angular.module('emission.main.control',['emission.services', }); }; - $scope.getCollectionSettings = function() { - ControlCollectionHelper.getCollectionSettings().then(function(showConfig) { - $scope.$apply(function() { - $scope.settings.collect.show_config = showConfig; - }) - }); - }; + // this logic now lives in ProfileSettings' refreshCollectionSettings; remove this + // $scope.getCollectionSettings = function() { + // ControlCollectionHelper.getCollectionSettings().then(function(showConfig) { + // $scope.$apply(function() { + // $scope.settings.collect.show_config = showConfig; + // }) + // }); + // }; $scope.getSyncSettings = function() { ControlSyncHelper.getSyncSettings().then(function(showConfig) { @@ -297,9 +298,10 @@ angular.module('emission.main.control',['emission.services', } $scope.getState = function() { return ControlCollectionHelper.getState().then(function(response) { - $scope.$apply(function() { - $scope.settings.collect.state = response; - }); + /* collect state is now stored in ProfileSettings' collectSettings */ + // $scope.$apply(function() { + // $scope.settings.collect.state = response; + // }); return response; }, function(error) { Logger.displayError("while getting current state", error); @@ -381,33 +383,34 @@ angular.module('emission.main.control',['emission.services', $scope.refreshScreen = function() { console.log("Refreshing screen"); $scope.settings = {}; - $scope.settings.collect = {}; + // $scope.settings.collect = {}; // collectSettings are moved to ProfileSettings; remove this $scope.settings.sync = {}; $scope.settings.notification = {}; $scope.settings.auth = {}; $scope.settings.connect = {}; $scope.settings.clientAppVer = ClientStats.getAppVersion(); $scope.getConnectURL(); - $scope.getCollectionSettings(); + // $scope.getCollectionSettings(); // collectSettings are moved to ProfileSettings; remove this $scope.getSyncSettings(); $scope.getOPCode(); - $scope.getState().then($scope.isTrackingOn).then(function(isTracking) { - $scope.$apply(function() { - console.log("Setting settings.collect.trackingOn = "+isTracking); - $scope.settings.collect.trackingOn = isTracking; - }); - }); - KVStore.get("OP_GEOFENCE_CFG").then(function(storedCfg) { - $scope.$apply(function() { - if (storedCfg == null) { - console.log("Setting settings.collect.experimentalGeofenceOn = false"); - $scope.settings.collect.experimentalGeofenceOn = false; - } else { - console.log("Setting settings.collect.experimentalGeofenceOn = true"); - $scope.settings.collect.experimentalGeofenceOn = true; - } - }); - }); + /* this is moved to ProfileSettings; remove this */ + // $scope.getState().then($scope.isTrackingOn).then(function(isTracking) { + // $scope.$apply(function() { + // console.log("Setting settings.collect.trackingOn = "+isTracking); + // $scope.settings.collect.trackingOn = isTracking; + // }); + // }); + // KVStore.get("OP_GEOFENCE_CFG").then(function(storedCfg) { + // $scope.$apply(function() { + // if (storedCfg == null) { + // console.log("Setting settings.collect.experimentalGeofenceOn = false"); + // $scope.settings.collect.experimentalGeofenceOn = false; + // } else { + // console.log("Setting settings.collect.experimentalGeofenceOn = true"); + // $scope.settings.collect.experimentalGeofenceOn = true; + // } + // }); + // }); if ($scope.ui_config.reminderSchemes) { NotificationScheduler.getReminderPrefs().then((prefs) => { $scope.$apply(() => { @@ -569,15 +572,16 @@ angular.module('emission.main.control',['emission.services', }).then(function(popover) { $scope.syncSettingsPopup = popover; }); - $scope.isTrackingOn = function() { - return $ionicPlatform.ready().then(function() { - if($scope.isAndroid()){ - return $scope.settings.collect.state != "local.state.tracking_stopped"; - } else if ($scope.isIOS()) { - return $scope.settings.collect.state != "STATE_TRACKING_STOPPED"; - } - }); - }; + // moved to ProfileSettings + // $scope.isTrackingOn = function() { + // return $ionicPlatform.ready().then(function() { + // if($scope.isAndroid()){ + // return $scope.settings.collect.state != "local.state.tracking_stopped"; + // } else if ($scope.isIOS()) { + // return $scope.settings.collect.state != "STATE_TRACKING_STOPPED"; + // } + // }); + // }; //moved this into ProfileSettings :) // $scope.userStartStopTracking = function() { // if ($scope.settings.collect.trackingOn){ From bfac9c83f50973c5436615aaa4bffc9f52a0197c Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Thu, 13 Jul 2023 22:50:00 -0400 Subject: [PATCH 070/154] profile settings: tweak styling --- www/js/control/ExpandMenu.jsx | 16 ++++++++-------- www/js/control/SettingRow.jsx | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/www/js/control/ExpandMenu.jsx b/www/js/control/ExpandMenu.jsx index 8cf0a91a3..e7fb715d4 100644 --- a/www/js/control/ExpandMenu.jsx +++ b/www/js/control/ExpandMenu.jsx @@ -1,19 +1,20 @@ import React from "react"; import { angularize} from "../angular-react-helper"; import { StyleSheet } from 'react-native'; -import { List } from 'react-native-paper'; +import { List, useTheme } from 'react-native-paper'; import { useTranslation } from "react-i18next"; import { string } from "prop-types"; const ExpansionSection = (props) => { const { t } = useTranslation(); //this accesses the translations + const { colors } = useTheme(); // use this to get the theme colors instead of hardcoded #hex colors const [expanded, setExpanded] = React.useState(false); const handlePress = () => setExpanded(!expanded); return ( { ); }; const styles = StyleSheet.create({ - section:{ + section: (surfaceColor) => ({ justifyContent: 'space-between', - backgroundColor: '#fff', - height: 75, - margin: 5, - }, + backgroundColor: surfaceColor, + margin: 1, + }), title: { fontSize: 16, marginVertical: 2, @@ -39,4 +39,4 @@ ExpansionSection.propTypes = { } angularize(ExpansionSection, 'ExpansionSection', 'emission.main.control.expansionSection'); -export default ExpansionSection; \ No newline at end of file +export default ExpansionSection; diff --git a/www/js/control/SettingRow.jsx b/www/js/control/SettingRow.jsx index 80b4633b3..0e4000afc 100644 --- a/www/js/control/SettingRow.jsx +++ b/www/js/control/SettingRow.jsx @@ -1,12 +1,13 @@ import React from "react"; import { angularize} from "../angular-react-helper"; import { StyleSheet } from 'react-native'; -import { List, IconButton, Switch} from 'react-native-paper'; +import { List, Switch, useTheme} from 'react-native-paper'; import { useTranslation } from "react-i18next"; import { string, func, bool} from "prop-types"; const SettingRow = ({textKey, iconName, action, desc, switchValue}) => { const { t } = useTranslation(); //this accesses the translations + const { colors } = useTheme(); // use this to get the theme colors instead of hardcoded #hex colors let rightComponent; if (iconName) { @@ -26,7 +27,7 @@ const SettingRow = ({textKey, iconName, action, desc, switchValue}) => { return ( { ); }; const styles = StyleSheet.create({ - item:{ + item: (surfaceColor) => ({ justifyContent: 'space-between', alignContent: 'center', - backgroundColor: '#fff', - height: 75, - margin: 5, - }, + backgroundColor: surfaceColor, + margin: 1, + }), title: { fontSize: 16, marginVertical: 2, @@ -57,4 +57,4 @@ SettingRow.propTypes = { } angularize(SettingRow, 'SettingRow', 'emission.main.control.settingRow'); -export default SettingRow; \ No newline at end of file +export default SettingRow; From 32b4254b8cf057c5bf3e2f80ca3cbd89333a433d Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 14 Jul 2023 10:54:30 -0400 Subject: [PATCH 071/154] ProfileSettings: rename variable 'mainControlEl' this was named wrong by accident --- www/js/control/ProfileSettings.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 58a1c807d..da91332c5 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -20,8 +20,8 @@ const ProfileSettings = () => { const { appConfig, loading } = useAppConfig(); // get the scope of the general-settings.js file - const mainMetricsEl = document.getElementById('main-control').querySelector('ion-view'); - const settingsScope = angular.element(mainMetricsEl).scope(); + const mainControlEl = document.getElementById('main-control').querySelector('ion-view'); + const settingsScope = angular.element(mainControlEl).scope(); // grab any variables or functions we need from it like this: const { settings, logOut, viewPrivacyPolicy, fixAppStatus, forceSync, openDatePicker, From 19fa99f5f82926825b1e4377046342735931e9e3 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 14 Jul 2023 11:23:29 -0400 Subject: [PATCH 072/154] remove unused props from These properties are no longer used by ProfileSettings. The general-settings scope is now retrieved globally via a DOM lookup. --- www/templates/control/main-control.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index dceb8f7b4..234af655c 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -1,7 +1,7 @@ - +
From c75d73263b1bc5f43935c1dc6d55c9fc6a4ed8f7 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 14 Jul 2023 11:16:32 -0600 Subject: [PATCH 073/154] fix userData dropdown in order to have functional user data dropdown, moved userData methods back to general-settings and getting them from the scope --- www/js/control/ControlDataTable.jsx | 2 +- www/js/control/ProfileSettings.jsx | 76 ++++++++++++++--------------- www/js/control/general-settings.js | 4 +- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/www/js/control/ControlDataTable.jsx b/www/js/control/ControlDataTable.jsx index 84f6d724e..4fc4af299 100644 --- a/www/js/control/ControlDataTable.jsx +++ b/www/js/control/ControlDataTable.jsx @@ -6,7 +6,7 @@ import { array } from "prop-types"; // Note the camelCase to dash-case conventions when translating to .html files! // val with explicit call toString() to resolve bool values not showing const ControlDataTable = ({ controlData }) => { - // console.log("Printing data trying to tabulate", controlData); + console.log("Printing data trying to tabulate", controlData); return ( //rows require unique keys! diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index da91332c5..a46aa6dbb 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -27,7 +27,7 @@ const ProfileSettings = () => { fixAppStatus, forceSync, openDatePicker, eraseUserData, refreshScreen, endForceSync, checkConsent, dummyNotification, invalidateCache, showLog, showSensed, - parseState } = settingsScope; + parseState, userDataSaved, userData } = settingsScope; //angular services needed const CarbonDatasetHelper = getAngularService('CarbonDatasetHelper'); @@ -114,8 +114,8 @@ const ProfileSettings = () => { refreshCollectSettings(); } - var userData = []; - var rawUserData; + // var userData = []; + // var rawUserData; const shareQR = function() { var prepopulateQRMessage = {}; const c = document.getElementsByClassName('qrcode-link'); @@ -136,40 +136,40 @@ const ProfileSettings = () => { setOpCodeVis(true); } - const getUserData = function() { - return CalorieCal.get().then(function(userDataFromStorage) { - rawUserData = userDataFromStorage; - if(userDataSaved()) { - userData = [] - var height = userDataFromStorage.height.toString(); - var weight = userDataFromStorage.weight.toString(); - var temp = { - age: userDataFromStorage.age, - height: height + (userDataFromStorage.heightUnit == 1? ' cm' : ' ft'), - weight: weight + (userDataFromStorage.weightUnit == 1? ' kg' : ' lb'), - gender: userDataFromStorage.gender == 1? t('gender-male') : t('gender-female') - } - for (var i in temp) { - userData.push({key: i, val: temp[i]}); //changed from value to val! watch for rammifications! - } - } - }); - } - const userDataSaved = function() { - console.log(rawUserData); - var defined; - if(rawUserData){ - defined = true; - } - else{ - defined = false; - } - if (defined && rawUserData != null) { - return rawUserData.userDataSaved; - } else{ - return false; - } - } + // const getUserData = function() { + // return CalorieCal.get().then(function(userDataFromStorage) { + // rawUserData = userDataFromStorage; + // if(userDataSaved()) { + // userData = [] + // var height = userDataFromStorage.height.toString(); + // var weight = userDataFromStorage.weight.toString(); + // var temp = { + // age: userDataFromStorage.age, + // height: height + (userDataFromStorage.heightUnit == 1? ' cm' : ' ft'), + // weight: weight + (userDataFromStorage.weightUnit == 1? ' kg' : ' lb'), + // gender: userDataFromStorage.gender == 1? t('gender-male') : t('gender-female') + // } + // for (var i in temp) { + // userData.push({key: i, val: temp[i]}); //changed from value to val! watch for rammifications! + // } + // } + // }); + // } + // const userDataSaved = function() { + // console.log("user data?", rawUserData); + // var defined; + // if(rawUserData){ + // defined = true; + // } + // else{ + // defined = false; + // } + // if (defined && rawUserData != null) { + // return rawUserData.userDataSaved; + // } else{ + // return false; + // } + // } var prepopulateMessage = { message: t('general-settings.share-message'), @@ -186,7 +186,7 @@ const ProfileSettings = () => { }); } - //conditional creation of the user dropdown + //conditional creation of setting sections let userDataSection; if(userDataSaved()) { diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index 781e3957e..aca33c37a 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -155,7 +155,7 @@ angular.module('emission.main.control',['emission.services', $scope.appStatusModal.hide(); } - // $scope.userData = [] + $scope.userData = [] $scope.getUserData = function() { return CalorieCal.get().then(function(userDataFromStorage) { $scope.rawUserData = userDataFromStorage; @@ -170,7 +170,7 @@ angular.module('emission.main.control',['emission.services', gender: userDataFromStorage.gender == 1? i18next.t('gender-male') : i18next.t('gender-female') } for (var i in temp) { - $scope.userData.push({key: i, value: temp[i]}); + $scope.userData.push({key: i, val: temp[i]}); //needs to be val for the data table! } } }); From f7da968f52e4827a42c4bf2ab0ffa9528ddb7160 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 14 Jul 2023 11:17:09 -0600 Subject: [PATCH 074/154] remove unused imports no longer using Platform because Modal is better and no longer importing an object as a parameter --- www/js/control/ProfileSettings.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index a46aa6dbb..baed25793 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,8 +1,7 @@ import React, { useState, useEffect } from "react"; -import { Platform, Modal } from "react-native"; +import { Modal } from "react-native"; import { Dialog, Button } from "react-native-paper"; import { angularize, getAngularService } from "../angular-react-helper"; -import { object } from "prop-types"; import { useTranslation } from "react-i18next"; import ExpansionSection from "./ExpandMenu"; import SettingRow from "./SettingRow"; From 26fc5c36a222ef5449b57ff934fb11b87b2a0fad Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 14 Jul 2023 12:01:09 -0600 Subject: [PATCH 075/154] fix qrSharing the way the link to the qr code document was being accessed was broken - this seems to fix it, but testing in the devapp is difficult --- www/js/control/ProfileSettings.jsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index baed25793..926f78fb3 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -117,10 +117,9 @@ const ProfileSettings = () => { // var rawUserData; const shareQR = function() { var prepopulateQRMessage = {}; - const c = document.getElementsByClassName('qrcode-link'); - const cbase64 = c[0].getAttribute('href'); - prepopulateQRMessage.files = [cbase64]; - prepopulateQRMessage.url = $scope.settings.auth.opcode; + var qrAddress = "emission://login_token?token="+settings?.auth?.opcode; + prepopulateQRMessage.files = [qrAddress]; + prepopulateQRMessage.url = settings.auth.opcode; window.plugins.socialsharing.shareWithOptions(prepopulateQRMessage, function(result) { console.log("Share completed? " + result.completed); // On Android apps mostly return false even while it's true @@ -131,7 +130,6 @@ const ProfileSettings = () => { } const viewQRCode = function(e) { - // tokenURL = "emission://login_token?token="+settings.auth.opcode; setOpCodeVis(true); } From d9afec80668dc692c990e2b561c4ec5e19bfbb87 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 14 Jul 2023 12:02:10 -0600 Subject: [PATCH 076/154] removal of converted code deleted commented out code that was confirmed to be working in ProfileSettings! --- www/js/control/general-settings.js | 182 ----------------------------- 1 file changed, 182 deletions(-) diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index aca33c37a..2af058158 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -77,18 +77,6 @@ angular.module('emission.main.control',['emission.services', ionicDatePicker.openDatePicker(datepickerObject); }; - //these have been converted!! - // $scope.carbonDatasetString = i18next.t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); - - // $scope.uploadLog = function () { - // UploadHelper.uploadFile("loggerDB") - // }; - - // $scope.emailLog = function () { - // // Passing true, we want to send logs - // EmailHelper.sendEmail("loggerDB") - // }; - //this function used in ProfileSettings to viewPrivacyPolicy $scope.viewPrivacyPolicy = function($event) { // button -> list element -> scroll @@ -212,39 +200,6 @@ angular.module('emission.main.control',['emission.services', $scope.refreshScreen(); }); }); - // $scope.getLowAccuracy = function() { - // // return true: toggle on; return false: toggle off. - // var isMediumAccuracy = ControlCollectionHelper.isMediumAccuracy(); - // if (!angular.isDefined(isMediumAccuracy)) { - // // config not loaded when loading ui, set default as false - // // TODO: Read the value if it is not defined. - // // Otherwise, don't we have a race with reading? - // // we don't really $apply on this field... - // return false; - // } else { - // $scope.settings.collect.lowAccuracy = isMediumAccuracy; //adding to scope to use w/ switches - // return isMediumAccuracy; - // } - // } - - //this is the action called in ProfileSettings for the accuracy toggle - // $scope.toggleLowAccuracy = ControlCollectionHelper.toggleLowAccuracy; - - // $scope.getTracking = function() { - // console.log("tracking on or off?", $scope.settings.collect.trackingOn); - // // return true: toggle on; return false: toggle off. - // var isTracking = $scope.settings.collect.trackingOn; - // if (!angular.isDefined(isTracking)) { - // // config not loaded when loading ui, set default as false - // // TODO: Read the value if it is not defined. - // // Otherwise, don't we have a race with reading? - // // we don't really $apply on this field... - // return false; - // } else { - // console.log("tracking on or off?", $scope.settings.collect.trackingOn); - // return $scope.settings.collect.trackingOn; - // } - // } $scope.getConnectURL = function() { ControlHelper.getSettings().then(function(response) { @@ -257,15 +212,6 @@ angular.module('emission.main.control',['emission.services', }); }; - // this logic now lives in ProfileSettings' refreshCollectionSettings; remove this - // $scope.getCollectionSettings = function() { - // ControlCollectionHelper.getCollectionSettings().then(function(showConfig) { - // $scope.$apply(function() { - // $scope.settings.collect.show_config = showConfig; - // }) - // }); - // }; - $scope.getSyncSettings = function() { ControlSyncHelper.getSyncSettings().then(function(showConfig) { $scope.$apply(function() { @@ -324,26 +270,6 @@ angular.module('emission.main.control',['emission.services', }); } - //in ProfileSettings in DevZone - // $scope.nukeUserCache = function() { - // var nukeChoiceActions = [{text: i18next.t('general-settings.nuke-ui-state-only'), - // action: KVStore.clearOnlyLocal}, - // {text: i18next.t('general-settings.nuke-native-cache-only'), - // action: KVStore.clearOnlyNative}, - // {text: i18next.t('general-settings.nuke-everything'), - // action: KVStore.clearAll}]; - - // $ionicActionSheet.show({ - // titleText: i18next.t('general-settings.clear-data'), - // cancelText: i18next.t('general-settings.cancel'), - // buttons: nukeChoiceActions, - // buttonClicked: function(index, button) { - // button.action(); - // return true; - // } - // }); - // } - //in ProfileSettings in DevZone $scope.invalidateCache = function() { window.cordova.plugins.BEMUserCache.invalidateAllCache().then(function(result) { @@ -383,34 +309,14 @@ angular.module('emission.main.control',['emission.services', $scope.refreshScreen = function() { console.log("Refreshing screen"); $scope.settings = {}; - // $scope.settings.collect = {}; // collectSettings are moved to ProfileSettings; remove this $scope.settings.sync = {}; $scope.settings.notification = {}; $scope.settings.auth = {}; $scope.settings.connect = {}; $scope.settings.clientAppVer = ClientStats.getAppVersion(); $scope.getConnectURL(); - // $scope.getCollectionSettings(); // collectSettings are moved to ProfileSettings; remove this $scope.getSyncSettings(); $scope.getOPCode(); - /* this is moved to ProfileSettings; remove this */ - // $scope.getState().then($scope.isTrackingOn).then(function(isTracking) { - // $scope.$apply(function() { - // console.log("Setting settings.collect.trackingOn = "+isTracking); - // $scope.settings.collect.trackingOn = isTracking; - // }); - // }); - // KVStore.get("OP_GEOFENCE_CFG").then(function(storedCfg) { - // $scope.$apply(function() { - // if (storedCfg == null) { - // console.log("Setting settings.collect.experimentalGeofenceOn = false"); - // $scope.settings.collect.experimentalGeofenceOn = false; - // } else { - // console.log("Setting settings.collect.experimentalGeofenceOn = true"); - // $scope.settings.collect.experimentalGeofenceOn = true; - // } - // }); - // }); if ($scope.ui_config.reminderSchemes) { NotificationScheduler.getReminderPrefs().then((prefs) => { $scope.$apply(() => { @@ -554,11 +460,6 @@ angular.module('emission.main.control',['emission.services', }).then($scope.forceSync); } - //migrated! - // $scope.forceState = ControlCollectionHelper.forceState; - // $scope.editCollectionConfig = ControlCollectionHelper.editConfig; - // $scope.editSyncConfig = ControlSyncHelper.editConfig; - $scope.isAndroid = function() { return ionic.Platform.isAndroid(); } @@ -572,32 +473,6 @@ angular.module('emission.main.control',['emission.services', }).then(function(popover) { $scope.syncSettingsPopup = popover; }); - // moved to ProfileSettings - // $scope.isTrackingOn = function() { - // return $ionicPlatform.ready().then(function() { - // if($scope.isAndroid()){ - // return $scope.settings.collect.state != "local.state.tracking_stopped"; - // } else if ($scope.isIOS()) { - // return $scope.settings.collect.state != "STATE_TRACKING_STOPPED"; - // } - // }); - // }; - //moved this into ProfileSettings :) - // $scope.userStartStopTracking = function() { - // if ($scope.settings.collect.trackingOn){ - // return ControlCollectionHelper.forceTransition('STOP_TRACKING'); - // } else { - // return ControlCollectionHelper.forceTransition('START_TRACKING'); - // } - // } - - //using the react accordians now! - // $scope.getExpandButtonClass = function() { - // return ($scope.expanded)? "icon ion-ios-arrow-up" : "icon ion-ios-arrow-down"; - // } - // $scope.getUserDataExpandButtonClass = function() { - // return ($scope.dataExpanded)? "icon ion-ios-arrow-up" : "icon ion-ios-arrow-down"; - // } //in ProfileSettings in UserData $scope.eraseUserData = function() { @@ -631,32 +506,6 @@ angular.module('emission.main.control',['emission.services', // }; //this was eliminated in coversion because the React accordians handle their state - // $scope.expandDeveloperZone = function() { - // if ($scope.collectionExpanded()) { - // $scope.expanded = false; - // $ionicScrollDelegate.resize(); - // $ionicScrollDelegate.scrollTo(0, 0, true); - - // } else { - // $scope.expanded = true; - // $ionicScrollDelegate.resize(); - // $ionicScrollDelegate.scrollTo(0, 1000, true); - // } - // } - // $scope.toggleUserData = function() { - // if ($scope.dataExpanded) { - // $scope.dataExpanded = false; - // } else { - // $scope.dataExpanded = true; - // } - // } - // $scope.collectionExpanded = function() { - // return $scope.expanded; - // } - // $scope.userDataExpanded = function() { - // return $scope.dataExpanded && $scope.userDataSaved(); - // } - var handleNoConsent = function(resultDoc) { $ionicPopup.confirm({template: i18next.t('general-settings.consent-not-found')}) .then(function(res){ @@ -698,35 +547,4 @@ angular.module('emission.main.control',['emission.services', }); } - var prepopulateMessage = { - message: i18next.t('general-settings.share-message'), // not supported on some apps (Facebook, Instagram) - subject: i18next.t('general-settings.share-subject'), // fi. for email - url: i18next.t('general-settings.share-url') - } - - // //in ProfileSettings above is a helper var! - // $scope.share = function() { - // window.plugins.socialsharing.shareWithOptions(prepopulateMessage, 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) - // }, function(msg) { - // console.log("Sharing failed with message: " + msg); - // }); - // } - - // $scope.shareQR = function() { - // var prepopulateQRMessage = {}; - // const c = document.getElementsByClassName('qrcode-link'); - // const cbase64 = c[0].getAttribute('href'); - // prepopulateQRMessage.files = [cbase64]; - // prepopulateQRMessage.url = $scope.settings.auth.opcode; - - // 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) - // }, function(msg) { - // console.log("Sharing failed with message: " + msg); - // }); - // } - }); From 3f86035ad9a2026281249e5bc2178b202f5dcb67 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 14 Jul 2023 14:50:29 -0600 Subject: [PATCH 077/154] formatting opcode popup --- www/js/control/PopOpCode.jsx | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/www/js/control/PopOpCode.jsx b/www/js/control/PopOpCode.jsx index f124b04e9..d04801ab9 100644 --- a/www/js/control/PopOpCode.jsx +++ b/www/js/control/PopOpCode.jsx @@ -1,30 +1,52 @@ import React from "react"; import { angularize} from "../angular-react-helper"; -import { Modal } from 'react-native'; -import { Text, IconButton, Dialog } from 'react-native-paper'; +import { Modal, StyleSheet } from 'react-native'; +import { Button, Text, IconButton, Dialog, useTheme } from 'react-native-paper'; import { useTranslation } from "react-i18next"; import { bool, string, func } from "prop-types"; import QrCode from "../components/QrCode"; const PopOpCode = ({visibilityValue, tokenURL, action, setVis}) => { const { t } = useTranslation(); + const { colors } = useTheme(); return ( setVis(false)} - elevated={true} - style={{ elevation: 3 }} transparent={true}> - setVis(false)}> + setVis(false)} + style={styles.dialog(colors.elevation.level3)}> {t("general-settings.qrcode")} - + {t("general-settings.qrcode-share-title")} - action()}/> + action()} style={styles.button}/> + + + ) } +const styles = StyleSheet.create({ + dialog: (surfaceColor) => ({ + backgroundColor: surfaceColor, + margin: 1, + }), + title: + { + alignItems: 'center', + justifyContent: 'center', + }, + content: { + alignItems: 'center', + justifyContent: 'center', + }, + button: { + margin: 'auto', + } + }); PopOpCode.prototypes = { visibilityValue: bool, tokenURL: string, From 5ae257ba911f4c9b14f8ffa079d171ea98b47fa4 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 14 Jul 2023 15:18:54 -0600 Subject: [PATCH 078/154] convert forceState the actionsheet coming from ControlCollectionHelper was buggy - this rough migration allows React to handle the popup, while using CCH's forceTransition to complete the action --- www/js/control/ProfileSettings.jsx | 37 ++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 926f78fb3..784e1b2d0 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -47,7 +47,6 @@ const ProfileSettings = () => { } //functions that come directly from an Angular service - const forceState = ControlCollectionHelper.forceState; const editCollectionConfig = ControlCollectionHelper.editConfig; const editSyncConfig = ControlSyncHelper.editConfig; @@ -55,9 +54,16 @@ const ProfileSettings = () => { const [opCodeVis, setOpCodeVis] = useState(false); const [nukeSetVis, setNukeVis] = useState(false); const [carbonDataVis, setCarbonDataVis] = useState(false); + const [forceStateVis, setForceStateVis] = useState(false); const [collectSettings, setCollectSettings] = useState({}); let carbonDatasetString = t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); const carbonOptions = CarbonDatasetHelper.getCarbonDatasetOptions(); + const stateActions = [{text: "Initialize", transition: "INITIALIZE"}, + {text: 'Start trip', transition: "EXITED_GEOFENCE"}, + {text: 'End trip', transition: "STOPPED_MOVING"}, + {text: 'Visit ended', transition: "VISIT_ENDED"}, + {text: 'Visit started', transition: "VISIT_STARTED"}, + {text: 'Remote push', transition: "RECEIVED_SILENT_PUSH"}] useEffect(() => { if (appConfig) { @@ -227,7 +233,7 @@ const ProfileSettings = () => { setNukeVis(true)}> - + setForceStateVis(true)}> @@ -294,6 +300,33 @@ const ProfileSettings = () => { + {/* force state sheet */} + setForceStateVis(false)} + elevated={true} + style={{ elevation: 3 }} + transparent={true}> + setForceStateVis(false)}> + {"Force State"} + + {stateActions.map((e) => + + )} + + + + + + + ); From 1b050128da8ac26da7478d0369e0bbda82488a14 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 14 Jul 2023 15:33:22 -0600 Subject: [PATCH 079/154] formatting popups --- www/js/control/ProfileSettings.jsx | 32 +++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 784e1b2d0..ed8179950 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; -import { Modal } from "react-native"; -import { Dialog, Button } from "react-native-paper"; +import { Modal, StyleSheet } from "react-native"; +import { Dialog, Button, useTheme } from "react-native-paper"; import { angularize, getAngularService } from "../angular-react-helper"; import { useTranslation } from "react-i18next"; import ExpansionSection from "./ExpandMenu"; @@ -17,6 +17,7 @@ const ProfileSettings = () => { // anything that mutates must go in --- depend on props or state... const { t } = useTranslation(); const { appConfig, loading } = useAppConfig(); + const { colors } = useTheme(); // get the scope of the general-settings.js file const mainControlEl = document.getElementById('main-control').querySelector('ion-view'); @@ -245,11 +246,10 @@ const ProfileSettings = () => { {/* menu for "nuke data" -- elevation not really working?? */} setNukeVis(false)} - elevated={true} - transparent={true} - style={{ elevation: 3 }}> + transparent={true}> setNukeVis(false)}> + onDismiss={() => setNukeVis(false)} + style={styles.dialog(colors.elevation.level3)}> {t('general-settings.clear-data')} diff --git a/www/js/diary/LabelDetailsScreen.tsx b/www/js/diary/LabelDetailsScreen.tsx index 5a3385336..f05b95881 100644 --- a/www/js/diary/LabelDetailsScreen.tsx +++ b/www/js/diary/LabelDetailsScreen.tsx @@ -4,7 +4,7 @@ import React, { useContext } from "react"; import { View, ScrollView, StyleSheet, useWindowDimensions } from "react-native"; -import { Appbar, Divider, IconButton, Surface, Text, useTheme } from "react-native-paper"; +import { Appbar, Divider, Surface, Text, useTheme } from "react-native-paper"; import { LabelTabContext } from "./LabelTab"; import { cardStyles } from "./cards/DiaryCard"; import LeafletView from "../components/LeafletView"; @@ -14,6 +14,7 @@ import UserInputButton from "../survey/enketo/UserInputButton"; import { getAngularService } from "../angular-react-helper"; import { useImperialConfig } from "../config/useImperialConfig"; import { useAddressNames } from "./addressNamesHelper"; +import { Icon } from "../components/Icon"; const LabelScreenDetails = ({ route, navigation }) => { @@ -29,101 +30,100 @@ const LabelScreenDetails = ({ route, navigation }) => { const DiaryHelper = getAngularService('DiaryHelper'); const sectionsFormatted = DiaryHelper.getFormattedSectionProperties(trip, {getFormattedDistance, distanceSuffix}); - + return (<> - - { navigation.goBack() }} /> - - - - - - {trip.display_start_time} - - - - {tripStartDisplayName} - - - - - - {trip.display_end_time} - - - - {tripEndDisplayName} + + { navigation.goBack() }} /> + + + + + + {trip.display_start_time} - - - - - - - - - {t('diary.distance')} - - - {`${getFormattedDistance(trip.distance)} ${distanceSuffix}`} - - - - - {t('diary.time')} - - - {trip.display_time} + + + {tripStartDisplayName} - - - {trip.percentages?.map?.((pct, i) => ( - - - - {pct.pct}% - - - ))} - - - {surveyOpt?.elementTag == 'multilabel' && - } - {surveyOpt?.elementTag == 'enketo-trip-button' - && } + + + + {trip.display_end_time} + + + + {tripEndDisplayName} + - {/* for multi-section trips, show a list of sections */} - {sectionsFormatted?.length > 1 && - - {sectionsFormatted.map((section, i) => ( - - - {section.fmt_time_range} - {section.fmt_time} - - - - {`${section.fmt_distance} ${section.fmt_distance_suffix}`} + + + + + + + + {t('diary.distance')} + + + {`${getFormattedDistance(trip.distance)} ${distanceSuffix}`} + + + + + {t('diary.time')} + + + {trip.display_time} + + + + {trip.percentages?.map?.((pct, i) => ( + + + + {pct.pct}% - - - - - ))} + ))} + + + + {surveyOpt?.elementTag == 'multilabel' && + } + {surveyOpt?.elementTag == 'enketo-trip-button' + && } - } + {/* for multi-section trips, show a list of sections */} + {sectionsFormatted?.length > 1 && + + {sectionsFormatted.map((section, i) => ( + + + {section.fmt_time_range} + {section.fmt_time} + + + + {`${section.fmt_distance} ${section.fmt_distance_suffix}`} + + + + + + + ))} + + } - {/* TODO: show speed graph here */} + {/* TODO: show speed graph here */} - - + + ) } diff --git a/www/js/diary/cards/PlaceCard.tsx b/www/js/diary/cards/PlaceCard.tsx index fd21e8126..f68cdc3ed 100644 --- a/www/js/diary/cards/PlaceCard.tsx +++ b/www/js/diary/cards/PlaceCard.tsx @@ -8,7 +8,7 @@ import React from "react"; import { View, StyleSheet } from 'react-native'; -import { IconButton, Text } from 'react-native-paper'; +import { Text } from 'react-native-paper'; import { object } from "prop-types"; import useAppConfig from "../../useAppConfig"; import AddNoteButton from "../../survey/enketo/AddNoteButton"; @@ -16,6 +16,7 @@ import AddedNotesList from "../../survey/enketo/AddedNotesList"; import { getTheme } from "../../appTheme"; import { DiaryCard, cardStyles } from "./DiaryCard"; import { useAddressNames } from "../addressNamesHelper"; +import { Icon } from "../../components/Icon"; const PlaceCard = ({ place }) => { @@ -34,9 +35,8 @@ const PlaceCard = ({ place }) => { {/* place name */} - - + + {placeDisplayName} diff --git a/www/js/diary/cards/TripCard.tsx b/www/js/diary/cards/TripCard.tsx index 52467cc5c..560819a45 100644 --- a/www/js/diary/cards/TripCard.tsx +++ b/www/js/diary/cards/TripCard.tsx @@ -7,7 +7,7 @@ import React, { useEffect, useState } from "react"; import { getAngularService } from "../../angular-react-helper"; import { View, useWindowDimensions, StyleSheet } from 'react-native'; -import { Divider, IconButton, Text } from 'react-native-paper'; +import { Divider, Text, IconButton } from 'react-native-paper'; import { object } from "prop-types"; import LeafletView from "../../components/LeafletView"; import { useTranslation } from "react-i18next"; @@ -21,6 +21,7 @@ import { DiaryCard, cardStyles } from "./DiaryCard"; import { useNavigation } from "@react-navigation/native"; import { useImperialConfig } from "../../config/useImperialConfig"; import { useAddressNames } from "../addressNamesHelper"; +import { Icon } from "../../components/Icon"; const TripCard = ({ trip }) => { @@ -88,17 +89,19 @@ const TripCard = ({ trip }) => { {/* start and end locations */} - - + + {tripStartDisplayName} - - + + {tripEndDisplayName} diff --git a/www/js/diary/cards/UntrackedTimeCard.tsx b/www/js/diary/cards/UntrackedTimeCard.tsx index fa0648216..1c2c01bbc 100644 --- a/www/js/diary/cards/UntrackedTimeCard.tsx +++ b/www/js/diary/cards/UntrackedTimeCard.tsx @@ -9,12 +9,13 @@ import React from "react"; import { View, StyleSheet } from 'react-native'; -import { Divider, IconButton, Text } from 'react-native-paper'; +import { Divider, Text } from 'react-native-paper'; import { object } from "prop-types"; import { getTheme } from "../../appTheme"; import { useTranslation } from "react-i18next"; import { DiaryCard, cardStyles } from "./DiaryCard"; import { useAddressNames } from "../addressNamesHelper"; +import { Icon } from "../../components/Icon"; const UntrackedTimeCard = ({ triplike }) => { const { t } = useTranslation(); @@ -37,16 +38,16 @@ const UntrackedTimeCard = ({ triplike }) => { {/* start and end locations */} - + {triplikeStartDisplayName} - + {triplikeEndDisplayName} diff --git a/www/js/diary/list/TimelineScrollList.tsx b/www/js/diary/list/TimelineScrollList.tsx index 776046ecc..2acdcc70f 100644 --- a/www/js/diary/list/TimelineScrollList.tsx +++ b/www/js/diary/list/TimelineScrollList.tsx @@ -5,9 +5,10 @@ import TripCard from '../cards/TripCard'; import PlaceCard from '../cards/PlaceCard'; import UntrackedTimeCard from '../cards/UntrackedTimeCard'; import { View } from 'react-native'; -import { ActivityIndicator, Banner, IconButton, Text } from 'react-native-paper'; +import { ActivityIndicator, Banner, Text } from 'react-native-paper'; import LoadMoreButton from './LoadMoreButton'; import { useTranslation } from 'react-i18next'; +import { Icon } from '../../components/Icon'; const renderCard = ({ item: listEntry }) => { if (listEntry.origin_key.includes('trip')) { @@ -44,8 +45,7 @@ const TimelineScrollList = ({ listEntries, queriedRange, pipelineRange, loadMore const noTravelBanner = ( + ({ size }) => }> {t('diary.no-travel')} diff --git a/www/js/survey/enketo/AddedNotesList.tsx b/www/js/survey/enketo/AddedNotesList.tsx index 6db967284..9a97a39a8 100644 --- a/www/js/survey/enketo/AddedNotesList.tsx +++ b/www/js/survey/enketo/AddedNotesList.tsx @@ -7,9 +7,10 @@ import { angularize, createScopeWithVars, getAngularService } from "../../angula import { array, object } from "prop-types"; import moment from "moment"; import { Text } from "react-native" -import { DataTable, IconButton } from "react-native-paper"; +import { DataTable } from "react-native-paper"; import { LabelTabContext } from "../../diary/LabelTab"; import { getFormattedDateAbbr, isMultiDay } from "../../diary/diaryHelper"; +import { Icon } from "../../components/Icon"; const AddedNotesList = ({ timelineEntry, additionEntries }) => { @@ -109,7 +110,7 @@ const AddedNotesList = ({ timelineEntry, additionEntries }) => { confirmDeleteEntry(entry)} style={[styles.cell, {flex: 1}]}> - + ) From 29d951d7445a351e79b6e7e47cee2b8be7022279 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 11 Aug 2023 16:11:14 -0400 Subject: [PATCH 101/154] wrap LabelDetailsScreen in This ensures that when the details screen is opened, the focus is moved to the new screen rather than staying on the previous main screen. We don't want screenreaders to stay focused on elements that are no longer visible. --- www/js/diary/LabelDetailsScreen.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/www/js/diary/LabelDetailsScreen.tsx b/www/js/diary/LabelDetailsScreen.tsx index f05b95881..5b19011c5 100644 --- a/www/js/diary/LabelDetailsScreen.tsx +++ b/www/js/diary/LabelDetailsScreen.tsx @@ -3,7 +3,7 @@ Navigated to from the main LabelListScreen by clicking a trip card. */ import React, { useContext } from "react"; -import { View, ScrollView, StyleSheet, useWindowDimensions } from "react-native"; +import { View, Modal, ScrollView, useWindowDimensions } from "react-native"; import { Appbar, Divider, Surface, Text, useTheme } from "react-native-paper"; import { LabelTabContext } from "./LabelTab"; import { cardStyles } from "./cards/DiaryCard"; @@ -31,7 +31,8 @@ const LabelScreenDetails = ({ route, navigation }) => { const DiaryHelper = getAngularService('DiaryHelper'); const sectionsFormatted = DiaryHelper.getFormattedSectionProperties(trip, {getFormattedDistance, distanceSuffix}); - return (<> + return ( + { navigation.goBack() }} /> @@ -124,7 +125,8 @@ const LabelScreenDetails = ({ route, navigation }) => { - ) + + ) } export default LabelScreenDetails; From 1a0abae6b1758dfd8b5af84c537549c58e8b6ac5 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 11 Aug 2023 16:16:55 -0400 Subject: [PATCH 102/154] aria: add labels, roles to DiaryCard components In TripCard, the left and right panels were reordered. They still appear on the left and right respectively, but now the right panel is ordered before the left panel. This is so that screenreaders read the right panel (which has the basic trip info like date, duration, locations) before the left panel. This should be clearer to blind/visually impaired users who rely on audio cues. Visually, the panels are still rendered the same as before because the flexDirection of their parent is now 'row-reverse' --- www/js/diary/cards/DiaryCard.tsx | 4 +- www/js/diary/cards/PlaceCard.tsx | 7 ++- www/js/diary/cards/TripCard.tsx | 55 ++++++++++++------------ www/js/diary/cards/UntrackedTimeCard.tsx | 5 ++- 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/www/js/diary/cards/DiaryCard.tsx b/www/js/diary/cards/DiaryCard.tsx index 7e74d05a3..98df05041 100644 --- a/www/js/diary/cards/DiaryCard.tsx +++ b/www/js/diary/cards/DiaryCard.tsx @@ -20,12 +20,12 @@ export const DiaryCard = ({ timelineEntry, children, flavoredTheme, ...otherProp - + {children} - + diff --git a/www/js/diary/cards/PlaceCard.tsx b/www/js/diary/cards/PlaceCard.tsx index f68cdc3ed..1400144a4 100644 --- a/www/js/diary/cards/PlaceCard.tsx +++ b/www/js/diary/cards/PlaceCard.tsx @@ -27,7 +27,8 @@ const PlaceCard = ({ place }) => { return ( - + {/* date and distance */} {place.display_date} @@ -57,6 +58,10 @@ const PlaceCard = ({ place }) => { }; const s = StyleSheet.create({ + placeCardContent: { + marginTop: 12, + marginBottom: 6, + }, notesButton: { paddingHorizontal: 8, minWidth: 150, diff --git a/www/js/diary/cards/TripCard.tsx b/www/js/diary/cards/TripCard.tsx index 560819a45..277386516 100644 --- a/www/js/diary/cards/TripCard.tsx +++ b/www/js/diary/cards/TripCard.tsx @@ -52,32 +52,12 @@ const TripCard = ({ trip }) => { const mapStyle = showAddNoteButton ? s.shortenedMap : s.fullHeightMap; return ( showDetail()}> - - + showDetail()} style={{position: 'absolute', right: 0, top: 0, height: 16, width: 32, justifyContent: 'center', margin: 4}} /> - {/* left panel */} - - - {trip.percentages?.map?.((pct, i) => ( - - - {pct.pct}% - - ))} - - {showAddNoteButton && - - - - } - {/* right panel */} {/* date and distance */} @@ -106,13 +86,34 @@ const TripCard = ({ trip }) => { - {/* mode and purpose buttons / survey button */} + {/* mode and purpose buttons / survey button */} {surveyOpt?.elementTag == 'multilabel' && } {surveyOpt?.elementTag == 'enketo-trip-button' && } + {/* left panel */} + + + {trip.percentages?.map?.((pct, i) => ( + + + {pct.pct}% + + ))} + + {showAddNoteButton && + + + + } + {trip.additionsList?.length != 0 && @@ -147,14 +148,14 @@ const s = StyleSheet.create({ }, notesButton: { paddingHorizontal: 8, - paddingVertical: 12, + paddingVertical: 8, minWidth: 150, margin: 'auto', }, rightPanel: { flex: 1, paddingHorizontal: 5, - paddingVertical: 12, + paddingVertical: 8, }, locationText: { fontSize: 12, diff --git a/www/js/diary/cards/UntrackedTimeCard.tsx b/www/js/diary/cards/UntrackedTimeCard.tsx index 1c2c01bbc..656b67400 100644 --- a/www/js/diary/cards/UntrackedTimeCard.tsx +++ b/www/js/diary/cards/UntrackedTimeCard.tsx @@ -25,8 +25,9 @@ const UntrackedTimeCard = ({ triplike }) => { return ( - - {/* date and distance */} + + {/* date and distance */} {triplike.display_date} From 5970f8911cbb61e1251a86fb5da2486e5b55ce72 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 11 Aug 2023 16:18:16 -0400 Subject: [PATCH 103/154] aria: add accessibilityLabels to Header components --- www/js/diary/LabelListScreen.tsx | 2 +- www/js/diary/list/DateSelect.tsx | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/www/js/diary/LabelListScreen.tsx b/www/js/diary/LabelListScreen.tsx index 8b50f7e47..3801d88a9 100644 --- a/www/js/diary/LabelListScreen.tsx +++ b/www/js/diary/LabelListScreen.tsx @@ -21,7 +21,7 @@ const LabelListScreen = () => { numListTotal={timelineMap?.size} /> - refresh()} + refresh()} accessibilityLabel="Refresh" style={{marginLeft: 'auto'}} /> diff --git a/www/js/diary/list/DateSelect.tsx b/www/js/diary/list/DateSelect.tsx index 773379d50..a75e685f7 100644 --- a/www/js/diary/list/DateSelect.tsx +++ b/www/js/diary/list/DateSelect.tsx @@ -59,15 +59,19 @@ const DateSelect = ({ tsRange, loadSpecificWeekFn }) => { }, [setOpen, loadSpecificWeekFn] ); + const dateRangeEnd = dateRange[1] || t('diary.today'); return (<> - setOpen(true)}> + setOpen(true)}> {dateRange[0] && (<> {dateRange[0]} )} - {dateRange[1] || t('diary.today')} + {dateRangeEnd} Date: Mon, 14 Aug 2023 18:15:49 -0400 Subject: [PATCH 104/154] create EnketoModal.tsx The Enketo services, written in Angular, will be rewritten and consolidated into React components and Typescript functions. EnketoModal will be the UI component that displays embedded surveys in a popup modal. It accepts the same arguments and options as the EnketoSurvey service. --- www/js/survey/enketo/EnketoModal.tsx | 130 +++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 www/js/survey/enketo/EnketoModal.tsx diff --git a/www/js/survey/enketo/EnketoModal.tsx b/www/js/survey/enketo/EnketoModal.tsx new file mode 100644 index 000000000..78e4edd86 --- /dev/null +++ b/www/js/survey/enketo/EnketoModal.tsx @@ -0,0 +1,130 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Form } from 'enketo-core'; +import { Modal, ScrollView, SafeAreaView, Pressable } from 'react-native'; +import { Appbar, ModalProps } from 'react-native-paper'; +import useAppConfig from '../../useAppConfig'; +import { useTranslation } from 'react-i18next'; +// import { transform } from 'enketo-transformer/web'; + +type Props = ModalProps & { + surveyName: string, + opts?: { + timelineEntry?: any; + instanceStr?: string; + prefilledSurveyResponse?: string; + prefillFields?: {[key: string]: string}; + dataKey?: string; + } +} + +const EnketoModal = ({ surveyName, opts, ...rest } : Props) => { + + const { t, i18n } = useTranslation(); + const headerEl = useRef(null); + const surveyJson = useRef(null); + // const loadedForm = useRef(null); + // const loadedModel = useRef(null); + const enketoForm = useRef
(null); + const { appConfig, loading } = useAppConfig(); + + async function fetchSurveyJson(url) { + const res = await fetch(url); + if (url.toUpperCase().endsWith('.JSON')) { + return await res.json(); + } else { + return Promise.reject('downloaded survey was not JSON; enketo-transformer is not available yet'); + /* uncomment once enketo-transformer is available */ + // if `response` is not JSON, so it is an XML string and needs transformation to JSON + // const xmlText = await res.text(); + // return await transform({xform: xmlText}); + } + } + + function validateAndSave() { + + } + + // init logic: retrieve form -> inject into DOM -> initialize Enketo -> show modal + useEffect(() => { + if (!rest.visible) return; + if (!appConfig || loading) return console.error('App config not loaded yet'); + console.debug('Loading survey', surveyName); + const formPath = appConfig.survey_info?.surveys?.[surveyName]?.formPath; + if (!formPath) return console.error('No form path found for survey', surveyName); + + fetchSurveyJson(formPath).then(({ form, model }) => { + surveyJson.current = { form, model }; + headerEl?.current.insertAdjacentHTML('afterend', form); + const formEl = document.querySelector('form.or'); + const data = { + // required string of the default instance defined in the XForm + modelStr: model, + // optional string of an existing instance to be edited + instanceStr: opts.instanceStr || null, + submitted: opts.submitted || false, + external: opts.external || [], + session: opts.session || {} + }; + const currLang = i18n.resolvedLanguage || 'en'; + enketoForm.current = new Form(formEl, data, { language: currLang }); + enketoForm.current.init(); + }); + }, [appConfig, loading, rest.visible]); + + 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 ( + {/* this element can be placed anywhere, leaving it out will prevent running ToC-generating code */} +
    +
    + + + + {/* The retrieved form will be injected here */} + +
    + {/* Used some quick-and-dirty inline CSS styles here because the form-footer should be styled in the + mother application. The HTML markup can be changed as well. */} + {t('survey.back')} + + {t('survey.next')} + +
    {t('survey.powered-by')} enketo logo
    + + {/*
      */} +
      +
      +
      + ); + + return ( + + + + { rest.onDismiss() }} /> + + + + +
      + {enketoContent} +
      +
      +
      +
      +
      + ); +} + +export default EnketoModal; From 7e04637a99ce840fa4794e36aebb310ac4694cfc Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 14 Aug 2023 21:19:51 -0400 Subject: [PATCH 105/154] finish implementation of EnketoModal add the validateAndSave function to EnketoModal, along with all the logic needed to support it. Functions have been rewritten from enketo/service.js and enketo/launch.js. I did not rewrite enketo/answer.js, so the EnketoSurveyAnswer service is still used in enketoHelper.ts. Nothing has dramatically changed here - I just rewrote the same logic in Typescript. One thing that has changed is the way that validation errors appear. If something is wrong in the survey response, we used to show an IonicPopup describing the issue. IonicPopup has a lower z-index than the React Native Modal. Rather than try to hack the z-index and compete for the foreground, I opted to show these popups using `window.alert` instead. I tested it on both Android and iOS, and found that the window alerts look like the native popups on their respective OS. It actually provide a much nicer UX for errors and I will move the Logger over to using this as well. --- www/js/survey/enketo/AddNoteButton.tsx | 38 +++++++---- www/js/survey/enketo/EnketoModal.tsx | 86 ++++++++++++++++-------- www/js/survey/enketo/enketoHelper.ts | 92 ++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 www/js/survey/enketo/enketoHelper.ts diff --git a/www/js/survey/enketo/AddNoteButton.tsx b/www/js/survey/enketo/AddNoteButton.tsx index 465622ccf..d880e94e1 100644 --- a/www/js/survey/enketo/AddNoteButton.tsx +++ b/www/js/survey/enketo/AddNoteButton.tsx @@ -14,15 +14,13 @@ import DiaryButton from "../../diary/DiaryButton"; import { useTranslation } from "react-i18next"; import moment from "moment"; import { LabelTabContext } from "../../diary/LabelTab"; +import EnketoModal from "./EnketoModal"; const AddNoteButton = ({ timelineEntry, notesConfig, storeKey }) => { const { t, i18n } = useTranslation(); const [displayLabel, setDisplayLabel] = useState(''); const { repopulateTimelineEntry } = useContext(LabelTabContext) - const EnketoSurveyLaunch = getAngularService("EnketoSurveyLaunch"); - const $rootScope = getAngularService("$rootScope"); - useEffect(() => { let newLabel: string; const localeCode = i18n.resolvedLanguage; @@ -76,21 +74,35 @@ const AddNoteButton = ({ timelineEntry, notesConfig, storeKey }) => { function launchAddNoteSurvey() { const surveyName = notesConfig.surveyName; console.log('About to launch survey ', surveyName); - const prefillFields = getPrefillTimes(); - - return EnketoSurveyLaunch - .launch($rootScope, surveyName, { timelineEntry, prefillFields, dataKey: storeKey }) - .then(result => { - if (!result) return; - repopulateTimelineEntry(timelineEntry._id.$oid); - }); + setPrefillTimes(getPrefillTimes()); + setModalVisible(true); }; - return ( + function onResponseSaved(result) { + if (result) { + console.log('AddNoteButton: response was saved, about to repopulateTimelineEntry; result=', result); + repopulateTimelineEntry(timelineEntry._id.$oid); + } else { + console.error('AddNoteButton: response was not saved, result=', result); + } + } + + const [prefillTimes, setPrefillTimes] = useState(null); + const [modalVisible, setModalVisible] = useState(false); + + return (<> launchAddNoteSurvey()} /> - ); + setModalVisible(false)} + onResponseSaved={onResponseSaved} + surveyName={notesConfig?.surveyName} + opts={{ timelineEntry, + dataKey: storeKey, + prefillFields: prefillTimes + }} /> + ); }; AddNoteButton.propTypes = { diff --git a/www/js/survey/enketo/EnketoModal.tsx b/www/js/survey/enketo/EnketoModal.tsx index 78e4edd86..3341151a9 100644 --- a/www/js/survey/enketo/EnketoModal.tsx +++ b/www/js/survey/enketo/EnketoModal.tsx @@ -1,23 +1,20 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useMemo } from 'react'; import { Form } from 'enketo-core'; -import { Modal, ScrollView, SafeAreaView, Pressable } from 'react-native'; +import { StyleSheet, Modal, ScrollView, SafeAreaView, Pressable } from 'react-native'; import { Appbar, ModalProps } from 'react-native-paper'; import useAppConfig from '../../useAppConfig'; import { useTranslation } from 'react-i18next'; +import { SurveyOptions, getInstanceStr, saveResponse } from './enketoHelper'; +import { getAngularService } from '../../angular-react-helper'; // import { transform } from 'enketo-transformer/web'; type Props = ModalProps & { surveyName: string, - opts?: { - timelineEntry?: any; - instanceStr?: string; - prefilledSurveyResponse?: string; - prefillFields?: {[key: string]: string}; - dataKey?: string; - } + onResponseSaved: (response: any) => void, + opts?: SurveyOptions, } -const EnketoModal = ({ surveyName, opts, ...rest } : Props) => { +const EnketoModal = ({ surveyName, onResponseSaved, opts, ...rest } : Props) => { const { t, i18n } = useTranslation(); const headerEl = useRef(null); @@ -34,41 +31,56 @@ const EnketoModal = ({ surveyName, opts, ...rest } : Props) => { } else { return Promise.reject('downloaded survey was not JSON; enketo-transformer is not available yet'); /* uncomment once enketo-transformer is available */ - // if `response` is not JSON, so it is an XML string and needs transformation to JSON + // if `response` is not JSON, it is an XML string and needs transformation to JSON // const xmlText = await res.text(); // return await transform({xform: xmlText}); } } - function validateAndSave() { - + async function validateAndSave() { + const $ionicPopup = getAngularService('$ionicPopup'); + const valid = await enketoForm.current.validate(); + if (!valid) return false; + const result = await saveResponse(surveyName, enketoForm.current, appConfig, opts); + if (!result) { // validation failed + window.alert(t('survey.enketo-form-errors')); + console.error(t('survey.enketo-form-errors')); + } else if (result instanceof Error) { // error thrown in saveResponse + window.alert(result.message); + console.error(result); + } else { // success + rest.onDismiss(); + onResponseSaved(result); + return; + } } // init logic: retrieve form -> inject into DOM -> initialize Enketo -> show modal - useEffect(() => { - if (!rest.visible) return; - if (!appConfig || loading) return console.error('App config not loaded yet'); + function initSurvey() { console.debug('Loading survey', surveyName); const formPath = appConfig.survey_info?.surveys?.[surveyName]?.formPath; if (!formPath) return console.error('No form path found for survey', surveyName); fetchSurveyJson(formPath).then(({ form, model }) => { surveyJson.current = { form, model }; - headerEl?.current.insertAdjacentHTML('afterend', form); + headerEl?.current.insertAdjacentHTML('afterend', form); // inject form into DOM const formEl = document.querySelector('form.or'); const data = { - // required string of the default instance defined in the XForm - modelStr: model, - // optional string of an existing instance to be edited - instanceStr: opts.instanceStr || null, - submitted: opts.submitted || false, - external: opts.external || [], - session: opts.session || {} + modelStr: model, // the XML model for this form + instanceStr: getInstanceStr(model, opts), // existing XML instance (if any), may be a previous response or a pre-filled model + /* There are a few other opts that can be passed to Enketo Core. + We don't use these now, but we may want them later: https://github.com/enketo/enketo-core#usage-as-a-library */ }; const currLang = i18n.resolvedLanguage || 'en'; enketoForm.current = new Form(formEl, data, { language: currLang }); enketoForm.current.init(); }); + } + + useEffect(() => { + if (!rest.visible) return; + if (!appConfig || loading) return console.error('App config not loaded yet'); + initSurvey(); }, [appConfig, loading, rest.visible]); const enketoContent = ( @@ -94,7 +106,11 @@ const EnketoModal = ({ surveyName, opts, ...rest } : Props) => { {/* Used some quick-and-dirty inline CSS styles here because the form-footer should be styled in the mother application. The HTML markup can be changed as well. */} {t('survey.back')} - + validateAndSave()}> + + {t('survey.next')}
      {t('survey.powered-by')} enketo logo
      @@ -111,9 +127,10 @@ const EnketoModal = ({ surveyName, opts, ...rest } : Props) => { return ( - - { rest.onDismiss() }} /> - + + { rest.onDismiss() }} style={{margin: 0}} /> + rest.onDismiss()} /> @@ -127,4 +144,17 @@ const EnketoModal = ({ surveyName, opts, ...rest } : Props) => { ); } +const s = StyleSheet.create({ + appBar: { + height: 40, + backgroundColor: 'white', + elevation: 3, + position: 'absolute', + paddingRight: 12, + border: '1px solid rgba(0,0,0,.2)', + borderTopWidth: 0, + borderBottomRightRadius: 10, + } +}); + export default EnketoModal; diff --git a/www/js/survey/enketo/enketoHelper.ts b/www/js/survey/enketo/enketoHelper.ts new file mode 100644 index 000000000..398a2ed97 --- /dev/null +++ b/www/js/survey/enketo/enketoHelper.ts @@ -0,0 +1,92 @@ +import { getAngularService } from "../../angular-react-helper"; +import { Form } from 'enketo-core'; +import { XMLParser } from 'fast-xml-parser'; +import i18next from 'i18next'; + +export type PrefillFields = {[key: string]: string}; + +export type SurveyOptions = { + timelineEntry?: any; + prefilledSurveyResponse?: string; + prefillFields?: PrefillFields; + dataKey?: string; +}; + +/** + * @param xmlModel the blank XML model to be prefilled + * @param prefillFields an object with keys that are the XML tag names and values that are the values to be prefilled + * @returns serialized XML of the prefilled model response + */ +function getXmlWithPrefills(xmlModel: string, prefillFields: PrefillFields) { + if (!prefillFields) return null; + const xmlParser = new window.DOMParser(); + const xmlDoc = xmlParser.parseFromString(xmlModel, 'text/xml'); + + for (const [tagName, value] of Object.entries(prefillFields)) { + const vals = xmlDoc.getElementsByTagName(tagName); + vals[0].innerHTML = value; + } + const instance = xmlDoc.getElementsByTagName('instance')[0].children[0]; + return new XMLSerializer().serializeToString(instance); +} + +/** + * @param xmlModel the blank XML model response for the survey + * @param opts object with options like 'prefilledSurveyResponse' or 'prefillFields' + * @returns XML string of an existing or prefilled model response, or null if no response is available + */ +export function getInstanceStr(xmlModel: string, opts: SurveyOptions): string|null { + if (!xmlModel) return null; + if (opts.prefilledSurveyResponse) + return opts.prefilledSurveyResponse; + if (opts.prefillFields) + return getXmlWithPrefills(xmlModel, opts.prefillFields); + return null; +} + +/** + * @param surveyName the name of the survey (e.g. "TimeUseSurvey") + * @param enketoForm the Form object from enketo-core that contains this survey + * @param appConfig the dynamic config file for the app + * @param opts object with SurveyOptions like 'timelineEntry' or 'dataKey' + * @returns Promise of the saved result, or an Error if there was a problem + */ +export function saveResponse(surveyName: string, enketoForm: Form, appConfig, opts: SurveyOptions) { + const EnketoSurveyAnswer = getAngularService('EnketoSurveyAnswer'); + const xmlParser = new window.DOMParser(); + const xmlResponse = enketoForm.getDataStr(); + const xmlDoc = xmlParser.parseFromString(xmlResponse, 'text/xml'); + const xml2js = new XMLParser({ignoreAttributes: false, attributeNamePrefix: 'attr'}); + const jsonDocResponse = xml2js.parse(xmlResponse); + return EnketoSurveyAnswer.resolveLabel(surveyName, xmlDoc).then(rsLabel => { + const data: any = { + label: rsLabel, + name: surveyName, + version: appConfig.survey_info.surveys[surveyName].version, + xmlResponse, + jsonDocResponse, + }; + if (opts.timelineEntry) { + let timestamps = EnketoSurveyAnswer.resolveTimestamps(xmlDoc, opts.timelineEntry); + if (timestamps === undefined) { + // timestamps were resolved, but they are invalid + return new Error(i18next.t('survey.enketo-timestamps-invalid')); //"Timestamps are invalid. Please ensure that the start time is before the end time."); + } + // if timestamps were not resolved from the survey, we will use the trip or place timestamps + timestamps ||= opts.timelineEntry; + data.start_ts = timestamps.start_ts || timestamps.enter_ts; + data.end_ts = timestamps.end_ts || timestamps.exit_ts; + // UUID generated using this method https://stackoverflow.com/a/66332305 + data.match_id = URL.createObjectURL(new Blob([])).slice(-36); + } else { + const now = Date.now(); + data.ts = now/1000; // convert to seconds to be consistent with the server + data.fmt_time = new Date(now); + } + // use dataKey passed into opts if available, otherwise get it from the config + const dataKey = opts.dataKey || appConfig.survey_info.surveys[surveyName].dataKey; + return window['cordova'].plugins.BEMUserCache + .putMessage(dataKey, data) + .then(() => data); + }).then(data => data); +} From 4341f4c6afb73a5cd5a7569e47cf57903b480766 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 14 Aug 2023 23:16:07 -0400 Subject: [PATCH 106/154] logger.js -> logger.ts --- www/index.js | 2 +- www/js/plugin/{logger.js => logger.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename www/js/plugin/{logger.js => logger.ts} (100%) diff --git a/www/index.js b/www/index.js index 22ec4704b..ff3b18446 100644 --- a/www/index.js +++ b/www/index.js @@ -50,6 +50,6 @@ import './js/control/collect-settings.js'; import './js/control/sync-settings.js'; import './js/metrics-factory.js'; import './js/metrics-mappings.js'; -import './js/plugin/logger.js'; +import './js/plugin/logger.ts'; import './js/plugin/storage.js'; import './js/appstatus/permissioncheck.js'; diff --git a/www/js/plugin/logger.js b/www/js/plugin/logger.ts similarity index 100% rename from www/js/plugin/logger.js rename to www/js/plugin/logger.ts From 55cf0196cfdaf445201f27d669dd3f3004fec066 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 14 Aug 2023 23:19:02 -0400 Subject: [PATCH 107/154] add functions to logger.ts + use in EnketoModal I am leaving the original Angular service in here because other files still use it. Both can remain in this file until nothing uses the Angular service anymore. --- www/js/plugin/logger.ts | 22 +++++++++++++++++++++- www/js/survey/enketo/EnketoModal.tsx | 8 +++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/www/js/plugin/logger.ts b/www/js/plugin/logger.ts index abf74d459..ff7a95e61 100644 --- a/www/js/plugin/logger.ts +++ b/www/js/plugin/logger.ts @@ -3,7 +3,7 @@ import angular from 'angular'; angular.module('emission.plugin.logger', []) .factory('Logger', function($window, $ionicPopup) { - var loggerJs = {} + var loggerJs: any = {}; loggerJs.log = function(message) { $window.Logger.log($window.Logger.LEVEL_DEBUG, message); } @@ -22,3 +22,23 @@ angular.module('emission.plugin.logger', []) } return loggerJs; }); + +export function log(message) { + window['Logger'].log(window['Logger'].LEVEL_DEBUG, message); +} + +export function displayError(error, title?) { + const errorMsg = error.message ? error.message + '\n' + error.stack : JSON.stringify(error); + displayErrorMsg(errorMsg, title); +} + +export function displayErrorMsg(errorMsg, title?) { + // Check for OPcode 'Does Not Exist' errors and prepend the title with "Invalid OPcode" + if (errorMsg.includes?.("403")) { + title = "Invalid OPcode: " + (title || ''); + } + const displayMsg = title ? title + '\n' + errorMsg : errorMsg; + window.alert(displayMsg); + console.error(displayMsg); + window['Logger'].log(window['Logger'].LEVEL_ERROR, displayMsg); +} diff --git a/www/js/survey/enketo/EnketoModal.tsx b/www/js/survey/enketo/EnketoModal.tsx index 3341151a9..86ccbacad 100644 --- a/www/js/survey/enketo/EnketoModal.tsx +++ b/www/js/survey/enketo/EnketoModal.tsx @@ -6,6 +6,7 @@ import useAppConfig from '../../useAppConfig'; import { useTranslation } from 'react-i18next'; import { SurveyOptions, getInstanceStr, saveResponse } from './enketoHelper'; import { getAngularService } from '../../angular-react-helper'; +import { displayError, displayErrorMsg } from '../../plugin/logger'; // import { transform } from 'enketo-transformer/web'; type Props = ModalProps & { @@ -38,16 +39,13 @@ const EnketoModal = ({ surveyName, onResponseSaved, opts, ...rest } : Props) => } async function validateAndSave() { - const $ionicPopup = getAngularService('$ionicPopup'); const valid = await enketoForm.current.validate(); if (!valid) return false; const result = await saveResponse(surveyName, enketoForm.current, appConfig, opts); if (!result) { // validation failed - window.alert(t('survey.enketo-form-errors')); - console.error(t('survey.enketo-form-errors')); + displayErrorMsg(t('survey.enketo-form-errors')); } else if (result instanceof Error) { // error thrown in saveResponse - window.alert(result.message); - console.error(result); + displayError(result); } else { // success rest.onDismiss(); onResponseSaved(result); From 374553597cd51b545d8ec6f8b32e60961938ff89 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 14 Aug 2023 23:36:10 -0400 Subject: [PATCH 108/154] add more levels to logger, also add types --- www/js/plugin/logger.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/www/js/plugin/logger.ts b/www/js/plugin/logger.ts index ff7a95e61..1782a68f3 100644 --- a/www/js/plugin/logger.ts +++ b/www/js/plugin/logger.ts @@ -23,16 +23,21 @@ angular.module('emission.plugin.logger', []) return loggerJs; }); -export function log(message) { +export const logDebug = (message: string) => window['Logger'].log(window['Logger'].LEVEL_DEBUG, message); -} -export function displayError(error, title?) { +export const logInfo = (message: string) => + window['Logger'].log(window['Logger'].LEVEL_INFO, message); + +export const logWarn = (message: string) => + 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); displayErrorMsg(errorMsg, title); } -export function displayErrorMsg(errorMsg, title?) { +export function displayErrorMsg(errorMsg: string, title?: string) { // Check for OPcode 'Does Not Exist' errors and prepend the title with "Invalid OPcode" if (errorMsg.includes?.("403")) { title = "Invalid OPcode: " + (title || ''); From e084b59deece5604f1d1aaa9410a3e5ca58e5d1e Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 14 Aug 2023 23:56:59 -0400 Subject: [PATCH 109/154] add caching for loading surveys This handy function can be used for any URL resource that we'd like to be cached to localStorage. For right now we'll just use this for any surveys loaded by EnketoModal. I decided to put this in a new file called commHelper.ts. This is a new function, but the existing Angular CommHelper service will be rewritten at some point and those new functions can go in this file too. --- www/js/commHelper.ts | 19 +++++++++++++++++++ www/js/survey/enketo/EnketoModal.tsx | 10 ++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 www/js/commHelper.ts diff --git a/www/js/commHelper.ts b/www/js/commHelper.ts new file mode 100644 index 000000000..074093999 --- /dev/null +++ b/www/js/commHelper.ts @@ -0,0 +1,19 @@ +import { logDebug } from "./plugin/logger"; + +/** + * @param url URL endpoint for the request + * @returns Promise of the fetched response (as text) or cached text from local storage + */ +export async function fetchUrlCached(url) { + const stored = localStorage.getItem(url); + if (stored) { + logDebug(`fetchUrlCached: found cached data for url ${url}, returning`); + return Promise.resolve(stored); + } + logDebug(`fetchUrlCached: found no cached data for url ${url}, fetching`); + const response = await fetch(url); + const text = await response.text(); + localStorage.setItem(url, text); + logDebug(`fetchUrlCached: fetched data for url ${url}, returning`); + return text; +} diff --git a/www/js/survey/enketo/EnketoModal.tsx b/www/js/survey/enketo/EnketoModal.tsx index 86ccbacad..c9d9f3ece 100644 --- a/www/js/survey/enketo/EnketoModal.tsx +++ b/www/js/survey/enketo/EnketoModal.tsx @@ -6,6 +6,7 @@ import useAppConfig from '../../useAppConfig'; import { useTranslation } from 'react-i18next'; import { SurveyOptions, getInstanceStr, saveResponse } from './enketoHelper'; import { getAngularService } from '../../angular-react-helper'; +import { fetchUrlCached } from '../../commHelper'; import { displayError, displayErrorMsg } from '../../plugin/logger'; // import { transform } from 'enketo-transformer/web'; @@ -26,10 +27,11 @@ const EnketoModal = ({ surveyName, onResponseSaved, opts, ...rest } : Props) => const { appConfig, loading } = useAppConfig(); async function fetchSurveyJson(url) { - const res = await fetch(url); - if (url.toUpperCase().endsWith('.JSON')) { - return await res.json(); - } else { + const responseText = await fetchUrlCached(url); + try { + return JSON.parse(responseText); + } catch ({name, message}) { + // not JSON, so it must be XML return Promise.reject('downloaded survey was not JSON; enketo-transformer is not available yet'); /* uncomment once enketo-transformer is available */ // if `response` is not JSON, it is an XML string and needs transformation to JSON From fd1ee5396b032a36ff6d26ccb90e13ef59e3a4c7 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 14 Aug 2023 23:57:53 -0400 Subject: [PATCH 110/154] remove ReactHello.jsx I think we are done learning how to use React, with plenty of working examples in the codebase --- www/js/ReactHello.jsx | 93 ------------------------------------------- 1 file changed, 93 deletions(-) delete mode 100644 www/js/ReactHello.jsx diff --git a/www/js/ReactHello.jsx b/www/js/ReactHello.jsx deleted file mode 100644 index 84b81f71e..000000000 --- a/www/js/ReactHello.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import React from "react"; -import { Image, Pressable, StyleSheet, Text, View } from "react-native"; -import { angularize } from "./angular-react-helper"; - -const logoUri = `data:image/svg+xml;utf8,`; - -function Link(props) { - return ; -} - -function ReactHello() { - return ( - - - - React Native for Web - - - This is an example of an app built with{" "} - - Create React App - {" "} - and{" "} - - React Native for Web - - - - To get started, edit{" "} - - src/App.js - - . - - {}} style={buttonStyles.button}> - Example button - - - ); -} - -const styles = StyleSheet.create({ - app: { - marginHorizontal: "auto", - maxWidth: 500 - }, - logo: { - height: 80 - }, - header: { - padding: 20 - }, - title: { - fontWeight: "bold", - fontSize: "1.5rem", - marginVertical: "1em", - textAlign: "center" - }, - text: { - lineHeight: "1.5em", - fontSize: "1.125rem", - marginVertical: "1em", - textAlign: "center" - }, - link: { - color: "#1B95E0" - }, - code: { - fontFamily: "monospace, monospace" - } -}); - -const buttonStyles = StyleSheet.create({ - button: { - backgroundColor: "#2196F3", - borderRadius: 2 - }, - text: { - color: "#fff", - fontWeight: "500", - padding: 8, - textAlign: "center", - textTransform: "uppercase" - } -}); - -angularize(ReactHello, 'ReactHello', 'emission.main.reacthello'); -export default ReactHello; From b48196bb1c703d380f6ffd2477f9bed5d92da3e1 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 15 Aug 2023 00:10:12 -0400 Subject: [PATCH 111/154] tweak enketo styles for new EnketoModal --- www/css/style.css | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/www/css/style.css b/www/css/style.css index b5186e001..93f1889a5 100644 --- a/www/css/style.css +++ b/www/css/style.css @@ -2,6 +2,15 @@ /* if we don't contain them here, they will leak into the rest of the app */ .enketo-plugin { @import 'enketo-core/src/sass/formhub/formhub.scss'; + .question.non-select { + display: inline-block; + } + .question input[name*="_date"], + .question input[name*="_time"] { + width: calc(40vw - 10px); + margin-right: 5px; + display: flex; + } } .enketo-plugin .form-header { @@ -1471,16 +1480,3 @@ svg { width: 3ch; left: calc(8px + 2.5ch); } - -.enketo-plugin .question.inline-datetime > input[name*="_date"], -.enketo-plugin .question.inline-datetime > input[name*="_time"] { - display: flex; - width: auto; - min-width: 65%; - max-width: 85%; -} - -.enketo-plugin .question.inline-datetime { - display: inline-grid; - width: 50%; -} From 4cf9f589025ac3ff544cab0ebbacda3ea3a48ce3 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 15 Aug 2023 01:01:37 -0400 Subject: [PATCH 112/154] Icon refactor, support other IconButton props too We'll capture any other props with ...rest and apply these to the underlying IconButton --- www/js/components/Icon.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/www/js/components/Icon.tsx b/www/js/components/Icon.tsx index 1e01fca7e..0b4c7253e 100644 --- a/www/js/components/Icon.tsx +++ b/www/js/components/Icon.tsx @@ -7,12 +7,11 @@ import React from 'react'; import { StyleSheet } from 'react-native'; import { IconButton } from 'react-native-paper'; -import { Props } from 'react-native-paper/lib/typescript/src/components/IconButton/IconButton' +import { Props as IconButtonProps } from 'react-native-paper/lib/typescript/src/components/IconButton/IconButton' -export const Icon = (props: Props) => { +export const Icon = ({style, ...rest}: IconButtonProps) => { return ( - ); } From 47966266d89557a3c126529fab206417877fa73d Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 15 Aug 2023 01:06:21 -0400 Subject: [PATCH 113/154] LabelDetailsScreen: UI fixes, accessibility fixes - fix Icon colors (the correct prop to use is iconColor, not color) - ensure display times always fit on 1 line - make top bar title smaller (it was huge) - wrap the contents in a SafeAreaView so we don't have to guess about padding offsets for statusbar / notch --- www/js/diary/LabelDetailsScreen.tsx | 169 ++++++++++++++-------------- 1 file changed, 86 insertions(+), 83 deletions(-) diff --git a/www/js/diary/LabelDetailsScreen.tsx b/www/js/diary/LabelDetailsScreen.tsx index 5b19011c5..96b77d0f5 100644 --- a/www/js/diary/LabelDetailsScreen.tsx +++ b/www/js/diary/LabelDetailsScreen.tsx @@ -15,6 +15,7 @@ import { getAngularService } from "../angular-react-helper"; import { useImperialConfig } from "../config/useImperialConfig"; import { useAddressNames } from "./addressNamesHelper"; import { Icon } from "../components/Icon"; +import { SafeAreaView } from "react-native-safe-area-context"; const LabelScreenDetails = ({ route, navigation }) => { @@ -33,98 +34,100 @@ const LabelScreenDetails = ({ route, navigation }) => { return ( - - { navigation.goBack() }} /> - - - - - - {trip.display_start_time} - - - - {tripStartDisplayName} + + + { navigation.goBack() }} /> + + + + + + {trip.display_start_time} - - - - - {trip.display_end_time} - - - - {tripEndDisplayName} - - - - - - - - - - {t('diary.distance')} - - - {`${getFormattedDistance(trip.distance)} ${distanceSuffix}`} - - - - - {t('diary.time')} + + + {tripStartDisplayName} - - {trip.display_time} - - - - {trip.percentages?.map?.((pct, i) => ( - - - - {pct.pct}% - - - ))} - - - {surveyOpt?.elementTag == 'multilabel' && - } - {surveyOpt?.elementTag == 'enketo-trip-button' - && } + + + + {trip.display_end_time} + + + + {tripEndDisplayName} + - {/* for multi-section trips, show a list of sections */} - {sectionsFormatted?.length > 1 && - - {sectionsFormatted.map((section, i) => ( - - - {section.fmt_time_range} - {section.fmt_time} - - - - {`${section.fmt_distance} ${section.fmt_distance_suffix}`} + + + + + + + + {t('diary.distance')} + + + {`${getFormattedDistance(trip.distance)} ${distanceSuffix}`} + + + + + {t('diary.time')} + + + {trip.display_time} + + + + {trip.percentages?.map?.((pct, i) => ( + + + + {pct.pct}% - - - - - ))} + ))} + - } + + {surveyOpt?.elementTag == 'multilabel' && + } + {surveyOpt?.elementTag == 'enketo-trip-button' + && } + + {/* for multi-section trips, show a list of sections */} + {sectionsFormatted?.length > 1 && + + {sectionsFormatted.map((section, i) => ( + + + {section.fmt_time_range} + {section.fmt_time} + + + + {`${section.fmt_distance} ${section.fmt_distance_suffix}`} + + + + + + + ))} + + } - {/* TODO: show speed graph here */} + {/* TODO: show speed graph here */} - - + + + ) } From aaf6c0601080953a9c5345680a977f9eef2dba55 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 15 Aug 2023 12:43:12 -0400 Subject: [PATCH 114/154] use Dialogs for multilabel popups Instead of using Ionic popups and separate templates for the mode and purpose select menus, we can utilize a Dialog containing Radio buttons using components from React Native Paper. This is much better in terms of accessibility - the Ionic popups were not able to be navigated to by keyboard / screen reader controls, while the Dialog gains focus when opened because it is wrapped in a Modal. The UX for inputting 'other' has been changed as well. Instead of having 'other' entry be a separate popup, I incorporated it into the same Dialog. When the "Other" radio button is chosen, a text entry and Save button will appear right below it allowing the user to specify their custom label. --- .../multilabel/MultiLabelButtonGroup.tsx | 108 ++++++++++-------- 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/www/js/survey/multilabel/MultiLabelButtonGroup.tsx b/www/js/survey/multilabel/MultiLabelButtonGroup.tsx index c44a60422..110f8ff3d 100644 --- a/www/js/survey/multilabel/MultiLabelButtonGroup.tsx +++ b/www/js/survey/multilabel/MultiLabelButtonGroup.tsx @@ -2,26 +2,31 @@ In the default configuration, these are the "Mode" and "Purpose" buttons. Next to the buttons is a small checkmark icon, which marks inferrel labels as confirmed */ -import React, { useContext, useEffect, useState } from "react"; -import { angularize, createScopeWithVars, getAngularService } from "../../angular-react-helper"; -import { object, number } from "prop-types"; -import { View } from "react-native"; -import { IconButton, Text, useTheme } from "react-native-paper"; +import React, { useContext, useEffect, useState, useMemo } from "react"; +import { getAngularService } from "../../angular-react-helper"; +import { View, Modal, ScrollView, Pressable, useWindowDimensions } from "react-native"; +import { IconButton, Text, Dialog, useTheme, RadioButton, Button, TextInput } from "react-native-paper"; import DiaryButton from "../../diary/DiaryButton"; import { useTranslation } from "react-i18next"; import { LabelTabContext } from "../../diary/LabelTab"; +import { displayErrorMsg, logDebug } from "../../plugin/logger"; const MultilabelButtonGroup = ({ trip }) => { const { colors } = useTheme(); const { t } = useTranslation(); const { repopulateTimelineEntry } = useContext(LabelTabContext); + const { height: windowHeight } = useWindowDimensions(); const [ inputParams, setInputParams ] = useState({}); - let closePopover; + // modal visible for which input type? (mode or purpose or replaced_mode, null if not visible) + const [ modalVisibleFor, setModalVisibleFor ] = useState<'MODE'|'PURPOSE'|'REPLACED_MODE'|null>(null); + const [otherLabel, setOtherLabel] = useState(null); + const chosenLabel = useMemo(() => { + if (otherLabel != null) return 'other'; + return trip.userInput[modalVisibleFor]?.value + }, [modalVisibleFor, otherLabel]); const ConfirmHelper = getAngularService("ConfirmHelper"); - const $ionicPopup = getAngularService("$ionicPopup"); - const $ionicPopover = getAngularService("$ionicPopover"); useEffect(() => { console.log("During initialization, trip is ", trip); @@ -38,39 +43,22 @@ const MultilabelButtonGroup = ({ trip }) => { } } - function openPopover(e, inputType) { - let popoverPath = 'templates/diary/'+inputType.toLowerCase()+'-popover.html'; - const scope = createScopeWithVars({inputParams, choose}); - $ionicPopover.fromTemplateUrl(popoverPath, {scope}).then((pop) => { - closePopover = () => pop.hide(); - pop.show(e); - }); - } - - function choose(inputType, chosenLabel, wasOther=false) { - if (chosenLabel && chosenLabel != "other") { - store(inputType, chosenLabel, wasOther); + function onChooseLabel(chosenValue) { + logDebug(`onChooseLabel with chosen ${modalVisibleFor} as ${chosenValue}`); + if (chosenValue == 'other') { + setOtherLabel(''); } else { - const scope = createScopeWithVars({other: {text: ''}}); - $ionicPopup.show({ - scope, - title: t("trip-confirm.services-please-fill-in", { text: inputType.toLowerCase() }), - template: '', - buttons: [ - { - text: t('trip-confirm.services-cancel') - }, - { - text: `${t('trip-confirm.services-save')}`, - type: 'button-positive', - onTap: () => choose(inputType, scope.other.text, true) - } - ] - }); + store(modalVisibleFor, chosenValue, false); } - }; + } + + function dismiss() { + setModalVisibleFor(null); + setOtherLabel(null); + } function store(inputType, chosenLabel, isOther) { + if (!chosenLabel) return displayErrorMsg("Label is empty"); if (isOther) { /* Let's make the value for user entered inputs look consistent with our other values (i.e. lowercase, and with underscores instead of spaces) */ @@ -84,14 +72,14 @@ const MultilabelButtonGroup = ({ trip }) => { const storageKey = ConfirmHelper.inputDetails[inputType].key; window['cordova'].plugins.BEMUserCache.putMessage(storageKey, inputDataToStore).then(() => { - closePopover?.(); + dismiss(); repopulateTimelineEntry(trip._id.$oid); - console.debug("Successfully stored input data "+JSON.stringify(inputDataToStore)); + logDebug("Successfully stored input data "+JSON.stringify(inputDataToStore)); }); } const inputKeys = Object.keys(trip.inputDetails); - return ( + return (<> {inputKeys.map((key, i) => { @@ -110,7 +98,7 @@ const MultilabelButtonGroup = ({ trip }) => { {t(input.labeltext)} openPopover(e, input.name)} + onPress={(e) => setModalVisibleFor(input.name)} fillColor={fillColor} /> ) @@ -122,13 +110,37 @@ const MultilabelButtonGroup = ({ trip }) => { style={{width: 20, height: 20, margin: 3}}/> - ); + dismiss()}> + dismiss()}> + + + {(modalVisibleFor == 'MODE') && t('diary.select-mode-scroll') || + (modalVisibleFor == 'PURPOSE') && t('diary.select-purpose-scroll') || + (modalVisibleFor == 'REPLACED_MODE') && t('diary.select-replaced-mode-scroll')} + + + + onChooseLabel(val)} value={chosenLabel}> + {inputParams[modalVisibleFor]?.options?.map((o, i) => ( + // @ts-ignore + + ))} + + + + {otherLabel != null && <> + setOtherLabel(t)} /> + + + + } + + + + ); }; -MultilabelButtonGroup.propTypes = { - trip: object, - recomputeDelay: number, -} - -angularize(MultilabelButtonGroup, 'MultilabelButtonGroup', 'emission.main.diary.multilabelbtngroup'); export default MultilabelButtonGroup; From a72fd4f548d47fb8d16a29ca1a98038b530fa57e Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Tue, 15 Aug 2023 12:51:33 -0400 Subject: [PATCH 115/154] EnketoModal: tweak "Dismiss" and "Save" buttons The button to dismiss the Enketo Modal has been incorporated into the Enketo header so it doesn't take up a whole extra row. The accessibility roles / visibility have been tweaked for both the dismiss and save buttons so that they are exposed correctly to the accessibility tree as clickable buttons without extra nesting. --- www/js/survey/enketo/EnketoModal.tsx | 38 +++++++++++++--------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/www/js/survey/enketo/EnketoModal.tsx b/www/js/survey/enketo/EnketoModal.tsx index c9d9f3ece..2c5842135 100644 --- a/www/js/survey/enketo/EnketoModal.tsx +++ b/www/js/survey/enketo/EnketoModal.tsx @@ -1,11 +1,10 @@ -import React, { useRef, useEffect, useMemo } from 'react'; +import React, { useRef, useEffect } from 'react'; import { Form } from 'enketo-core'; import { StyleSheet, Modal, ScrollView, SafeAreaView, Pressable } from 'react-native'; -import { Appbar, ModalProps } from 'react-native-paper'; +import { ModalProps } from 'react-native-paper'; import useAppConfig from '../../useAppConfig'; import { useTranslation } from 'react-i18next'; import { SurveyOptions, getInstanceStr, saveResponse } from './enketoHelper'; -import { getAngularService } from '../../angular-react-helper'; import { fetchUrlCached } from '../../commHelper'; import { displayError, displayErrorMsg } from '../../plugin/logger'; // import { transform } from 'enketo-transformer/web'; @@ -21,8 +20,6 @@ const EnketoModal = ({ surveyName, onResponseSaved, opts, ...rest } : Props) => const { t, i18n } = useTranslation(); const headerEl = useRef(null); const surveyJson = useRef(null); - // const loadedForm = useRef(null); - // const loadedModel = useRef(null); const enketoForm = useRef(null); const { appConfig, loading } = useAppConfig(); @@ -90,6 +87,13 @@ const EnketoModal = ({ surveyName, onResponseSaved, opts, ...rest } : Props) => Just make sure to keep a .form-language-selector element into which the form language selector () will be appended by Enketo Core. */}
      - rest.onDismiss()} accessibilityRole={'button'}> - - {/* arrow-left glyph from https://pictogrammers.com/library/mdi/icon/arrow-left/ */} - 󰁍 - + Choose Language