diff --git a/lib/controls/wgs84_coordinate_formatter.dart b/lib/controls/wgs84_coordinate_formatter.dart new file mode 100644 index 0000000..d8fb9d0 --- /dev/null +++ b/lib/controls/wgs84_coordinate_formatter.dart @@ -0,0 +1,88 @@ +enum Wgs84CoordinateFormat { + decimalDegrees, + degreesDecimalMinutes, + degreesMinutesSeconds +} + +extension Wgs84CoordinateFormatLabel on Wgs84CoordinateFormat { + String get label { + switch (this) { + case Wgs84CoordinateFormat.decimalDegrees: + return 'Tizedes fok'; + case Wgs84CoordinateFormat.degreesDecimalMinutes: + return 'Fok + tizedes perc'; + case Wgs84CoordinateFormat.degreesMinutesSeconds: + return 'Fok - perc - másodperc'; + } + } + + String get shortLabel { + switch (this) { + case Wgs84CoordinateFormat.decimalDegrees: + return 'DD'; + case Wgs84CoordinateFormat.degreesDecimalMinutes: + return 'DM'; + case Wgs84CoordinateFormat.degreesMinutesSeconds: + return 'DMS'; + } + } +} + +class Wgs84CoordinateFormatter { + static String formatLatitude( + double? value, + Wgs84CoordinateFormat format, + ) { + return _format( + value: value, + format: format, + positiveHemisphere: 'N', + negativeHemisphere: 'S', + ); + } + + static String formatLongitude( + double? value, + Wgs84CoordinateFormat format, + ) { + return _format( + value: value, + format: format, + positiveHemisphere: 'E', + negativeHemisphere: 'W', + ); + } + + static String _format({ + required double? value, + required Wgs84CoordinateFormat format, + required String positiveHemisphere, + required String negativeHemisphere, + }) { + if (value == null || value.isNaN || value.isInfinite) { + return '-'; + } + + final hemisphere = value >= 0 ? positiveHemisphere : negativeHemisphere; + final absValue = value.abs(); + + switch (format) { + case Wgs84CoordinateFormat.decimalDegrees: + return '${absValue.toStringAsFixed(8)}° $hemisphere'; + + case Wgs84CoordinateFormat.degreesDecimalMinutes: + final deg = absValue.floor(); + final minutes = (absValue - deg) * 60.0; + + return '$deg° ${minutes.toStringAsFixed(5)}′ $hemisphere'; + + case Wgs84CoordinateFormat.degreesMinutesSeconds: + final deg = absValue.floor(); + final fullMinutes = (absValue - deg) * 60.0; + final min = fullMinutes.floor(); + final sec = (fullMinutes - min) * 60.0; + + return '$deg° $min′ ${sec.toStringAsFixed(3)}″ $hemisphere'; + } + } +} diff --git a/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart b/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart index c33e7f1..fd90c9a 100644 --- a/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart +++ b/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart @@ -18,6 +18,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:terepi_seged/controls/geoid_grid.dart'; +import 'package:terepi_seged/controls/wgs84_coordinate_formatter.dart'; import 'package:terepi_seged/enums/map_survey_mode.dart'; import 'package:terepi_seged/eov/convert_coordinate.dart'; import 'package:terepi_seged/eov/eov.dart'; @@ -65,6 +66,10 @@ class MapSurveyController extends GetxController { bool get gpsIsConnected => _gnss.connectionState.value == GnssConnectionState.connected; + final wgs84CoordinateFormat = Wgs84CoordinateFormat.decimalDegrees.obs; + final showWgs84Card = true.obs; + final showEovCard = true.obs; + // NTRIP állapot RxBool get ntripIsConnected => _ntrip.isConnected; RxInt get ntripDataPacketNumbers => _ntrip.packetCount; @@ -781,4 +786,26 @@ class MapSurveyController extends GetxController { targetEovX.value = x; mode.value = MapSurveyMode.stakeout; } + + void setWgs84CoordinateFormat(Wgs84CoordinateFormat format) { + wgs84CoordinateFormat.value = format; + } + + void toggleShowWgs84Card() { + showWgs84Card.value = !showWgs84Card.value; + } + + void toggleShowEovCard() { + showEovCard.value = !showEovCard.value; + } + + String get latitudeText { + return Wgs84CoordinateFormatter.formatLatitude( + gpsLatitude.value, wgs84CoordinateFormat.value); + } + + String get longitudeText { + return Wgs84CoordinateFormatter.formatLongitude( + gpsLongitude.value, wgs84CoordinateFormat.value); + } } diff --git a/lib/pages/map_survey/presentations/views/map_survey_view.dart b/lib/pages/map_survey/presentations/views/map_survey_view.dart index deb8d5c..88b482d 100644 --- a/lib/pages/map_survey/presentations/views/map_survey_view.dart +++ b/lib/pages/map_survey/presentations/views/map_survey_view.dart @@ -9,6 +9,7 @@ import 'package:terepi_seged/pages/map_survey/presentations/views/settings_dialo import 'package:terepi_seged/utils/rive_utils.dart'; import 'package:terepi_seged/widgets/coordinate_panel.dart'; import 'package:terepi_seged/widgets/map_bottom_panel.dart'; +import 'package:terepi_seged/widgets/map_info_card_column.dart'; import 'package:terepi_seged/widgets/save_point_fab.dart'; import 'package:terepi_seged/widgets/shared_map_widgets.dart'; @@ -32,7 +33,13 @@ class MapSurveyView extends GetView { ], ), Positioned( - top: 8, + top: 12, + left: 12, + child: MapInfoCardColumn(controller: controller), + ), + + Positioned( + top: 300, right: 60, left: 8, child: CoordinatePanel( diff --git a/lib/widgets/coordinate_row.dart b/lib/widgets/coordinate_row.dart new file mode 100644 index 0000000..20c071a --- /dev/null +++ b/lib/widgets/coordinate_row.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class CoordinateRow extends StatelessWidget { + final String label; + final String value; + + const CoordinateRow({ + required this.label, + required this.value, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 34, + child: Text( + label, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w600, + ), + ), + ), + Expanded( + child: SelectableText( + value, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontFeatures: const [ + FontFeature.tabularFigures(), + ], + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/eov_coordinate_card.dart b/lib/widgets/eov_coordinate_card.dart new file mode 100644 index 0000000..00c290d --- /dev/null +++ b/lib/widgets/eov_coordinate_card.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; +import 'package:terepi_seged/widgets/map_info_card.dart'; + +import 'coordinate_row.dart'; + +class EovCoordinateCard extends StatelessWidget { + final MapSurveyController controller; + + const EovCoordinateCard({ + super.key, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return Obx(() { + return MapInfoCard( + onClose: controller.toggleShowEovCard, + title: Row( + children: [ + const Icon(Icons.grid_on, size: 18), + const SizedBox(width: 6), + Text( + 'EOV', + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + ], + ), + child: Column( + children: [ + CoordinateRow( + label: 'Y', + value: controller.eovY.value.toStringAsFixed(3), + ), + const SizedBox(height: 4), + CoordinateRow( + label: 'X', + value: controller.eovX.value.toStringAsFixed(3), + ), + const SizedBox(height: 4), + CoordinateRow( + label: 'Z', + value: controller.eovHeight.value?.toStringAsFixed(3) ?? '-', + ), + ], + ), + ); + }); + } +} diff --git a/lib/widgets/format_menu_button.dart b/lib/widgets/format_menu_button.dart new file mode 100644 index 0000000..76dfb36 --- /dev/null +++ b/lib/widgets/format_menu_button.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:terepi_seged/controls/wgs84_coordinate_formatter.dart'; +import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; + +class FormatMenuButton extends StatelessWidget { + final MapSurveyController controller; + + const FormatMenuButton({ + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + tooltip: 'Koordinátaformátum', + initialValue: controller.wgs84CoordinateFormat.value, + onSelected: controller.setWgs84CoordinateFormat, + itemBuilder: (context) { + return Wgs84CoordinateFormat.values.map((format) { + return CheckedPopupMenuItem( + value: format, + checked: controller.wgs84CoordinateFormat.value == format, + child: Text(format.label), + ); + }).toList(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + controller.wgs84CoordinateFormat.value.shortLabel, + style: Theme.of(context).textTheme.labelMedium?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + const Icon(Icons.arrow_drop_down, size: 18), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/map_info_card.dart b/lib/widgets/map_info_card.dart new file mode 100644 index 0000000..bd44b7b --- /dev/null +++ b/lib/widgets/map_info_card.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +class MapInfoCard extends StatelessWidget { + final Widget title; + final Widget child; + final VoidCallback? onClose; + + const MapInfoCard({ + super.key, + required this.title, + required this.child, + this.onClose, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final screenWidth = MediaQuery.sizeOf(context).width; + + return Material( + elevation: 6, + color: colorScheme.surface.withOpacity(0.64), + borderRadius: BorderRadius.circular(18), + child: Container( + width: screenWidth - 60, + padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.7), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded(child: title), + if (onClose != null) + IconButton( + visualDensity: VisualDensity.compact, + tooltip: 'Kártya bezárása', + icon: const Icon(Icons.close, size: 18), + onPressed: onClose, + ), + ], + ), + const SizedBox(height: 6), + child, + ], + ), + ), + ); + } +} diff --git a/lib/widgets/map_info_card_column.dart b/lib/widgets/map_info_card_column.dart new file mode 100644 index 0000000..b1e5bde --- /dev/null +++ b/lib/widgets/map_info_card_column.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; +import 'package:terepi_seged/widgets/wgs84_coordinate_card.dart'; + +import 'eov_coordinate_card.dart'; + +class MapInfoCardColumn extends StatelessWidget { + final MapSurveyController controller; + + const MapInfoCardColumn({ + super.key, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.sizeOf(context).height; + final screenWidth = MediaQuery.sizeOf(context).width; + + return Obx(() { + final cards = []; + + if (controller.showWgs84Card.value) { + cards.add(Wgs84CoordinateCard(controller: controller)); + } + if (controller.showEovCard.value) { + cards.add(EovCoordinateCard(controller: controller)); + } + + if (cards.isEmpty) { + return const SizedBox.shrink(); + } + + return ConstrainedBox( + constraints: BoxConstraints( + maxWidth: screenWidth - 50, maxHeight: screenHeight * 0.55), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (var i = 0; i < cards.length; i++) ...[ + cards[i], + if (i != cards.length - 1) const SizedBox(height: 8) + ] + ], + ), + )); + }); + } +} diff --git a/lib/widgets/wgs84_coordinate_card.dart b/lib/widgets/wgs84_coordinate_card.dart new file mode 100644 index 0000000..2d810eb --- /dev/null +++ b/lib/widgets/wgs84_coordinate_card.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; + +import 'map_info_card.dart'; +import 'format_menu_button.dart'; +import 'coordinate_row.dart'; + +class Wgs84CoordinateCard extends StatelessWidget { + final MapSurveyController controller; + + const Wgs84CoordinateCard({ + super.key, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return Obx(() { + return MapInfoCard( + onClose: controller.toggleShowWgs84Card, + title: Row( + children: [ + const Icon(Icons.public, size: 18), + const SizedBox(width: 6), + Text( + 'WGS84', + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + const Spacer(), + FormatMenuButton(controller: controller), + ], + ), + child: Column( + children: [ + CoordinateRow( + label: 'Lat', + value: controller.latitudeText, + ), + const SizedBox(height: 4), + CoordinateRow( + label: 'Lon', + value: controller.longitudeText, + ), + ], + ), + ); + }); + } +}