Skip to content

Commit 3df95e2

Browse files
authored
Merge pull request #30 from ActivitySpaceProject/development
[Take Survey] Share location history with researchers when user agrees to it
2 parents 2073deb + 8652d51 commit 3df95e2

File tree

14 files changed

+288
-65
lines changed

14 files changed

+288
-65
lines changed

Space_Mapper/lang/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@
2323
"select_age_group": "Select age group",
2424
"submit": "Submit",
2525
"reset": "Reset",
26-
"gender": "gender"
26+
"gender": "gender",
27+
"about_the_project": "About the project",
28+
"consent_form" : "Consent Form",
29+
"do_you_agree_to_share_your_anonymous_location_with" : "Do you agree to share your anonymous locations to "
2730
}

Space_Mapper/lang/es.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@
2323
"select_age_group": "Selecciona grupo de edad",
2424
"submit": "Enviar",
2525
"reset": "Restablecer",
26-
"gender": "género"
26+
"gender": "género",
27+
"about_the_project": "Sobre el proyecto",
28+
"consent_form" : "Consentimiento",
29+
"do_you_agree_to_share_your_anonymous_location_with" : "¿Estás de acuerdo en compartir tu historial de ubicaciones anónimo con "
2730
}

Space_Mapper/lib/mocks/mock_survey.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,24 @@ import '../models/survey.dart';
33
mixin MockSurvey implements Survey {
44
static final List<Survey> items = [
55
Survey(
6-
1,
6+
0,
77
"Mosquito Alert",
88
"https://play.google.com/store/apps/details?id=ceab.movelab.tigatrapp&hl=es&gl=US",
99
"https://www.periodismociudadano.com/wp-content/uploads/2020/11/mosquito-49141_640.jpg",
1010
"Mosquito Alert is a citizen science platform for studying and controlling the tiger mosquito (Aedes albopictus) and the yellow fever mosquito (Aedes aegypti).",
1111
),
1212
Survey(
13-
2,
13+
1,
1414
"Max Planck Institute",
1515
"https://www.mpg.de/institutes",
1616
"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",
1717
"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.",
1818
),
1919
Survey(
20-
3,
20+
2,
2121
"Space Mapper Form Test",
22-
"https://ee.kobotoolbox.org/single/asCwpCjZ",
22+
//"https://ee.kobotoolbox.org/single/asCwpCjZ",
23+
"https://ee.kobotoolbox.org/x/AG5j1vFN",
2324
"https://raw.githubusercontent.com/ActivitySpaceProject/space_mapper/master/Assets/images/3.0.2%2B18_screenshots.png",
2425
"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.",
2526
)
@@ -33,7 +34,7 @@ mixin MockSurvey implements Survey {
3334
return items;
3435
}
3536

36-
static Survey fetch(int index) {
37+
static Survey fetchByID(int index) {
3738
return items[index];
3839
}
3940
}

Space_Mapper/lib/models/list_view.dart renamed to Space_Mapper/lib/models/custom_locations.dart

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ class CustomLocationsManager {
2929
continue; //CustomLocationsManager.customLocations[j].getUUID()) continue;
3030
}
3131
// Match not found, we add the location
32-
CustomLocation newLocation = await CustomLocationsManager.createCustomLocation(
33-
recordedLocations[i]
34-
);
32+
CustomLocation newLocation =
33+
await CustomLocationsManager.createCustomLocation(
34+
recordedLocations[i]);
3535
customLocations.add(newLocation);
3636
}
3737
return customLocations;
@@ -54,7 +54,7 @@ class CustomLocationsManager {
5454

5555
static Future<CustomLocation> createCustomLocation(
5656
var recordedLocation) async {
57-
CustomLocation location = new CustomLocation();
57+
CustomLocation location = new CustomLocation();
5858

5959
//Save data from flutter_background_geolocation library
6060
location.setUUID(recordedLocation['uuid']);
@@ -65,8 +65,9 @@ class CustomLocationsManager {
6565
location.setAltitude(recordedLocation['coords']['altitude'],
6666
recordedLocation['coords']['altitude_accuracy']);
6767

68-
Placemark? placemark = await getLocationData(recordedLocation['coords']['latitude'],
69-
recordedLocation['coords']['longitude']);
68+
Placemark? placemark = await getLocationData(
69+
recordedLocation['coords']['latitude'],
70+
recordedLocation['coords']['longitude']);
7071

7172
//Add our custom data
7273
if (placemark != null) {
@@ -84,6 +85,7 @@ class CustomLocationsManager {
8485
}
8586
return location;
8687
}
88+
8789
///Get data such as city, province, postal code, street name, country...
8890
static Future<Placemark?> getLocationData(double lat, double long) async {
8991
try {
@@ -98,6 +100,23 @@ class CustomLocationsManager {
98100
}
99101
}
100102

103+
/// This class should be used to share your location history to other people
104+
class ShareLocation {
105+
late final String _timestamp;
106+
final double _lat;
107+
final double _long;
108+
109+
ShareLocation(this._timestamp, this._lat, this._long);
110+
111+
Map<String, dynamic> toJson() => {
112+
'timestamp': _timestamp,
113+
'coords': {
114+
'latitude': _lat,
115+
'longitude': _long,
116+
}
117+
};
118+
}
119+
101120
class CustomLocation {
102121
late final String _uuid;
103122
late String _locality = "";
@@ -228,4 +247,4 @@ class CustomLocation {
228247
num getAltitudeAcc() {
229248
return _altitude;
230249
}
231-
}
250+
}

Space_Mapper/lib/models/survey.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ class Survey {
1010
: id = 0,
1111
name = ' ',
1212
webUrl = ' ',
13-
imageUrl = ' ',
13+
imageUrl =
14+
'', // Leave this without space ('' instead of ' ') to avoid an exception
1415
summary = ' ';
1516

1617
static Future<List<Survey>> fetchAll() async {

Space_Mapper/lib/ui/list_view.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import '../app_localizations.dart';
2-
import '../models/list_view.dart';
2+
import '../models/custom_locations.dart';
33
import 'package:flutter/material.dart';
44

55
class STOListView extends StatefulWidget {
@@ -9,8 +9,7 @@ class STOListView extends StatefulWidget {
99
_STOListViewState createState() => _STOListViewState();
1010
}
1111

12-
class _STOListViewState extends State<STOListView> {
13-
12+
class _STOListViewState extends State<STOListView> {
1413
@override
1514
void initState() {
1615
super.initState();

Space_Mapper/lib/ui/side_drawer.dart

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'dart:convert';
22
import 'package:asm/app_localizations.dart';
3-
import 'package:asm/models/list_view.dart';
3+
import 'package:asm/models/custom_locations.dart';
44
import 'package:asm/ui/list_view.dart';
55
import 'package:asm/ui/report_an_issue.dart';
66
import 'package:flutter/material.dart';
@@ -9,25 +9,7 @@ import 'package:share/share.dart';
99
import 'package:flutter_background_geolocation/flutter_background_geolocation.dart'
1010
as bg;
1111

12-
import 'available_surveys.dart';
13-
14-
class ShareLocation {
15-
late final String _timestamp;
16-
final double _lat;
17-
final double _long;
18-
19-
ShareLocation(timestamp, this._lat, this._long) {
20-
_timestamp = CustomLocationsManager.formatTimestamp(timestamp);
21-
}
22-
23-
Map<String, dynamic> toJson() => {
24-
'timestamp': _timestamp,
25-
'coords': {
26-
'latitude': _lat,
27-
'longitude': _long,
28-
}
29-
};
30-
}
12+
import 'surveys_list.dart';
3113

3214
class SpaceMapperSideDrawer extends StatelessWidget {
3315
_shareLocations() async {
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import 'dart:convert';
2+
3+
import 'package:flutter_background_geolocation/flutter_background_geolocation.dart'
4+
as bg;
5+
import 'package:flutter/material.dart';
6+
7+
import '../app_localizations.dart';
8+
import '../components/banner_image.dart';
9+
import '../components/survey_tile.dart';
10+
import '../mocks/mock_survey.dart';
11+
import '../models/survey.dart';
12+
import '../models/custom_locations.dart';
13+
import '../ui/web_view.dart';
14+
import '../styles.dart';
15+
16+
const BannerImageHeight = 300.0;
17+
const BodyVerticalPadding = 20.0;
18+
const FooterHeight = 100.0;
19+
20+
class SurveyDetail extends StatefulWidget {
21+
final int surveyID;
22+
23+
SurveyDetail(this.surveyID);
24+
25+
@override
26+
_SurveyDetailState createState() => _SurveyDetailState(surveyID);
27+
}
28+
29+
class _SurveyDetailState extends State<SurveyDetail> {
30+
final int surveyID;
31+
Survey survey = Survey.blank();
32+
bool consent = false;
33+
34+
_SurveyDetailState(this.surveyID);
35+
36+
@override
37+
void initState() {
38+
super.initState();
39+
loadData();
40+
}
41+
42+
@override
43+
Widget build(BuildContext context) {
44+
return Scaffold(
45+
appBar: AppBar(
46+
title: Text(
47+
AppLocalizations.of(context)!.translate("about_the_project"))),
48+
body: Stack(
49+
children: [
50+
_renderBody(context, survey),
51+
_renderFooter(context),
52+
//_renderConsentForm(),
53+
],
54+
),
55+
);
56+
}
57+
58+
loadData() {
59+
final survey = MockSurvey.fetchByID(this.surveyID);
60+
61+
if (mounted) {
62+
setState(() {
63+
this.survey = survey;
64+
});
65+
}
66+
}
67+
68+
Widget _renderBody(BuildContext context, Survey survey) {
69+
var result = <Widget>[];
70+
result.add(BannerImage(url: survey.imageUrl, height: BannerImageHeight));
71+
result.add(_renderHeader());
72+
result.add(_renderConsentForm());
73+
result.add(_renderBottomSpacer());
74+
return SingleChildScrollView(
75+
child: Column(
76+
mainAxisAlignment: MainAxisAlignment.start,
77+
crossAxisAlignment: CrossAxisAlignment.stretch,
78+
children: result));
79+
}
80+
81+
Widget _renderHeader() {
82+
return Container(
83+
padding: EdgeInsets.symmetric(
84+
vertical: BodyVerticalPadding,
85+
horizontal: Styles.horizontalPaddingDefault),
86+
child: SurveyTile(survey: survey, darkTheme: false),
87+
);
88+
}
89+
90+
Widget _renderFooter(BuildContext contexty) {
91+
return Column(
92+
mainAxisAlignment: MainAxisAlignment.end,
93+
crossAxisAlignment: CrossAxisAlignment.stretch,
94+
children: [
95+
Container(
96+
decoration: BoxDecoration(color: Colors.white.withOpacity(0.5)),
97+
height: FooterHeight,
98+
child: Container(
99+
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 30.0),
100+
child: _renderTakeSurveyButton(),
101+
),
102+
)
103+
],
104+
);
105+
}
106+
107+
Widget _renderConsentForm() {
108+
String title = AppLocalizations.of(context)!.translate("consent_form");
109+
String text = AppLocalizations.of(context)!.translate("do_you_agree_to_share_your_anonymous_location_with") + "${survey.name}?";
110+
111+
return Container(
112+
height: SurveyTileHeight,
113+
padding: EdgeInsets.symmetric(
114+
//vertical: BodyVerticalPadding,
115+
horizontal: Styles.horizontalPaddingDefault),
116+
child: Column(
117+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
118+
crossAxisAlignment: CrossAxisAlignment.start,
119+
children: [
120+
Text('$title',
121+
overflow: TextOverflow.ellipsis,
122+
maxLines: 1,
123+
style: Styles.surveyTileTitleLight),
124+
Row(
125+
children: [
126+
Checkbox(
127+
value: consent,
128+
onChanged: (bool? newValue) {
129+
setState(() {
130+
consent = newValue!;
131+
});
132+
},
133+
),
134+
Expanded(
135+
child: Text('$text',
136+
overflow: TextOverflow.ellipsis,
137+
maxLines: 3,
138+
style: Styles.surveyTileCaption),
139+
)
140+
],
141+
),
142+
],
143+
),
144+
);
145+
}
146+
147+
Widget _renderTakeSurveyButton() {
148+
return TextButton(
149+
//color: Styles.accentColor,
150+
//textColor: Styles.textColorBright,
151+
style: ButtonStyle(
152+
backgroundColor: MaterialStateProperty.all(Colors.blue),
153+
),
154+
onPressed: () => {
155+
_navigationToSurvey(context),
156+
},
157+
child: Text(
158+
'Take Survey'.toUpperCase(),
159+
style: Styles.textCTAButton,
160+
),
161+
);
162+
}
163+
164+
Future<void> _navigationToSurvey(BuildContext context) async {
165+
String locationHistoryJSON = "";
166+
167+
// If we have consent, send location history. Otherwise, send empty string
168+
if (consent) {
169+
List allLocations = await bg.BackgroundGeolocation.locations;
170+
List<ShareLocation> customLocation = [];
171+
172+
// We get only timestamp and coordinates into our custom class
173+
for (var thisLocation in allLocations) {
174+
ShareLocation _loc = new ShareLocation(
175+
bg.Location(thisLocation).timestamp,
176+
bg.Location(thisLocation).coords.latitude,
177+
bg.Location(thisLocation).coords.longitude);
178+
customLocation.add(_loc);
179+
}
180+
181+
locationHistoryJSON = jsonEncode(customLocation);
182+
locationHistoryJSON = locationHistoryJSON.replaceAll("\"",
183+
"'"); //We replace " into ' to avoid a javascript exception when we post it in the webview's form
184+
} else {
185+
locationHistoryJSON = "I do not agree to share my location history.";
186+
}
187+
188+
Navigator.push(
189+
context,
190+
MaterialPageRoute(
191+
builder: (context) =>
192+
MyWebView(survey.webUrl, locationHistoryJSON)));
193+
}
194+
195+
Widget _renderBottomSpacer() {
196+
return Container(height: FooterHeight);
197+
}
198+
}

0 commit comments

Comments
 (0)