import 'dart:typed_data'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'satellite_layer.dart'; import 'osm_layer.dart'; import 'stat_charts.dart'; import '../../consts.dart'; import 'choropleth_map.dart'; import '../data_classes.dart'; import 'quick_view_chart.dart'; /// MapContainerWidget is the main widget that contains the map with all /// its layers, polygons and markers. class MapContainerWidget extends StatefulWidget { final List<Measurement> markerList; final Uint8List relation; final bool serverConnection; const MapContainerWidget({Key? key, required this.markerList, required this.relation, required this.serverConnection, }) : super(key: key); @override _MapContainerWidgetState createState() => _MapContainerWidgetState(); } class _MapContainerWidgetState extends State<MapContainerWidget> { Measurement? selectedTile; // Containing data for selected marker bool isMinimized = true; // Quick view box state tacker bool satLayer = false; // Satellite layer visibility tracker bool isTapped = false; // Button tap state tracker // 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) { String indexString = index.toString(); setState(() { // NB should be optimalised for (Measurement measurement in widget.markerList) { for (SubDiv subdivision in measurement.subDivs) { if (subdivision.sub_div_id == indexString) { selectedTile= widget.markerList[index]; break; } } } }); } @override Widget build(BuildContext context) { // Initialise selectedMarker to first element in markerList selectedTile ??= widget.markerList[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), /*if (true) NB: add search bar const SearchBar(), const SizedBox(height: contPadding),*/ ClipRRect( borderRadius: BorderRadius.circular(20), child: Stack( // Stack of quick view, map layer, satellite layer, and buttons children: [ SizedBox( // Colored box behind map width: screenWidth * boxWidth, height: screenWidth * boxHeight, child: Container( color: Colors.white12, ), ), SizedBox( width: screenWidth * boxWidth, height: screenWidth * boxHeight, child: Stack( children: [ OSM(markerList: widget.markerList), // OSM widget as the bottom layer Positioned.fill( child: Container( color: Colors.grey.shade900.withOpacity(0.35), // Grey box with 50% opacity // Additional content or widgets can be added here ), ), ], ), ), SizedBox( // Color coded lake polygon width: screenWidth * boxWidth, height: screenWidth * boxHeight, child: Padding( padding: const EdgeInsets.all(15.0), // Padding around map child: ChoroplethMap( relation: widget.relation, measurements: widget.markerList, onSelectionChanged: handleSelection,), ), ), Positioned( // Satellite button top: 10, right: 10, child: GestureDetector( onTap: () { setState(() { satLayer = !satLayer; // Toggle satellite layer state on press }); }, child: Container( padding: const EdgeInsets.all(8), decoration: satLayer ? const BoxDecoration( // Add decoration only when pressed shape: BoxShape.circle, color: Colors.grey, ) : null, child: const Icon( Icons.satellite_alt_outlined, color: Colors.white54, ), ), ), ), Positioned( // No wifi icon top: 80, right: 10, child: GestureDetector( onTapDown: (_) { setState(() { // Add functionality }); }, onTapUp: (_) { setState(() { // Add functionality }); }, onTapCancel: () { setState(() { // Add functionality }); }, child: Visibility( // Show icon only when no server connection visible: !widget.serverConnection, child: Container( padding: const EdgeInsets.all(8), decoration: isTapped ? const BoxDecoration( shape: BoxShape.circle, color: Colors.blue, ) : null, child: const Icon(Icons.perm_scan_wifi, color: Color(0xFF5B0000)), ), ), ), ), ], ), ), 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}-${lastUpdate?.month}-${lastUpdate?.year}') : ''}', style: GoogleFonts.dmSans( fontSize: 14, color: Colors.white60, ), ), ], ), const SizedBox(height: contPadding), // Padding between containers ClipRRect( borderRadius: BorderRadius.circular(20), child: SizedBox( width: screenWidth * boxWidth, height: screenWidth * boxHeight * 1.5, // NB: make dynamic child: Align( alignment: Alignment.topLeft, child: Padding( padding: const EdgeInsets.only(top: 20, left: 20), // Edge padding, text child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Ice stats', style: titleStyle, ), const Divider(), const Padding(padding: EdgeInsets.all(10)), Text( 'Measured at ', style: subHeadingStyle, ), Text( 'Date: ${(selectedTile?.timeMeasured.day ?? '-')}/${(selectedTile?.timeMeasured.month ?? '-')}/${(selectedTile?.timeMeasured.year ?? '-')}', style: regTextStyle, ), Text( 'Time: ${selectedTile?.timeMeasured.hour}:00', style: regTextStyle, ), const SizedBox(height: contPadding), Text( 'Measuring point: (${selectedTile?.measurementID}, ${selectedTile?.measurementID})', style: regTextStyle, ), const SizedBox(height: contPadding), const SizedBox(height: 15), const StatCharts(), ], ), ), ), ), ), ], ); }, ); } }