Skip to content
Snippets Groups Projects
main_layout.dart 15.1 KiB
Newer Older
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'osm_layer.dart';
import 'stat_charts.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<SubDiv> subdivisions;
  final List<Measurement> measurements;
  final  Uint8List relation;
  final bool serverConnection;
  const MapContainerWidget({Key? key,
    required this.subdivisions,
    required this.measurements,
    required this.relation,
    required this.serverConnection,
  }) : super(key: key);

  @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
    String indexString = index.toString();
      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;
            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(
Sara Savanovic Djordjevic's avatar
Sara Savanovic Djordjevic committed
                      color: Colors.white12,
                  SizedBox( // Color coded lake polygon
                    width: screenWidth * boxWidth,
                    height: screenWidth * boxHeight,
                    child: Padding(
                      padding: const EdgeInsets.all(15.0), // Padding around map
                        key: _choroplethMapKey,
                        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,
Sara Savanovic Djordjevic's avatar
Sara Savanovic Djordjevic committed
                          color: Colors.grey,
Sara Savanovic Djordjevic's avatar
Sara Savanovic Djordjevic committed
                        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);

                      child: Container(
                          padding: const EdgeInsets.all(8),
                          decoration: isSatTapped ? const BoxDecoration(
                            shape: BoxShape.circle,
                            color: Colors.blue,
                          ) : null,
                        child: const Icon(
                          Icons.share,
                          color: Colors.white54,
                  Positioned( // Recenter map button
                    top: 130,
                    right: 10,
                    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
                    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,
                        ),
                      ),
                    ),
                  ),
Sara Savanovic Djordjevic's avatar
Sara Savanovic Djordjevic committed
            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}') : ''}',
Sara Savanovic Djordjevic's avatar
Sara Savanovic Djordjevic committed
                  style: GoogleFonts.dmSans(
                    fontSize: 14,
                    color: Colors.white60,
                  ),
                ),
              ],
            ),
            const SizedBox(height: contPadding), // Padding between containers
            Column( // Ice stats container
              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(),
                          const SizedBox(height: 10),
                          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),