Newer
Older
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'info_layer.dart';
import '../../consts.dart';
import 'choropleth_map.dart';
import '../data_classes.dart';
import '../utils/export_data.dart';
import '../utils/format_month.dart';
/// MapContainerWidget is the main widget that contains the map with all
/// its layers, polygons and markers.
class MapContainerWidget extends StatefulWidget {
final List<Measurement> measurements;
const MapContainerWidget({Key? key,
required this.measurements,
required this.serverConnection,
@override
_MapContainerWidgetState createState() => _MapContainerWidgetState();
}
class _MapContainerWidgetState extends State<MapContainerWidget> {
// Button state trackers
bool isMinimized = true; // Quick view box state tacker
bool satLayer = false; // Satellite layer visibility state
bool osmLayer = false; // OSM layer visibility state
bool isSatTapped = false; // Satellite button tap state tracker
bool isMapTapped = false; // OSM button tap state tracker
bool infoLayer = false; // Additional color information visibility
bool centerMap = false; // Map centering button state tracker
// Initialise selected measurement to arbitrary value
Measurement? selectedMeasurement = selectedMeasurements[0];
final GlobalKey<ChoroplethMapState> _choroplethMapKey = GlobalKey();
/// Initialise lastUpdate variable from persistent storage if server fetch fails
Future<void> checkAndSetLastUpdate() async {
if (lastUpdate == null) {
final prefs = await SharedPreferences.getInstance();
final updateString = prefs.getString('lastUpdate');
if (updateString != null && updateString.isNotEmpty) {
final updateData = DateTime.parse(updateString);
setState(() {
lastUpdate = updateData;
});
}
}
}
/// Tile selection handler
void handleSelection(int index) {
setState(() {
selectedSubDiv = widget.subdivisions[index];
for (Measurement measurement in widget.measurements) {
for (SubDiv subdivision in measurement.subDivs) {
if (subdivision.sub_div_id == indexString) {
selectedMeasurement = measurement;
});
}
@override
Widget build(BuildContext context) {
// Initialise selectedMarker to first element in markerList
selectedSubDiv ??= widget.measurements[0].subDivs[0];
checkAndSetLastUpdate();
const double contPadding = 30; // Container padding space
return LayoutBuilder(
builder: (context, constraints) {
double screenWidth = constraints.maxWidth;
double boxWidth = 0.86;
double boxHeight = 1.4;
return Column(
children: [
const SizedBox(height: contPadding*1.5),
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack( // Stack of quick view, map layer, satellite layer, and buttons
SizedBox( // Colored box behind map
width: screenWidth * boxWidth,
height: screenWidth * boxHeight,
child: Container(
width: screenWidth * boxWidth,
height: screenWidth * boxHeight,
child: Padding(
padding: const EdgeInsets.all(15.0), // Padding around map
child: ChoroplethMap(
key: _choroplethMapKey,
relation: widget.relation,
measurements: widget.measurements,
subdivisions: widget.subdivisions,
onSelectionChanged: handleSelection,
),
SizedBox(
width: screenWidth * boxWidth,
height: screenWidth * boxHeight,
child: Visibility(
visible: osmLayer,
child: OSM(markerList: widget.measurements),
SizedBox(
width: screenWidth * boxWidth,
height: screenWidth * boxHeight,
child: Visibility(
visible: infoLayer,
child: const InfoLayer(),
),
),
Positioned( // Satellite button
top: 10,
right: 10,
child: GestureDetector(
onTap: () {
setState(() {
satLayer = !satLayer; // Toggle satellite layer state on press
osmLayer = false; // Turn OSM layer off if it was turned on
});
},
child: Container(
padding: const EdgeInsets.all(8),
decoration: satLayer ? const BoxDecoration( // Add decoration only when pressed
shape: BoxShape.circle,
child: const Icon(
Icons.satellite_alt_outlined,
color: Colors.white54,
),
Positioned( // OSmap button
top: 50,
right: 10,
child: GestureDetector(
onTap: () {
setState(() {
osmLayer = !osmLayer; // Toggle satellite layer state on press
satLayer = false; // Turn sat layer off if it was turned on
});
},
child: Container(
padding: const EdgeInsets.all(8),
decoration: osmLayer ? const BoxDecoration( // Add decoration only when pressed
shape: BoxShape.circle,
color: Colors.grey,
) : null,
child: const Icon(
Icons.map,
color: Colors.white54,
),
),
),
),
Positioned( // Export button
top: 90,
right: 10,
child: GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return SizedBox(
height: 200,
width: screenWidth,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Export ice data for $selectedLake",
style: const TextStyle(fontSize: 20),
),
const SizedBox(height: contPadding),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Colors.white24),
),
child: const Text("Export JSON"),
onPressed: () {
// Close bottom sheet before displaying progress bar
showProgressIndicator(context);
decoration: isSatTapped ? const BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue,
) : null,
child: const Icon(
Icons.share,
color: Colors.white54,
Positioned( // Recenter map button
child: GestureDetector(
onTapDown: (_) {
setState(() {
centerMap = true;
});
},
onTapUp: (_) {
setState(() {
centerMap = false;
_choroplethMapKey.currentState?.resetZoom();
});
},
onTapCancel: () {
setState(() {
centerMap = false;
});
},
child: Container(
padding: const EdgeInsets.all(8),
decoration: centerMap ? const BoxDecoration( // Add decoration only while pressed
shape: BoxShape.circle,
color: Colors.grey,
) : null,
child: const Icon(
Icons.center_focus_strong,
color: Colors.white54,
),
),
),
),
Positioned( // Color info layer button
top: 170,
right: 10,
child: GestureDetector(
onTap: () {
setState(() {
infoLayer = !infoLayer; // Toggle satellite layer state on press
});
},
child: Container(
padding: const EdgeInsets.all(8),
decoration: infoLayer ? const BoxDecoration( // Add decoration only when pressed
shape: BoxShape.circle,
color: Colors.grey,
) : null,
child: const Icon(
Icons.info,
color: Colors.white54,
),
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 5),
Text( // Display time of most recent server fetch
'Last updated at ${lastUpdate != null ?
(lastUpdate?.day == DateTime.now().day &&
lastUpdate?.month == DateTime.now().month &&
lastUpdate?.year == DateTime.now().year ?
'${lastUpdate?.hour}:${lastUpdate?.minute}' :
'${lastUpdate?.day}.${formatMonth(lastUpdate!.month)} ${lastUpdate?.year}') : ''}',
style: GoogleFonts.dmSans(
fontSize: 14,
color: Colors.white60,
),
),
],
),
const SizedBox(height: contPadding), // Padding between containers
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: screenWidth * boxWidth,
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(top: 20, left: 30), // Custom padding
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Ice stats',
style: titleStyle,
),
const Divider(),
Text(
'Tile ID: ${selectedSubDiv?.sub_div_id}',
style: regTextStyle,
),
const SizedBox(height: 20),
Text(
'Measured at ',
style: subHeadingStyle,
),
Text(
'Date: ${(selectedMeasurement?.timeMeasured.day ?? '-')}/${(selectedMeasurement?.timeMeasured.month ?? '-')}/${(selectedMeasurement?.timeMeasured.year ?? '-')}',
style: regTextStyle,
),
Text(
'Time: ${selectedMeasurement?.timeMeasured.hour}:00',
style: regTextStyle,
),
const SizedBox(height: contPadding),
Text(
'Center coordinate: (${selectedSubDiv?.center.latitude}, ${selectedSubDiv?.center.longitude})',
style: regTextStyle,
),
const SizedBox(height: contPadding/3),
Text(
'Data certainty: ${selectedSubDiv?.accuracy}/4',
style: subHeadingStyle,
],
),
),
),
),
const SizedBox(height: contPadding*2.5),
SizedBox(
width: screenWidth * boxWidth * 1.2,
child: const StatCharts(),
const SizedBox(height: contPadding*4),
],
);
},