diff --git a/lib/presentation/screens/order_details.dart b/lib/presentation/screens/order_details.dart index 75177d6..1ed3d56 100644 --- a/lib/presentation/screens/order_details.dart +++ b/lib/presentation/screens/order_details.dart @@ -2,6 +2,7 @@ import 'package:cargo/configs/configs.dart'; import 'package:flutter/material.dart'; import '../../core/core.dart'; +import '../widgets/map/clustering.dart'; import '../widgets/widgets.dart'; class OrderDetailsScreen extends StatelessWidget { @@ -25,8 +26,11 @@ class OrderDetailsScreen extends StatelessWidget { body: CustomScrollView( slivers: [ /// map - const SliverToBoxAdapter( - child: OrderHeader(), + SliverToBoxAdapter( + child: SizedBox( + height: AppDimensions.normalize(140), + child: const ClusteringPage(), + ), ), /// info text diff --git a/lib/presentation/widgets/map/clustering.dart b/lib/presentation/widgets/map/clustering.dart new file mode 100644 index 0000000..0972dfb --- /dev/null +++ b/lib/presentation/widgets/map/clustering.dart @@ -0,0 +1,272 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart'; +import 'package:latlong2/latlong.dart'; + +class ClusteringPage extends StatefulWidget { + static const String route = 'clusteringPage'; + + const ClusteringPage({super.key}); + + @override + State createState() => _ClusteringPageState(); +} + +class _ClusteringPageState extends State { + final PopupController _popupController = PopupController(); + + late List markers; + late int pointIndex; + List points = [ + const LatLng(37.90970, 58.39795), + const LatLng(37.90491, 58.39742), + ]; + + @override + void initState() { + pointIndex = 0; + markers = [ + Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: points[pointIndex], + child: const Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + const Marker( + alignment: Alignment.center, + height: 30, + width: 30, + point: LatLng(49.8566, 3.3522), + child: Icon(Icons.pin_drop), + ), + ]; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () { + pointIndex++; + if (pointIndex >= points.length) { + pointIndex = 0; + } + setState(() { + markers[0] = Marker( + point: points[pointIndex], + alignment: Alignment.center, + height: 30, + width: 30, + child: const Icon(Icons.pin_drop), + ); + markers = List.from(markers); + }); + }, + child: const Icon(Icons.refresh), + ), + body: PopupScope( + popupController: _popupController, + child: FlutterMap( + options: MapOptions( + initialCenter: points[0], + initialZoom: 4.5, + maxZoom: 45, + onTap: (_, __) => _popupController.hideAllPopups(), // Hide popup when the map is tapped. + ), + children: [ + TileLayer( + urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: const ['a', 'b', 'c'], + ), + MarkerClusterLayerWidget( + options: MarkerClusterLayerOptions( + spiderfyCircleRadius: 80, + spiderfySpiralDistanceMultiplier: 2, + circleSpiralSwitchover: 12, + maxClusterRadius: 120, + rotate: true, + size: const Size(40, 40), + alignment: Alignment.center, + padding: const EdgeInsets.all(50), + maxZoom: 15, + markers: markers, + polygonOptions: const PolygonOptions( + borderColor: Colors.blueAccent, + color: Colors.black12, + borderStrokeWidth: 3, + ), + popupOptions: PopupOptions( + popupSnap: PopupSnap.markerTop, + popupController: _popupController, + popupBuilder: (_, marker) => Container( + width: 200, + height: 100, + color: Colors.white, + child: GestureDetector( + onTap: () => debugPrint('Popup tap!'), + child: const Text( + 'Container popup for marker', + ), + ), + ), + ), + builder: (context, markers) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.blue, + ), + child: Center( + child: Text( + markers.length.toString(), + style: const TextStyle(color: Colors.white), + ), + ), + ); + }, + ), + ), + PolylineLayer( + polylineCulling: true, + polylines: [ + Polyline( + points: [ + // const LatLng(51.5, -0.09), + // const LatLng(49.8566, 3.3522), + // const LatLng(49.8566, 1.3522), + // const LatLng(47.8566, 1.3522), + // const LatLng(47.8566, 5.3522), + // const LatLng(44.8566, 5.3522), + // const LatLng(49.8566, 12.3522), + // const LatLng(49.8864, 13.4522), + // const LatLng(49.8966, 13.6522), + const LatLng(37.90970, 58.39795), + const LatLng(37.90491, 58.39742) + ], + borderStrokeWidth: 6.6, + color: Colors.red, + borderColor: Colors.green, + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/widgets/map/clustering_many_markers_page.dart b/lib/presentation/widgets/map/clustering_many_markers_page.dart new file mode 100644 index 0000000..439b111 --- /dev/null +++ b/lib/presentation/widgets/map/clustering_many_markers_page.dart @@ -0,0 +1,96 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart'; +import 'package:latlong2/latlong.dart'; + +class ClusteringManyMarkersPage extends StatefulWidget { + static const String route = 'clusteringManyMarkersPage'; + + const ClusteringManyMarkersPage({super.key}); + + @override + State createState() => _ClusteringManyMarkersPageState(); +} + +class _ClusteringManyMarkersPageState extends State { + static const totalMarkers = 2000.0; + final minLatLng = const LatLng(49.8566, 1.3522); + final maxLatLng = const LatLng(58.3498, -10.2603); + + late List markers; + + @override + void initState() { + final latitudeRange = maxLatLng.latitude - minLatLng.latitude; + final longitudeRange = maxLatLng.longitude - minLatLng.longitude; + + final stepsInEachDirection = sqrt(totalMarkers).floor(); + final latStep = latitudeRange / stepsInEachDirection; + final lonStep = longitudeRange / stepsInEachDirection; + + markers = []; + for (var i = 0; i < stepsInEachDirection; i++) { + for (var j = 0; j < stepsInEachDirection; j++) { + final latLng = LatLng( + minLatLng.latitude + i * latStep, + minLatLng.longitude + j * lonStep, + ); + + markers.add( + Marker( + height: 30, + width: 30, + point: latLng, + child: const Icon(Icons.pin_drop), + ), + ); + } + } + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Clustering Many Markers Page')), + body: FlutterMap( + options: MapOptions( + initialCenter: + LatLng((maxLatLng.latitude + minLatLng.latitude) / 2, (maxLatLng.longitude + minLatLng.longitude) / 2), + initialZoom: 6, + maxZoom: 15, + ), + children: [ + TileLayer( + urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: const ['a', 'b', 'c'], + ), + MarkerClusterLayerWidget( + options: MarkerClusterLayerOptions( + maxClusterRadius: 45, + size: const Size(40, 40), + alignment: Alignment.center, + padding: const EdgeInsets.all(50), + maxZoom: 15, + markers: markers, + builder: (context, markers) { + return Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: Colors.blue), + child: Center( + child: Text( + markers.length.toString(), + style: const TextStyle(color: Colors.white), + ), + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/order_card.dart b/lib/presentation/widgets/order_card.dart index 105b96d..2d3b941 100644 --- a/lib/presentation/widgets/order_card.dart +++ b/lib/presentation/widgets/order_card.dart @@ -75,15 +75,19 @@ class OrderCard extends StatelessWidget { flex: 2, child: Row( children: [ - SizedBox( + /// vertical status line + Container( width: AppDimensions.normalize(15), + alignment: Alignment.topLeft, child: VerticalLine( - width: AppDimensions.normalize(6), - height: AppDimensions.normalize(40), + width: AppDimensions.normalize(1), + height: AppDimensions.normalize(2.5), topColor: AppColors.green, bottomColor: AppColors.grey, ), ), + + /// info Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/presentation/widgets/order_header.dart b/lib/presentation/widgets/order_header.dart index 2a84f70..a789fb6 100644 --- a/lib/presentation/widgets/order_header.dart +++ b/lib/presentation/widgets/order_header.dart @@ -15,6 +15,7 @@ class OrderHeader extends StatelessWidget { Image.asset( AppAssets.header, fit: BoxFit.fill, + width: double.infinity, ), Positioned( top: 0, diff --git a/lib/presentation/widgets/vertical_line.dart b/lib/presentation/widgets/vertical_line.dart index 6ae2298..1989cec 100644 --- a/lib/presentation/widgets/vertical_line.dart +++ b/lib/presentation/widgets/vertical_line.dart @@ -16,9 +16,12 @@ class VerticalLine extends StatelessWidget { @override Widget build(BuildContext context) { - return CustomPaint( - size: Size(width, height), - painter: VerticalLinePainter(topColor: topColor, bottomColor: bottomColor), + return AspectRatio( + aspectRatio: width / height, + child: CustomPaint( + size: Size(width, height), + painter: VerticalLinePainter(topColor: topColor, bottomColor: bottomColor), + ), ); } } diff --git a/pubspec.lock b/pubspec.lock index 91dd2f1..52ce7d8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + animated_stack_widget: + dependency: transitive + description: + name: animated_stack_widget + sha256: ce4788dd158768c9d4388354b6fb72600b78e041a37afc4c279c63ecafcb9408 + url: "https://pub.dev" + source: hosted + version: "0.0.4" args: dependency: transitive description: @@ -126,6 +134,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_map: + dependency: "direct main" + description: + name: flutter_map + sha256: "87cc8349b8fa5dccda5af50018c7374b6645334a0d680931c1fe11bce88fa5bb" + url: "https://pub.dev" + source: hosted + version: "6.2.1" + flutter_map_marker_cluster: + dependency: "direct main" + description: + name: flutter_map_marker_cluster + sha256: a324f48da5ee83a3f29fd8d08b4b1e6e3114ff5c6cab910124d6a2e1f06f08cc + url: "https://pub.dev" + source: hosted + version: "1.3.6" + flutter_map_marker_popup: + dependency: transitive + description: + name: flutter_map_marker_popup + sha256: ec563bcbae24a18ac16815fb75ac5ab33ccba609e14db70e252a67de19c6639c + url: "https://pub.dev" + source: hosted + version: "6.1.2" flutter_phoenix: dependency: "direct main" description: @@ -232,6 +264,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0+1" + intl: + dependency: transitive + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" js: dependency: transitive description: @@ -240,6 +280,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + latlong2: + dependency: "direct main" + description: + name: latlong2 + sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe" + url: "https://pub.dev" + source: hosted + version: "0.9.1" leak_tracker: dependency: transitive description: @@ -272,6 +320,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + lists: + dependency: transitive + description: + name: lists + sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + logger: + dependency: transitive + description: + name: logger + sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" + url: "https://pub.dev" + source: hosted + version: "2.4.0" matcher: dependency: transitive description: @@ -296,6 +360,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.0" + mgrs_dart: + dependency: transitive + description: + name: mgrs_dart + sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7 + url: "https://pub.dev" + source: hosted + version: "2.0.0" nested: dependency: transitive description: @@ -392,6 +464,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + polylabel: + dependency: transitive + description: + name: polylabel + sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + proj4dart: + dependency: transitive + description: + name: proj4dart + sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e + url: "https://pub.dev" + source: hosted + version: "2.1.0" provider: dependency: transitive description: @@ -517,6 +605,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + unicode: + dependency: transitive + description: + name: unicode + sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1" + url: "https://pub.dev" + source: hosted + version: "0.3.1" vector_graphics: dependency: transitive description: @@ -573,6 +669,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.5.1" + wkt_parser: + dependency: transitive + description: + name: wkt_parser + sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13" + url: "https://pub.dev" + source: hosted + version: "2.0.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c9d0641..871540b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,9 @@ dependencies: flutter_phoenix: ^1.1.1 flutter_svg: ^2.0.10+1 flutter_secure_storage: ^9.2.2 + flutter_map: ">=6.0.0 <7.0.0" + latlong2: ^0.9.0 + flutter_map_marker_cluster: ^1.3.6 dev_dependencies: flutter_test: