Skip to content
Snippets Groups Projects
Commit ae76be83 authored by Hoa Ben The Nguyen's avatar Hoa Ben The Nguyen
Browse files

merge:

parents 104b221e ea862781
No related branches found
No related tags found
No related merge requests found
Showing
with 75 additions and 140 deletions
app/assets/icons/circle-red.png

160 KiB

app/assets/icons/marker.png

4.7 KiB

...@@ -34,6 +34,7 @@ class Measurement { ...@@ -34,6 +34,7 @@ class Measurement {
class SubDiv { class SubDiv {
String sub_div_id; String sub_div_id;
int groupID; int groupID;
String group_id;
double minThickness; double minThickness;
double avgThickness; double avgThickness;
LatLng center; LatLng center;
...@@ -44,6 +45,7 @@ class SubDiv { ...@@ -44,6 +45,7 @@ class SubDiv {
SubDiv({ SubDiv({
required this.sub_div_id, required this.sub_div_id,
required this.groupID, required this.groupID,
required this.group_id,
required this.minThickness, required this.minThickness,
required this.avgThickness, required this.avgThickness,
required this.center, required this.center,
...@@ -56,6 +58,7 @@ class SubDiv { ...@@ -56,6 +58,7 @@ class SubDiv {
return SubDiv( return SubDiv(
sub_div_id: json['SubdivID'].toString(), sub_div_id: json['SubdivID'].toString(),
groupID: json['GroupID'], groupID: json['GroupID'],
group_id: '', // Initialise empty, will be set later
minThickness: json['MinThickness'], minThickness: json['MinThickness'],
avgThickness: json['AvgThickness'], avgThickness: json['AvgThickness'],
center: LatLng(json['CenLatitude'], json['CenLongitude']), center: LatLng(json['CenLatitude'], json['CenLongitude']),
......
import 'package:flutter/material.dart';
import 'pages/default_page.dart'; import 'pages/default_page.dart';
import 'package:flutter/material.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
......
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:latlong2/latlong.dart';
// API variables
const String port = "8443";
const String serverURI = "https://127.0.0.1:$port/";
const String mapEndpoint = "update_map";
const int fetchInterval = 60; // Fetch marker data every n minutes
// Map variables
LatLng mapCenter = LatLng(60.7666, 10.8471);
DateTime ?lastUpdate; // Last time data was fetched from server
// Font variables
const textColor = Colors.white;
final appTitleStyle = GoogleFonts.dmSans(
fontSize: 35,
color: Colors.black,
fontWeight: FontWeight.bold,
);
final titleStyle = GoogleFonts.dmSans(
fontSize: 35,
color: textColor,
fontWeight: FontWeight.bold,
);
final regTextStyle = GoogleFonts.dmSans(fontSize: 19, color: textColor);
final chartTextStyle = GoogleFonts.dmSans(fontSize: 14, color: textColor);
final subHeadingStyle = GoogleFonts.dmSans(fontSize: 22, color: textColor, fontWeight: FontWeight.bold);
// Colors
const darkBlue = Color(0xFF015E8F);
const teal = Color(0xFF00B4D8);
const darkestBlue = Color(0xFF03045E);
const lightBlue = Color(0xFFCAF0F8);
const superLightBlue = Color(0xFFCAF0F8);
const barBlue = Color(0xFF0077B6);
\ No newline at end of file
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'widgets/map_widget.dart';
import 'marker_handler/marker_data.dart';
import 'consts.dart';
import 'marker_handler/get_markers.dart';
import 'marker_handler/get_relation.dart';
import 'dart:typed_data'; import 'dart:typed_data';
import '../consts.dart';
import '../widgets/main_layout.dart';
import '../data_classes.dart';
import '../server_requests/fetch_markers.dart';
import '../server_requests/fetch_relation.dart';
class DefaultPage extends StatefulWidget { class DefaultPage extends StatefulWidget {
const DefaultPage({Key? key}) : super(key: key); const DefaultPage({Key? key}) : super(key: key);
...@@ -18,6 +19,7 @@ class _DefaultPageState extends State<DefaultPage> { ...@@ -18,6 +19,7 @@ class _DefaultPageState extends State<DefaultPage> {
late Timer _timer; late Timer _timer;
bool showBar = false; bool showBar = false;
bool serverConnection = true; bool serverConnection = true;
bool dialogShown = false;
late Future<List<Measurement>> markerListFuture; late Future<List<Measurement>> markerListFuture;
late Future<Uint8List> relationFuture; late Future<Uint8List> relationFuture;
...@@ -26,35 +28,62 @@ class _DefaultPageState extends State<DefaultPage> { ...@@ -26,35 +28,62 @@ class _DefaultPageState extends State<DefaultPage> {
void initState() { void initState() {
super.initState(); super.initState();
// Try to fetch measurement data from server // Try to fetch measurement data from server
markerListFuture = fetchMarkerData().then((fetchResult) { markerListFuture = fetchMeasurements().then((fetchResult) {
List<Measurement> measurements = fetchResult.measurements; List<Measurement> measurements = fetchResult.measurements;
serverConnection = fetchResult.connected; serverConnection = fetchResult.connected;
// Return the measurements // Return the measurements
return measurements; return measurements;
}).catchError((error) { }).catchError((error) {
serverConnection = false;
throw Exception("Failed to fetch measurements: $error"); throw Exception("Failed to fetch measurements: $error");
}); });
// Attempt to fetch relation from server if app establishes connection
if (serverConnection){
relationFuture = fetchRelation(); relationFuture = fetchRelation();
//relationFuture = Future.value(Uint8List(0)); } else { // Read last saved data
relationFuture = loadSavedRelation();
}
// Schedule fetchMarkerData to run periodically based on fetchInterval from consts // Schedule fetchMarkerData to run periodically based on fetchInterval
const Duration interval = Duration(minutes: fetchInterval); // NB fetchInterval value to be determined const Duration interval = Duration(minutes: fetchInterval); // NB fetchInterval value to be determined
_timer = Timer.periodic(interval, (timer) { _timer = Timer.periodic(interval, (timer) {
fetchMarkerData(); fetchMeasurements();
}); });
} }
// Fetch-interval timer
@override @override
void dispose() { void dispose() {
// Cancel timer on widget termination
_timer.cancel(); _timer.cancel();
super.dispose(); super.dispose();
} }
/// Display message to user
void showConnectionMessage() {
showDialog(
context: context,
builder: (context) => AlertDialog(
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("Ok"),
)
],
title: const Center(
child: Text("No server connection")
),
contentPadding: const EdgeInsets.all(10.0),
content: const Text(
"The app may display outdated information. Use with caution!",
textAlign: TextAlign.center, // Align text center
),
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
...@@ -75,22 +104,36 @@ class _DefaultPageState extends State<DefaultPage> { ...@@ -75,22 +104,36 @@ class _DefaultPageState extends State<DefaultPage> {
body: FutureBuilder( body: FutureBuilder(
future: Future.wait([markerListFuture, relationFuture]), future: Future.wait([markerListFuture, relationFuture]),
builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot) { builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot) {
// Display loading screen until data is fetched // Display loading screen while app state is being set
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: Icon( return Container(
Icons.severe_cold, decoration: const BoxDecoration( // Loading screen
color: Colors.blue, color: darkestBlue,
size: 100, ),
)); child: Center(
child: Image.asset(
'assets/icons/frozen.png', // Loading screen icon
// Icon from: https://www.flaticon.com/free-icons/cold-water"
color: lightBlue,
width: 170,
height: 170,
),
),
);
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}')); return Center(child: Text('Error: ${snapshot.error}'));
} else { } else {
if (!serverConnection) { // NB: implement alert // If no server connection, alert user upon completing build
print("Failed to connect to server"); WidgetsBinding.instance.addPostFrameCallback((_) {
if (!serverConnection && !dialogShown) {
dialogShown = true;
showConnectionMessage();
} }
// Display default page once all data is loaded from server });
List<Measurement> markerList = snapshot.data![0] as List<Measurement>; List<Measurement> markerList = snapshot.data![0] as List<Measurement>;
Uint8List relation = snapshot.data![1] as Uint8List; Uint8List relation = snapshot.data![1] as Uint8List;
return Container( // Return container with list view and background color return Container( // Return container with list view and background color
decoration: const BoxDecoration( // Set background color decoration: const BoxDecoration( // Set background color
gradient: LinearGradient( gradient: LinearGradient(
...@@ -101,7 +144,11 @@ class _DefaultPageState extends State<DefaultPage> { ...@@ -101,7 +144,11 @@ class _DefaultPageState extends State<DefaultPage> {
), ),
child: ListView( child: ListView(
children: [ children: [
MapContainerWidget(markerList: markerList, relation: relation), MapContainerWidget(
markerList: markerList,
relation: relation,
serverConnection: serverConnection,
),
], ],
), ),
); );
...@@ -112,4 +159,3 @@ class _DefaultPageState extends State<DefaultPage> { ...@@ -112,4 +159,3 @@ class _DefaultPageState extends State<DefaultPage> {
); );
} }
} }
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import '../consts.dart';
import 'marker_data.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/material.dart';
class FetchResult {
final List<Measurement> measurements;
final bool connected;
FetchResult(this.measurements, this.connected);
}
// fetchMarkerTemplate requests all marker data from the server
Future<FetchResult> fetchMarkerData() async {
try {
// Custom HTTP client
HttpClient client = HttpClient()
..badCertificateCallback = // NB: temporary disable SSL certificate validation
(X509Certificate cert, String host, int port) => true;
// Request markers from server
var request = await client.getUrl(Uri.parse(serverURI + mapEndpoint));
var response = await request.close(); // Close response body at end of function
// Parse body to JSON if request is ok
if (response.statusCode == 200) {
var responseBody = await response.transform(utf8.decoder).join();
if (responseBody.isNotEmpty) {
var jsonData = json.decode(responseBody);
// Attempt to parse response to Measurement object only if the body
// contains correctly formatted data
if (jsonData != null && jsonData is List) {
Directory appDocumentsDirectory = await getApplicationDocumentsDirectory();
String filePath = '${appDocumentsDirectory.path}/last_data.json';
try { // Write most recent time of update to file
await File(filePath).writeAsString(responseBody, mode: FileMode.write);
print('Lake data written to file');
} catch (error) { print('Error in writing to file: $error');}
// Update local and persistent lastUpdate variable
lastUpdate = DateTime.now();
final prefs = await SharedPreferences.getInstance();
await prefs.setString('lastUpdate', '${DateTime.now()}');
return FetchResult(jsonData.map((data) => Measurement.fromJson(data)).toList(), true);
}
}
}
return loadSavedData();
} catch (e) {
return loadSavedData();
}
}
Future<FetchResult> loadSavedData() async {
// Get latest saved data from file if the server does not respond
Directory appDocumentsDirectory = await getApplicationDocumentsDirectory();
String filePath = '${appDocumentsDirectory.path}/last_data.json';
// Read file contents
File file = File(filePath);
if (await file.exists()) {
String contents = await file.readAsString();
List<dynamic> jsonData = json.decode(contents); // Parse JSON string from file
List<Measurement> measurements = jsonData.map((data) => Measurement.fromJson(data)).toList();
return FetchResult(measurements, false);
} else {
throw Exception('File does not exist');
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment