import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; // Import LatLng class from the latlong package import 'dart:async'; import 'dart:io'; import 'dart:convert'; const String port = "8443"; const String serverURI = "https://127.0.0.1:$port/"; const String mapEndpoint = "update_map"; // NB: if http connection fails, run: adb reverse tcp:8443 tcp:8443 const int fetchInterval = 5; // main is the entry point for the application, and starts the App() function void main() { runApp(const App()); } class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: DefaultPage(), ); } } class DefaultPage extends StatefulWidget { const DefaultPage({Key? key}) : super(key: key); @override _DefaultPageState createState() => _DefaultPageState(); } // MarkerData holds data for the dynamicllay allocated markers class MarkerData { final LatLng location; final double size; final Color color; MarkerData({required this.location, required this.size, required this.color}); } // parseMarkerData parses jsonData into an object of type MakerData List<MarkerData> parseMarkerData(String jsonString) { final parsed = json.decode(jsonString); return List<MarkerData>.from(parsed.map((data) => MarkerData( location: LatLng(data['latitude'], data['longitude']), size: data['size'].toDouble(), color: parseColor(data['color']), ))); } // parseColor parses the color strings into Colors types Color parseColor(String colorString) { switch (colorString) { case 'blue': return Colors.blue; case 'red': return Colors.red; case 'green': return Colors.green; default: return Colors.black; // Default color if unrecognized } } class _DefaultPageState extends State<DefaultPage> { late Timer _timer; List<MarkerData> markerList = []; // fetchMarkerData requests data from the update_map endpoint Future<void> fetchMarkerData() async { try { // Custom HTTP client HttpClient client = HttpClient() ..badCertificateCallback = // NB: temporary disable SSL certificate validation (X509Certificate cert, String host, int port) => true; var request = await client.getUrl(Uri.parse(serverURI+mapEndpoint)); var response = await request.close(); // Parse json response to list of MarkerData objects if request is ok if (response.statusCode == 200) { var responseBody = await response.transform(utf8.decoder).join(); setState(() { markerList = parseMarkerData(responseBody); }); } else { print('Request failed with status: ${response.statusCode}'); } } catch (e) { print('Failed to connect to the server: $e'); } } // Timer initializer @override void initState() { super.initState(); // Call fetchMarkerData when the widget is first created fetchMarkerData(); // Schedule fetchMarkerData to run periodically based on fetchInterval const const Duration fiveMinutes = Duration(minutes: fetchInterval); _timer = Timer.periodic(fiveMinutes, (timer) { fetchMarkerData(); }); } // Fetch timer @override void dispose() { // Cancel timer on widget termination _timer.cancel(); super.dispose(); } // Main widget @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; double boxWidth = 0.9; double boxHeight = 1.5; const double markerSize = 40; return Scaffold( appBar: AppBar( title: const Text('IceMap'), ), body: Center( child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( // Map container width: screenWidth * boxWidth, height: screenWidth * boxHeight, color: Colors.blue, child: FlutterMap( options: MapOptions( center: LatLng(60.7666, 10.8471), zoom: 9.0, ), children: [ TileLayer( urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", subdomains: const ['a', 'b', 'c'], ), MarkerLayer( // Dynamically allocate markers based on a list markers: markerList .map( (MarkerData markerData) => Marker( width: markerData.size, height: markerData.size, point: markerData.location, builder: (ctx) => Icon( Icons.favorite, color: markerData.color, size: markerData.size, ), ), ) .toList(), ), ], ), ), const SizedBox(height: 20), Container( // Detailed info container width: screenWidth * boxWidth, height: screenWidth * boxHeight, color: Colors.blue, child: const Align( alignment: Alignment.topLeft, child: Padding( padding: EdgeInsets.only(top: 10, left: 10), // Edge padding, text child: Text( 'Placeholder text', style: TextStyle(fontSize: 20, color: Colors.black), ), ), ), ), const SizedBox(height: 20), ], ), ), ), ); } }