Skip to content
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ If this project is useful for you, please consider starring this repository and

## Contributors
- Universitat Pompeu Fabra
- John Palmer
- John R.B. Palmer
- [github.com/johnpalmer](https://github.com/johnpalmer)
- CEAB-CSIC
- Pablo Galve Millán
Expand All @@ -44,7 +44,7 @@ This repository contains the source code development version of Space Mapper. Pr

This project is licensed under the [GNU GENERAL PUBLIC LICENSE](https://github.com/ActivitySpaceProject/space_mapper/blob/master/LICENSE)

Copyright 2020 John R.B. Palmer
Copyright 2021 John R.B. Palmer and Pablo Galve Millán

Space Mapper is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Expand Down
24 changes: 24 additions & 0 deletions Space_Mapper/lib/components/banner_image.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:flutter/material.dart';

class BannerImage extends StatelessWidget {
final String url;
final double height;

BannerImage({required this.url, required this.height});

@override
Widget build(BuildContext context) {
if (url.isEmpty) return Container();
Image image;
try {
image = Image.network(url, fit: BoxFit.cover);
return Container(
constraints: BoxConstraints.expand(height: height),
child: image,
);
} catch (e) {
print('could not load image $url');
return Container();
}
}
}
40 changes: 40 additions & 0 deletions Space_Mapper/lib/components/survey_tile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';

import '../models/survey.dart';
import '../styles.dart';

const SurveyTileHeight = 100.0;

class SurveyTile extends StatelessWidget {
final Survey survey;
final bool darkTheme;

SurveyTile({required this.survey, required this.darkTheme});

@override
Widget build(BuildContext context) {
final title = survey.name.toUpperCase();
final summary = survey.summary;

return Container(
padding: EdgeInsets.all(0),
height: SurveyTileHeight,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('$title',
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: this.darkTheme
? Styles.surveyTileTitleDark
: Styles.surveyTileTitleLight),
Text('$summary',
overflow: TextOverflow.ellipsis,
maxLines: 3,
style: Styles.surveyTileCaption),
],
),
);
}
}
39 changes: 39 additions & 0 deletions Space_Mapper/lib/mocks/mock_survey.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import '../models/survey.dart';

mixin MockSurvey implements Survey {
static final List<Survey> items = [
Survey(
1,
"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,
"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,
"Space Mapper Form Test",
"https://ee.kobotoolbox.org/single/asCwpCjZ",
"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.",
)
];

static Survey fetchFirst() {
return items[0];
}

static fetchAll() {
return items;
}

static Survey fetch(int index) {
return items[index];
}
}
21 changes: 21 additions & 0 deletions Space_Mapper/lib/models/survey.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class Survey {
final int id;
final String name;
final String webUrl;
final String imageUrl;
final String summary;
Survey(this.id, this.name, this.webUrl, this.imageUrl, this.summary);

Survey.blank()
: id = 0,
name = ' ',
webUrl = ' ',
imageUrl = ' ',
summary = ' ';

static Future<List<Survey>> fetchAll() async {
// TODO: In a future version we'll fetch data from a web app
List<Survey> list = [];
return list;
}
}
62 changes: 62 additions & 0 deletions Space_Mapper/lib/styles.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';

class Styles {
static const _textSizeLarge = 25.0;
static const _textSizeDefault = 16.0;
static const _textSizeSmall = 12.0;
static const horizontalPaddingDefault = 12.0;
static final Color _textColorStrong = _hexToColor('000000');
static final Color _textColorDefault = _hexToColor('666666');
static final Color _textColorFaint = _hexToColor('999999');
static final Color textColorBright = _hexToColor('FFFFFF');
static final Color accentColor = _hexToColor('FF0000');
static final String _fontNameDefault = 'Montserrat';
static final navBarTitle = TextStyle(
fontFamily: _fontNameDefault,
fontWeight: FontWeight.w600,
fontSize: _textSizeDefault,
color: _textColorDefault,
);
static final headerLarge = TextStyle(
fontFamily: _fontNameDefault,
fontSize: _textSizeLarge,
color: _textColorStrong,
);
static final textDefault = TextStyle(
fontFamily: _fontNameDefault,
fontSize: _textSizeDefault,
color: _textColorDefault,
);

static final textCTAButton = TextStyle(
fontFamily: _fontNameDefault,
fontSize: _textSizeLarge,
color: textColorBright,
);

static final surveyTileTitleLight = TextStyle(
fontFamily: _fontNameDefault,
fontSize: _textSizeLarge,
color: _textColorStrong,
);
static final surveyTileTitleDark = TextStyle(
fontFamily: _fontNameDefault,
fontSize: _textSizeLarge,
color: textColorBright,
);

static final surveyTileSubTitle = TextStyle(
fontFamily: _fontNameDefault,
fontSize: _textSizeDefault,
color: accentColor,
);
static final surveyTileCaption = TextStyle(
fontFamily: _fontNameDefault,
fontSize: _textSizeSmall,
color: _textColorFaint,
);

static Color _hexToColor(String code) {
return Color(int.parse(code.substring(0, 6), radix: 16) + 0xFF000000);
}
}
106 changes: 106 additions & 0 deletions Space_Mapper/lib/ui/available_surveys.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import 'package:asm/ui/web_view.dart';
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 '../ui/web_view.dart';
import '../styles.dart';

const ListItemHeight = 245.0;

class AvailableSurveysScreen extends StatefulWidget {
@override
_AvailableSurveysScreenState createState() => _AvailableSurveysScreenState();
}

class _AvailableSurveysScreenState extends State<AvailableSurveysScreen> {
List<Survey> surveys = [];
bool loading = false;

@override
void initState() {
super.initState();
loadData();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:
Text(AppLocalizations.of(context)!.translate("take_survey"))),
body: RefreshIndicator(
onRefresh: loadData,
child: Column(
children: [
renderProgressBar(context),
Expanded(child: renderListView(context))
],
),
));
}

Future<void> loadData() async {
if (this.mounted) {
setState(() => this.loading = true);
final surveys = await MockSurvey.fetchAll();
setState(() {
this.surveys = surveys;
this.loading = false;
});
}
}

Widget renderProgressBar(BuildContext context) {
return (this.loading
? LinearProgressIndicator(
value: null,
backgroundColor: Colors.white,
valueColor: AlwaysStoppedAnimation<Color>(Colors.grey))
: Container());
}

Widget renderListView(BuildContext context) {
return ListView.builder(
itemCount: this.surveys.length, itemBuilder: _listViewItemBuilder);
}

Widget _listViewItemBuilder(BuildContext context, int index) {
final survey = this.surveys[index];
return GestureDetector(
onTap: () => _navigationToLocationDetail(context, survey.webUrl),
child: Container(
height: ListItemHeight,
child: Stack(
children: [
BannerImage(url: survey.imageUrl, height: 300.0),
_tileFooter(survey),
],
),
),
);
}

void _navigationToLocationDetail(BuildContext context, String surveyWebUrl) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => MyWebView(surveyWebUrl)));
}

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,
);
return Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [overlay],
);
}
}
9 changes: 6 additions & 3 deletions Space_Mapper/lib/ui/side_drawer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import 'package:asm/app_localizations.dart';
import 'package:asm/models/list_view.dart';
import 'package:asm/ui/list_view.dart';
import 'package:asm/ui/report_an_issue.dart';
import 'package:asm/ui/web_view.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
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;
Expand Down Expand Up @@ -82,8 +83,10 @@ class SpaceMapperSideDrawer extends StatelessWidget {
title: Text(
AppLocalizations.of(context)!.translate("take_survey")),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => MyWebView()));
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AvailableSurveysScreen()));
})),
Card(
child: ListTile(
Expand Down
10 changes: 7 additions & 3 deletions Space_Mapper/lib/ui/web_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:webview_flutter/webview_flutter.dart';

const kAndroidUserAgent =
'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36';
const String selectedUrl = 'https://ee.kobotoolbox.org/single/asCwpCjZ';

const String userUUID_element = '/asRrkkAw4mUtpTDkjdzZzt/group_survey/userUUID';
const String userUUID_label = userUUID_element + ':label';

Expand All @@ -17,15 +17,19 @@ final Set<JavascriptChannel> jsChannels = [
}),
].toSet();

// ignore: must_be_immutable
class MyWebView extends StatefulWidget {
String selectedUrl;
MyWebView(this.selectedUrl);
@override
_MyWebViewState createState() => _MyWebViewState();
_MyWebViewState createState() => _MyWebViewState(selectedUrl);
}

class _MyWebViewState extends State<MyWebView> {
String selectedUrl;
final Completer<WebViewController> _controller =
Completer<WebViewController>();

_MyWebViewState(this.selectedUrl);
@override
void initState() {
super.initState();
Expand Down
22 changes: 22 additions & 0 deletions Space_Mapper/test/unit/mock_survey_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:asm/mocks/mock_survey.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
test('test fetchAny', () {
final mockSurvey = MockSurvey.fetchFirst();
expect(mockSurvey, isNotNull);
expect(mockSurvey.name, isNotEmpty);
});

test('test fetchAll', () {
final mockSurvey = MockSurvey.fetchAll();
expect(mockSurvey.length, greaterThan(0));
expect(mockSurvey[0].name, isNotEmpty);
});

test('test fetch', () {
final mockSurvey = MockSurvey.fetch(0);
expect(mockSurvey, isNotNull);
expect(mockSurvey.name, isNotEmpty);
});
}