diff options
95 files changed, 1624 insertions, 515 deletions
@@ -16,3 +16,4 @@ locale/lang_list /config.xml /node_modules /package.json +/package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..735c921 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +## Releases + +* v2.1 (3rd May 2018) + - New features + - Multiple photo support. #107 + - iPhone X support. #259 + - Button to hide pins on map. #175 + - Bugfixes + - Skip empty report details screen. + - Correctly set user title. #156 + - Show more helpful error if server rejects password. #156 + - Allow mailto: links to be followed. #263 + - Stop keyboard obscuring report details text field. #222 + - Development improvements + - CONFIG.SKIP_CONFIRM_REPORT flag to skip confirmation screen. + - Ensure compatibility with latest Cordova versions. + - CONFIG.PASSWORD_MIN_LENGTH key to validate user password. + - CONFIG.PASSWORD_CHECK_COMMON flag to test password against server. @@ -25,6 +25,9 @@ You should also create a `config.xml` file based on `config.xml-example`. The only change you should need to make is to add the hostname of your FMS installation in an `<access origin=""/>` tag. +You'll also need `package.json` and `package-lock.json` which can be similarly +created from the `-example` files. + Setup ----- This project uses Apache Cordova to produce Android and iOS apps. There is diff --git a/config.xml-example b/config.xml-example index db64ef0..22c5332 100644 --- a/config.xml-example +++ b/config.xml-example @@ -1,5 +1,5 @@ <?xml version='1.0' encoding='utf-8'?> -<widget android-versionCode="42" id="org.mysociety.FixMyStreet" version="2.0.7" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> +<widget id="org.mysociety.FixMyStreet" version="2.1" ios-CFBundleVersion="2.1.20180503.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> <name>FixMyStreet</name> <description> Report problems on FixMyStreet @@ -8,7 +8,7 @@ mySociety </author> <content src="index.html" /> - + <allow-intent href="mailto:*" /> <access origin="http://127.0.0.1*" /> <access origin="*://*.openstreetmap.org" /> <access origin="*://*.opencyclemap.org" /> @@ -35,7 +35,7 @@ <preference name="AndroidPersistentFileLocation" value="Compatibility" /> <!-- If you change the background colour of the app, change this value to match. --> <preference name="StatusBarBackgroundColor" value="#F6BE41" /> - <preference name="deployment-target" value="6.0" /> + <preference name="deployment-target" value="9.0" /> <platform name="android"> <icon density="ldpi" src="res/android/drawable-ldpi/icon.png" /> @@ -51,60 +51,65 @@ <splash density="xxhdpi" src="res/android/drawable-xxhdpi/screen.9.png" /> <splash density="xxxhdpi" src="res/android/drawable-xxxhdpi/screen.9.png" /> </platform> - <platform name="ios"> - <!-- iOS 8.0+ --> - <!-- iPhone 6 Plus --> - <icon src="res/ios/icons/Icon-60@3x.png" width="180" height="180" /> - <!-- iOS 7.0+ --> - <!-- iPhone / iPod Touch --> - <icon src="res/ios/icons/Icon-60.png" width="60" height="60" /> - <icon src="res/ios/icons/Icon-60@2x.png" width="120" height="120" /> - <!-- iPad --> - <icon src="res/ios/icons/Icon-76.png" width="76" height="76" /> - <icon src="res/ios/icons/Icon-76@2x.png" width="152" height="152" /> - <!-- iOS 6.1 --> - <!-- Spotlight Icon --> - <icon src="res/ios/icons/Icon-40.png" width="40" height="40" /> - <icon src="res/ios/icons/Icon-40@2x.png" width="80" height="80" /> - <!-- iPhone / iPod Touch --> - <icon src="res/ios/icons/Icon.png" width="57" height="57" /> - <icon src="res/ios/icons/Icon@2x.png" width="114" height="114" /> - <!-- iPad --> - <icon src="res/ios/icons/Icon-72.png" width="72" height="72" /> - <icon src="res/ios/icons/Icon-72@2x.png" width="144" height="144" /> - <!-- iPhone Spotlight and Settings Icon --> - <icon src="res/ios/icons/Icon-Small.png" width="29" height="29" /> - <icon src="res/ios/icons/Icon-Small@2x.png" width="58" height="58" /> - <!-- iPad Spotlight and Settings Icon --> - <icon src="res/ios/icons/Icon-50.png" width="50" height="50" /> - <icon src="res/ios/icons/Icon-50@2x.png" width="100" height="100" /> - <splash src="res/ios/splash/Default~iphone.png" width="320" height="480"/> - <splash src="res/ios/splash/Default@2x~iphone_640x960.png" width="640" height="960"/> - <splash src="res/ios/splash/Default-Portrait~ipad_768x1024.png" width="768" height="1024"/> - <splash src="res/ios/splash/Default-Portrait@2x~ipad_1536x2048.png" width="1536" height="2048"/> - <splash src="res/ios/splash/Default-Portrait@2x~ipad_1536x2008.png" width="1536" height="2008"/> - <splash src="res/ios/splash/Default-568h@2x~iphone_640x1136.png" width="640" height="1136"/> - <splash src="res/ios/splash/Default-750@2x~iphone6-portrait_750x1334.png" width="750" height="1334"/> - <splash src="res/ios/splash/Default-1242@3x~iphone6s-portrait_1242x2208.png" width="1242" height="2208"/> + <icon height="180" src="res/ios/icons/Icon-60@3x.png" width="180" /> + <icon height="60" src="res/ios/icons/Icon-60.png" width="60" /> + <icon height="120" src="res/ios/icons/Icon-60@2x.png" width="120" /> + <icon height="76" src="res/ios/icons/Icon-76.png" width="76" /> + <icon height="152" src="res/ios/icons/Icon-76@2x.png" width="152" /> + <icon height="40" src="res/ios/icons/Icon-40.png" width="40" /> + <icon height="80" src="res/ios/icons/Icon-40@2x.png" width="80" /> + <icon height="57" src="res/ios/icons/Icon.png" width="57" /> + <icon height="114" src="res/ios/icons/Icon@2x.png" width="114" /> + <icon height="72" src="res/ios/icons/Icon-72.png" width="72" /> + <icon height="144" src="res/ios/icons/Icon-72@2x.png" width="144" /> + <icon height="29" src="res/ios/icons/Icon-Small.png" width="29" /> + <icon height="58" src="res/ios/icons/Icon-Small@2x.png" width="58" /> + <icon height="87" src="res/ios/icons/Icon-Small@3x.png" width="87" /> + <icon height="50" src="res/ios/icons/Icon-50.png" width="50" /> + <icon height="100" src="res/ios/icons/Icon-50@2x.png" width="100" /> + <icon height="167" src="res/ios/icons/Icon-83.5@2x.png" width="167" /> + <icon height="1024" src="res/ios/icons/iTunesArtwork@2x.png" width="1024" /> + <splash height="480" src="res/ios/splash/Default~iphone.png" width="320" /> + <splash height="960" src="res/ios/splash/Default@2x~iphone_640x960.png" width="640" /> + <splash height="1024" src="res/ios/splash/Default-Portrait~ipad_768x1024.png" width="768" /> + <splash height="2048" src="res/ios/splash/Default-Portrait@2x~ipad_1536x2048.png" width="1536" /> + <splash height="2008" src="res/ios/splash/Default-Portrait@2x~ipad_1536x2008.png" width="1536" /> + <splash height="1136" src="res/ios/splash/Default-568h@2x~iphone_640x1136.png" width="640" /> + <splash height="1334" src="res/ios/splash/Default-750@2x~iphone6-portrait_750x1334.png" width="750" /> + <splash height="2208" src="res/ios/splash/Default-1242@3x~iphone6s-portrait_1242x2208.png" width="1242" /> + <splash height="2208" src="res/ios/splash/Default-1242@3x~iphone6s-portrait_1242x2208.png" width="1242" /> + <splash src="res/ios/splash/Default-1125@3x~iphonex-portrait_1125x2436.png" width="1125" height="2436"/> </platform> - - <engine name="ios" spec="~4.1.1" /> - <plugin name="cordova-plugin-camera" spec="~2.1.1" /> - <plugin name="cordova-plugin-device" spec="~1.1.1" /> - <plugin name="cordova-plugin-dialogs" spec="~1.2.0" /> - <plugin name="cordova-plugin-file" spec="~4.1.1" /> - <plugin name="cordova-plugin-file-transfer" spec="~1.5.0" /> - <plugin name="cordova-plugin-geolocation" spec="~2.1.0" /> - <plugin name="cordova-plugin-inappbrowser" spec="~1.3.0" /> - <plugin name="cordova-plugin-media" spec="~2.2.0" /> - <plugin name="cordova-plugin-media-capture" spec="~1.2.0" /> - <plugin name="cordova-plugin-network-information" spec="~1.2.0" /> - <plugin name="cordova-plugin-splashscreen" spec="~3.2.1" /> - <plugin name="cordova-plugin-statusbar" spec="~2.1.2" /> - <plugin name="cordova-plugin-whitelist" spec="~1.2.1" /> - <plugin name="ionic-plugin-keyboard" spec="~2.0.1" /> - <engine name="android" spec="~5.1.1" /> <hook src="scripts/prepare_index_html.js" type="after_prepare" /> + <plugin name="cordova-plugin-camera" spec="^4.0.3" /> + <plugin name="cordova-plugin-compat" spec="^1.2.0" /> + <plugin name="cordova-plugin-device" spec="^2.0.2" /> + <plugin name="cordova-plugin-dialogs" spec="^2.0.1" /> + <plugin name="cordova-plugin-file" spec="^6.0.1" /> + <plugin name="cordova-plugin-file-transfer" spec="^1.7.1" /> + <plugin name="cordova-plugin-geolocation" spec="^4.0.1" /> + <plugin name="cordova-plugin-inappbrowser" spec="^3.0.0" /> + <plugin name="cordova-plugin-media" spec="^5.0.2"> + <variable name="KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE" value="NO" /> + </plugin> + <plugin name="cordova-plugin-media-capture" spec="^3.0.2" /> + <plugin name="cordova-plugin-network-information" spec="^2.0.1" /> + <plugin name="cordova-plugin-splashscreen" spec="^5.0.2" /> + <plugin name="cordova-plugin-statusbar" spec="^2.4.2" /> + <plugin name="cordova-plugin-whitelist" spec="^1.3.3" /> + <plugin name="ionic-plugin-keyboard" spec="^2.2.1" /> + <engine name="ios" spec="^4.5.4" /> + <edit-config file="*-Info.plist" mode="merge" target="NSCameraUsageDescription"> + <string>To take photos of problems.</string> + </edit-config> + <edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryUsageDescription"> + <string>To attach photos of problems to your report.</string> + </edit-config> + <edit-config file="*-Info.plist" mode="merge" target="NSLocationWhenInUseUsageDescription"> + <string>To create accurately positioned reports.</string> + </edit-config> + <edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryAddUsageDescription"> + <string>To save photos of problems.</string> + </edit-config> </widget> - diff --git a/hooks/after_prepare/allow-sd-installation.js b/hooks/after_prepare/allow-sd-installation.js index 4a5af5e..d6d24d9 100755 --- a/hooks/after_prepare/allow-sd-installation.js +++ b/hooks/after_prepare/allow-sd-installation.js @@ -2,11 +2,13 @@ var fs = require('fs'); -if (fs.existsSync('platforms/android')) { - var filename = 'platforms/android/AndroidManifest.xml', - manifest = fs.readFileSync(filename).toString(); +var filename = 'platforms/android/app/src/main/AndroidManifest.xml'; +if (fs.existsSync(filename)) { + var manifest = fs.readFileSync(filename).toString(); if (manifest.indexOf('android:installLocation="auto"') == -1) { manifest = manifest.replace(/<manifest/, '<manifest android:installLocation="auto"'); fs.writeFileSync(filename, manifest); } +} else { + console.log("file didn't exist: ", filename); } diff --git a/hooks/after_prepare/remove-permissions.js b/hooks/after_prepare/remove-permissions.js index 5628647..9ae133b 100755 --- a/hooks/after_prepare/remove-permissions.js +++ b/hooks/after_prepare/remove-permissions.js @@ -1,15 +1,15 @@ #!/usr/bin/env node var fs = require('fs'); -if(fs.existsSync('platforms/android')) { +var filename = 'platforms/android/app/src/main/AndroidManifest.xml'; +if (fs.existsSync(filename)) { var PERMISSIONS_TO_REMOVE = [ 'READ_PHONE_STATE', 'RECORD_AUDIO', 'MODIFY_AUDIO_SETTINGS', 'RECORD_VIDEO' ]; - var MANIFEST = 'platforms/android/AndroidManifest.xml'; - var manifestLines = fs.readFileSync(MANIFEST).toString().split('\n'); + var manifestLines = fs.readFileSync(filename).toString().split('\n'); var newManifestLines = []; var PERMISSIONS_REGEX = PERMISSIONS_TO_REMOVE.join('|'); @@ -19,5 +19,7 @@ if(fs.existsSync('platforms/android')) { } }); - fs.writeFileSync(MANIFEST, newManifestLines.join('\n')); -}
\ No newline at end of file + fs.writeFileSync(filename, newManifestLines.join('\n')); +} else { + console.log("file didn't exist: ", filename); +} diff --git a/locale/FixMyStreetMobileApp.po b/locale/FixMyStreetMobileApp.po index 2f162fc..7dd7b39 100644 --- a/locale/FixMyStreetMobileApp.po +++ b/locale/FixMyStreetMobileApp.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 1.0\n" "Report-Msgid-Bugs-To: struan@mysociety.org\n" -"POT-Creation-Date: 2017-06-27 19:15+0200\n" +"POT-Creation-Date: 2017-06-23 17:59+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <team@fixmystreet.com>\n" @@ -21,7 +21,11 @@ msgstr "" msgid "(optional)" msgstr "" -#: templates/existing.html:12 templates/strings.js:43 +#: templates/photo.html:20 +msgid "(required)" +msgstr "" + +#: templates/existing.html:12 templates/strings.js:42 msgid "-- Pick a category --" msgstr "" @@ -38,10 +42,10 @@ msgid "Account" msgstr "" #: templates/photo.html:3 -msgid "Add Photo" +msgid "Add Photos" msgstr "" -#: templates/photo.html:18 +#: templates/photo.html:16 msgid "Add a Photo" msgstr "" @@ -49,7 +53,7 @@ msgstr "" msgid "Add a Photo " msgstr "" -#: templates/offline.html:56 templates/photo.html:34 +#: templates/offline.html:56 templates/photo.html:45 msgid "Add an existing photo" msgstr "" @@ -209,7 +213,7 @@ msgstr "" msgid "New Report Here" msgstr "" -#: templates/details.html:4 templates/details_extra.html:4 templates/photo.html:7 templates/strings.js:23 +#: templates/details.html:4 templates/details_extra.html:4 templates/photo.html:9 templates/strings.js:23 msgid "Next" msgstr "" @@ -249,8 +253,8 @@ msgstr "" msgid "Photo added" msgstr "" -#: templates/photo.html:21 -msgid "Photo for report" +#: templates/photo.html:25 +msgid "Photos for report" msgstr "" #: templates/strings.js:11 @@ -393,7 +397,7 @@ msgstr "" msgid "Signed out!" msgstr "" -#: templates/offline.html:4 templates/photo.html:5 +#: templates/offline.html:4 templates/photo.html:6 msgid "Skip" msgstr "" @@ -413,7 +417,7 @@ msgstr "" msgid "Submit" msgstr "" -#: templates/offline.html:55 templates/photo.html:35 +#: templates/offline.html:55 templates/photo.html:46 msgid "Take a new photo" msgstr "" diff --git a/locale/en_GB.UTF-8/LC_MESSAGES/FixMyStreetMobileApp.po b/locale/en_GB.UTF-8/LC_MESSAGES/FixMyStreetMobileApp.po index 73047be..50ddb36 100644 --- a/locale/en_GB.UTF-8/LC_MESSAGES/FixMyStreetMobileApp.po +++ b/locale/en_GB.UTF-8/LC_MESSAGES/FixMyStreetMobileApp.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: 1.0\n" "Report-Msgid-Bugs-To: struan@mysociety.org\n" -"POT-Creation-Date: 2017-06-27 19:15+0200\n" +"POT-Creation-Date: 2017-06-23 17:59+0100\n" "PO-Revision-Date: 2013-09-18 18:01+0100\n" "Last-Translator: Struan <struan@mysociety.org>\n" "Language-Team: LANGUAGE <team@fixmystreet.com>\n" @@ -21,7 +21,11 @@ msgstr "" msgid "(optional)" msgstr "" -#: templates/existing.html:12 templates/strings.js:43 +#: templates/photo.html:20 +msgid "(required)" +msgstr "" + +#: templates/existing.html:12 templates/strings.js:42 msgid "-- Pick a category --" msgstr "" @@ -38,10 +42,10 @@ msgid "Account" msgstr "" #: templates/photo.html:3 -msgid "Add Photo" +msgid "Add Photos" msgstr "" -#: templates/photo.html:18 +#: templates/photo.html:16 msgid "Add a Photo" msgstr "" @@ -49,7 +53,7 @@ msgstr "" msgid "Add a Photo " msgstr "" -#: templates/offline.html:56 templates/photo.html:34 +#: templates/offline.html:56 templates/photo.html:45 msgid "Add an existing photo" msgstr "" @@ -215,7 +219,7 @@ msgid "New Report Here" msgstr "" #: templates/details.html:4 templates/details_extra.html:4 -#: templates/photo.html:7 templates/strings.js:23 +#: templates/photo.html:9 templates/strings.js:23 msgid "Next" msgstr "" @@ -255,8 +259,8 @@ msgstr "" msgid "Photo added" msgstr "" -#: templates/photo.html:21 -msgid "Photo for report" +#: templates/photo.html:25 +msgid "Photos for report" msgstr "" #: templates/strings.js:11 @@ -403,7 +407,7 @@ msgstr "" msgid "Signed out!" msgstr "" -#: templates/offline.html:4 templates/photo.html:5 +#: templates/offline.html:4 templates/photo.html:6 msgid "Skip" msgstr "" @@ -423,7 +427,7 @@ msgstr "" msgid "Submit" msgstr "" -#: templates/offline.html:55 templates/photo.html:35 +#: templates/offline.html:55 templates/photo.html:46 msgid "Take a new photo" msgstr "" diff --git a/locale/es_DO.UTF-8/LC_MESSAGES/FixMyStreetMobileApp.po b/locale/es_DO.UTF-8/LC_MESSAGES/FixMyStreetMobileApp.po index 1275d70..fc441c7 100644 --- a/locale/es_DO.UTF-8/LC_MESSAGES/FixMyStreetMobileApp.po +++ b/locale/es_DO.UTF-8/LC_MESSAGES/FixMyStreetMobileApp.po @@ -25,7 +25,12 @@ msgstr "" msgid "(optional)" msgstr "(opcional)" -#: templates/existing.html:12 templates/strings.js:43 +#: templates/photo.html:20 +#, fuzzy +msgid "(required)" +msgstr "requerido(a)" + +#: templates/existing.html:12 templates/strings.js:42 msgid "-- Pick a category --" msgstr "-- Seleccione una categoría --" @@ -42,10 +47,11 @@ msgid "Account" msgstr "Cuenta" #: templates/photo.html:3 -msgid "Add Photo" +#, fuzzy +msgid "Add Photos" msgstr "Añadir foto" -#: templates/photo.html:18 +#: templates/photo.html:16 msgid "Add a Photo" msgstr "Añadir una foto" @@ -53,7 +59,7 @@ msgstr "Añadir una foto" msgid "Add a Photo " msgstr "Añadir una foto " -#: templates/offline.html:56 templates/photo.html:34 +#: templates/offline.html:56 templates/photo.html:45 msgid "Add an existing photo" msgstr "Añadir una foto existente" @@ -219,7 +225,7 @@ msgid "New Report Here" msgstr "Nuevo informe aquí" #: templates/details.html:4 templates/details_extra.html:4 -#: templates/photo.html:7 templates/strings.js:23 +#: templates/photo.html:9 templates/strings.js:23 msgid "Next" msgstr "Siguiente" @@ -259,8 +265,9 @@ msgstr "Número de teléfono" msgid "Photo added" msgstr "Foto añadida" -#: templates/photo.html:21 -msgid "Photo for report" +#: templates/photo.html:25 +#, fuzzy +msgid "Photos for report" msgstr "Foto para el informe" #: templates/strings.js:11 @@ -407,7 +414,7 @@ msgstr "¡Sesión abierta!" msgid "Signed out!" msgstr "¡Sesión cerrada!" -#: templates/offline.html:4 templates/photo.html:5 +#: templates/offline.html:4 templates/photo.html:6 msgid "Skip" msgstr "Saltar" @@ -427,7 +434,7 @@ msgstr "Título" msgid "Submit" msgstr "Enviar" -#: templates/offline.html:55 templates/photo.html:35 +#: templates/offline.html:55 templates/photo.html:46 msgid "Take a new photo" msgstr "Tomar una nueva foto" diff --git a/package-lock.json-example b/package-lock.json-example new file mode 100644 index 0000000..ab66fd7 --- /dev/null +++ b/package-lock.json-example @@ -0,0 +1,600 @@ +{ + "name": "org.mysociety.groundcontrolapp", + "version": "1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "cordova-android": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-6.2.3.tgz", + "integrity": "sha1-JJ8hts5fHxyEenq4OxaQnb7Vqig=", + "requires": { + "cordova-common": "2.0.2", + "elementtree": "0.1.6", + "nopt": "3.0.6", + "properties-parser": "0.2.3", + "q": "1.5.0", + "shelljs": "0.5.3" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true + }, + "ansi": { + "version": "0.3.1", + "bundled": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true + }, + "base64-js": { + "version": "0.0.8", + "bundled": true + }, + "big-integer": { + "version": "1.6.22", + "bundled": true + }, + "bplist-parser": { + "version": "0.1.1", + "bundled": true, + "requires": { + "big-integer": "1.6.22" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "cordova-common": { + "version": "2.0.2", + "bundled": true, + "requires": { + "ansi": "0.3.1", + "bplist-parser": "0.1.1", + "cordova-registry-mapper": "1.1.15", + "elementtree": "0.1.6", + "glob": "5.0.15", + "minimatch": "3.0.3", + "osenv": "0.1.4", + "plist": "1.2.0", + "q": "1.5.0", + "semver": "5.3.0", + "shelljs": "0.5.3", + "underscore": "1.8.3", + "unorm": "1.4.1" + } + }, + "cordova-registry-mapper": { + "version": "1.1.15", + "bundled": true + }, + "elementtree": { + "version": "0.1.6", + "bundled": true, + "requires": { + "sax": "0.3.5" + } + }, + "glob": { + "version": "5.0.15", + "bundled": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.3", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "lodash": { + "version": "3.10.1", + "bundled": true + }, + "minimatch": { + "version": "3.0.3", + "bundled": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1.1.0" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "plist": { + "version": "1.2.0", + "bundled": true, + "requires": { + "base64-js": "0.0.8", + "util-deprecate": "1.0.2", + "xmlbuilder": "4.0.0", + "xmldom": "0.1.27" + } + }, + "properties-parser": { + "version": "0.2.3", + "bundled": true + }, + "q": { + "version": "1.5.0", + "bundled": true + }, + "sax": { + "version": "0.3.5", + "bundled": true + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "shelljs": { + "version": "0.5.3", + "bundled": true + }, + "underscore": { + "version": "1.8.3", + "bundled": true + }, + "unorm": { + "version": "1.4.1", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "xmlbuilder": { + "version": "4.0.0", + "bundled": true, + "requires": { + "lodash": "3.10.1" + } + }, + "xmldom": { + "version": "0.1.27", + "bundled": true + } + } + }, + "cordova-ios": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/cordova-ios/-/cordova-ios-4.5.4.tgz", + "integrity": "sha1-yAZIBYlyloVw3BXalzFP+S0H3+c=", + "requires": { + "cordova-common": "2.1.0", + "ios-sim": "6.1.2", + "nopt": "3.0.6", + "plist": "1.2.0", + "q": "1.5.1", + "shelljs": "0.5.3", + "xcode": "0.9.3", + "xml-escape": "1.1.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ansi": { + "version": "0.3.1", + "bundled": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "base64-js": { + "version": "0.0.8", + "bundled": true + }, + "big-integer": { + "version": "1.6.25", + "bundled": true + }, + "bplist-creator": { + "version": "0.0.7", + "bundled": true, + "requires": { + "stream-buffers": "2.2.0" + } + }, + "bplist-parser": { + "version": "0.1.1", + "bundled": true, + "requires": { + "big-integer": "1.6.25" + } + }, + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "cordova-common": { + "version": "2.1.0", + "bundled": true, + "requires": { + "ansi": "0.3.1", + "bplist-parser": "0.1.1", + "cordova-registry-mapper": "1.1.15", + "elementtree": "0.1.6", + "glob": "5.0.15", + "minimatch": "3.0.4", + "osenv": "0.1.4", + "plist": "1.2.0", + "q": "1.5.1", + "semver": "5.4.1", + "shelljs": "0.5.3", + "underscore": "1.8.3", + "unorm": "1.4.1" + } + }, + "cordova-registry-mapper": { + "version": "1.1.15", + "bundled": true + }, + "elementtree": { + "version": "0.1.6", + "bundled": true, + "requires": { + "sax": "0.3.5" + } + }, + "glob": { + "version": "5.0.15", + "bundled": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ios-sim": { + "version": "6.1.2", + "bundled": true, + "requires": { + "bplist-parser": "0.0.6", + "nopt": "1.0.9", + "plist": "1.2.0", + "simctl": "1.1.1" + }, + "dependencies": { + "bplist-parser": { + "version": "0.0.6", + "bundled": true + }, + "nopt": { + "version": "1.0.9", + "bundled": true, + "requires": { + "abbrev": "1.1.1" + } + } + } + }, + "lodash": { + "version": "3.10.1", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "pegjs": { + "version": "0.10.0", + "bundled": true + }, + "plist": { + "version": "1.2.0", + "bundled": true, + "requires": { + "base64-js": "0.0.8", + "util-deprecate": "1.0.2", + "xmlbuilder": "4.0.0", + "xmldom": "0.1.27" + } + }, + "q": { + "version": "1.5.1", + "bundled": true + }, + "sax": { + "version": "0.3.5", + "bundled": true + }, + "semver": { + "version": "5.4.1", + "bundled": true + }, + "shelljs": { + "version": "0.5.3", + "bundled": true + }, + "simctl": { + "version": "1.1.1", + "bundled": true, + "requires": { + "shelljs": "0.2.6", + "tail": "0.4.0" + }, + "dependencies": { + "shelljs": { + "version": "0.2.6", + "bundled": true + } + } + }, + "simple-plist": { + "version": "0.2.1", + "bundled": true, + "requires": { + "bplist-creator": "0.0.7", + "bplist-parser": "0.1.1", + "plist": "2.0.1" + }, + "dependencies": { + "base64-js": { + "version": "1.1.2", + "bundled": true + }, + "plist": { + "version": "2.0.1", + "bundled": true, + "requires": { + "base64-js": "1.1.2", + "xmlbuilder": "8.2.2", + "xmldom": "0.1.27" + } + }, + "xmlbuilder": { + "version": "8.2.2", + "bundled": true + } + } + }, + "stream-buffers": { + "version": "2.2.0", + "bundled": true + }, + "tail": { + "version": "0.4.0", + "bundled": true + }, + "underscore": { + "version": "1.8.3", + "bundled": true + }, + "unorm": { + "version": "1.4.1", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "xcode": { + "version": "0.9.3", + "bundled": true, + "requires": { + "pegjs": "0.10.0", + "simple-plist": "0.2.1", + "uuid": "3.0.1" + } + }, + "xml-escape": { + "version": "1.1.0", + "bundled": true + }, + "xmlbuilder": { + "version": "4.0.0", + "bundled": true, + "requires": { + "lodash": "3.10.1" + } + }, + "xmldom": { + "version": "0.1.27", + "bundled": true + } + } + }, + "cordova-plugin-camera": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/cordova-plugin-camera/-/cordova-plugin-camera-2.4.1.tgz", + "integrity": "sha1-GV404S22TxxJ52T31OPyYao9lME=" + }, + "cordova-plugin-compat": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-compat/-/cordova-plugin-compat-1.1.0.tgz", + "integrity": "sha1-kr3xaf62EgUyPcyWo/+DSON+h/w=" + }, + "cordova-plugin-device": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/cordova-plugin-device/-/cordova-plugin-device-1.1.7.tgz", + "integrity": "sha1-/JQRG+aTJijGaGiTjd89yCyfv+Y=" + }, + "cordova-plugin-dialogs": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/cordova-plugin-dialogs/-/cordova-plugin-dialogs-1.3.4.tgz", + "integrity": "sha1-XMlm7nyZsvW1s934SQAmKLDacVc=" + }, + "cordova-plugin-file": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/cordova-plugin-file/-/cordova-plugin-file-4.3.3.tgz", + "integrity": "sha1-AS6Xqhr7kfhJFuY0G1SDZtI96bk=" + }, + "cordova-plugin-file-transfer": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/cordova-plugin-file-transfer/-/cordova-plugin-file-transfer-1.7.1.tgz", + "integrity": "sha1-p12L4uvDu5sjxbG70ZkhTsJnWGs=" + }, + "cordova-plugin-geolocation": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/cordova-plugin-geolocation/-/cordova-plugin-geolocation-2.4.3.tgz", + "integrity": "sha1-HdAAI9Hxos4zD4dn7Qj3tzj+cYQ=" + }, + "cordova-plugin-inappbrowser": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/cordova-plugin-inappbrowser/-/cordova-plugin-inappbrowser-1.7.2.tgz", + "integrity": "sha1-ZHY0lb6H6y562xoI8CCblupc7uA=" + }, + "cordova-plugin-media": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/cordova-plugin-media/-/cordova-plugin-media-2.4.1.tgz", + "integrity": "sha1-kEcG76alns95awXBar4o/Jic1Wk=" + }, + "cordova-plugin-media-capture": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cordova-plugin-media-capture/-/cordova-plugin-media-capture-1.4.3.tgz", + "integrity": "sha1-IOO74zT7uJ9iSwIYjqxTcnQ5IPo=" + }, + "cordova-plugin-network-information": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/cordova-plugin-network-information/-/cordova-plugin-network-information-1.3.4.tgz", + "integrity": "sha1-KPQmzCZZF03GIqBxgPm6DYVfc4M=" + }, + "cordova-plugin-splashscreen": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/cordova-plugin-splashscreen/-/cordova-plugin-splashscreen-3.2.2.tgz", + "integrity": "sha1-1WOL+wi9vJqz59owwhdL5kzwJ7A=" + }, + "cordova-plugin-statusbar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/cordova-plugin-statusbar/-/cordova-plugin-statusbar-2.4.1.tgz", + "integrity": "sha1-IiYop4qlTTTUySeZACDQo7rOVUU=" + }, + "cordova-plugin-whitelist": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/cordova-plugin-whitelist/-/cordova-plugin-whitelist-1.3.3.tgz", + "integrity": "sha1-tehezbv+Wu3tQKG/TuI3LmfZb7Q=" + }, + "ionic-plugin-keyboard": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ionic-plugin-keyboard/-/ionic-plugin-keyboard-2.1.0.tgz", + "integrity": "sha1-EOVAuYg9XHhuZXv/gXIDGmMtC2Q=" + } + } +} diff --git a/package.json-example b/package.json-example new file mode 100644 index 0000000..439a306 --- /dev/null +++ b/package.json-example @@ -0,0 +1,49 @@ +{ + "name": "org.mysociety.fixmystreet", + "version": "2.1", + "displayName": "FixMyStreet", + "cordova": { + "platforms": [ + "android", + "ios" + ], + "plugins": { + "cordova-plugin-camera": {}, + "cordova-plugin-compat": {}, + "cordova-plugin-device": {}, + "cordova-plugin-dialogs": {}, + "cordova-plugin-file": {}, + "cordova-plugin-file-transfer": {}, + "cordova-plugin-geolocation": {}, + "cordova-plugin-inappbrowser": {}, + "cordova-plugin-media": { + "KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE": "NO" + }, + "cordova-plugin-media-capture": {}, + "cordova-plugin-network-information": {}, + "cordova-plugin-splashscreen": {}, + "cordova-plugin-statusbar": {}, + "cordova-plugin-whitelist": {}, + "ionic-plugin-keyboard": {} + } + }, + "dependencies": { + "cordova-android": "^7.1.0", + "cordova-ios": "^4.5.4", + "cordova-plugin-camera": "^4.0.3", + "cordova-plugin-compat": "^1.2.0", + "cordova-plugin-device": "^2.0.2", + "cordova-plugin-dialogs": "^2.0.1", + "cordova-plugin-file": "^6.0.1", + "cordova-plugin-file-transfer": "^1.7.1", + "cordova-plugin-geolocation": "^4.0.1", + "cordova-plugin-inappbrowser": "^3.0.0", + "cordova-plugin-media": "^5.0.2", + "cordova-plugin-media-capture": "^3.0.2", + "cordova-plugin-network-information": "^2.0.1", + "cordova-plugin-splashscreen": "^5.0.2", + "cordova-plugin-statusbar": "^2.4.2", + "cordova-plugin-whitelist": "^1.3.3", + "ionic-plugin-keyboard": "^2.2.1" + } +}
\ No newline at end of file diff --git a/res/ios/icons/Icon-40.png b/res/ios/icons/Icon-40.png Binary files differindex 0f7be76..edaa501 100755 --- a/res/ios/icons/Icon-40.png +++ b/res/ios/icons/Icon-40.png diff --git a/res/ios/icons/Icon-40@2x.png b/res/ios/icons/Icon-40@2x.png Binary files differindex 5fbf89a..a4889cb 100755 --- a/res/ios/icons/Icon-40@2x.png +++ b/res/ios/icons/Icon-40@2x.png diff --git a/res/ios/icons/Icon-50.png b/res/ios/icons/Icon-50.png Binary files differindex d5ae255..d59ebcb 100755 --- a/res/ios/icons/Icon-50.png +++ b/res/ios/icons/Icon-50.png diff --git a/res/ios/icons/Icon-50@2x.png b/res/ios/icons/Icon-50@2x.png Binary files differindex 5727ab8..602ef2d 100755 --- a/res/ios/icons/Icon-50@2x.png +++ b/res/ios/icons/Icon-50@2x.png diff --git a/res/ios/icons/Icon-60.png b/res/ios/icons/Icon-60.png Binary files differindex 8670077..d4b20c3 100755 --- a/res/ios/icons/Icon-60.png +++ b/res/ios/icons/Icon-60.png diff --git a/res/ios/icons/Icon-60@2x.png b/res/ios/icons/Icon-60@2x.png Binary files differindex fe9f27b..047f807 100755 --- a/res/ios/icons/Icon-60@2x.png +++ b/res/ios/icons/Icon-60@2x.png diff --git a/res/ios/icons/Icon-60@3x.png b/res/ios/icons/Icon-60@3x.png Binary files differindex 3936bab..9f48cfc 100755 --- a/res/ios/icons/Icon-60@3x.png +++ b/res/ios/icons/Icon-60@3x.png diff --git a/res/ios/icons/Icon-72.png b/res/ios/icons/Icon-72.png Binary files differindex d4cbd78..8b9f485 100755 --- a/res/ios/icons/Icon-72.png +++ b/res/ios/icons/Icon-72.png diff --git a/res/ios/icons/Icon-72@2x.png b/res/ios/icons/Icon-72@2x.png Binary files differindex 99d7ccd..4e3af31 100755 --- a/res/ios/icons/Icon-72@2x.png +++ b/res/ios/icons/Icon-72@2x.png diff --git a/res/ios/icons/Icon-76.png b/res/ios/icons/Icon-76.png Binary files differindex 8aee2db..388029c 100755 --- a/res/ios/icons/Icon-76.png +++ b/res/ios/icons/Icon-76.png diff --git a/res/ios/icons/Icon-76@2x.png b/res/ios/icons/Icon-76@2x.png Binary files differindex c46bddb..0e27d9a 100755 --- a/res/ios/icons/Icon-76@2x.png +++ b/res/ios/icons/Icon-76@2x.png diff --git a/res/ios/icons/Icon-83.5@2x.png b/res/ios/icons/Icon-83.5@2x.png Binary files differnew file mode 100644 index 0000000..fe096a0 --- /dev/null +++ b/res/ios/icons/Icon-83.5@2x.png diff --git a/res/ios/icons/Icon-Small.png b/res/ios/icons/Icon-Small.png Binary files differindex 85f4601..e7ed4b3 100755 --- a/res/ios/icons/Icon-Small.png +++ b/res/ios/icons/Icon-Small.png diff --git a/res/ios/icons/Icon-Small@2x.png b/res/ios/icons/Icon-Small@2x.png Binary files differindex 08e9bcb..61a5dc4 100755 --- a/res/ios/icons/Icon-Small@2x.png +++ b/res/ios/icons/Icon-Small@2x.png diff --git a/res/ios/icons/Icon-Small@3x.png b/res/ios/icons/Icon-Small@3x.png Binary files differnew file mode 100644 index 0000000..f1dbf68 --- /dev/null +++ b/res/ios/icons/Icon-Small@3x.png diff --git a/res/ios/icons/Icon.png b/res/ios/icons/Icon.png Binary files differindex 465a1d1..31e72f8 100755 --- a/res/ios/icons/Icon.png +++ b/res/ios/icons/Icon.png diff --git a/res/ios/icons/Icon@2x.png b/res/ios/icons/Icon@2x.png Binary files differindex cd028d4..f3a67db 100755 --- a/res/ios/icons/Icon@2x.png +++ b/res/ios/icons/Icon@2x.png diff --git a/res/ios/icons/iTunesArtwork.png b/res/ios/icons/iTunesArtwork.png Binary files differindex 01a2163..30dac6d 100755 --- a/res/ios/icons/iTunesArtwork.png +++ b/res/ios/icons/iTunesArtwork.png diff --git a/res/ios/icons/iTunesArtwork@2x.png b/res/ios/icons/iTunesArtwork@2x.png Binary files differindex c166da1..0fc5d36 100755 --- a/res/ios/icons/iTunesArtwork@2x.png +++ b/res/ios/icons/iTunesArtwork@2x.png diff --git a/res/ios/splash/Default-1125@3x~iphonex-portrait_1125x2436.png b/res/ios/splash/Default-1125@3x~iphonex-portrait_1125x2436.png Binary files differnew file mode 100644 index 0000000..bdf6d12 --- /dev/null +++ b/res/ios/splash/Default-1125@3x~iphonex-portrait_1125x2436.png diff --git a/res/ios/splash/Default-1242@3x~iphone6s-portrait_1242x2208.png b/res/ios/splash/Default-1242@3x~iphone6s-portrait_1242x2208.png Binary files differindex e60610d..e60610d 100755..100644 --- a/res/ios/splash/Default-1242@3x~iphone6s-portrait_1242x2208.png +++ b/res/ios/splash/Default-1242@3x~iphone6s-portrait_1242x2208.png diff --git a/res/ios/splash/Default-568h@2x~iphone_640x1136.png b/res/ios/splash/Default-568h@2x~iphone_640x1136.png Binary files differindex a9acffc..a9acffc 100755..100644 --- a/res/ios/splash/Default-568h@2x~iphone_640x1136.png +++ b/res/ios/splash/Default-568h@2x~iphone_640x1136.png diff --git a/res/ios/splash/Default-750@2x~iphone6-portrait_750x1334.png b/res/ios/splash/Default-750@2x~iphone6-portrait_750x1334.png Binary files differindex 4926f98..4926f98 100755..100644 --- a/res/ios/splash/Default-750@2x~iphone6-portrait_750x1334.png +++ b/res/ios/splash/Default-750@2x~iphone6-portrait_750x1334.png diff --git a/res/ios/splash/Default-Portrait@2x~ipad_1536x2008.png b/res/ios/splash/Default-Portrait@2x~ipad_1536x2008.png Binary files differindex 6504a06..6504a06 100755..100644 --- a/res/ios/splash/Default-Portrait@2x~ipad_1536x2008.png +++ b/res/ios/splash/Default-Portrait@2x~ipad_1536x2008.png diff --git a/res/ios/splash/Default-Portrait@2x~ipad_1536x2048.png b/res/ios/splash/Default-Portrait@2x~ipad_1536x2048.png Binary files differindex 33a18fc..33a18fc 100755..100644 --- a/res/ios/splash/Default-Portrait@2x~ipad_1536x2048.png +++ b/res/ios/splash/Default-Portrait@2x~ipad_1536x2048.png diff --git a/res/ios/splash/Default-Portrait~ipad_768x1024.png b/res/ios/splash/Default-Portrait~ipad_768x1024.png Binary files differindex 20c5109..20c5109 100755..100644 --- a/res/ios/splash/Default-Portrait~ipad_768x1024.png +++ b/res/ios/splash/Default-Portrait~ipad_768x1024.png diff --git a/res/ios/splash/Default@2x~iphone_640x960.png b/res/ios/splash/Default@2x~iphone_640x960.png Binary files differindex facda8e..facda8e 100755..100644 --- a/res/ios/splash/Default@2x~iphone_640x960.png +++ b/res/ios/splash/Default@2x~iphone_640x960.png diff --git a/res/ios/splash/Default~iphone.png b/res/ios/splash/Default~iphone.png Binary files differindex 50741df..50741df 100755..100644 --- a/res/ios/splash/Default~iphone.png +++ b/res/ios/splash/Default~iphone.png diff --git a/scripts/prepare_index_html.js b/scripts/prepare_index_html.js index 379b0d9..f1aa5c5 100755 --- a/scripts/prepare_index_html.js +++ b/scripts/prepare_index_html.js @@ -20,7 +20,7 @@ module.exports = function(context) { var CONFIG = require("../www/js/config.js"); var files = [ - "platforms/android/assets/www/index.html", + "platforms/android/app/src/main/assets/www/index.html", "platforms/ios/www/index.html", ]; files.forEach(function(file) { diff --git a/templates/around.html b/templates/around.html index 6419d6c..d5ff4f3 100644 --- a/templates/around.html +++ b/templates/around.html @@ -33,6 +33,7 @@ <% } %> </form> <a data-role="none" id="relocate" class="nodisplay"></a> + <a data-role="none" id="hidepins" class="nodisplay"></a> <div class="nodisplay" id="front-howto"></div> </div> diff --git a/templates/details_extra.html b/templates/details_extra.html index 6ef02b3..22d18b2 100644 --- a/templates/details_extra.html +++ b/templates/details_extra.html @@ -1,7 +1,11 @@ <div data-role="header" data-position="fixed"> <a data-rel="back" data-icon="arrow-l" class="ui-btn-left">[% loc('Back') %]</a> <h1>[% loc('Further Details') %]</h1> - <a id="next" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">[% loc('Next') %]</a> + <% if (CONFIG.SKIP_CONFIRM_REPORT) { %> + <a id="next" class="ui-btn-right">[% loc('Submit') %]</a> + <% } else { %> + <a id="next" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">[% loc('Next') %]</a> + <% } %> </div> <div data-role="content" data-enhance="false"> <%= category_extras %> diff --git a/templates/existing.html b/templates/existing.html index 1c326b8..ef9effb 100644 --- a/templates/existing.html +++ b/templates/existing.html @@ -17,8 +17,8 @@ <p><% print( moment( created ).fromNow() ) %></p> </div> - <% if ( file ) { %> - <div class="photo" style="background-image: url(<%= file %>)"></div> + <% if ( files.length > 0 ) { %> + <div class="photo" style="background-image: url(<%= files[0] %>)"></div> <% } %> <% if ( details ) { %> diff --git a/templates/login.html b/templates/login.html index 43e93d9..0cc7c51 100644 --- a/templates/login.html +++ b/templates/login.html @@ -12,13 +12,9 @@ <input type="button" id="logout" name="logout" value="[% loc('Sign Out') %]" data-theme="a"> </div> </div> - <div id="password_row" class="nodisplay"> - <p class="notopmargin"> - [% loc('Signed out!') %] - </p> <% } else { %> <div id="password_row"> - <% } %> + <p class="notopmargin">[% loc('You are not signed in.') %]</p> <form name="signinForm" class="inputcard" id="signinForm"> <div> <input data-role="none" type="email" value="" name="email" id="form_email" placeholder="[% loc('Your Email') %]" required> @@ -31,13 +27,5 @@ </div> </form> </div> - <div id="success_row" class="nodisplay"> - <p class="notopmargin"> - [% loc('Signed in!') %] - </p> - <div id="err" class="nodisplay"></div> - <div class="bottom-btn"> - <input type="button" id="logout" name="logout" value="[% loc('Sign Out') %]" data-theme="a"> - </div> - </div> + <% } %> </div> diff --git a/templates/offline.html b/templates/offline.html index 432fb09..214170e 100644 --- a/templates/offline.html +++ b/templates/offline.html @@ -1,7 +1,7 @@ <div data-role="header" data-position="fixed"> <a id="offline-prev-btn" class="ui-btn-left">[% loc('Try Again') %]</a> <h1>[% loc('Offline') %]</h1> - <a id="offline-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">[% loc('Skip') %]</a> + <a id="offline-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right"><%= files.length == 0 ? "[% loc('Skip') %]" : "[% loc('Next') %]" %></a> </div> <div id="locating" class="nodisplay"> <div class="radar"></div> @@ -42,30 +42,25 @@ </div> <div> - <textarea data-role="none" rows="7" cols="26" name="detail" id="form_detail" placeholder="Please fill in details of the problem." required><%= details %></textarea> + <textarea data-role="none" rows="2" cols="26" name="detail" id="form_detail" placeholder="Please fill in details of the problem." required><%= details %></textarea> </div> </div> - <% if ( file != '' ) { %> - <div id="add_photo" style="display: none"> - <% } else { %> - <div id="add_photo"> + <% if ( files.length < CONFIG.MAX_PHOTOS ) { %> + <div id="add_photo"> + <input data-icon="fms-photo-new" value="[% loc('Take a new photo') %]" type="button" name="photo_button" id="id_photo_button" data-role="button" data-theme="a"> + <input data-icon="fms-photo-existing" value="[% loc('Add an existing photo') %]" type="button" name="existing" id="id_existing" data-role="button" data-theme="c"> + </div> <% } %> - <label>[% loc('Add a Photo ') %]<em>[% loc('(optional)') %]</em></label> - <input data-icon="fms-photo-new" value="[% loc('Take a new photo') %]" type="button" name="photo_button" id="id_photo_button" data-role="button" data-theme="a"> - <input data-icon="fms-photo-existing" value="[% loc('Add an existing photo') %]" type="button" name="existing" id="id_existing" data-role="button" data-theme="c"> - </div> - <% if ( file == '' ) { %> - <div id="display_photo" style="display: none"> - <% } else { %> - <div id="display_photo"> - <% } %> - <label>[% loc('Your Photo') %]</label> - <div class="photo"> - <img class="small" id="photo" src="<%= file %>" /> + <% if ( files.length > 0 ) { %> + <div class="photo-wrapper"> + <% _.each(files, function(file, index) { %> + <div class="photo" style="background-image: url('<%= file %>');"> + <a data-role="none" class="photo-corner-button del_photo_button" data-file-index="<%= index %>">X</a> + </div> + <% }); %> </div> - <input value="[% loc('Remove Photo') %]" type="button" name="del_photo_button" id="id_del_photo_button" data-role="button" data-theme="a"> - </div> + <% } %> </div> </div> diff --git a/templates/photo.html b/templates/photo.html index 286dac3..0646b4a 100644 --- a/templates/photo.html +++ b/templates/photo.html @@ -1,37 +1,49 @@ <div data-role="header"> <a data-rel="back" data-icon="arrow-l" class="ui-btn-left">[% loc('Back') %]</a> - <h1>[% loc('Add Photo') %]</h1> - <% if ( file == '' ) { %> - <a id="photo-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">[% loc('Skip') %]</a> + <h1>[% loc('Add Photos') %]</h1> + <% if ( files.length == 0 ) { %> + <% if ( !CONFIG.PHOTO_REQUIRED ) { %> + <a id="photo-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">[% loc('Skip') %]</a> + <% } %> <% } else { %> <a id="photo-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">[% loc('Next') %]</a> <% } %> </div> + <div data-role="content"> - <% var photoClass = 'nodisplay', nophotoClass = 'nodisplay'; - if ( file != '' ) { - photoClass = ''; - } else { - nophotoClass = ''; - } %> - <h2 id="nophoto_title" class="<% print( nophotoClass ); %>"> - [% loc('Add a Photo') %] <em>[% loc('(optional)') %]</em> - </h2> - <h2 id="photo_title" class="<% print( photoClass ); %>"> - [% loc('Photo for report') %] - </h2> - <div class="photo-wrapper"> - <div class="photo"> - <% if ( file == '' ) { %> - <img class="placeholder" src="images/placeholder-photo.png" alt="" id="photo"> + <% if ( files.length == 0 ) { %> + <h2 id="nophoto_title"> + [% loc('Add a Photo') %] + <% if ( !CONFIG.PHOTO_REQUIRED ) { %> + <em>[% loc('(optional)') %]</em> + <% } else if ( files.length == 0) { %> + <em>[% loc('(required)') %]</em> + <%} %> + </h2> <% } else { %> - <img class="small" id="photo" src="<%= file %>" /> + <h2 id="photo_title"> + [% loc('Photos for report') %] + </h2> <% } %> - <a data-role="none" id="id_del_photo_button" class="photo-corner-button">X</a> - </div> - </div> - <div class="bottom-btn"> - <input data-icon="fms-photo-existing" type="button" id="id_existing" data-theme="c" value="[% loc('Add an existing photo') %]" > - <input data-icon="fms-photo-new" id="id_photo_button" type="button" data-theme="a" value="[% loc('Take a new photo') %]"> + + <div class="photo-wrapper"> + <% if ( files.length == 0 ) { %> + <div class="photo"> + <img class="placeholder" src="images/placeholder-photo.png" alt=""> + </div> + <% } else { %> + <% _.each(files, function(file, index) { %> + <div class="photo" style="background-image: url('<%= file %>');"> + <a data-role="none" class="photo-corner-button del_photo_button" data-file-index="<%= index %>">X</a> + </div> + <% });%> + <% } %> </div> + + <% if ( files.length < CONFIG.MAX_PHOTOS ) { %> + <div class="bottom-btn"> + <input data-icon="fms-photo-existing" type="button" id="id_existing" data-theme="c" value="[% loc('Add an existing photo') %]" > + <input data-icon="fms-photo-new" id="id_photo_button" type="button" data-theme="a" value="[% loc('Take a new photo') %]"> + </div> + <% } %> </div> diff --git a/templates/reports.html b/templates/reports.html index e70dd3d..a08ffc6 100644 --- a/templates/reports.html +++ b/templates/reports.html @@ -15,7 +15,7 @@ if ( draft.get('lat') ) { classes.push('with_location'); } - if ( draft.get('file') ) { + if ( draft.get('files').length > 0 ) { classes.push('with_photo'); } var draftClass = ''; diff --git a/templates/strings.js b/templates/strings.js index 55f68d4..b7b2388 100644 --- a/templates/strings.js +++ b/templates/strings.js @@ -17,7 +17,10 @@ required: '[% loc('Please enter your email') %]', email: '[% loc('Please enter a valid email') %]' }, - password: '[% loc('Please enter a password') %]' + password: { + required: '[% loc('Please enter a password') %]', + short: '[% loc('Please enter a password at least %d characters long') %]' + } }, strings: { next: '[% loc('Next') %]', diff --git a/templates/submit_confirm.html b/templates/submit_confirm.html index bca55fc..1d3e862 100644 --- a/templates/submit_confirm.html +++ b/templates/submit_confirm.html @@ -3,7 +3,7 @@ <h1>[% loc('Your details') %]</h1> </div> -<div data-role="content"> +<div data-role="content"<% if (CONFIG.SKIP_CONFIRM_REPORT) { %> class="hidden"<% } %>> <p class="notopmargin"> [% tprintf(loc('You are logged in as %s'), '<%= user.email %>') %]. </p> diff --git a/templates/submit_set_password.html b/templates/submit_set_password.html index ee681cc..ef06040 100644 --- a/templates/submit_set_password.html +++ b/templates/submit_set_password.html @@ -12,7 +12,7 @@ </p> <div class="inputcard"> <div> - <input data-role="none" type="password" value="" name="password" id="form_password" placeholder="[% loc('Your password') %]" required> + <input data-role="none" type="password" value="<%= FMS.currentUser.get('password') %>" name="password" id="form_password" placeholder="[% loc('Your password') %]" required> </div> </div> <div class="noborder"> diff --git a/www/cobrands/cuidomiciudad/css/style.css b/www/cobrands/cuidomiciudad/css/style.css index b50f822..db3112f 100644 --- a/www/cobrands/cuidomiciudad/css/style.css +++ b/www/cobrands/cuidomiciudad/css/style.css @@ -1,11 +1,11 @@ /* line 8, style.scss */ -.ios7 .ui-bar-a, -body.ios7 { +.ios .ui-bar-a, +body.ios { background-color: #005cb9; } /* line 13, style.scss */ -.ios7 .ui-bar-a { +.ios .ui-bar-a { border-color: #005cb9; } diff --git a/www/cobrands/cuidomiciudad/css/style.scss b/www/cobrands/cuidomiciudad/css/style.scss index 3976a23..71cb081 100644 --- a/www/cobrands/cuidomiciudad/css/style.scss +++ b/www/cobrands/cuidomiciudad/css/style.scss @@ -5,12 +5,12 @@ $color-cmc-yellow: #eed11c; $color-cmc-green: #00ad68; $text-color: #eee; -.ios7 .ui-bar-a, -body.ios7 { +.ios .ui-bar-a, +body.ios { background-color: $color-cmc-blue; } -.ios7 .ui-bar-a { +.ios .ui-bar-a { border-color: $color-cmc-blue; } diff --git a/www/cobrands/makemyisland/css/style.css b/www/cobrands/makemyisland/css/style.css index e0ee20c..106d6e0 100644 --- a/www/cobrands/makemyisland/css/style.css +++ b/www/cobrands/makemyisland/css/style.css @@ -1,8 +1,8 @@ -.ios7 .ui-bar-a, -body.ios7 { +.ios .ui-bar-a, +body.ios { background-color: #9fd9ea; } -.ios7 .ui-bar-a { +.ios .ui-bar-a { border-color: #9fd9ea; } #load-screen, diff --git a/www/cobrands/makemyisland/css/style.scss b/www/cobrands/makemyisland/css/style.scss index ea8c43c..195d7f1 100644 --- a/www/cobrands/makemyisland/css/style.scss +++ b/www/cobrands/makemyisland/css/style.scss @@ -3,12 +3,12 @@ $yellow: rgb(249, 255, 198); $blue: rgb(159, 217, 234); -.ios7 .ui-bar-a, -body.ios7 { +.ios .ui-bar-a, +body.ios { background-color: $blue; } -.ios7 .ui-bar-a { +.ios .ui-bar-a { border-color: $blue; } diff --git a/www/css/fms.css b/www/css/fms.css index aa93acc..d800619 100644 --- a/www/css/fms.css +++ b/www/css/fms.css @@ -160,38 +160,37 @@ background-color: rgba(0,0,0,0); } -/* ios 7 tweaks to deal with different status bar behaviour */ - .ios7 .ui-mobile-viewport { +/* ios tweaks to deal with different status bar behaviour */ + .ios .ui-mobile-viewport { background-color: none; background-image: none; } - .ios7 .ui-bar-a, - body.ios7 { + .ios .ui-bar-a, + body.ios { background-color: #F6BE41; background-image: none; } - .ios7 .ui-bar-a { + .ios .ui-bar-a { border-color: #F6BE41; } - .ios7 .ui-header-fixed .ui-btn-left, - .ios7 .ui-header-fixed .ui-btn-right { + .ios .ui-header-fixed .ui-btn-left, + .ios .ui-header-fixed .ui-btn-right { top: 4px; } - .ios7 #map_box { - top: 20px; + .ios #map_box { background-color: #534741; background-image: url('../images/mapbg.png'); } - .ios7 #around-page #locating { - bottom: -22px; + .ios #around-page #locating { + bottom: -2px; } - .ios7 #map_box .olLayerDiv div { + .ios #map_box .olLayerDiv div { display: block !important; } @@ -210,7 +209,7 @@ #drafts, div[data-role='content'] { position: relative; - margin: 20px 10px; + margin: 10px 10px 20px 10px; background-color: rgba(255, 255, 255, 0.9); } @@ -401,6 +400,10 @@ text-align: center; } + #offline textarea { + min-height: 0; + } + /* home screen */ #front-page div[data-role='content'] { @@ -424,6 +427,9 @@ -webkit-filter: blur(3px); opacity: 0.9; } + #map_box.background-map .olControlAttribution{ + display: none; + } #map{ opacity: 0.85; @@ -592,19 +598,31 @@ } - #relocate { - display: block; + #relocate, #hidepins { + display: inline-block; height: 30px; width: 30px; padding: 5px; margin: 0px; background-color: rgba(248,248,248,0.9); + } + + #relocate { background-image: url(../images/location@x2.png); background-repeat: no-repeat; background-position: center center; background-size: 50%; } + #hidepins { + background-image: url(../images/hide-pins-link.png); + background-image: url(../images/hide-pins-link.svg); + background-repeat: no-repeat; + background-position: center center; + background-size: 80%; + margin-left: .3em; + } + #pc { padding-left: 0.2em; border-left: 1px solid #F6BE41; @@ -726,11 +744,29 @@ div.photo { position: relative; display: inline-block; + background-size: contain; + background-repeat: no-repeat; + background-position: top center; + width: 100%; + margin: 0 0 1em 0; + -webkit-box-flex: 1; + flex: 1; + } + + div.photo:nth-child(3) { + margin-bottom: 0; } div.photo-wrapper { margin: auto; text-align: center; + display: -webkit-box; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + -webkit-box-align: center; + align-items: center; } img.small { @@ -744,9 +780,9 @@ width: 270px; } - #id_del_photo_button.photo-corner-button { + .del_photo_button.photo-corner-button { position: absolute; - bottom: -15px; + top: -15px; right: 0px; -webkit-border-radius: 50%; border-radius: 50%; @@ -758,6 +794,12 @@ font-size: 30px; } + /* On the offline page the UI is ordered differently, so we want + to stop the button overlapping the 'add another photo' butto' */ + #offline .del_photo_button.photo-corner-button { + top: 0; + } + @media only screen and (max-width: 800px) and (min-width: 600px) { img.placeholder { height: 398px; @@ -814,6 +856,11 @@ } #category_meta { margin: 0px 15px 15px 15px; + padding: 1em 0; + } + + #category_meta h2 { + line-height: 1em; } #category_meta h4 { @@ -931,3 +978,28 @@ top: 5px; right: 5px; } + + .ui-content.hidden { + visibility: hidden; + } + +/* iPhone X has an 'unsafe' area occupied by the home +indicator at the bottom of screen, which we need to stop UI controls +clashing with. */ +body.iphone-x { + /* iOS 11–11.1 use constant(), 11.2 uses env() - assign whatever one is valid + to a CSS variable as this makes things simpler (e.g. with calc()) */ + --safe-area-inset-bottom: constant(safe-area-inset-bottom); + --safe-area-inset-bottom: env(safe-area-inset-bottom); +} + +/* The OpenLayers attribution control */ +body.iphone-x .olControlAttribution { + padding-bottom: var(--safe-area-inset-bottom); +} + +/* The "new report here" button */ +body.iphone-x .map-bottom-btn { + /* Can't use env() inside calc(), so assign value to a CSS variable instead */ + bottom: calc(20px + var(--safe-area-inset-bottom)); +} diff --git a/www/images/hide-pins-link.png b/www/images/hide-pins-link.png Binary files differnew file mode 100644 index 0000000..d2b48ac --- /dev/null +++ b/www/images/hide-pins-link.png diff --git a/www/images/hide-pins-link.svg b/www/images/hide-pins-link.svg new file mode 100644 index 0000000..1c8ed76 --- /dev/null +++ b/www/images/hide-pins-link.svg @@ -0,0 +1 @@ +<svg width="36" height="36" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><g fill="#000" fill-rule="evenodd"><path d="M25.4 21.1l-3.7-3.6A4 4 0 0 0 18 12a4 4 0 0 0-1.5.3L13 8.6a9 9 0 0 1 12.5 12.5zm-4.5 4l-3 6-3.4-6.8a9 9 0 0 1-5.2-10.7L21 25.2zM7.4 6L30 28.6 28.6 30 6 7.4z"/></g></svg>
\ No newline at end of file diff --git a/www/index.html b/www/index.html index aae1e71..b60ab56 100644 --- a/www/index.html +++ b/www/index.html @@ -8,7 +8,7 @@ --> <head> <title></title> - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" /> <meta charset="utf-8"> <script type="text/javascript" src="jslib/jquery-1.9.1.min.js"></script> diff --git a/www/js/app.js b/www/js/app.js index 282e921..8b7432c 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -100,6 +100,11 @@ var tpl = { printDebug: function(msg) { if ( CONFIG.DEBUG ) { console.log(msg); + + // Some messages get logged before we've had a chance to + // attach the debugger, so keep them all for later reference. + FMS.debug_messages = FMS.debug_messages || []; + FMS.debug_messages.push(msg); } }, @@ -113,21 +118,28 @@ var tpl = { }, checkLoggedInStatus: function() { + var p = $.Deferred(); + if ( FMS.isOffline ) { + p.resolve(); } else { $.ajax( { url: CONFIG.FMS_URL + '/auth/ajax/check_auth', type: 'GET', dataType: 'json', timeout: 30000, - success: function( data, status ) { - FMS.isLoggedIn = 1; - }, - error: function() { - FMS.isLoggedIn = 0; - } - } ); + }) + .done(function() { + FMS.isLoggedIn = 1; + p.resolve(); + }) + .fail(function() { + FMS.isLoggedIn = 0; + p.resolve(); + }) } + + return p; }, saveCurrentDraft: function(force) { @@ -151,12 +163,12 @@ var tpl = { removeDraft: function(draftID, removePhoto) { var draft = FMS.allDrafts.get(draftID); - var uri = draft.get('file'); + var files = draft.get('files'); FMS.allDrafts.remove(draft); draft.destroy(); - if ( removePhoto && uri ) { - return FMS.files.deleteURI( uri ); + if ( removePhoto && files.length ) { + return FMS.files.deleteURIs( files ); } var p = $.Deferred(); p.resolve(); @@ -266,6 +278,13 @@ var tpl = { cordova.plugins.Keyboard.hideKeyboardAccessoryBar(false); } $('#load-screen').height( $(window).height() ); + + // Rough-and-ready iPhone X detection so CSS can stop things + // obscuring the home indicator at the bottom of the screen. + if (window.screen.width == 375 && window.screen.height == 812) { + $("body").addClass("iphone-x"); + } + FMS.initialized = 1; if ( navigator && navigator.splashscreen ) { navigator.splashscreen.hide(); @@ -283,11 +302,7 @@ var tpl = { if ( typeof device !== 'undefined' && device.platform === 'iOS' ) { var model = parseInt(device.model.replace('iPhone',''), 10); FMS.iPhoneModel = model; - - // fix overlap of status bar in ios7 - if (parseFloat(window.device.version) >= 7.0) { - $('body').addClass('ios7'); - } + $('body').addClass('ios'); } _.extend(FMS, { @@ -306,9 +321,6 @@ var tpl = { } FMS.windowHeight = $(window).height(); - if ( $('body').hasClass('ios7') ) { - FMS.windowHeight -= 20; - } if ( localStorage.usedBefore ) { FMS.usedBefore = 1; @@ -327,23 +339,37 @@ var tpl = { $(document).on('ajaxStart', function() { $.mobile.loading('show'); } ); $(document).on('ajaxStop', function() { $.mobile.loading('hide'); } ); - $('#display-help').on('vclick', function(e) { FMS.helpShow(e); } ); + $('#display-help').on('vclick', function(e) { + // Avoid a problem with input cursors being visible through + // the help layer on Web View, by unfocusing the element + if (device.platform === 'iOS') { + $('input').blur(); + } + + FMS.helpShow(e); + }); + $('#dismiss').on('vclick', function(e) { FMS.helpHide(e); } ); FMS.allDrafts.comparator = function(a,b) { var a_date = a.get('created'), b_date = b.get('created'); return a_date === b_date ? 0 : a_date < b_date ? 1 : -1; }; FMS.allDrafts.fetch(); FMS.checkOnlineStatus(); FMS.loadCurrentDraft(); - FMS.checkLoggedInStatus(); - FMS.setupHelp(); - - Backbone.history.start(); - if ( navigator && navigator.splashscreen ) { - navigator.splashscreen.hide(); - } else { - $('#load-screen').hide(); - } - $('#display-help').show(); + FMS.checkLoggedInStatus().done(function() { + if (!CONFIG.HELP_DISABLED) { + FMS.setupHelp(); + } + + Backbone.history.start(); + if ( navigator && navigator.splashscreen ) { + navigator.splashscreen.hide(); + } else { + $('#load-screen').hide(); + } + if (!CONFIG.HELP_DISABLED) { + $('#display-help').show(); + } + }); }); } }); diff --git a/www/js/config.js-example b/www/js/config.js-example index 2fceda7..1f1eef1 100644 --- a/www/js/config.js-example +++ b/www/js/config.js-example @@ -43,7 +43,50 @@ var CONFIG = { image_svg: 'images/pin.svg', background_svg: 'images/pin_shadow.svg' } - } + }, + + // Set this to true if you want to disable the help button on the right hand + // side of the screen. NB you'll also need to hide #display-help and #help + // elements in your CSS. + HELP_DISABLED: false, + + // Set this to true if the user must provide at least one photo when making + // a report. If this is true the 'skip' button on the photo page is removed + // and 'next' doesn't appear until at least one photo is attached. + PHOTO_REQUIRED: false, + + // The maximum number of photos the user can attach to a report. + MAX_PHOTOS: 3, + + // If this is true then the user must login as the first step after + // installing the app, and before making any reports. + LOGIN_REQUIRED: false, + + // The ratio of the data bounds to the viewport bounds (in each dimension). + // See http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/Strategy/BBOX-js.html + MAP_LOADING_RATIO: 2, + + // If the user is logged in and this setting is true, the 'Your details' + // page is skipped and the report is sent immediately after the report + // details have been entered. + SKIP_CONFIRM_REPORT: false, + + // You can optionally enforce a minimum password length if the user is + // registering an account when submitting a report. This should match the + // same minimum length required by your FixMyStreet server. + // Set this to 0 if you wish to disable this check. NB: If the check is + // active on the server the user's password may still be rejected if it's + // too short. + PASSWORD_MIN_LENGTH: 6, + + // FMS provides a mechanism for rejecting passwords that are too common. + // Set this flag to true if the password should be checked against the + // server when a user registers an account via the app. + // NB: If this flag is false here but the check is active on the FMS server, + // common passwords will still be rejected at the point the report is sent + // from the app to the server - which may be a large POST if the report has + // photos attached. + PASSWORD_CHECK_COMMON: true }; diff --git a/www/js/files.js b/www/js/files.js index eea38c3..0725323 100644 --- a/www/js/files.js +++ b/www/js/files.js @@ -57,6 +57,16 @@ }, + deleteURIs: function(uris) { + console.log("deleteURIs", uris); + var deferred = $.Deferred(); + deferred.resolve(); + uris.forEach(function(uri) { + deferred = deferred.then(FMS.files.deleteURI(uri)); + }); + return deferred; + }, + // Delete a file from the filesystem deleteFile: function (path) { @@ -124,7 +134,7 @@ function moveFile (src, dest, newName) { - FMS.printDebug( 'moveing file ' + src.fullPath + ' to ' + dest.fullPath ); + FMS.printDebug( 'moving file ' + src.fullPath + ' to ' + dest.fullPath ); var move = $.Deferred(); @@ -166,7 +176,7 @@ var file = $.Deferred(); - window.resolveLocalFileSystemURI( uri, file.resolve, file.reject); + window.resolveLocalFileSystemURL( uri, file.resolve, file.reject); return file.promise(); } diff --git a/www/js/map-OpenLayers.js b/www/js/map-OpenLayers.js index d458641..1de220d 100644 --- a/www/js/map-OpenLayers.js +++ b/www/js/map-OpenLayers.js @@ -128,7 +128,7 @@ function fixmystreet_onload() { fixmystreet.map.addLayer(fixmystreet.report_location); if (fixmystreet.page == 'around') { - fixmystreet.bbox_strategy = new OpenLayers.Strategy.BBOX({ ratio: 1 }); + fixmystreet.bbox_strategy = new OpenLayers.Strategy.BBOX({ ratio: CONFIG.MAP_LOADING_RATIO }); pin_layer_options.strategies = [ fixmystreet.bbox_strategy ]; pin_layer_options.protocol = new OpenLayers.Protocol.HTTP({ url: CONFIG.FMS_URL + '/ajax', @@ -202,13 +202,9 @@ OpenLayers.Map.prototype.getCurrentSize = function() { function show_map(event) { if (typeof fixmystreet !== 'undefined' && fixmystreet.page == 'around') { // Immediately go full screen map if on around page - var mapTop = 0; - if ( $('body').hasClass('ios7') ) { - mapTop = 20; - } $('#map_box').css({ position: 'fixed', - top: mapTop, left: 0, right: 0, bottom: 0, + top: 0, left: 0, right: 0, bottom: 0, height: FMS.windowHeight, margin: 0 }); @@ -408,7 +404,7 @@ OpenLayers.Control.ActionAfterDrag = OpenLayers.Class(OpenLayers.Control, { if ( $('#confirm-map').css('display') == 'block' ) { $('#reposition').show(); } else { - $('#relocate').show(); + $('#relocate, #hidepins').removeClass('nodisplay'); $('#front-howto').hide(); } } diff --git a/www/js/models/draft.js b/www/js/models/draft.js index 56d8111..e517f1b 100644 --- a/www/js/models/draft.js +++ b/www/js/models/draft.js @@ -3,17 +3,19 @@ Draft: Backbone.Model.extend({ localStorage: new Backbone.LocalStorage(CONFIG.NAMESPACE + '-drafts'), - defaults: { - lat: 0, - lon: 0, - title: '', - details: '', - may_show_name: '', - category: '', - phone: '', - pc: '', - file: '', - created: moment.utc() + defaults: function() { + return { + lat: 0, + lon: 0, + title: '', + details: '', + may_show_name: '', + category: '', + phone: '', + pc: '', + files: [], + created: moment.utc() + }; }, description: function() { @@ -33,7 +35,7 @@ this.get('title') || this.get('details') || this.get('category') || - this.get('file') + this.get('files').length ) { return true; } diff --git a/www/js/models/report.js b/www/js/models/report.js index 1a72e73..6926980 100644 --- a/www/js/models/report.js +++ b/www/js/models/report.js @@ -3,16 +3,18 @@ Report: Backbone.Model.extend({ urlRoot: CONFIG.FMS_URL + '/report/ajax', - defaults: { - lat: 0, - lon: 0, - title: '', - details: '', - may_show_name: '', - category: '', - phone: '', - pc: '', - file: '' + defaults: function() { + return { + lat: 0, + lon: 0, + title: '', + details: '', + may_show_name: '', + category: '', + phone: '', + pc: '', + files: [] + }; }, sync: function(method, model, options) { @@ -51,6 +53,74 @@ return false; }, + _readFileAsBase64String: function(file, success, error) { + return this._readFileAsBinaryString(file, function(data) { + var b64 = btoa(data); + success(b64); + }, error); + }, + + _readFileAsBinaryString: function(file, success, error) { + var reader = new FileReader(); + reader.onloadend = function() { + success(this.result); + }; + reader.onerror = error; + return reader.readAsBinaryString(file); + }, + + _getParamName: function(field, encoding, length) { + // The FileTransfer plugin technically only supports a single + // file in each upload. However, we can force other files to + // be added with a little workaround. + // FileTransfer allows extra parameters to be sent with the + // HTTP POST request, each of which is its own part of the + // multipart-encoded request. + // For a part to be treated as a file by the backend we need + // to provide a 'filename' value in the Content-Disposition + // header. The FileTransfer code doesn't escape the names of + // extra POST parameters[0][1], so we can take advantage of this + // and essentially inject our own header lines and filename + // value with a carefully-crafted HTTP POST field name that's + // passed to FileTransfer.upload. + // FIXME: This is basically a hack, and needs a better + // solution at some point. + // [0]: https://github.com/apache/cordova-plugin-file-transfer/blob/49c21f951f51381d887646b38823222ed11c60c1/src/ios/CDVFileTransfer.m#L208 + // [1]: https://github.com/apache/cordova-plugin-file-transfer/blob/49c21f951f51381d887646b38823222ed11c60c1/src/android/FileTransfer.java#L369 + var name = field + '"; filename="' + field + '.jpg"\r\n'; + name += "Content-Type: image/jpeg\r\n"; + name += "Content-Transfer-Encoding: " + encoding + "\r\n"; + name += "Content-Length: " + length + "\r\n"; + name += 'X-Ignore-This-Header: "'; // to close the open quotes + return name; + }, + + _addExtraPhotos: function(files, options, success, error) { + var photos = []; + for (var i = 0; i < files.length; i++) { + var uri = files[i]; + photos.push({field: "photo"+(i+2), uri: uri}); + } + this._addNextExtraPhoto(photos, options, success, error); + }, + + _addNextExtraPhoto: function(photos, options, success, error) { + var photo = photos.shift(); + if (photo === undefined) { + success(); + return; + } + var self = this; + resolveLocalFileSystemURL(photo.uri, function(fileentry) { + fileentry.file(function(file) { + self._readFileAsBase64String(file, function(data) { + options.params[self._getParamName(photo.field, "base64", data.length)] = data; + self._addNextExtraPhoto(photos, options, success, error); + }, error); + }, error); + }, error); + }, + post: function(model,options) { var params = { @@ -86,7 +156,7 @@ } var that = this; - if ( model.get('file') && model.get('file') !== '' ) { + if ( model.get('files') && model.get('files').length > 0 ) { var fileUploadSuccess = function(r) { FMS.uploading = false; $.mobile.loading('hide'); @@ -122,7 +192,8 @@ } }; - fileURI = model.get('file'); + var files = model.get('files').slice(); + fileURI = files.shift(); var fileOptions = new FileUploadOptions(); fileOptions.fileKey="photo"; @@ -169,14 +240,25 @@ uploadPcnt++; } }; - $.mobile.loading('show', { - text: FMS.strings.photo_loading, - textVisible: true, - html: '<span class="ui-icon ui-icon-loading"></span><h1>' + FMS.strings.photo_loading + '</h1><span id="progress"></span>' - }); - window.setTimeout( checkUpload, 15000 ); - FMS.uploading = true; - ft.upload(fileURI, CONFIG.FMS_URL + "/report/new/mobile", fileUploadSuccess, fileUploadFail, fileOptions); + + // If file2 or file3 have been set on this model we need to + // add the photos to the file upload request manually + // as FileTransfer only supports a single file upload. + that._addExtraPhotos( + files, + fileOptions, + function() { + $.mobile.loading('show', { + text: FMS.strings.photo_loading, + textVisible: true, + html: '<span class="ui-icon ui-icon-loading"></span><h1>' + FMS.strings.photo_loading + '</h1><span id="progress"></span>' + }); + window.setTimeout( checkUpload, 15000 ); + FMS.uploading = true; + ft.upload(fileURI, CONFIG.FMS_URL + "/report/new/mobile", fileUploadSuccess, fileUploadFail, fileOptions); + }, + fileUploadFail + ); }; setupChecker(); } else { diff --git a/www/js/router.js b/www/js/router.js index c691abb..2a23708 100644 --- a/www/js/router.js +++ b/www/js/router.js @@ -159,7 +159,7 @@ // any transitions as they just add visual distraction to no end // likewise displaying the offline page var options = { changeHash: false }; - if ( !this.currentView || this.currentView.id == 'front-page' || view.id == 'offline' ) { + if ( !this.currentView || this.currentView.id == 'front-page' || view.id == 'offline' || view.id === this.currentView.id) { options.transition = 'none'; } if ( this.reverse ) { diff --git a/www/js/strings.en.js b/www/js/strings.en.js index 1dabd30..bf67b14 100644 --- a/www/js/strings.en.js +++ b/www/js/strings.en.js @@ -17,7 +17,10 @@ required: 'Please enter your email', email: 'Please enter a valid email' }, - password: 'Please enter a password' + password: { + required: 'Please enter a password', + short: 'Please enter a password at least %d characters long' + } }, strings: { next: 'Next', diff --git a/www/js/strings.es.js b/www/js/strings.es.js index 9c8b950..5b2481b 100644 --- a/www/js/strings.es.js +++ b/www/js/strings.es.js @@ -17,7 +17,10 @@ required: 'Por favor, introduzca su email', email: 'Por favor, introduzca un email válido' }, - password: 'Por favor, introduzca la contraseña' + password: { + required: 'Por favor, introduzca la contraseña', + short: 'Please enter a password at least %d characters long' + } }, strings: { next: 'Siguiente', diff --git a/www/js/views/around.js b/www/js/views/around.js index f0b8692..4afc5bb 100644 --- a/www/js/views/around.js +++ b/www/js/views/around.js @@ -15,6 +15,7 @@ 'vclick .ui-input-clear': 'clearSearchErrors', 'blur #pc': 'clearSearchErrors', 'vclick #relocate': 'centerMapOnPosition', + 'vclick #hidepins': 'toggleMarkersVisibility', 'vclick #cancel': 'onClickCancel', 'vclick #confirm-map': 'onClickReport', 'vclick #mark-here': 'onClickMark', @@ -45,7 +46,7 @@ $('#view-my-reports').hide(); $('#login-options').hide(); $('#postcodeForm').hide(); - $('#relocate').hide(); + $('#relocate, #hidepins').addClass("nodisplay"); $('#cancel').hide(); $('#map_box').removeClass('background-map'); this.fixPageHeight(); @@ -101,7 +102,7 @@ }, gotLocation: function( info ) { - $('#relocate').show(); + $('#relocate, #hidepins').removeClass("nodisplay"); this.finishedLocating(); this.listenTo(FMS.locator, 'gps_current_position', this.positionUpdate); @@ -121,7 +122,7 @@ positionUpdate: function( info ) { if ( $('#front-howto').is(':hidden') ) { - $('#relocate').show(); + $('#relocate, #hidepins').removeClass("nodisplay"); } FMS.currentPosition = info.coordinates; var centre = this.projectCoords( info.coordinates ); @@ -182,7 +183,7 @@ msg = FMS.strings.location_problem; } if ( !fixmystreet.map ) { - $('#relocate').hide(); + $('#relocate, #hidepins').addClass("nodisplay"); $('#mark-here').hide(); // if we are going to display the help then we don't want to focus on // the search box as it will show through the help @@ -197,7 +198,7 @@ }, displayHelpIfFirstTime: function() { - if ( !FMS.usedBefore ) { + if ( !FMS.usedBefore && !CONFIG.HELP_DISABLED ) { FMS.helpShow(); } }, @@ -267,7 +268,6 @@ onClickCancel: function(e) { e.preventDefault(); - fixmystreet.markers.removeAllFeatures(); fixmystreet_activate_drag(); // force pins to be refetched and displayed fixmystreet.bbox_strategy.update({force: true}); @@ -352,7 +352,7 @@ }, goAddress: function(e) { - $('#relocate').show(); + $('#relocate, #hidepins').removeClass("nodisplay"); $('#front-howto').html('').hide(); var t = $(e.target); var lat = t.attr('data-lat'); @@ -371,7 +371,7 @@ $('#pc').attr('placeholder', msg).addClass('error');; } else { $('#front-howto').html(msg); - $('#relocate').hide(); + $('#relocate, #hidepins').addClass("nodisplay"); $('#front-howto').show(); } }, @@ -380,7 +380,7 @@ $('#pc').attr('placeholder', this.origPcPlaceholder).removeClass('error');; if ( fixmystreet.map ) { $('#front-howto').hide(); - $('#relocate').show(); + $('#relocate, #hidepins').removeClass("nodisplay"); } }, @@ -400,7 +400,7 @@ } $('#front-howto').html('<p>' + FMS.strings.multiple_matches + '</p><ul data-role="listview" data-inset="true">' + multiple + '</ul>'); $('.ui-page').trigger('create'); - $('#relocate').hide(); + $('#relocate, #hidepins').addClass("nodisplay"); $('#front-howto').show(); } else { this.searchError( FMS.strings.location_problem ); @@ -410,9 +410,7 @@ pauseMap: function() { this.stopListening(FMS.locator); FMS.locator.stopTracking(); - if ( FMS.iPhoneModel > 3 ) { - $('#map_box').addClass('background-map'); - } + $('#map_box').addClass('background-map'); $('#map_box').off('touchend'); if ( fixmystreet.map ) { fixmystreet.nav.deactivate(); @@ -442,7 +440,7 @@ e.preventDefault(); if ( !fixmystreet.map ) { this.$('#mark-here').hide(); - this.$('#relocate').hide(); + this.$('#relocate, #hidepins').addClass("nodisplay"); $('#front-howto').html('<p>' + FMS.strings.locate_dismissed + '</p>'); $('#front-howto').show(); } @@ -497,6 +495,11 @@ ); return centre; + }, + + toggleMarkersVisibility: function(e) { + e.preventDefault(); + fixmystreet.markers.setVisibility(!fixmystreet.markers.getVisibility()); } }) }); diff --git a/www/js/views/details.js b/www/js/views/details.js index 9f03d58..84b6ac2 100644 --- a/www/js/views/details.js +++ b/www/js/views/details.js @@ -18,6 +18,16 @@ 'blur input': 'updateCurrentReport' }, + initialize: function() { + var that = this; + window.addEventListener('native.keyboardshow', function(e) { + that.fixDetailTextAreaHeight(e.keyboardHeight); + }); + window.addEventListener('native.keyboardhide', function(e) { + that.fixDetailTextAreaHeight(); + }); + }, + afterRender: function() { this.$('#form_category').attr('data-role', 'none'); @@ -28,13 +38,18 @@ }, - beforeDisplay: function() { - this.fixPageHeight(); + beforeDisplay: function(extra) { + this.fixDetailTextAreaHeight(); + }, + + fixDetailTextAreaHeight: function(extra) { + extra = extra || 0; + this.fixPageHeight(extra); var header = this.$("div[data-role='header']:visible"), detail = this.$('#form_detail'), top = detail.position().top, viewHeight = $(window).height(), - contentHeight = viewHeight - header.outerHeight() + 15; + contentHeight = viewHeight - header.outerHeight() + 15 - extra; detail.height( contentHeight - top ); }, @@ -87,8 +102,18 @@ timeout: 30000, success: function( data, status ) { if ( data && data.category_extra && data.category_extra.length > 0 ) { - that.model.set('category_extras', data.category_extra); - that.navigate('details_extra'); + // Some categories have only hidden fields - in that case we + // don't want to navigate to the details_extra view. + var all_hidden = data.category_extra_json.reduce(function(accumulator, field) { + return accumulator && (field.automated === "hidden_field"); + }, true); + + if (all_hidden) { + that.navigate( that.next ); + } else { + that.model.set('category_extras', data.category_extra); + that.navigate('details_extra'); + } } else { that.navigate( that.next ); } diff --git a/www/js/views/details_extra.js b/www/js/views/details_extra.js index 160ff11..e5f63ba 100644 --- a/www/js/views/details_extra.js +++ b/www/js/views/details_extra.js @@ -19,15 +19,19 @@ afterRender: function() { this.populateFields(); + this.enableScrolling(); }, - onClickButtonPrev: function() { + onClickButtonPrev: function(e) { + e.preventDefault(); + this.disableScrolling(); this.model.set('hasExtras', 0); this.updateCurrentReport(); this.navigate( this.prev, true ); }, onClickButtonNext: function() { + this.disableScrolling(); this.clearValidationErrors(); var valid = 1; var that = this; @@ -49,6 +53,8 @@ this.clearValidationErrors(); this.updateCurrentReport(); this.navigate( this.next ); + } else { + this.enableScrolling(); } }, diff --git a/www/js/views/fms.js b/www/js/views/fms.js index 6ff569b..ae1174f 100644 --- a/www/js/views/fms.js +++ b/www/js/views/fms.js @@ -2,7 +2,7 @@ _.extend( FMS, { FMSView: Backbone.View.extend({ tag: 'div', - bottomMargin: 20, + bottomMargin: 0, contentSelector: '[data-role="content"]', events: { @@ -45,12 +45,21 @@ return this; }, - fixPageHeight: function() { + fixPageHeight: function(extra) { + extra = extra || 0; var header = this.$("div[data-role='header']:visible"), content = this.$(this.contentSelector), top = content.position().top, viewHeight = $(window).height(), - contentHeight = FMS.windowHeight - header.outerHeight() - this.bottomMargin; + contentHeight = FMS.windowHeight - header.outerHeight() - this.bottomMargin - extra; + + if ($("body").hasClass("iphone-x")) { + var body = $("body").get(0); + var inset = window.getComputedStyle(body).getPropertyValue("--safe-area-inset-bottom"); + // We want the pixel value, not the CSS string + inset = parseInt(inset.replace(/[^\d]*/g, '')); + contentHeight -= inset; + } this.setHeight( content, contentHeight - top ); }, @@ -107,7 +116,20 @@ $('.form-error').removeClass('form-error'); }, - destroy: function() { FMS.printDebug('destory for ' + this.id); this._destroy(); this.remove(); }, + disableScrolling: function() { + if ( typeof cordova !== 'undefined' ) { + cordova.plugins.Keyboard.disableScroll(true); + $('body').scrollTop(0); + } + }, + + enableScrolling: function() { + if ( typeof cordova !== 'undefined' ) { + cordova.plugins.Keyboard.disableScroll(false); + } + }, + + destroy: function() { FMS.printDebug('destroy for ' + this.id); this._destroy(); this.remove(); }, _destroy: function() {} }) diff --git a/www/js/views/home.js b/www/js/views/home.js index 998af1c..d82b874 100644 --- a/www/js/views/home.js +++ b/www/js/views/home.js @@ -24,11 +24,14 @@ afterDisplay: function() { $('#load-screen').hide(); + if ( FMS.isOffline ) { this.navigate( 'offline' ); + } else if ( !FMS.isLoggedIn && CONFIG.LOGIN_REQUIRED ) { + this.navigate( 'login' ); } else if ( FMS.currentDraft && ( FMS.currentDraft.get('title') || FMS.currentDraft.get('lat') || - FMS.currentDraft.get('details') || FMS.currentDraft.get('file') ) + FMS.currentDraft.get('details') || FMS.currentDraft.get('files').length > 0 ) ) { this.navigate( 'existing' ); } else { diff --git a/www/js/views/login.js b/www/js/views/login.js index c0f16ba..9225c18 100644 --- a/www/js/views/login.js +++ b/www/js/views/login.js @@ -40,8 +40,7 @@ that.model.set('name', data.name); that.model.save(); FMS.isLoggedIn = 1; - that.$('#password_row').hide(); - that.$('#success_row').show(); + that.rerender(); } else { that.validationError('signinForm', FMS.strings.login_details_error); } @@ -65,11 +64,7 @@ FMS.isLoggedIn = 0; that.model.set('password', ''); that.model.save(); - that.$('#form_email').val(''); - that.$('#form_password').val(''); - that.$('#success_row').hide(); - that.$('#signed_in_row').hide(); - that.$('#password_row').show(); + that.rerender(); }, error: function() { that.validationError('err', FMS.strings.logout_error); @@ -83,7 +78,7 @@ if ( !$('#form_password').val() ) { isValid = 0; - this.validationError('form_password', FMS.validationStrings.password ); + this.validationError('form_password', FMS.validationStrings.password.required); } var email = $('#form_email').val(); @@ -102,6 +97,23 @@ } return isValid; + }, + + beforeDisplay: function() { + this.fixPageHeight(); + if ( !FMS.isLoggedIn && CONFIG.LOGIN_REQUIRED ) { + this.$("#reports-next-btn").hide(); + } + }, + + rerender: function() { + // Simply calling this.render() breaks the DOM in a weird and + // interesting way - somehow the main view element is duplicated + // instead of replaced and none of the event handlers are + // hooked up so you end up with a blank screen. + // This is a convenience wrapper around the correct router call + // which works around the problem. + FMS.router.login(); } }) }); diff --git a/www/js/views/offline.js b/www/js/views/offline.js index ac007d1..3ce94a9 100644 --- a/www/js/views/offline.js +++ b/www/js/views/offline.js @@ -9,17 +9,18 @@ events: { 'pagehide': 'destroy', - 'pagebeforeshow': 'beforeShow', + 'pagebeforeshow': 'beforeDisplay', 'pageshow': 'afterDisplay', 'vclick .ui-btn-left': 'onClickButtonPrev', 'vclick .ui-btn-right': 'onClickButtonNext', 'vclick #id_photo_button': 'takePhoto', 'vclick #id_existing': 'addPhoto', - 'vclick #id_del_photo_button': 'deletePhoto', + 'vclick .del_photo_button': 'deletePhoto', 'vclick #locate': 'onClickLocate', 'vclick #locate_cancel': 'onClickCancel', 'blur input': 'toggleNextButton', - 'blur textarea': 'toggleNextButton' + 'blur textarea': 'blurTextArea', + 'focus textarea': 'focusTextArea' }, _back: function() { @@ -30,7 +31,7 @@ var hasContent = false; if ( $('#form_title').val() || $('#form_detail').val() || - this.model.get('lat') || this.model.get('file') ) { + this.model.get('lat') || this.model.get('files').length > 0 ) { hasContent = true; } @@ -39,11 +40,22 @@ afterDisplay: function() { $('body')[0].scrollTop = 0; - $('div[data-role="content"]').show(); - }, - beforeShow: function() { - $('div[data-role="content"]').hide(); + // The height of the photos container needs to be adjusted + // depending on the number of photos - if the max number of + // photos have already been added then the 'add photo' UI isn't + // shown so we should use all the vertical space for the + // thumbnails. + var wrapperHeight = $(".ui-content").height(); + wrapperHeight -= $(".ui-content .notopmargin").outerHeight(true); + wrapperHeight -= $(".ui-content #locate_result").outerHeight(true); + wrapperHeight -= $(".ui-content .inputcard").outerHeight(true); + wrapperHeight -= $(".ui-content #add_photo").outerHeight(true); + $(".photo-wrapper").height(wrapperHeight); + }, + + beforeDisplay: function() { + this.fixPageHeight(); this.toggleNextButton(); }, @@ -55,6 +67,15 @@ } }, + focusTextArea: function() { + $("textarea#form_detail").get(0).rows = 7; + }, + + blurTextArea: function(e) { + $("textarea#form_detail").get(0).rows = 2; + this.toggleNextButton(); + }, + failedLocation: function(details) { this.finishedLocating(); this.locateCount = 21; @@ -73,32 +94,47 @@ takePhoto: function() { var that = this; + $.mobile.loading('show'); navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, { saveToPhotoAlbum: true, quality: 49, destinationType: Camera.DestinationType.FILE_URI, sourceType: navigator.camera.PictureSourceType.CAMERA, correctOrientation: true }); }, addPhoto: function() { var that = this; + $.mobile.loading('show'); navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, { saveToPhotoAlbum: false, quality: 49, destinationType: Camera.DestinationType.FILE_URI, sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY, correctOrientation: true }); }, addPhotoSuccess: function(imgURI) { - var move = FMS.files.moveURI( imgURI ); + var move; + // on iOS the photos go into a temp folder in the apps own filespace so we + // can move them, and indeed have to as the tmp space is cleaned out by the OS + // so draft reports might have their images removed. on android you access the + // images where they are stored on the filesystem so if you move, and then delete + // them, you are moving and deleting the only copy of them which is likely to be + // surprising and unwelcome so we copy them instead. + var fileName = CONFIG.NAMESPACE + '_' + this.model.cid + '_' + moment().unix() + '.jpg'; + if ( FMS.isAndroid ) { + move = FMS.files.copyURI( imgURI, fileName ); + } else { + move = FMS.files.moveURI( imgURI, fileName ); + } var that = this; move.done( function( file ) { - $('#photo').attr('src', file.toURL()); - that.model.set('file', file.toURL()); - FMS.saveCurrentDraft(); + var files = that.model.get('files'); + files.push(file.toURL()); + that.model.set('files', files); + that.updateCurrentReport(); - $('#photo-next-btn .ui-btn-text').text(FMS.strings.next); - $('#display_photo').show(); - $('#add_photo').hide(); + $.mobile.loading('hide'); + that.rerender(); }); move.fail( function() { that.addPhotoFail(); } ); }, addPhotoFail: function(message) { + $.mobile.loading('hide'); if ( message != 'no image selected' && message != 'Selection cancelled.' && message != 'Camera cancelled.' ) { @@ -106,18 +142,22 @@ } }, - deletePhoto: function() { - var that = this; - var del = FMS.files.deleteURI( this.model.get('file') ); + deletePhoto: function(e) { + e.preventDefault(); + $.mobile.loading('show'); + var files = this.model.get('files'); + var index = parseInt($(e.target).data('fileIndex')); + var deleted_file = files.splice(index, 1)[0]; + var del = FMS.files.deleteURI( deleted_file ); + + var that = this; del.done( function() { - that.model.set('file', ''); - FMS.saveCurrentDraft(); - $('#photo').attr('src', ''); + that.model.set('files', files); + that.updateCurrentReport(); - $('#photo-next-btn .ui-btn-text').text(FMS.strings.skip); - $('#display_photo').hide(); - $('#add_photo').show(); + $.mobile.loading('hide'); + that.rerender(); }); }, @@ -154,7 +194,12 @@ this.model.set('title', $('#form_title').val()); this.model.set('details', $('#form_detail').val()); FMS.saveCurrentDraft(); + }, + + rerender: function() { + FMS.router.offline(); } + }) }); })(FMS, Backbone, _, $); diff --git a/www/js/views/photo.js b/www/js/views/photo.js index 485b74c..0b0c5e9 100644 --- a/www/js/views/photo.js +++ b/www/js/views/photo.js @@ -14,17 +14,23 @@ 'vclick .ui-btn-right': 'onClickButtonNext', 'vclick #id_photo_button': 'takePhoto', 'vclick #id_existing': 'addPhoto', - 'vclick #id_del_photo_button': 'deletePhoto' + 'vclick .del_photo_button': 'deletePhoto' }, beforeDisplay: function() { this.fixPageHeight(); - this.$('#id_del_photo_button').hide(); - if ( this.model.get('file') ) { - $('#id_photo_button').parents('.ui-btn').hide(); - $('#id_existing').parents('.ui-btn').hide(); - window.setTimeout( function() { $('#id_del_photo_button').show(); }, 250 ); - } + }, + + afterDisplay: function() { + // The height of the photos container needs to be adjusted + // depending on the number of photos - if the max number of + // photos have already been added then the 'add photo' UI isn't + // shown so we should use all the vertical space for the + // thumbnails. + var wrapperHeight = $(".ui-content").height(); + wrapperHeight -= $(".ui-content h2").outerHeight(true); + wrapperHeight -= $(".ui-content .bottom-btn").outerHeight(true) + $(".photo-wrapper").height(wrapperHeight); }, getOptions: function(isFromAlbum) { @@ -52,7 +58,7 @@ takePhoto: function(e) { e.preventDefault(); $.mobile.loading('show'); - $('#photo').hide(); + $('.photo-wrapper .photo img').hide(); var that = this; var options = this.getOptions(); @@ -63,7 +69,7 @@ addPhoto: function(e) { e.preventDefault(); $.mobile.loading('show'); - $('#photo').hide(); + $('.photo-wrapper .photo img').hide(); var that = this; var options = this.getOptions(true); navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, options); @@ -86,25 +92,19 @@ var that = this; move.done( function( file ) { - $('#nophoto_title').hide(); - $('#photo_title').html(FMS.strings.photo_added).show(); - $('#photo').attr('src', file.toURL()).addClass('small').removeClass('placeholder'); - that.model.set('file', file.toURL()); + var files = that.model.get('files'); + files.push(file.toURL()); + that.model.set('files', files); FMS.saveCurrentDraft(); - - $('#photo-next-btn .ui-btn-text').text(FMS.strings.next); - $('#id_photo_button').parents('.ui-btn').hide(); - $('#id_existing').parents('.ui-btn').hide(); - $('#photo').show(); - window.setTimeout(function() { $('#id_del_photo_button').show() }, 500); - window.setTimeout(function() { $.mobile.loading('hide') }, 100); + $.mobile.loading('hide'); + that.rerender(); }); move.fail( function() { that.addPhotoFail(); } ); }, addPhotoFail: function(message) { - $('#photo').show(); + $('.photo-wrapper .photo img').show(); $.mobile.loading('hide'); if ( message != 'no image selected' && message != 'Selection cancelled.' && @@ -115,22 +115,25 @@ deletePhoto: function(e) { e.preventDefault(); - var that = this; - var del = FMS.files.deleteURI( this.model.get('file') ); + var files = this.model.get('files'); + var index = parseInt($(e.target).data('fileIndex')); + var deleted_file = files.splice(index, 1)[0]; + var del = FMS.files.deleteURI( deleted_file ); + + var that = this; del.done( function() { - $('#photo_title').hide(); - $('#nophoto_title').show(); - $('#id_del_photo_button').hide(); - that.model.set('file', ''); + that.model.set('files', files); FMS.saveCurrentDraft(true); - $('#photo').attr('src', 'images/placeholder-photo.png').addClass('placeholder').removeClass('small'); - - $('#photo-next-btn .ui-btn-text').text(FMS.strings.skip); - $('#id_photo_button').parents('.ui-btn').show(); - $('#id_existing').parents('.ui-btn').show(); + that.rerender(); }); + }, + rerender: function() { + // Simply calling this.render() breaks the DOM in a weird and + // interesting way, so this is a convenience wrapper around + // the correct router call. + FMS.router.photo(); } }) }); diff --git a/www/js/views/submit.js b/www/js/views/submit.js index 6a7c946..4a92fe3 100644 --- a/www/js/views/submit.js +++ b/www/js/views/submit.js @@ -86,7 +86,7 @@ if ( !this._handleInvalid( model, err, options ) ) { var errors = err.errors; var errorList = '<ul><li class="plain">' + FMS.strings.invalid_report + '</li>'; - var validErrors = [ 'password', 'category', 'name' ]; + var validErrors = [ 'password', 'password_register', 'category', 'name' ]; for ( var k in errors ) { if ( validErrors.indexOf(k) >= 0 || errors[k].match(/required/) ) { if ( k === 'password' ) { @@ -128,6 +128,22 @@ } }, + validateUserTitle: function() { + if ( this.model.get('titles_list') && this.model.get('titles_list').length > 0 ) { + if ( $('#form_title').val() === '' ) { + this.validationError('form_title', FMS.strings.required); + return false; + } + } + return true; + }, + + setUserTitle: function() { + if ( this.model.get('titles_list') && this.model.get('titles_list').length > 0 ) { + FMS.currentUser.set('title', $('#form_title').val()); + } + }, + beforeSubmit: function() {}, afterSubmit: function() {}, @@ -145,7 +161,8 @@ (function (FMS, Backbone, _, $) { _.extend( FMS, { SubmitInitialPageView: FMS.SubmitView.extend({ - onClickButtonPrev: function() { + onClickButtonPrev: function(e) { + e.preventDefault(); if ( this.model.get('hasExtras') == 1 ) { this.navigate( 'details_extra', true ); } else { @@ -264,11 +281,8 @@ } } - if ( this.model.get('titles_list') && this.model.get('titles_list').length > 0 ) { - if ( $('#form_title').val() === '' ) { - this.validationError('form_title', FMS.strings.required); - isValid = 0; - } + if (!this.validateUserTitle()) { + isValid = 0; } return isValid; @@ -282,10 +296,7 @@ this.model.set('may_show_name', $('#form_may_show_name').is(':checked')); FMS.currentUser.set('name', $('#form_name').val()); FMS.currentUser.set('may_show_name', $('#form_may_show_name').is(':checked')); - - if ( this.model.get('titles_list') && this.model.get('titles_list').length > 0 ) { - FMS.currentUser.set('title', $('#form_title').val()); - } + this.setUserTitle(); if ( FMS.currentUser ) { FMS.currentUser.save(); @@ -329,20 +340,21 @@ }, validate: function() { + this.clearValidationErrors(); var isValid = 1; if ( !$('#form_password').val() ) { isValid = 0; - this.validationError('form_password', FMS.validationStrings.password ); + this.validationError('form_password', FMS.validationStrings.password.required ); + } else if ( CONFIG.PASSWORD_MIN_LENGTH && $('#form_password').val().length < CONFIG.PASSWORD_MIN_LENGTH ) { + isValid = 0; + var msg = FMS.validationStrings.password.short.replace('%d', CONFIG.PASSWORD_MIN_LENGTH); + this.validationError('form_password', msg); } - if ( $('#form_name').val() && this.model.get('titles_list') && this.model.get('titles_list').length > 0 ) { - if ( $('#form_title').val() === '' ) { - this.validationError('form_title', FMS.strings.required); - isValid = 0; - } + if ($('#form_name').val() && !this.validateUserTitle()) { + isValid = 0; } - return isValid; }, @@ -357,9 +369,7 @@ this.model.set('may_show_name', $('#form_may_show_name').is(':checked')); FMS.currentUser.set('name', $('#form_name').val()); FMS.currentUser.set('may_show_name', $('#form_may_show_name').is(':checked')); - if ( this.model.get('titles_list') && this.model.get('titles_list').length > 0 ) { - FMS.currentUser.set('title', $('#form_title').val()); - } + this.setUserTitle(); FMS.currentUser.save(); } else { // if this is set then we are registering a password @@ -408,14 +418,43 @@ onClickContinue: function(e) { e.preventDefault(); - if ( this.validate() ) { - $('#continue').focus(); - if ( ! this.model.get('submit_clicked') ) { - this.model.set('submit_clicked', 'submit_sign_in'); + if (this.validate()) { + // The password may be long enough, but is it going to be + // accepted by the server? Check before proceeding. + if (CONFIG.PASSWORD_CHECK_COMMON) { + var that = this; + $.post( + CONFIG.FMS_URL + "/auth/common_password", + { password_register: $('#form_password').val() }, + null, + 'json' + ) + .done(function(result) { + if (result === true) { + that.savePasswordAndContinue(); + } else { + that.validationError('form_password', result); + } + }) + .fail(function() { + // If this failed for whatever reason (e.g. network + // error etc), don't worry about it as it'll be + // resubmitted with the report. + that.savePasswordAndContinue(); + }); + } else { + this.savePasswordAndContinue(); } - FMS.currentUser.set('password', $('#form_password').val()); - this.navigate( this.next ); } + }, + + savePasswordAndContinue: function() { + $('#continue').focus(); + if ( ! this.model.get('submit_clicked') ) { + this.model.set('submit_clicked', 'submit_sign_in'); + } + FMS.currentUser.set('password', $('#form_password').val()); + this.navigate( this.next ); } }) }); @@ -462,18 +501,33 @@ this.model.set('submit_clicked', 'submit_register'); FMS.currentUser.set('name', $('#form_name').val()); FMS.currentUser.set('may_show_name', $('#form_may_show_name').is(':checked')); + this.setUserTitle(); }, onReportError: function(model, err, options) { // TODO: this is a temporary measure which should be replaced by a more // sensible login mechanism if ( err.check_name ) { - this.onClickSubmit(); + this.doSubmit(); } else { if ( err.errors && err.errors.password ) { this.validationError('form_password', err.errors.password ); } } + }, + + afterRender: function() { + console.log("SubmitConfirmView.afterRender"); + if (CONFIG.SKIP_CONFIRM_REPORT) { + var that = this; + setTimeout(function() { + // This needs to be in a setTimeout call otherwise + // the app gets stuck on an empty "Your Details" page. + // This is something to do with the way Backbone routes + // between views, I believe. + that.doSubmit(); + }, 10); + } } }) }); diff --git a/www/templates/en/around.html b/www/templates/en/around.html index 7c64fe5..8328a95 100644 --- a/www/templates/en/around.html +++ b/www/templates/en/around.html @@ -33,6 +33,7 @@ <% } %> </form> <a data-role="none" id="relocate" class="nodisplay"></a> + <a data-role="none" id="hidepins" class="nodisplay"></a> <div class="nodisplay" id="front-howto"></div> </div> diff --git a/www/templates/en/details_extra.html b/www/templates/en/details_extra.html index 339cc9d..c4390b4 100644 --- a/www/templates/en/details_extra.html +++ b/www/templates/en/details_extra.html @@ -1,7 +1,11 @@ <div data-role="header" data-position="fixed"> <a data-rel="back" data-icon="arrow-l" class="ui-btn-left">Back</a> <h1>Further Details</h1> - <a id="next" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Next</a> + <% if (CONFIG.SKIP_CONFIRM_REPORT) { %> + <a id="next" class="ui-btn-right">Submit</a> + <% } else { %> + <a id="next" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Next</a> + <% } %> </div> <div data-role="content" data-enhance="false"> <%= category_extras %> diff --git a/www/templates/en/existing.html b/www/templates/en/existing.html index 5e27785..c0a90ec 100644 --- a/www/templates/en/existing.html +++ b/www/templates/en/existing.html @@ -17,8 +17,8 @@ <p><% print( moment( created ).fromNow() ) %></p> </div> - <% if ( file ) { %> - <div class="photo" style="background-image: url(<%= file %>)"></div> + <% if ( files.length > 0 ) { %> + <div class="photo" style="background-image: url(<%= files[0] %>)"></div> <% } %> <% if ( details ) { %> diff --git a/www/templates/en/help.html b/www/templates/en/help.html index 3722544..2917c67 100644 --- a/www/templates/en/help.html +++ b/www/templates/en/help.html @@ -40,7 +40,7 @@ <dt>Is it free?</dt> <dd>The site and app are free to use, yes. <% print( CONFIG.APP_NAME ) %> is run by a registered charity, though, so if you want to make a contribution, <a - onclick="FMS.openExternal(event); return false;" href="https://secure.mysociety.org/donate/">please do</a>. + onclick="FMS.openExternal(event); return false;" href="https://www.mysociety.org/donate/">please do</a>. For more information see the <a onclick="FMS.openExternal(event); return false;" href="http://www.fixmystreet.com/faq/">website</a>. </dd> diff --git a/www/templates/en/login.html b/www/templates/en/login.html index 13944e8..bbe6267 100644 --- a/www/templates/en/login.html +++ b/www/templates/en/login.html @@ -12,13 +12,9 @@ <input type="button" id="logout" name="logout" value="Sign Out" data-theme="a"> </div> </div> - <div id="password_row" class="nodisplay"> - <p class="notopmargin"> - Signed out! - </p> <% } else { %> <div id="password_row"> - <% } %> + <p class="notopmargin">You are not signed in.</p> <form name="signinForm" class="inputcard" id="signinForm"> <div> <input data-role="none" type="email" value="" name="email" id="form_email" placeholder="Your Email" required> @@ -31,13 +27,5 @@ </div> </form> </div> - <div id="success_row" class="nodisplay"> - <p class="notopmargin"> - Signed in! - </p> - <div id="err" class="nodisplay"></div> - <div class="bottom-btn"> - <input type="button" id="logout" name="logout" value="Sign Out" data-theme="a"> - </div> - </div> + <% } %> </div> diff --git a/www/templates/en/offline.html b/www/templates/en/offline.html index 10e87d9..4fde64f 100644 --- a/www/templates/en/offline.html +++ b/www/templates/en/offline.html @@ -1,7 +1,7 @@ <div data-role="header" data-position="fixed"> <a id="offline-prev-btn" class="ui-btn-left">Try Again</a> <h1>Offline</h1> - <a id="offline-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Skip</a> + <a id="offline-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right"><%= files.length == 0 ? "Skip" : "Next" %></a> </div> <div id="locating" class="nodisplay"> <div class="radar"></div> @@ -42,30 +42,25 @@ </div> <div> - <textarea data-role="none" rows="7" cols="26" name="detail" id="form_detail" placeholder="Please fill in details of the problem." required><%= details %></textarea> + <textarea data-role="none" rows="2" cols="26" name="detail" id="form_detail" placeholder="Please fill in details of the problem." required><%= details %></textarea> </div> </div> - <% if ( file != '' ) { %> - <div id="add_photo" style="display: none"> - <% } else { %> - <div id="add_photo"> + <% if ( files.length < CONFIG.MAX_PHOTOS ) { %> + <div id="add_photo"> + <input data-icon="fms-photo-new" value="Take a new photo" type="button" name="photo_button" id="id_photo_button" data-role="button" data-theme="a"> + <input data-icon="fms-photo-existing" value="Add an existing photo" type="button" name="existing" id="id_existing" data-role="button" data-theme="c"> + </div> <% } %> - <label>Add a Photo <em>(optional)</em></label> - <input data-icon="fms-photo-new" value="Take a new photo" type="button" name="photo_button" id="id_photo_button" data-role="button" data-theme="a"> - <input data-icon="fms-photo-existing" value="Add an existing photo" type="button" name="existing" id="id_existing" data-role="button" data-theme="c"> - </div> - <% if ( file == '' ) { %> - <div id="display_photo" style="display: none"> - <% } else { %> - <div id="display_photo"> - <% } %> - <label>Your Photo</label> - <div class="photo"> - <img class="small" id="photo" src="<%= file %>" /> + <% if ( files.length > 0 ) { %> + <div class="photo-wrapper"> + <% _.each(files, function(file, index) { %> + <div class="photo" style="background-image: url('<%= file %>');"> + <a data-role="none" class="photo-corner-button del_photo_button" data-file-index="<%= index %>">X</a> + </div> + <% }); %> </div> - <input value="Remove Photo" type="button" name="del_photo_button" id="id_del_photo_button" data-role="button" data-theme="a"> - </div> + <% } %> </div> </div> diff --git a/www/templates/en/photo.html b/www/templates/en/photo.html index 8dac456..3221192 100644 --- a/www/templates/en/photo.html +++ b/www/templates/en/photo.html @@ -1,37 +1,49 @@ <div data-role="header"> <a data-rel="back" data-icon="arrow-l" class="ui-btn-left">Back</a> - <h1>Add Photo</h1> - <% if ( file == '' ) { %> - <a id="photo-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Skip</a> + <h1>Add Photos</h1> + <% if ( files.length == 0 ) { %> + <% if ( !CONFIG.PHOTO_REQUIRED ) { %> + <a id="photo-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Skip</a> + <% } %> <% } else { %> <a id="photo-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Next</a> <% } %> </div> + <div data-role="content"> - <% var photoClass = 'nodisplay', nophotoClass = 'nodisplay'; - if ( file != '' ) { - photoClass = ''; - } else { - nophotoClass = ''; - } %> - <h2 id="nophoto_title" class="<% print( nophotoClass ); %>"> - Add a Photo <em>(optional)</em> - </h2> - <h2 id="photo_title" class="<% print( photoClass ); %>"> - Photo for report - </h2> - <div class="photo-wrapper"> - <div class="photo"> - <% if ( file == '' ) { %> - <img class="placeholder" src="images/placeholder-photo.png" alt="" id="photo"> + <% if ( files.length == 0 ) { %> + <h2 id="nophoto_title"> + Add a Photo + <% if ( !CONFIG.PHOTO_REQUIRED ) { %> + <em>(optional)</em> + <% } else if ( files.length == 0) { %> + <em>(required)</em> + <%} %> + </h2> <% } else { %> - <img class="small" id="photo" src="<%= file %>" /> + <h2 id="photo_title"> + Photos for report + </h2> <% } %> - <a data-role="none" id="id_del_photo_button" class="photo-corner-button">X</a> - </div> - </div> - <div class="bottom-btn"> - <input data-icon="fms-photo-existing" type="button" id="id_existing" data-theme="c" value="Add an existing photo" > - <input data-icon="fms-photo-new" id="id_photo_button" type="button" data-theme="a" value="Take a new photo"> + + <div class="photo-wrapper"> + <% if ( files.length == 0 ) { %> + <div class="photo"> + <img class="placeholder" src="images/placeholder-photo.png" alt=""> + </div> + <% } else { %> + <% _.each(files, function(file, index) { %> + <div class="photo" style="background-image: url('<%= file %>');"> + <a data-role="none" class="photo-corner-button del_photo_button" data-file-index="<%= index %>">X</a> + </div> + <% });%> + <% } %> </div> + + <% if ( files.length < CONFIG.MAX_PHOTOS ) { %> + <div class="bottom-btn"> + <input data-icon="fms-photo-existing" type="button" id="id_existing" data-theme="c" value="Add an existing photo" > + <input data-icon="fms-photo-new" id="id_photo_button" type="button" data-theme="a" value="Take a new photo"> + </div> + <% } %> </div> diff --git a/www/templates/en/reports.html b/www/templates/en/reports.html index 047fa76..e04f5f6 100644 --- a/www/templates/en/reports.html +++ b/www/templates/en/reports.html @@ -15,7 +15,7 @@ if ( draft.get('lat') ) { classes.push('with_location'); } - if ( draft.get('file') ) { + if ( draft.get('files').length > 0 ) { classes.push('with_photo'); } var draftClass = ''; diff --git a/www/templates/en/submit_confirm.html b/www/templates/en/submit_confirm.html index 5d2e6a0..f41e71c 100644 --- a/www/templates/en/submit_confirm.html +++ b/www/templates/en/submit_confirm.html @@ -3,7 +3,7 @@ <h1>Your details</h1> </div> -<div data-role="content"> +<div data-role="content"<% if (CONFIG.SKIP_CONFIRM_REPORT) { %> class="hidden"<% } %>> <p class="notopmargin"> You are logged in as <%= user.email %>. </p> diff --git a/www/templates/en/submit_set_password.html b/www/templates/en/submit_set_password.html index 1a24eae..75baf7a 100644 --- a/www/templates/en/submit_set_password.html +++ b/www/templates/en/submit_set_password.html @@ -12,7 +12,7 @@ </p> <div class="inputcard"> <div> - <input data-role="none" type="password" value="" name="password" id="form_password" placeholder="Your password" required> + <input data-role="none" type="password" value="<%= FMS.currentUser.get('password') %>" name="password" id="form_password" placeholder="Your password" required> </div> </div> <div class="noborder"> diff --git a/www/templates/es/around.html b/www/templates/es/around.html index 8686933..5f47ed4 100644 --- a/www/templates/es/around.html +++ b/www/templates/es/around.html @@ -33,6 +33,7 @@ <% } %> </form> <a data-role="none" id="relocate" class="nodisplay"></a> + <a data-role="none" id="hidepins" class="nodisplay"></a> <div class="nodisplay" id="front-howto"></div> </div> diff --git a/www/templates/es/details_extra.html b/www/templates/es/details_extra.html index 2d782c8..0f302fe 100644 --- a/www/templates/es/details_extra.html +++ b/www/templates/es/details_extra.html @@ -1,7 +1,11 @@ <div data-role="header" data-position="fixed"> <a data-rel="back" data-icon="arrow-l" class="ui-btn-left">Atrás</a> <h1>Detalles adicionales</h1> - <a id="next" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Siguiente</a> + <% if (CONFIG.SKIP_CONFIRM_REPORT) { %> + <a id="next" class="ui-btn-right">Enviar</a> + <% } else { %> + <a id="next" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Siguiente</a> + <% } %> </div> <div data-role="content" data-enhance="false"> <%= category_extras %> diff --git a/www/templates/es/existing.html b/www/templates/es/existing.html index b45b3d8..baecd89 100644 --- a/www/templates/es/existing.html +++ b/www/templates/es/existing.html @@ -17,8 +17,8 @@ <p><% print( moment( created ).fromNow() ) %></p> </div> - <% if ( file ) { %> - <div class="photo" style="background-image: url(<%= file %>)"></div> + <% if ( files.length > 0 ) { %> + <div class="photo" style="background-image: url(<%= files[0] %>)"></div> <% } %> <% if ( details ) { %> diff --git a/www/templates/es/login.html b/www/templates/es/login.html index 4dff7a6..3fcaa99 100644 --- a/www/templates/es/login.html +++ b/www/templates/es/login.html @@ -12,13 +12,9 @@ <input type="button" id="logout" name="logout" value="Cerrar sesión" data-theme="a"> </div> </div> - <div id="password_row" class="nodisplay"> - <p class="notopmargin"> - ¡Sesión cerrada! - </p> <% } else { %> <div id="password_row"> - <% } %> + <p class="notopmargin">You are not signed in.</p> <form name="signinForm" class="inputcard" id="signinForm"> <div> <input data-role="none" type="email" value="" name="email" id="form_email" placeholder="Su email" required> @@ -31,13 +27,5 @@ </div> </form> </div> - <div id="success_row" class="nodisplay"> - <p class="notopmargin"> - ¡Sesión abierta! - </p> - <div id="err" class="nodisplay"></div> - <div class="bottom-btn"> - <input type="button" id="logout" name="logout" value="Cerrar sesión" data-theme="a"> - </div> - </div> + <% } %> </div> diff --git a/www/templates/es/offline.html b/www/templates/es/offline.html index 1051f57..35190df 100644 --- a/www/templates/es/offline.html +++ b/www/templates/es/offline.html @@ -1,7 +1,7 @@ <div data-role="header" data-position="fixed"> <a id="offline-prev-btn" class="ui-btn-left">Inténtelo nuevamente</a> <h1>Sin conexión</h1> - <a id="offline-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Saltar</a> + <a id="offline-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right"><%= files.length == 0 ? "Saltar" : "Siguiente" %></a> </div> <div id="locating" class="nodisplay"> <div class="radar"></div> @@ -42,30 +42,25 @@ </div> <div> - <textarea data-role="none" rows="7" cols="26" name="detail" id="form_detail" placeholder="Please fill in details of the problem." required><%= details %></textarea> + <textarea data-role="none" rows="2" cols="26" name="detail" id="form_detail" placeholder="Please fill in details of the problem." required><%= details %></textarea> </div> </div> - <% if ( file != '' ) { %> - <div id="add_photo" style="display: none"> - <% } else { %> - <div id="add_photo"> + <% if ( files.length < CONFIG.MAX_PHOTOS ) { %> + <div id="add_photo"> + <input data-icon="fms-photo-new" value="Tomar una nueva foto" type="button" name="photo_button" id="id_photo_button" data-role="button" data-theme="a"> + <input data-icon="fms-photo-existing" value="Añadir una foto existente" type="button" name="existing" id="id_existing" data-role="button" data-theme="c"> + </div> <% } %> - <label>Añadir una foto <em>(opcional)</em></label> - <input data-icon="fms-photo-new" value="Tomar una nueva foto" type="button" name="photo_button" id="id_photo_button" data-role="button" data-theme="a"> - <input data-icon="fms-photo-existing" value="Añadir una foto existente" type="button" name="existing" id="id_existing" data-role="button" data-theme="c"> - </div> - <% if ( file == '' ) { %> - <div id="display_photo" style="display: none"> - <% } else { %> - <div id="display_photo"> - <% } %> - <label>Su foto</label> - <div class="photo"> - <img class="small" id="photo" src="<%= file %>" /> + <% if ( files.length > 0 ) { %> + <div class="photo-wrapper"> + <% _.each(files, function(file, index) { %> + <div class="photo" style="background-image: url('<%= file %>');"> + <a data-role="none" class="photo-corner-button del_photo_button" data-file-index="<%= index %>">X</a> + </div> + <% }); %> </div> - <input value="Eliminar foto" type="button" name="del_photo_button" id="id_del_photo_button" data-role="button" data-theme="a"> - </div> + <% } %> </div> </div> diff --git a/www/templates/es/photo.html b/www/templates/es/photo.html index 62f36dc..75de686 100644 --- a/www/templates/es/photo.html +++ b/www/templates/es/photo.html @@ -1,37 +1,49 @@ <div data-role="header"> <a data-rel="back" data-icon="arrow-l" class="ui-btn-left">Atrás</a> - <h1>Añadir foto</h1> - <% if ( file == '' ) { %> - <a id="photo-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Saltar</a> + <h1>Add Photos</h1> + <% if ( files.length == 0 ) { %> + <% if ( !CONFIG.PHOTO_REQUIRED ) { %> + <a id="photo-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Saltar</a> + <% } %> <% } else { %> <a id="photo-next-btn" data-icon="arrow-r" data-iconpos="right" class="ui-btn-right">Siguiente</a> <% } %> </div> + <div data-role="content"> - <% var photoClass = 'nodisplay', nophotoClass = 'nodisplay'; - if ( file != '' ) { - photoClass = ''; - } else { - nophotoClass = ''; - } %> - <h2 id="nophoto_title" class="<% print( nophotoClass ); %>"> - Añadir una foto <em>(opcional)</em> - </h2> - <h2 id="photo_title" class="<% print( photoClass ); %>"> - Foto para el informe - </h2> - <div class="photo-wrapper"> - <div class="photo"> - <% if ( file == '' ) { %> - <img class="placeholder" src="images/placeholder-photo.png" alt="" id="photo"> + <% if ( files.length == 0 ) { %> + <h2 id="nophoto_title"> + Añadir una foto + <% if ( !CONFIG.PHOTO_REQUIRED ) { %> + <em>(opcional)</em> + <% } else if ( files.length == 0) { %> + <em>(required)</em> + <%} %> + </h2> <% } else { %> - <img class="small" id="photo" src="<%= file %>" /> + <h2 id="photo_title"> + Photos for report + </h2> <% } %> - <a data-role="none" id="id_del_photo_button" class="photo-corner-button">X</a> - </div> - </div> - <div class="bottom-btn"> - <input data-icon="fms-photo-existing" type="button" id="id_existing" data-theme="c" value="Añadir una foto existente" > - <input data-icon="fms-photo-new" id="id_photo_button" type="button" data-theme="a" value="Tomar una nueva foto"> + + <div class="photo-wrapper"> + <% if ( files.length == 0 ) { %> + <div class="photo"> + <img class="placeholder" src="images/placeholder-photo.png" alt=""> + </div> + <% } else { %> + <% _.each(files, function(file, index) { %> + <div class="photo" style="background-image: url('<%= file %>');"> + <a data-role="none" class="photo-corner-button del_photo_button" data-file-index="<%= index %>">X</a> + </div> + <% });%> + <% } %> </div> + + <% if ( files.length < CONFIG.MAX_PHOTOS ) { %> + <div class="bottom-btn"> + <input data-icon="fms-photo-existing" type="button" id="id_existing" data-theme="c" value="Añadir una foto existente" > + <input data-icon="fms-photo-new" id="id_photo_button" type="button" data-theme="a" value="Tomar una nueva foto"> + </div> + <% } %> </div> diff --git a/www/templates/es/reports.html b/www/templates/es/reports.html index cbd2acf..05772a5 100644 --- a/www/templates/es/reports.html +++ b/www/templates/es/reports.html @@ -15,7 +15,7 @@ if ( draft.get('lat') ) { classes.push('with_location'); } - if ( draft.get('file') ) { + if ( draft.get('files').length > 0 ) { classes.push('with_photo'); } var draftClass = ''; diff --git a/www/templates/es/submit_confirm.html b/www/templates/es/submit_confirm.html index a9dd90a..224195b 100644 --- a/www/templates/es/submit_confirm.html +++ b/www/templates/es/submit_confirm.html @@ -3,7 +3,7 @@ <h1>Sus detalles</h1> </div> -<div data-role="content"> +<div data-role="content"<% if (CONFIG.SKIP_CONFIRM_REPORT) { %> class="hidden"<% } %>> <p class="notopmargin"> Estas registrado como <%= user.email %>. </p> diff --git a/www/templates/es/submit_set_password.html b/www/templates/es/submit_set_password.html index a9d9b46..39403cb 100644 --- a/www/templates/es/submit_set_password.html +++ b/www/templates/es/submit_set_password.html @@ -12,7 +12,7 @@ </p> <div class="inputcard"> <div> - <input data-role="none" type="password" value="" name="password" id="form_password" placeholder="Su contraseña" required> + <input data-role="none" type="password" value="<%= FMS.currentUser.get('password') %>" name="password" id="form_password" placeholder="Su contraseña" required> </div> </div> <div class="noborder"> |