diff --git a/Space_Mapper/lang/en.json b/Space_Mapper/lang/en.json index 8affb8b9..e47c5c36 100644 --- a/Space_Mapper/lang/en.json +++ b/Space_Mapper/lang/en.json @@ -23,5 +23,8 @@ "select_age_group": "Select age group", "submit": "Submit", "reset": "Reset", - "gender": "gender" + "gender": "gender", + "about_the_project": "About the project", + "consent_form" : "Consent Form", + "do_you_agree_to_share_your_anonymous_location_with" : "Do you agree to share your anonymous locations to " } \ No newline at end of file diff --git a/Space_Mapper/lang/es.json b/Space_Mapper/lang/es.json index 14d15339..3c611c7a 100644 --- a/Space_Mapper/lang/es.json +++ b/Space_Mapper/lang/es.json @@ -23,5 +23,8 @@ "select_age_group": "Selecciona grupo de edad", "submit": "Enviar", "reset": "Restablecer", - "gender": "género" + "gender": "género", + "about_the_project": "Sobre el proyecto", + "consent_form" : "Consentimiento", + "do_you_agree_to_share_your_anonymous_location_with" : "¿Estás de acuerdo en compartir tu historial de ubicaciones anónimo con " } \ No newline at end of file diff --git a/Space_Mapper/lib/mocks/mock_survey.dart b/Space_Mapper/lib/mocks/mock_survey.dart index 2439e5e3..c8bd537f 100644 --- a/Space_Mapper/lib/mocks/mock_survey.dart +++ b/Space_Mapper/lib/mocks/mock_survey.dart @@ -3,23 +3,24 @@ import '../models/survey.dart'; mixin MockSurvey implements Survey { static final List items = [ Survey( - 1, + 0, "Mosquito Alert", "https://play.google.com/store/apps/details?id=ceab.movelab.tigatrapp&hl=es&gl=US", "https://www.periodismociudadano.com/wp-content/uploads/2020/11/mosquito-49141_640.jpg", "Mosquito Alert is a citizen science platform for studying and controlling the tiger mosquito (Aedes albopictus) and the yellow fever mosquito (Aedes aegypti).", ), Survey( - 2, + 1, "Max Planck Institute", "https://www.mpg.de/institutes", "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Max_Planck_Institute_for_the_Science_of_Light%2C_new_building%2C_July_2015.jpg/800px-Max_Planck_Institute_for_the_Science_of_Light%2C_new_building%2C_July_2015.jpg", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus a dui leo. Integer volutpat ipsum sed nulla luctus porttitor. Vivamus tincidunt iaculis purus. Sed lacinia faucibus dignissim.", ), Survey( - 3, + 2, "Space Mapper Form Test", - "https://ee.kobotoolbox.org/single/asCwpCjZ", + //"https://ee.kobotoolbox.org/single/asCwpCjZ", + "https://ee.kobotoolbox.org/x/AG5j1vFN", "https://raw.githubusercontent.com/ActivitySpaceProject/space_mapper/master/Assets/images/3.0.2%2B18_screenshots.png", "Nullam ac est non ante lobortis cursus. Sed nulla leo, venenatis at enim a, iaculis venenatis purus.Nullam ac est non ante lobortis cursus. Sed nulla leo, venenatis at enim a, iaculis venenatis purus.Nullam ac est non ante lobortis cursus. Sed nulla leo, venenatis at enim a, iaculis venenatis purus.Nullam ac est non ante lobortis cursus. Sed nulla leo, venenatis at enim a, iaculis venenatis purus.Nullam ac est non ante lobortis cursus. Sed nulla leo, venenatis at enim a, iaculis venenatis purus.", ) @@ -33,7 +34,7 @@ mixin MockSurvey implements Survey { return items; } - static Survey fetch(int index) { + static Survey fetchByID(int index) { return items[index]; } } diff --git a/Space_Mapper/lib/models/list_view.dart b/Space_Mapper/lib/models/custom_locations.dart similarity index 89% rename from Space_Mapper/lib/models/list_view.dart rename to Space_Mapper/lib/models/custom_locations.dart index 0d8f2761..28cfc5e4 100644 --- a/Space_Mapper/lib/models/list_view.dart +++ b/Space_Mapper/lib/models/custom_locations.dart @@ -29,9 +29,9 @@ class CustomLocationsManager { continue; //CustomLocationsManager.customLocations[j].getUUID()) continue; } // Match not found, we add the location - CustomLocation newLocation = await CustomLocationsManager.createCustomLocation( - recordedLocations[i] - ); + CustomLocation newLocation = + await CustomLocationsManager.createCustomLocation( + recordedLocations[i]); customLocations.add(newLocation); } return customLocations; @@ -54,7 +54,7 @@ class CustomLocationsManager { static Future createCustomLocation( var recordedLocation) async { - CustomLocation location = new CustomLocation(); + CustomLocation location = new CustomLocation(); //Save data from flutter_background_geolocation library location.setUUID(recordedLocation['uuid']); @@ -65,8 +65,9 @@ class CustomLocationsManager { location.setAltitude(recordedLocation['coords']['altitude'], recordedLocation['coords']['altitude_accuracy']); - Placemark? placemark = await getLocationData(recordedLocation['coords']['latitude'], - recordedLocation['coords']['longitude']); + Placemark? placemark = await getLocationData( + recordedLocation['coords']['latitude'], + recordedLocation['coords']['longitude']); //Add our custom data if (placemark != null) { @@ -84,6 +85,7 @@ class CustomLocationsManager { } return location; } + ///Get data such as city, province, postal code, street name, country... static Future getLocationData(double lat, double long) async { try { @@ -98,6 +100,23 @@ class CustomLocationsManager { } } +/// This class should be used to share your location history to other people +class ShareLocation { + late final String _timestamp; + final double _lat; + final double _long; + + ShareLocation(this._timestamp, this._lat, this._long); + + Map toJson() => { + 'timestamp': _timestamp, + 'coords': { + 'latitude': _lat, + 'longitude': _long, + } + }; +} + class CustomLocation { late final String _uuid; late String _locality = ""; @@ -228,4 +247,4 @@ class CustomLocation { num getAltitudeAcc() { return _altitude; } -} \ No newline at end of file +} diff --git a/Space_Mapper/lib/models/survey.dart b/Space_Mapper/lib/models/survey.dart index aa541057..876769ab 100644 --- a/Space_Mapper/lib/models/survey.dart +++ b/Space_Mapper/lib/models/survey.dart @@ -10,7 +10,8 @@ class Survey { : id = 0, name = ' ', webUrl = ' ', - imageUrl = ' ', + imageUrl = + '', // Leave this without space ('' instead of ' ') to avoid an exception summary = ' '; static Future> fetchAll() async { diff --git a/Space_Mapper/lib/ui/list_view.dart b/Space_Mapper/lib/ui/list_view.dart index 541b5fdc..be78181d 100644 --- a/Space_Mapper/lib/ui/list_view.dart +++ b/Space_Mapper/lib/ui/list_view.dart @@ -1,5 +1,5 @@ import '../app_localizations.dart'; -import '../models/list_view.dart'; +import '../models/custom_locations.dart'; import 'package:flutter/material.dart'; class STOListView extends StatefulWidget { @@ -9,8 +9,7 @@ class STOListView extends StatefulWidget { _STOListViewState createState() => _STOListViewState(); } -class _STOListViewState extends State { - +class _STOListViewState extends State { @override void initState() { super.initState(); diff --git a/Space_Mapper/lib/ui/side_drawer.dart b/Space_Mapper/lib/ui/side_drawer.dart index 3a769ed0..6e19b831 100644 --- a/Space_Mapper/lib/ui/side_drawer.dart +++ b/Space_Mapper/lib/ui/side_drawer.dart @@ -1,6 +1,6 @@ import 'dart:convert'; import 'package:asm/app_localizations.dart'; -import 'package:asm/models/list_view.dart'; +import 'package:asm/models/custom_locations.dart'; import 'package:asm/ui/list_view.dart'; import 'package:asm/ui/report_an_issue.dart'; import 'package:flutter/material.dart'; @@ -9,25 +9,7 @@ import 'package:share/share.dart'; import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; -import 'available_surveys.dart'; - -class ShareLocation { - late final String _timestamp; - final double _lat; - final double _long; - - ShareLocation(timestamp, this._lat, this._long) { - _timestamp = CustomLocationsManager.formatTimestamp(timestamp); - } - - Map toJson() => { - 'timestamp': _timestamp, - 'coords': { - 'latitude': _lat, - 'longitude': _long, - } - }; -} +import 'surveys_list.dart'; class SpaceMapperSideDrawer extends StatelessWidget { _shareLocations() async { diff --git a/Space_Mapper/lib/ui/survey_detail.dart b/Space_Mapper/lib/ui/survey_detail.dart new file mode 100644 index 00000000..560a5214 --- /dev/null +++ b/Space_Mapper/lib/ui/survey_detail.dart @@ -0,0 +1,198 @@ +import 'dart:convert'; + +import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' + as bg; +import 'package:flutter/material.dart'; + +import '../app_localizations.dart'; +import '../components/banner_image.dart'; +import '../components/survey_tile.dart'; +import '../mocks/mock_survey.dart'; +import '../models/survey.dart'; +import '../models/custom_locations.dart'; +import '../ui/web_view.dart'; +import '../styles.dart'; + +const BannerImageHeight = 300.0; +const BodyVerticalPadding = 20.0; +const FooterHeight = 100.0; + +class SurveyDetail extends StatefulWidget { + final int surveyID; + + SurveyDetail(this.surveyID); + + @override + _SurveyDetailState createState() => _SurveyDetailState(surveyID); +} + +class _SurveyDetailState extends State { + final int surveyID; + Survey survey = Survey.blank(); + bool consent = false; + + _SurveyDetailState(this.surveyID); + + @override + void initState() { + super.initState(); + loadData(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + AppLocalizations.of(context)!.translate("about_the_project"))), + body: Stack( + children: [ + _renderBody(context, survey), + _renderFooter(context), + //_renderConsentForm(), + ], + ), + ); + } + + loadData() { + final survey = MockSurvey.fetchByID(this.surveyID); + + if (mounted) { + setState(() { + this.survey = survey; + }); + } + } + + Widget _renderBody(BuildContext context, Survey survey) { + var result = []; + result.add(BannerImage(url: survey.imageUrl, height: BannerImageHeight)); + result.add(_renderHeader()); + result.add(_renderConsentForm()); + result.add(_renderBottomSpacer()); + return SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: result)); + } + + Widget _renderHeader() { + return Container( + padding: EdgeInsets.symmetric( + vertical: BodyVerticalPadding, + horizontal: Styles.horizontalPaddingDefault), + child: SurveyTile(survey: survey, darkTheme: false), + ); + } + + Widget _renderFooter(BuildContext contexty) { + return Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + decoration: BoxDecoration(color: Colors.white.withOpacity(0.5)), + height: FooterHeight, + child: Container( + padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 30.0), + child: _renderTakeSurveyButton(), + ), + ) + ], + ); + } + + Widget _renderConsentForm() { + String title = AppLocalizations.of(context)!.translate("consent_form"); + String text = AppLocalizations.of(context)!.translate("do_you_agree_to_share_your_anonymous_location_with") + "${survey.name}?"; + + return Container( + height: SurveyTileHeight, + padding: EdgeInsets.symmetric( + //vertical: BodyVerticalPadding, + horizontal: Styles.horizontalPaddingDefault), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('$title', + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: Styles.surveyTileTitleLight), + Row( + children: [ + Checkbox( + value: consent, + onChanged: (bool? newValue) { + setState(() { + consent = newValue!; + }); + }, + ), + Expanded( + child: Text('$text', + overflow: TextOverflow.ellipsis, + maxLines: 3, + style: Styles.surveyTileCaption), + ) + ], + ), + ], + ), + ); + } + + Widget _renderTakeSurveyButton() { + return TextButton( + //color: Styles.accentColor, + //textColor: Styles.textColorBright, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Colors.blue), + ), + onPressed: () => { + _navigationToSurvey(context), + }, + child: Text( + 'Take Survey'.toUpperCase(), + style: Styles.textCTAButton, + ), + ); + } + + Future _navigationToSurvey(BuildContext context) async { + String locationHistoryJSON = ""; + + // If we have consent, send location history. Otherwise, send empty string + if (consent) { + List allLocations = await bg.BackgroundGeolocation.locations; + List customLocation = []; + + // We get only timestamp and coordinates into our custom class + for (var thisLocation in allLocations) { + ShareLocation _loc = new ShareLocation( + bg.Location(thisLocation).timestamp, + bg.Location(thisLocation).coords.latitude, + bg.Location(thisLocation).coords.longitude); + customLocation.add(_loc); + } + + locationHistoryJSON = jsonEncode(customLocation); + locationHistoryJSON = locationHistoryJSON.replaceAll("\"", + "'"); //We replace " into ' to avoid a javascript exception when we post it in the webview's form + } else { + locationHistoryJSON = "I do not agree to share my location history."; + } + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + MyWebView(survey.webUrl, locationHistoryJSON))); + } + + Widget _renderBottomSpacer() { + return Container(height: FooterHeight); + } +} diff --git a/Space_Mapper/lib/ui/available_surveys.dart b/Space_Mapper/lib/ui/surveys_list.dart similarity index 87% rename from Space_Mapper/lib/ui/available_surveys.dart rename to Space_Mapper/lib/ui/surveys_list.dart index 73a8f3da..8b57fecf 100644 --- a/Space_Mapper/lib/ui/available_surveys.dart +++ b/Space_Mapper/lib/ui/surveys_list.dart @@ -1,4 +1,3 @@ -import 'package:asm/ui/web_view.dart'; import 'package:flutter/material.dart'; import '../app_localizations.dart'; @@ -6,9 +5,10 @@ import '../components/banner_image.dart'; import '../components/survey_tile.dart'; import '../mocks/mock_survey.dart'; import '../models/survey.dart'; -import '../ui/web_view.dart'; import '../styles.dart'; +import 'survey_detail.dart'; + const ListItemHeight = 245.0; class AvailableSurveysScreen extends StatefulWidget { @@ -71,7 +71,7 @@ class _AvailableSurveysScreenState extends State { Widget _listViewItemBuilder(BuildContext context, int index) { final survey = this.surveys[index]; return GestureDetector( - onTap: () => _navigationToLocationDetail(context, survey.webUrl), + onTap: () => _navigationToLocationDetail(context, survey.id), child: Container( height: ListItemHeight, child: Stack( @@ -84,18 +84,17 @@ class _AvailableSurveysScreenState extends State { ); } - void _navigationToLocationDetail(BuildContext context, String surveyWebUrl) { + void _navigationToLocationDetail(BuildContext context, int surveyID) { Navigator.push(context, - MaterialPageRoute(builder: (context) => MyWebView(surveyWebUrl))); + MaterialPageRoute(builder: (context) => SurveyDetail(surveyID))); } Widget _tileFooter(Survey survey) { - final info = SurveyTile(survey: survey, darkTheme: true); final overlay = Container( padding: EdgeInsets.symmetric( vertical: 5.0, horizontal: Styles.horizontalPaddingDefault), decoration: BoxDecoration(color: Colors.black.withOpacity(0.5)), - child: info, + child: SurveyTile(survey: survey, darkTheme: true), ); return Column( mainAxisAlignment: MainAxisAlignment.end, diff --git a/Space_Mapper/lib/ui/web_view.dart b/Space_Mapper/lib/ui/web_view.dart index be1b4858..73747552 100644 --- a/Space_Mapper/lib/ui/web_view.dart +++ b/Space_Mapper/lib/ui/web_view.dart @@ -19,17 +19,26 @@ final Set jsChannels = [ // ignore: must_be_immutable class MyWebView extends StatefulWidget { - String selectedUrl; - MyWebView(this.selectedUrl); + final String selectedUrl; + final String locationHistoryJSON; + MyWebView(this.selectedUrl, this.locationHistoryJSON); @override - _MyWebViewState createState() => _MyWebViewState(selectedUrl); + _MyWebViewState createState() => + _MyWebViewState(selectedUrl, locationHistoryJSON); } class _MyWebViewState extends State { - String selectedUrl; + final String selectedUrl; + final String locationHistoryJSON; + final String testJSON = + "[timestamp: 2019] fdsgdfgdfsg dfgdsf gdfsgdf sgdfs gdfsgdf 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 gfsagsfhas"; + final Completer _controller = Completer(); - _MyWebViewState(this.selectedUrl); + late WebViewController _webViewcontroller; + + _MyWebViewState(this.selectedUrl, this.locationHistoryJSON); + @override void initState() { super.initState(); @@ -54,6 +63,7 @@ class _MyWebViewState extends State { javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { _controller.complete(webViewController); + _webViewcontroller = webViewController; }, onProgress: (int progress) { print("WebView is loading (progress : $progress%)"); @@ -65,6 +75,7 @@ class _MyWebViewState extends State { print('Page started loading: $url'); }, onPageFinished: (String url) { + _setFormLocationHistory(); print('Page finished loading: $url'); }, gestureNavigationEnabled: true, @@ -77,12 +88,18 @@ class _MyWebViewState extends State { return JavascriptChannel( name: 'Toaster', onMessageReceived: (JavascriptMessage message) { - // ignore: deprecated_member_use - Scaffold.of(context).showSnackBar( + ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message.message)), ); }); } + + void _setFormLocationHistory() async { + sleep(Duration(seconds: 10)); + await _webViewcontroller.runJavascript( + 'var this_input = document.getElementsByName("/awLRwRXn4GTpdcq3aJE2WQ/Location_History")[0];this_input.value="$locationHistoryJSON"'); + print("Location History updated in webview."); + } } class NavigationControls extends StatelessWidget { diff --git a/Space_Mapper/pubspec.lock b/Space_Mapper/pubspec.lock index cd4d9b1e..74fd2631 100644 --- a/Space_Mapper/pubspec.lock +++ b/Space_Mapper/pubspec.lock @@ -28,7 +28,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.8.1" background_fetch: dependency: "direct main" description: @@ -70,7 +70,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.1.0" charcode: dependency: transitive description: @@ -328,7 +328,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.10" meta: dependency: transitive description: @@ -648,21 +648,21 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.17.12" + version: "1.17.10" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" + version: "0.4.2" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.0" transparent_image: dependency: transitive description: @@ -781,28 +781,28 @@ packages: name: webview_flutter url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.3.1" webview_flutter_android: dependency: transitive description: name: webview_flutter_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.15" + version: "2.3.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.5.1" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview url: "https://pub.dartlang.org" source: hosted - version: "2.0.14" + version: "2.4.0" win32: dependency: transitive description: diff --git a/Space_Mapper/pubspec.yaml b/Space_Mapper/pubspec.yaml index b21a09a0..08569bff 100644 --- a/Space_Mapper/pubspec.yaml +++ b/Space_Mapper/pubspec.yaml @@ -12,6 +12,7 @@ description: Flutter version of Space Mapper with 2020 upgrades # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 3.1.0+19 +publish_to: none environment: sdk: ">=2.12.0 <3.0.0" @@ -26,7 +27,7 @@ dependencies: background_fetch: ^1.0.1 http: ^0.13.4 flutter_map: ^0.14.0 - webview_flutter: ^2.1.1 + webview_flutter: ^2.3.1 sqflite: ^2.0.0+4 path_provider: ^2.0.5 share: ^2.0.4 diff --git a/Space_Mapper/test/unit/list_view_test.dart b/Space_Mapper/test/unit/list_view_test.dart index 6fca3138..379466f0 100644 --- a/Space_Mapper/test/unit/list_view_test.dart +++ b/Space_Mapper/test/unit/list_view_test.dart @@ -1,4 +1,4 @@ -import 'package:asm/models/list_view.dart'; +import 'package:asm/models/custom_locations.dart'; import 'package:test/test.dart'; //import 'package:faker/faker.dart'; diff --git a/Space_Mapper/test/unit/mock_survey_test.dart b/Space_Mapper/test/unit/mock_survey_test.dart index 112fe975..62ae65e4 100644 --- a/Space_Mapper/test/unit/mock_survey_test.dart +++ b/Space_Mapper/test/unit/mock_survey_test.dart @@ -15,7 +15,7 @@ void main() { }); test('test fetch', () { - final mockSurvey = MockSurvey.fetch(0); + final mockSurvey = MockSurvey.fetchByID(0); expect(mockSurvey, isNotNull); expect(mockSurvey.name, isNotEmpty); });