diff --git a/package.cordovabuild.json b/package.cordovabuild.json index 12495210c..4bab62860 100644 --- a/package.cordovabuild.json +++ b/package.cordovabuild.json @@ -75,10 +75,10 @@ "cordova-plugin-app-version": "0.1.9", "cordova-plugin-customurlscheme": "5.0.1", "cordova-plugin-device": "2.0.1", - "cordova-plugin-em-datacollection": "git+https://github.com/e-mission/e-mission-data-collection.git#v1.5.0", - "cordova-plugin-em-jwt-auth": "git+https://github.com/e-mission/cordova-jwt-auth.git#v1.6.3", + "cordova-plugin-em-datacollection": "git+https://github.com/e-mission/e-mission-data-collection.git#v1.6.0", + "cordova-plugin-em-jwt-auth": "git+https://github.com/e-mission/cordova-jwt-auth.git#v1.6.4", "cordova-plugin-em-server-communication": "git+https://github.com/e-mission/cordova-server-communication.git#v1.2.3", - "cordova-plugin-em-serversync": "git+https://github.com/e-mission/cordova-server-sync.git#v1.2.4", + "cordova-plugin-em-serversync": "git+https://github.com/e-mission/cordova-server-sync.git#v1.2.5", "cordova-plugin-em-settings": "git+https://github.com/e-mission/cordova-connection-settings.git#v1.2.2", "cordova-plugin-em-transition-notify": "git+https://github.com/e-mission/e-mission-transition-notify.git#v1.2.6", "cordova-plugin-em-unifiedlogger": "git+https://github.com/e-mission/cordova-unified-logger.git#v1.3.3", diff --git a/www/css/main.diary.css b/www/css/main.diary.css index 035b5833c..7b0becd5f 100644 --- a/www/css/main.diary.css +++ b/www/css/main.diary.css @@ -110,19 +110,32 @@ a.item-content { margin-left: 3px; margin-top: 3px; } +.btn-input-confirm-red, +.btn-input-confirm-red:hover, +.btn-input-confirm-red:active { + background-color: #ED2D3A; + color: white; +} +.btn-input-confirm-yellow, +.btn-input-confirm-yellow:hover, +.btn-input-confirm-yellow:active { + background-color: #FFC108; + color: white; +} .btn-input-confirm-green, .btn-input-confirm-green:hover, .btn-input-confirm-green:active { background-color: #30A64A; color: white; } -.btn-input-confirm-white, +/* White confirm buttons are currently unused */ +/* .btn-input-confirm-white, .btn-input-confirm-white:hover, .btn-input-confirm-white:active { background-color: #ddd; color: #333; font-size: 0.8em; -} +} */ .btn-input-confirm { line-height: 30px; min-height: 30px; @@ -149,6 +162,25 @@ a.item-content { border-width: 0; } -.button.on { - background-color: darkgray; +.button.labelfilter { + color: #01D0A7; + border-radius: 0px; + border-width: 0; + box-shadow: 0 1px 2px rgba(0,0,0,0.16), 0 2px 2px rgba(0,0,0,0.23); } + +.button.labelfilter.on { + background-color: #01D0A7; + color: white; +} + +.labelfilter:first-of-type { + margin-left:5%; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; +} + +.labelfilter:last-of-type { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; +} \ No newline at end of file diff --git a/www/css/style.css b/www/css/style.css index 2d47eed40..783b92425 100644 --- a/www/css/style.css +++ b/www/css/style.css @@ -887,6 +887,10 @@ button.button.back-button.buttons.button-clear.header-item { .list-card-lg { width: 95%; } +.list-card .row { + padding-left: 5px; + padding-right: 5px; +} .list-col-left-margin { text-align: center; padding: 0.7em 0.8em 0.4em 0.8em; border-right-width: 0.5px; border-right-color: #ccc; border-right-style: solid; border-bottom-color: #ccc; border-bottom-width: 0.5px; border-bottom-style: solid; } @@ -971,9 +975,37 @@ button.button.back-button.buttons.button-clear.header-item { display: inline-block; width: calc(100% - 25px); } -.diary-arrow-container i { +.diary-more-container i { font-size: 32px; - float: right; +} +.diary-checkmark-container i { + font-size: 24px; + padding: 3px; +} + +.diary-checkmark-container i.can-verify { + color: #30A64A; + background-color: #ddd; + border-radius: 5px; +} +.diary-checkmark-container i.cannot-verify { + color: #E6B8B8; +} +.diary-checkmark-container i.already-verified { + color: #B8E6C2; +} +/* .diary-checkmark-container i.already-verified, .diary-checkmark-container i.cannot-verify { + color: #BFBFBF; +} */ + +.center-vert { + display: flex; + align-items: center; +} + +.center-horiz { + display: flex; + justify-content: center; } .side-menu-item { diff --git a/www/i18n/en.json b/www/i18n/en.json index b1fc38bb6..3c4e5dd81 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -153,9 +153,10 @@ "mode": "Mode", "replaces": "Replaces Mode", "purpose": "Purpose", - "unlabeled": "Unlabeled🔍", - "invalid-ebike": "Invalid🔍", - "show-all": "All", + "unlabeled": "All Unlabeled", + "invalid-ebike": "Invalid", + "to-label": "To Label", + "show-all": "All Trips", "no-trips-found": "No trips found", "for-current-filter": "for current filter. Show All to remove filters", "scroll-to-load-more": "Scroll to load more", @@ -302,10 +303,11 @@ "intro": { "permissions": { "locationPermExplanation-android-lt-6": "you accepted the permission during installation. You don't need to do anything now.", - "locationPermExplanation-android-gte-6": "please select 'allow'", - "locationPermExplanation-android-gte-10": "please select 'Allow all the time'", + "locationPermExplanation-android-6-9": "please select 'allow'", + "locationPermExplanation-android-10": "please select 'Allow all the time'", + "locationPermExplanation-android-gte-11": "please select 'Allow all the time' for location permissions on the app page", "locationPermExplanation-ios-lt-13": "please select 'Always allow'. This allows us to understand your travel even when you are not actively using the app", - "locationPermExplanation-ios-gte-13": "please select 'when in use' now. After a few days, you will be asked whether you want to give the app background permission. Please say 'Always Allow' at that time. This allows us to understand your travel even when you are not actively using the app" + "locationPermExplanation-ios-gte-13": "please select 'always' and 'precise' in the app settings page and return here to continue" } }, "allow_background": { diff --git a/www/js/diary/infinite_scroll_filters.js b/www/js/diary/infinite_scroll_filters.js index e4c2b295f..f9d7e0188 100644 --- a/www/js/diary/infinite_scroll_filters.js +++ b/www/js/diary/infinite_scroll_filters.js @@ -17,7 +17,7 @@ angular.module('emission.main.diary.infscrollfilters',[ var unlabeledCheck = function(t) { return ConfirmHelper.INPUTS .map((inputType, index) => !angular.isDefined(t.userInput[inputType])) - .reduce((acc, val) => acc && val, true); + .reduce((acc, val) => acc || val, false); } var invalidCheck = function(t) { @@ -29,16 +29,24 @@ angular.module('emission.main.diary.infscrollfilters',[ return retVal; } + var toLabelCheck = function(trip) { + console.log(trip.expectation.to_label) + return trip.expectation.to_label && unlabeledCheck(trip); + } + sf.UNLABELED = { text: $translate.instant(".unlabeled"), - width: "col-40", filter: unlabeledCheck } sf.INVALID_EBIKE = { text: $translate.instant(".invalid-ebike"), - width: "col-40", filter: invalidCheck } + + sf.TO_LABEL = { + text: $translate.instant(".to-label"), + filter: toLabelCheck + } return sf; }); diff --git a/www/js/diary/infinite_scroll_list.js b/www/js/diary/infinite_scroll_list.js index b92cab4c8..8abd562fc 100644 --- a/www/js/diary/infinite_scroll_list.js +++ b/www/js/diary/infinite_scroll_list.js @@ -45,12 +45,14 @@ angular.module('emission.main.diary.infscrolllist',['ui-leaflet', $scope.data = {}; // reset all filters $scope.filterInputs = [ - InfScrollFilters.UNLABELED, - InfScrollFilters.INVALID_EBIKE + InfScrollFilters.TO_LABEL, + InfScrollFilters.UNLABELED ]; $scope.filterInputs.forEach((f) => { f.state = false; }); + $scope.filterInputs[0].state = true; + $scope.allTrips = false; const ONE_WEEK = 7 * 24 * 60 * 60; // seconds $scope.infScrollControl = {}; @@ -70,10 +72,15 @@ angular.module('emission.main.diary.infscrolllist',['ui-leaflet', ctList.reverse(); ctList.forEach($scope.populateBasicClasses); ctList.forEach((trip, tIndex) => { + console.log("Expectation: "+JSON.stringify(trip.expectation)); + // console.log("Inferred labels from server: "+JSON.stringify(trip.inferred_labels)); trip.userInput = {}; ConfirmHelper.INPUTS.forEach(function(item, index) { $scope.populateManualInputs(trip, ctList[tIndex+1], item, $scope.data.manualResultMap[item]); }); + trip.finalInference = {}; + $scope.inferFinalLabels(trip); + $scope.updateVerifiability(trip); }); ctList.forEach(function(trip, index) { fillPlacesForTripAsync(trip); @@ -156,6 +163,7 @@ angular.module('emission.main.diary.infscrolllist',['ui-leaflet', f.state = false; } }); + $scope.allTrips = false; $scope.recomputeDisplayTrips(); } @@ -163,6 +171,7 @@ angular.module('emission.main.diary.infscrolllist',['ui-leaflet', $scope.filterInputs.forEach((f) => { f.state = false; }); + $scope.allTrips = true; $scope.recomputeDisplayTrips(); } @@ -173,7 +182,9 @@ angular.module('emission.main.diary.infscrolllist',['ui-leaflet', if (alreadyFiltered) { Logger.displayError("multiple filters not supported!", undefined); } else { + // console.log("Trip n before: "+$scope.data.displayTrips.length); $scope.data.displayTrips = $scope.data.allTrips.filter(f.filter); + // console.log("Trip n after: "+$scope.data.displayTrips.length); alreadyFiltered = true; } } @@ -250,6 +261,29 @@ angular.module('emission.main.diary.infscrolllist',['ui-leaflet', return ($scope.differentCommon(tripgj))? "stop-time-tag-lower" : "stop-time-tag"; } + /** + * MODE (becomes manual/mode_confirm) becomes mode_confirm + */ + $scope.inputType2retKey = function(inputType) { + return ConfirmHelper.inputDetails[inputType].key.split("/")[1]; + } + + /** + * Insert the given userInputLabel into the given inputType's slot in inputField + */ + $scope.populateInput = function(tripField, inputType, userInputLabel) { + if (angular.isDefined(userInputLabel)) { + var userInputEntry = $scope.inputParams[inputType].value2entry[userInputLabel]; + if (!angular.isDefined(userInputEntry)) { + userInputEntry = ConfirmHelper.getFakeEntry(userInputLabel); + $scope.inputParams[inputType].options.push(userInputEntry); + $scope.inputParams[inputType].value2entry[userInputLabel] = userInputEntry; + } + // console.log("Mapped label "+userInputLabel+" to entry "+JSON.stringify(userInputEntry)); + tripField[inputType] = userInputEntry; + } + } + /** * Embed 'inputType' to the trip */ @@ -262,24 +296,97 @@ angular.module('emission.main.diary.infscrolllist',['ui-leaflet', inputList); var userInputLabel = unprocessedLabelEntry? unprocessedLabelEntry.data.label : undefined; if (!angular.isDefined(userInputLabel)) { - // manual/mode_confirm becomes mode_confirm - const retKey = ConfirmHelper.inputDetails[inputType].key.split("/")[1]; - userInputLabel = tripgj.user_input[retKey]; - } - if (angular.isDefined(userInputLabel)) { - var userInputEntry = $scope.inputParams[inputType].value2entry[userInputLabel]; - if (!angular.isDefined(userInputEntry)) { - userInputEntry = ConfirmHelper.getFakeEntry(userInputLabel); - $scope.inputParams[inputType].options.push(userInputEntry); - $scope.inputParams[inputType].value2entry[userInputLabel] = userInputEntry; - } - // console.log("Mapped label "+userInputLabel+" to entry "+JSON.stringify(userInputEntry)); - tripgj.userInput[inputType] = userInputEntry; + userInputLabel = tripgj.user_input[$scope.inputType2retKey(inputType)]; } + $scope.populateInput(tripgj.userInput, inputType, userInputLabel); // Logger.log("Set "+ inputType + " " + JSON.stringify(userInputEntry) + " for trip starting at " + JSON.stringify(tripgj.start_fmt_time)); $scope.editingTrip = angular.undefined; } + $scope.updateTripProperties = function(trip) { + $scope.inferFinalLabels(trip); + $scope.updateVerifiability(trip); + $scope.recomputeDisplayTrips(); + } + + /** + * Given the list of possible label tuples we've been sent and what the user has already input for the trip, choose the best labels to actually present to the user. + * The algorithm below operationalizes these principles: + * - Never consider label tuples that contradict a green label + * - Obey "conservation of uncertainty": the sum of probabilities after filtering by green labels must equal the sum of probabilities before + * - After filtering, predict the most likely choices at the level of individual labels, not label tuples + * - Never show user yellow labels that have a lower probability of being correct than confidenceThreshold + */ + $scope.inferFinalLabels = function(trip) { + // Display a label as red if its most probable inferred value has a probability of less than or equal to confidenceThreshold + // TODO: make this configurable + const confidenceThreshold = 0.5; + + // Deep copy the possibility tuples + let labelsList = JSON.parse(JSON.stringify(trip.inferred_labels)); + + // Capture the level of certainty so we can reconstruct it later + const totalCertainty = labelsList.map(item => item.p).reduce(((item, rest) => item + rest), 0); + + // Filter out the tuples that are inconsistent with existing green labels + for (const inputType of ConfirmHelper.INPUTS) { + const userInput = trip.userInput[inputType]; + if (userInput) { + const retKey = $scope.inputType2retKey(inputType); + labelsList = labelsList.filter(item => item.labels[retKey] == userInput.value); + } + } + + // Red labels if we have no possibilities left + if (labelsList.length == 0) { + for (const inputType of ConfirmHelper.INPUTS) $scope.populateInput(trip.finalInference, inputType, undefined); + } + else { + // Normalize probabilities to previous level of certainty + const certaintyScalar = totalCertainty/labelsList.map(item => item.p).reduce((item, rest) => item + rest); + labelsList.forEach(item => item.p*=certaintyScalar); + + for (const inputType of ConfirmHelper.INPUTS) { + // For each label type, find the most probable value by binning by label value and summing + const retKey = $scope.inputType2retKey(inputType); + let valueProbs = new Map(); + for (const tuple of labelsList) { + const labelValue = tuple.labels[retKey]; + if (!valueProbs.has(labelValue)) valueProbs.set(labelValue, 0); + valueProbs.set(labelValue, valueProbs.get(labelValue) + tuple.p); + } + let max = {p: 0, labelValue: undefined}; + for (const [thisLabelValue, thisP] of valueProbs) { + // In the case of a tie, keep the label with earlier first appearance in the labelsList (we used a Map to preserve this order) + if (thisP > max.p) max = {p: thisP, labelValue: thisLabelValue}; + } + + // Apply threshold + if (max.p <= confidenceThreshold) max.labelValue = undefined; + + $scope.populateInput(trip.finalInference, inputType, max.labelValue); + } + } + } + + /** + * For a given trip, compute how the "verify" button should behave. + * If the trip has at least one yellow label, the button should be clickable. + * If the trip has all green labels, the button should be disabled because everything has already been verified. + * If the trip has all red labels or a mix of red and green, the button should be disabled because we need more detailed user input. + */ + $scope.updateVerifiability = function(trip) { + var allGreen = true; + var someYellow = false; + for (const inputType of ConfirmHelper.INPUTS) { + const green = trip.userInput[inputType]; + const yellow = trip.finalInference[inputType] && !green; + if (yellow) someYellow = true; + if (!green) allGreen = false; + } + trip.verifiability = someYellow ? "can-verify" : (allGreen ? "already-verified" : "cannot-verify"); + } + $scope.getFormattedDistanceInMiles = function(input) { return (0.621371 * $scope.getFormattedDistance(input)).toFixed(1); } @@ -293,6 +400,10 @@ angular.module('emission.main.diary.infscrolllist',['ui-leaflet', tripgj.end_ts); tripgj.background = "bg-light"; tripgj.listCardClass = $scope.listCardClass(tripgj); + tripgj.verifiability = "cannot-verify"; + // Pre-populate start and end names with   so they take up the same amount of vertical space in the UI before they are populated with real data + tripgj.start_display_name = "\xa0"; + tripgj.end_display_name = "\xa0"; } const fillPlacesForTripAsync = function(tripgj) { @@ -529,6 +640,25 @@ angular.module('emission.main.diary.infscrolllist',['ui-leaflet', $scope.popovers[inputType].hide(); }; + /** + * verifyTrip turns all of a given trip's yellow labels green + */ + $scope.verifyTrip = function($event, trip) { + if (trip.verifiability != "can-verify") return; + + $scope.draftInput = { + "start_ts": trip.start_ts, + "end_ts": trip.end_ts + }; + $scope.editingTrip = trip; + + for (const inputType of ConfirmHelper.INPUTS) { + const inferred = trip.finalInference[inputType]; + // TODO: figure out what to do with "other". For now, do not verify. + if (inferred && !trip.userInput[inputType] && inferred != "other") $scope.store(inputType, inferred, false); + } + } + /** * Store selected value for options * $scope.selected is for display only @@ -595,6 +725,7 @@ angular.module('emission.main.diary.infscrolllist',['ui-leaflet', } else { tripToUpdate.userInput[inputType] = $scope.inputParams[inputType].value2entry[input.value]; } + $scope.updateTripProperties(tripToUpdate); // Redo our inferences, filters, etc. based on this new information }); }); if (isOther == true) diff --git a/www/js/intro.js b/www/js/intro.js index 972944f1d..b72dcd9fd 100755 --- a/www/js/intro.js +++ b/www/js/intro.js @@ -30,9 +30,11 @@ angular.module('emission.intro', ['emission.splash.startprefs', if($scope.osver < 6) { $scope.locationPermExplanation = $translate.instant('intro.permissions.locationPermExplanation-android-lt-6'); } else if ($scope.osver < 10) { - $scope.locationPermExplanation = $translate.instant("intro.permissions.locationPermExplanation-android-gte-6"); + $scope.locationPermExplanation = $translate.instant("intro.permissions.locationPermExplanation-android-6-9"); + } else if ($scope.osver < 11) { + $scope.locationPermExplanation = $translate.instant("intro.permissions.locationPermExplanation-android-10"); } else { - $scope.locationPermExplanation = $translate.instant("intro.permissions.locationPermExplanation-android-gte-10"); + $scope.locationPermExplanation = $translate.instant("intro.permissions.locationPermExplanation-android-gte-11"); } } diff --git a/www/manual_lib/angularjs-nvd3-directives/Gruntfile.js b/www/manual_lib/angularjs-nvd3-directives/Gruntfile.js deleted file mode 100644 index 2ce41eb54..000000000 --- a/www/manual_lib/angularjs-nvd3-directives/Gruntfile.js +++ /dev/null @@ -1,139 +0,0 @@ -/*global module:false*/ -module.exports = function (grunt) { - 'use strict'; - // Project configuration. - grunt.initConfig({ - // Metadata. - pkg: grunt.file.readJSON('package.json'), - banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + - '<%= grunt.template.today("yyyy-mm-dd") %>\n' + - '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + - '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + - ' Licensed <%= pkg.license %> */\n', - // Task configuration. - clean: ['dist/', 'generated/'], - ngmin: { - directives: { - expand: true, - cwd: 'src', - src: ['directives/nvd3Directives.js', 'directives/legendDirectives.js'], - dest: 'generated' - } - }, - concat: { - options: { - banner: '<%= banner %>', - stripBanners: true - }, - js: { - src: [ - 'src/**/intro.js', - 'generated/directives/legendDirectives.js', - 'src/**/nvD3LegendConfiguration.js', - 'src/**/nvD3Events.js', - 'src/**/nvD3AxisConfiguration.js', - 'generated/directives/nvd3Directives.js', - 'src/**/outro.js' - ], - dest: 'dist/<%= pkg.name %>.js' - } - }, - jshint: { - options: { - jshintrc: true - }, - afterconcat: ['dist/angularjs-nvd3-directives.js'], - gruntfile: { - src: 'Gruntfile.js' - } - }, - jsbeautifier : { - files : ['dist/angularjs-nvd3-directives.js'], - options : { - js: { - evalCode: true, - indentSize: 2, - indentChar: ' ', - spaceInParen: true, - jslintHappy: true, - indentLevel: 0 - } - } - }, - uglify: { - options: { - mangle: false - }, - min: { - files: { - 'dist/angularjs-nvd3-directives.min.js': ['dist/angularjs-nvd3-directives.js'] - } - } - }, - copy: { - main: { - files: [ - {src: ['build/components/angular/angular.js'], dest: 'examples/js/angular.js', filter: 'isFile'}, - {src: ['build/components/angular-route/angular-route.js'], dest: 'examples/js/angular-route.js', filter: 'isFile'}, - {src: ['build/components/d3/d3.js'], dest: 'examples/js/d3.js', filter: 'isFile'}, - {src: ['build/components/nvd3/nv.d3.js'], dest: 'examples/js/nv.d3.js', filter: 'isFile'}, - {src: ['build/components/nvd3/nv.d3.css'], dest: 'examples/stylesheets/nv.d3.css', filter: 'isFile'}, - {src: ['build/components/moment/moment.js'], dest: 'examples/js/moment.js', filter: 'isFile'} - ] - } - }, - bower: { - install: { - options: { - targetDir: './build/components', - layout: 'byComponent', - cleanTargetDir: true, - cleanBowerDir: false, - verbose: true - } - } - }, - release:{ - options: { - bump: false, - file: 'bower.json', - tag: true, - tagName: 'v<%= version %>', - npm: false, - npmtag: true, - github: { - repo: 'cmaurer/angularjs-nvd3-directives', //put your user/repo here - usernameVar: 'GITHUB_USERNAME', //ENVIRONMENT VARIABLE that contains Github username - passwordVar: 'GITHUB_PASSWORD' //ENVIRONMENT VARIABLE that contains Github password - } - } - }, - changelog: { - release: { - options: { - version: 'v0.0.7' - } - } - } - }); - - // These plugins provide necessary tasks. - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-bower-task'); - grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-karma-coveralls'); - grunt.loadNpmTasks('grunt-jsbeautifier'); - grunt.loadNpmTasks('grunt-ngmin'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-release'); - grunt.loadNpmTasks('grunt-templated-changelog'); - - grunt.registerTask('bower', ['bower:install']); - - // Default task. - grunt.registerTask('default', ['clean', 'ngmin', 'concat', 'jsbeautifier', 'jshint', 'uglify']); - -}; diff --git a/www/manual_lib/angularjs-nvd3-directives/package.json b/www/manual_lib/angularjs-nvd3-directives/package.json index e650928c7..c272d2389 100644 --- a/www/manual_lib/angularjs-nvd3-directives/package.json +++ b/www/manual_lib/angularjs-nvd3-directives/package.json @@ -37,21 +37,6 @@ "url": "https://github.com/cmaurer/angularjs-nvd3-directives/issues" }, "devDependencies": { - "grunt": "^0.4.2", - "grunt-contrib-concat": "^0.4.0", - "grunt-contrib-uglify": "^0.4.0", - "grunt-contrib-jshint": "^0.10.0", - "grunt-contrib-watch": "^0.6.1", - "grunt-contrib-clean": "^0.5.0", - "grunt-cli": "^0.1.11", - "grunt-bower-task": "^0.3.4", - "grunt-contrib-copy": "^0.5.0", - "grunt-karma-coveralls": "^2.4.0", - "grunt-jsbeautifier": "^0.2.6", - "grunt-ngmin": "^0.0.3", - "grunt-release": "^0.7.0", - "conventional-changelog": "^0.0.6", - "grunt-templated-changelog": "^0.1.3" }, "engines": { "node": ">=0.10.0", diff --git a/www/templates/diary/infinite_scroll_list.html b/www/templates/diary/infinite_scroll_list.html index 26a8ef0fe..05c9488cc 100644 --- a/www/templates/diary/infinite_scroll_list.html +++ b/www/templates/diary/infinite_scroll_list.html @@ -1,18 +1,18 @@ - -
- - - + + + + -
+
@@ -48,43 +48,52 @@

{{'diary.scroll-to-load-more' | transla
-
-
-
-
- - {{trip.start_display_name}} - +
+
+
+ +
-
- - {{trip.end_display_name}} +
+
+
+ + {{trip.start_display_name}} +
+
+ + {{trip.end_display_name}} +
+
+
+ +
-
-
- -
-
-
-
- {{input.labeltext}} -
-
-
-
-
- +
+
+ {{input.labeltext}}
-
-
+
+
+
+ +
+
+ +
+
+ +
+
+
-
-
{{trip.display_start_time}}
diff --git a/www/templates/intro/sensor_explanation.html b/www/templates/intro/sensor_explanation.html index 90725fae2..08b943769 100644 --- a/www/templates/intro/sensor_explanation.html +++ b/www/templates/intro/sensor_explanation.html @@ -6,9 +6,10 @@ We will now ask you for permissions to access the following sensors.

Location

-We need this permission in order to track your location and determine your travel -patterns. Since you are running {{platform}} {{osver}}, when prompted, -{{locationPermExplanation}}. +We need this permission in order to track your location in the background, even +when the app is closed. We use this location data to automatically generate a +trip log that is displayed to you for annotation. Since you are running +{{platform}} {{osver}}, when prompted, {{locationPermExplanation}}.

Notifications