Terepbejárás oldalon vonal és terület tulajdonságainak szerkesztése, mentés
This commit is contained in:
parent
537897005c
commit
1276ac0610
6
lib/enums/map_edit_tool.dart
Normal file
6
lib/enums/map_edit_tool.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
enum MapEditTool {
|
||||||
|
none,
|
||||||
|
point,
|
||||||
|
line,
|
||||||
|
polygon,
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import 'package:file_picker/file_picker.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:flutter_map_polygon_editor/polygon_editor/polygon_editor_controller.dart';
|
||||||
// import 'package:flutter_map_geojson/flutter_map_geojson.dart';
|
// import 'package:flutter_map_geojson/flutter_map_geojson.dart';
|
||||||
import 'package:flutter_map_polywidget/flutter_map_polywidget.dart';
|
import 'package:flutter_map_polywidget/flutter_map_polywidget.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
@ -20,6 +21,7 @@ import 'package:share_plus/share_plus.dart';
|
|||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
import 'package:terepi_seged/controls/geoid_grid.dart';
|
import 'package:terepi_seged/controls/geoid_grid.dart';
|
||||||
import 'package:terepi_seged/controls/wgs84_coordinate_formatter.dart';
|
import 'package:terepi_seged/controls/wgs84_coordinate_formatter.dart';
|
||||||
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
import 'package:terepi_seged/enums/map_survey_mode.dart';
|
import 'package:terepi_seged/enums/map_survey_mode.dart';
|
||||||
import 'package:terepi_seged/eov/convert_coordinate.dart';
|
import 'package:terepi_seged/eov/convert_coordinate.dart';
|
||||||
import 'package:terepi_seged/eov/eov.dart';
|
import 'package:terepi_seged/eov/eov.dart';
|
||||||
@ -35,6 +37,7 @@ import 'package:terepi_seged/services/gnss/gnss_connection.dart';
|
|||||||
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
|
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
|
||||||
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
||||||
import 'package:terepi_seged/services/ntrip_service.dart';
|
import 'package:terepi_seged/services/ntrip_service.dart';
|
||||||
|
import 'package:terepi_seged/widgets/map_edit_tools/map_feature_save_sheet.dart';
|
||||||
|
|
||||||
class MapSurveyController extends GetxController {
|
class MapSurveyController extends GetxController {
|
||||||
static MapSurveyController get to => Get.find();
|
static MapSurveyController get to => Get.find();
|
||||||
@ -159,6 +162,26 @@ class MapSurveyController extends GetxController {
|
|||||||
// ── Supabase ──────────────────────────────────────────────────────
|
// ── Supabase ──────────────────────────────────────────────────────
|
||||||
RealtimeChannel? _supaChannel;
|
RealtimeChannel? _supaChannel;
|
||||||
|
|
||||||
|
// ------- Map edit ----------------
|
||||||
|
final activeEditTool = MapEditTool.none.obs;
|
||||||
|
final editorPointCount = 0.obs;
|
||||||
|
final pointNotes = <Marker>[].obs;
|
||||||
|
final polylineNotes = <Polyline<Object>>[].obs;
|
||||||
|
final polygonNotes = <Polygon<Object>>[].obs;
|
||||||
|
|
||||||
|
late final PolygonEditorController polygonEditorController;
|
||||||
|
|
||||||
|
final activeEditColor = const Color(0xFF185FA5).obs;
|
||||||
|
final activeEditOpacity = 0.5.obs;
|
||||||
|
final activeEditStrokeWidth = 3.0.obs;
|
||||||
|
final activeEditStrokeColor = const Color(0xFFFFD700).obs;
|
||||||
|
final activeEditLabel = ''.obs;
|
||||||
|
|
||||||
|
final PolygonLabelPlacementCalculator _labelPlacementCalculator =
|
||||||
|
const PolygonLabelPlacementCalculator.centroid();
|
||||||
|
|
||||||
|
bool get isMapEditing => activeEditTool.value != MapEditTool.none;
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
@ -190,6 +213,12 @@ class MapSurveyController extends GetxController {
|
|||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
||||||
|
polygonEditorController =
|
||||||
|
PolygonEditorController(mode: PolygonEditorMode.polygon);
|
||||||
|
polygonEditorController.addListener(() {
|
||||||
|
editorPointCount.value = polygonEditorController.points.length;
|
||||||
|
});
|
||||||
|
|
||||||
mapIsInitialized.value = true;
|
mapIsInitialized.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,6 +243,7 @@ class MapSurveyController extends GetxController {
|
|||||||
gpsHeightController.dispose();
|
gpsHeightController.dispose();
|
||||||
pointPrefixController.dispose();
|
pointPrefixController.dispose();
|
||||||
pointPostfixController.dispose();
|
pointPostfixController.dispose();
|
||||||
|
polygonEditorController.dispose();
|
||||||
|
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
@ -846,4 +876,202 @@ class MapSurveyController extends GetxController {
|
|||||||
|
|
||||||
return '${value.toStringAsFixed(1)}m';
|
return '${value.toStringAsFixed(1)}m';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------- Térkép szerkesztési műveletek
|
||||||
|
IconData get activeEditToolIcon {
|
||||||
|
switch (activeEditTool.value) {
|
||||||
|
case MapEditTool.point:
|
||||||
|
return Icons.add_location_alt_outlined;
|
||||||
|
case MapEditTool.line:
|
||||||
|
return Icons.polyline_outlined;
|
||||||
|
case MapEditTool.polygon:
|
||||||
|
return Icons.border_outer_outlined;
|
||||||
|
case MapEditTool.none:
|
||||||
|
return Icons.edit_location_alt_outlined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get activeEditToolTitle {
|
||||||
|
switch (activeEditTool.value) {
|
||||||
|
case MapEditTool.point:
|
||||||
|
return 'Pont hozzáadása';
|
||||||
|
case MapEditTool.line:
|
||||||
|
return 'Vonal rögzítése';
|
||||||
|
case MapEditTool.polygon:
|
||||||
|
return 'Terület rögzítése';
|
||||||
|
case MapEditTool.none:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get activeEditToolHint {
|
||||||
|
switch (activeEditTool.value) {
|
||||||
|
case MapEditTool.point:
|
||||||
|
return 'Koppints a térképre a pont helyéhez.';
|
||||||
|
case MapEditTool.line:
|
||||||
|
return 'Hosszan nyomj a térképre a töréspontokhoz';
|
||||||
|
case MapEditTool.polygon:
|
||||||
|
return 'Hosszan nyomj a térképre a sarokpontokhoz.';
|
||||||
|
case MapEditTool.none:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get canFinishGeometry {
|
||||||
|
switch (activeEditTool.value) {
|
||||||
|
case MapEditTool.point:
|
||||||
|
return editorPointCount == 1;
|
||||||
|
case MapEditTool.line:
|
||||||
|
return editorPointCount >= 2;
|
||||||
|
case MapEditTool.polygon:
|
||||||
|
return editorPointCount >= 3;
|
||||||
|
case MapEditTool.none:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get finishButtonText {
|
||||||
|
switch (activeEditTool.value) {
|
||||||
|
case MapEditTool.point:
|
||||||
|
return 'Kész';
|
||||||
|
case MapEditTool.line:
|
||||||
|
return 'Kész';
|
||||||
|
case MapEditTool.polygon:
|
||||||
|
return 'Lezárás';
|
||||||
|
case MapEditTool.none:
|
||||||
|
return 'Kész';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void startPointTool() {
|
||||||
|
activeEditTool.value = MapEditTool.point;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startLineTool() {
|
||||||
|
polygonEditorController.clear();
|
||||||
|
polygonEditorController.setMode(PolygonEditorMode.line);
|
||||||
|
activeEditTool.value = MapEditTool.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startPolygonTool() {
|
||||||
|
polygonEditorController.clear();
|
||||||
|
polygonEditorController.setMode(PolygonEditorMode.polygon);
|
||||||
|
activeEditTool.value = MapEditTool.polygon;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelEditing() {
|
||||||
|
polygonEditorController.clear();
|
||||||
|
activeEditTool.value = MapEditTool.none;
|
||||||
|
}
|
||||||
|
|
||||||
|
void openFeatureList() {
|
||||||
|
// TODO: DraggableScrollableSheet / bottom sheet lista
|
||||||
|
}
|
||||||
|
|
||||||
|
void openLayerPanel() {
|
||||||
|
// TODO: rétegek sheet
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishGeometry() {
|
||||||
|
if (!canFinishGeometry) return;
|
||||||
|
|
||||||
|
// Itt nyisd majd meg a szerkesztő sheetet:
|
||||||
|
// openFeatureEditorSheet();
|
||||||
|
|
||||||
|
// Példa:
|
||||||
|
// Get.bottomSheet(
|
||||||
|
// FeatureEditorSheet(
|
||||||
|
// tool: activeTool.value,
|
||||||
|
// points: List<LatLng>.from(draftPoints),
|
||||||
|
// ),
|
||||||
|
// isScrollControlled: true,
|
||||||
|
// );
|
||||||
|
|
||||||
|
Get.bottomSheet(
|
||||||
|
DraggableScrollableSheet(
|
||||||
|
initialChildSize: 0.52,
|
||||||
|
minChildSize: 0.35,
|
||||||
|
maxChildSize: 0.85,
|
||||||
|
snap: true,
|
||||||
|
snapSizes: const [0.35, 0.52, 0.85],
|
||||||
|
expand: false,
|
||||||
|
builder: (_, scrollCtrl) =>
|
||||||
|
MapFeatureSaveSheet(ctrl: this, scrollCtrl: scrollCtrl)),
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
ignoreSafeArea: false);
|
||||||
|
|
||||||
|
//activeEditTool.value = MapEditTool.none;
|
||||||
|
//draftPoints.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> finishDraft() async {
|
||||||
|
if (polygonEditorController.mode == PolygonEditorMode.line) {
|
||||||
|
print("Points number in line: ${polygonEditorController.points.length}");
|
||||||
|
print(
|
||||||
|
"1. point coords: ${polygonEditorController.points[0].latitude} - ${polygonEditorController.points[0].longitude}");
|
||||||
|
if (polygonEditorController.points.length < 2) return;
|
||||||
|
|
||||||
|
Polyline polyline = Polyline(
|
||||||
|
points: List.from(polygonEditorController.points),
|
||||||
|
color: activeEditColor.value,
|
||||||
|
strokeWidth: activeEditStrokeWidth.value,
|
||||||
|
// hitValue: (
|
||||||
|
// title: 'Purple Line',
|
||||||
|
// subtitle: 'Nothing really special here...',
|
||||||
|
// ),
|
||||||
|
);
|
||||||
|
polylineNotes.add(polyline);
|
||||||
|
// polylineNotes.refresh();
|
||||||
|
|
||||||
|
print("Points number in polylineNotes: ${polylineNotes.length}");
|
||||||
|
print(
|
||||||
|
"1. point coords of polyline: ${polyline.points[0].latitude} - ${polyline.points[0].longitude}");
|
||||||
|
|
||||||
|
polygonEditorController.clear();
|
||||||
|
activeEditTool.value = MapEditTool.none;
|
||||||
|
}
|
||||||
|
if (polygonEditorController.mode == PolygonEditorMode.polygon) {
|
||||||
|
print(
|
||||||
|
"Points number in polygon: ${polygonEditorController.points.length}");
|
||||||
|
|
||||||
|
Polygon polygon = Polygon(
|
||||||
|
points: List.from(polygonEditorController.points),
|
||||||
|
color: activeEditColor.value.withValues(alpha: activeEditOpacity.value),
|
||||||
|
borderColor: activeEditStrokeColor.value,
|
||||||
|
borderStrokeWidth: activeEditStrokeWidth.value,
|
||||||
|
label: activeEditLabel.value,
|
||||||
|
labelPlacementCalculator: _labelPlacementCalculator,
|
||||||
|
// hitValue: (
|
||||||
|
// title: 'Basic Filled Polygon',
|
||||||
|
// subtitle: 'Nothing really special here...',
|
||||||
|
);
|
||||||
|
|
||||||
|
polygonNotes.add(polygon);
|
||||||
|
//polygonNotes.refresh();
|
||||||
|
//update();
|
||||||
|
|
||||||
|
print("Points number in polygonNotes: ${polygonNotes.length}");
|
||||||
|
|
||||||
|
polygonEditorController.clear();
|
||||||
|
activeEditTool.value = MapEditTool.none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveEditedPoint({required LatLng point}) {
|
||||||
|
Marker marker = Marker(
|
||||||
|
point: point,
|
||||||
|
width: 15.0,
|
||||||
|
height: 15.0,
|
||||||
|
child: Container(
|
||||||
|
width: 15.0,
|
||||||
|
height: 15.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.amber[700],
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(width: 1.0, color: Colors.black)),
|
||||||
|
));
|
||||||
|
pointNotes.add(marker);
|
||||||
|
activeEditTool.value = MapEditTool.none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map_polygon_editor/polygon_editor/polygon_editor.dart';
|
||||||
import 'package:flutter_map_polywidget/flutter_map_polywidget.dart';
|
import 'package:flutter_map_polywidget/flutter_map_polywidget.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
import 'package:terepi_seged/enums/map_survey_mode.dart';
|
import 'package:terepi_seged/enums/map_survey_mode.dart';
|
||||||
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
import 'package:terepi_seged/pages/map_survey/presentations/views/settings_dialog.dart';
|
import 'package:terepi_seged/pages/map_survey/presentations/views/settings_dialog.dart';
|
||||||
@ -10,6 +12,8 @@ import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_co
|
|||||||
import 'package:terepi_seged/utils/rive_utils.dart';
|
import 'package:terepi_seged/utils/rive_utils.dart';
|
||||||
import 'package:terepi_seged/widgets/coordinate_panel.dart';
|
import 'package:terepi_seged/widgets/coordinate_panel.dart';
|
||||||
import 'package:terepi_seged/widgets/map_bottom_panel.dart';
|
import 'package:terepi_seged/widgets/map_bottom_panel.dart';
|
||||||
|
import 'package:terepi_seged/widgets/map_edit_tools/map_edit_drawing_toolbar.dart';
|
||||||
|
import 'package:terepi_seged/widgets/map_edit_tools/map_edit_toolbar.dart';
|
||||||
import 'package:terepi_seged/widgets/map_info_card_column.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/save_point_fab.dart';
|
||||||
import 'package:terepi_seged/widgets/shared_map_widgets.dart';
|
import 'package:terepi_seged/widgets/shared_map_widgets.dart';
|
||||||
@ -28,6 +32,15 @@ class MapSurveyView extends GetView<MapSurveyController> {
|
|||||||
onZoomIn: controller.mapZoomIn,
|
onZoomIn: controller.mapZoomIn,
|
||||||
onZoomOut: controller.mapZoomOut,
|
onZoomOut: controller.mapZoomOut,
|
||||||
onCenterOnGps: controller.isMapMoveToCenter,
|
onCenterOnGps: controller.isMapMoveToCenter,
|
||||||
|
onLongPress: (tapPosition, point) {
|
||||||
|
if (controller.activeEditTool.value == MapEditTool.point) {
|
||||||
|
controller.saveEditedPoint(point: point);
|
||||||
|
}
|
||||||
|
if (controller.activeEditTool.value == MapEditTool.line ||
|
||||||
|
controller.activeEditTool.value == MapEditTool.polygon) {
|
||||||
|
controller.polygonEditorController.addPoint(point);
|
||||||
|
}
|
||||||
|
},
|
||||||
layers: [
|
layers: [
|
||||||
Obx(() =>
|
Obx(() =>
|
||||||
MarkerLayer(markers: controller.currentLocationMarker.toList())),
|
MarkerLayer(markers: controller.currentLocationMarker.toList())),
|
||||||
@ -41,6 +54,36 @@ class MapSurveyView extends GetView<MapSurveyController> {
|
|||||||
} else {
|
} else {
|
||||||
return _buildTrackLayer();
|
return _buildTrackLayer();
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
Obx(() {
|
||||||
|
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return PolygonEditor(
|
||||||
|
controller: controller.polygonEditorController,
|
||||||
|
throttleDuration: Duration.zero);
|
||||||
|
}),
|
||||||
|
Obx(() {
|
||||||
|
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return MarkerLayer(markers: [...controller.pointNotes]);
|
||||||
|
}),
|
||||||
|
Obx(() {
|
||||||
|
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return PolylineLayer(polylines: [...controller.polylineNotes]);
|
||||||
|
}),
|
||||||
|
Obx(() {
|
||||||
|
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return PolygonLayer(
|
||||||
|
polygons: [...controller.polygonNotes], useAltRendering: true);
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -50,23 +93,40 @@ class MapSurveyView extends GetView<MapSurveyController> {
|
|||||||
child: MapInfoCardColumn(controller: controller),
|
child: MapInfoCardColumn(controller: controller),
|
||||||
),
|
),
|
||||||
|
|
||||||
Positioned(
|
// Positioned(
|
||||||
top: 390,
|
// top: 390,
|
||||||
right: 60,
|
// right: 60,
|
||||||
left: 8,
|
// left: 8,
|
||||||
child: CoordinatePanel(
|
// child: CoordinatePanel(
|
||||||
eovY: controller.eovY,
|
// eovY: controller.eovY,
|
||||||
eovX: controller.eovX,
|
// eovX: controller.eovX,
|
||||||
horError: controller.gpsLatitudeError,
|
// horError: controller.gpsLatitudeError,
|
||||||
vertError: controller.gpsAltitudeError,
|
// vertError: controller.gpsAltitudeError,
|
||||||
altitudeMsl: controller.gpsAltitude,
|
// altitudeMsl: controller.gpsAltitude,
|
||||||
geoidSeparation: controller.gpsGeoidSeparation,
|
// geoidSeparation: controller.gpsGeoidSeparation,
|
||||||
ntripConnected: controller.ntripIsConnected,
|
// ntripConnected: controller.ntripIsConnected,
|
||||||
ntripBytes: controller.ntripReceivedData,
|
// ntripBytes: controller.ntripReceivedData,
|
||||||
ntripPackets: controller.ntripDataPacketNumbers,
|
// ntripPackets: controller.ntripDataPacketNumbers,
|
||||||
ggaPackets: controller.ggaSenDataPacketNumber,
|
// ggaPackets: controller.ggaSenDataPacketNumber,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
|
Obx(() {
|
||||||
|
if (controller.mode.value != MapSurveyMode.fieldWalk) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
if (controller.activeEditTool.value == MapEditTool.none) {
|
||||||
|
return Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: MapEditCompactToolbar(controller: controller));
|
||||||
|
}
|
||||||
|
return Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: MapEditDrawingToolbar(controller: controller));
|
||||||
|
})
|
||||||
// Positioned(top: 8, left: 0, right: 0, child: _ModeSelector()),
|
// Positioned(top: 8, left: 0, right: 0, child: _ModeSelector()),
|
||||||
// Positioned(
|
// Positioned(
|
||||||
// bottom: 80,
|
// bottom: 80,
|
||||||
|
|||||||
61
lib/widgets/map_edit_tools/color_row.dart
Normal file
61
lib/widgets/map_edit_tools/color_row.dart
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
|
class ColorRow extends StatelessWidget {
|
||||||
|
final MapSurveyController ctrl;
|
||||||
|
static const _palette = [
|
||||||
|
Color(0xFF6C63FF),
|
||||||
|
Color(0xFFE74C3C),
|
||||||
|
Color(0xFF27AE60),
|
||||||
|
Color(0xFFE91E8C),
|
||||||
|
Color(0xFFE67E22),
|
||||||
|
Color(0xFF3498DB),
|
||||||
|
Color(0xFF8BC34A),
|
||||||
|
];
|
||||||
|
const ColorRow({required this.ctrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Kitöltési szín',
|
||||||
|
style: TextStyle(fontSize: 13, color: Colors.grey.shade600)),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Obx(() => Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: _palette.map((color) {
|
||||||
|
final sel = ctrl.activeEditColor.value == color;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => ctrl.activeEditColor.value = color,
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: sel
|
||||||
|
? Theme.of(context).colorScheme.onSurface
|
||||||
|
: Colors.transparent,
|
||||||
|
width: 3,
|
||||||
|
),
|
||||||
|
boxShadow: sel
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: color.withOpacity(0.45),
|
||||||
|
blurRadius: 8,
|
||||||
|
spreadRadius: 2)
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
60
lib/widgets/map_edit_tools/label_field.dart
Normal file
60
lib/widgets/map_edit_tools/label_field.dart
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
|
class LabelField extends StatefulWidget {
|
||||||
|
final MapSurveyController ctrl;
|
||||||
|
const LabelField({required this.ctrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LabelField> createState() => LabelFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class LabelFieldState extends State<LabelField> {
|
||||||
|
late final TextEditingController _text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_text = TextEditingController(text: widget.ctrl.activeEditLabel.value);
|
||||||
|
_text.addListener(() => widget.ctrl.activeEditLabel.value = _text.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_text.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final hint = switch (widget.ctrl.activeEditTool.value) {
|
||||||
|
MapEditTool.point => 'Pont neve...',
|
||||||
|
MapEditTool.line => 'Vonal neve...',
|
||||||
|
MapEditTool.polygon => 'Terület neve...',
|
||||||
|
MapEditTool.none => 'Felirat...',
|
||||||
|
};
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Felirat',
|
||||||
|
style: TextStyle(fontSize: 13, color: Colors.grey.shade600)),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
TextField(
|
||||||
|
controller: _text,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: hint,
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey.shade100,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
lib/widgets/map_edit_tools/labeled_slider.dart
Normal file
58
lib/widgets/map_edit_tools/labeled_slider.dart
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class LabeledSlider extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final double value;
|
||||||
|
final double min, max;
|
||||||
|
final int? divisions;
|
||||||
|
final String display;
|
||||||
|
final Color color;
|
||||||
|
final ValueChanged<double> onChanged;
|
||||||
|
const LabeledSlider({
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
required this.min,
|
||||||
|
required this.max,
|
||||||
|
required this.display,
|
||||||
|
required this.color,
|
||||||
|
required this.onChanged,
|
||||||
|
this.divisions,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Row(children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 90,
|
||||||
|
child: Text(label,
|
||||||
|
style: TextStyle(fontSize: 13, color: Colors.grey.shade600)),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SliderTheme(
|
||||||
|
data: SliderTheme.of(context).copyWith(
|
||||||
|
activeTrackColor: color,
|
||||||
|
thumbColor: color,
|
||||||
|
overlayColor: color.withOpacity(0.15),
|
||||||
|
inactiveTrackColor: Colors.grey.shade200,
|
||||||
|
trackHeight: 3.0,
|
||||||
|
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8),
|
||||||
|
),
|
||||||
|
child: Slider(
|
||||||
|
value: value.clamp(min, max),
|
||||||
|
min: min,
|
||||||
|
max: max,
|
||||||
|
divisions: divisions,
|
||||||
|
onChanged: onChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 48,
|
||||||
|
child: Text(display,
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontFeatures: [FontFeature.tabularFigures()])),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
57
lib/widgets/map_edit_tools/map_edit_drawing_toolbar.dart
Normal file
57
lib/widgets/map_edit_tools/map_edit_drawing_toolbar.dart
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
|
import 'map_edit_line_or_polygon_drawing_content.dart';
|
||||||
|
import 'map_edit_point_drawing_content.dart';
|
||||||
|
|
||||||
|
class MapEditDrawingToolbar extends StatelessWidget {
|
||||||
|
final MapSurveyController controller;
|
||||||
|
|
||||||
|
const MapEditDrawingToolbar({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Obx(() {
|
||||||
|
final tool = controller.activeEditTool.value;
|
||||||
|
|
||||||
|
if (tool == MapEditTool.none) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SafeArea(
|
||||||
|
top: false,
|
||||||
|
minimum: const EdgeInsets.fromLTRB(10, 0, 10, 10),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Material(
|
||||||
|
elevation: 8,
|
||||||
|
color: Theme.of(context).colorScheme.surface.withOpacity(0.97),
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 560,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 8, 10, 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
border: Border.all(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.outline.withOpacity(0.22),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: tool == MapEditTool.point
|
||||||
|
? PointDrawingContent(controller: controller)
|
||||||
|
: LineOrPolygonDrawingContent(controller: controller),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
|
import 'map_edit_small_status_chip.dart';
|
||||||
|
import 'map_edit_square_toolbar_button.dart';
|
||||||
|
|
||||||
|
class LineOrPolygonDrawingContent extends StatelessWidget {
|
||||||
|
final MapSurveyController controller;
|
||||||
|
|
||||||
|
const LineOrPolygonDrawingContent({
|
||||||
|
required this.controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Obx(() {
|
||||||
|
final pointCount = controller.editorPointCount.value;
|
||||||
|
final canFinish = controller.canFinishGeometry;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
controller.activeEditToolIcon,
|
||||||
|
size: 20,
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'${controller.activeEditToolTitle} · $pointCount pont',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SmallStatusChip(
|
||||||
|
text: _requiredPointText(controller.activeEditTool.value),
|
||||||
|
color: canFinish
|
||||||
|
? colorScheme.primary
|
||||||
|
: colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
controller.activeEditToolHint,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
|
height: 1.15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
// SquareToolbarButton(
|
||||||
|
// tooltip: 'Utolsó pont törlése',
|
||||||
|
// icon: Icons.undo,
|
||||||
|
// // onPressed: controller.canUndo ? controller.undoLastPoint : null,
|
||||||
|
// onPressed: () {},
|
||||||
|
// ),
|
||||||
|
// const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
onPressed: controller.cancelEditing,
|
||||||
|
icon: const Icon(Icons.close, size: 18),
|
||||||
|
label: const Text('Mégse'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
onPressed: canFinish ? controller.finishGeometry : null,
|
||||||
|
icon: const Icon(Icons.check, size: 18),
|
||||||
|
label: Text(controller.finishButtonText),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String _requiredPointText(MapEditTool tool) {
|
||||||
|
switch (tool) {
|
||||||
|
case MapEditTool.line:
|
||||||
|
return 'min. 2';
|
||||||
|
case MapEditTool.polygon:
|
||||||
|
return 'min. 3';
|
||||||
|
case MapEditTool.point:
|
||||||
|
return '1 pont';
|
||||||
|
case MapEditTool.none:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
lib/widgets/map_edit_tools/map_edit_point_drawing_content.dart
Normal file
103
lib/widgets/map_edit_tools/map_edit_point_drawing_content.dart
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
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_edit_small_status_chip.dart';
|
||||||
|
|
||||||
|
class PointDrawingContent extends StatelessWidget {
|
||||||
|
final MapSurveyController controller;
|
||||||
|
|
||||||
|
const PointDrawingContent({
|
||||||
|
required this.controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Obx(() {
|
||||||
|
// final hasPoint = controller.draftPoints.isNotEmpty;
|
||||||
|
final hasPoint = true;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
controller.activeEditToolIcon,
|
||||||
|
size: 20,
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
hasPoint
|
||||||
|
? 'Pont kiválasztva'
|
||||||
|
: controller.activeEditToolTitle,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (hasPoint)
|
||||||
|
SmallStatusChip(
|
||||||
|
text: '1 pont',
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
hasPoint
|
||||||
|
? 'A pont helye kijelölve. A mentéshez nyomd meg a Kész gombot.'
|
||||||
|
: controller.activeEditToolHint,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
|
height: 1.15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
onPressed: controller.cancelEditing,
|
||||||
|
icon: const Icon(Icons.close, size: 18),
|
||||||
|
label: const Text('Mégse'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
//onPressed: controller.addPointFromCurrentPosition,
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.my_location, size: 18),
|
||||||
|
label: const Text('Saját hely'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
// onPressed: controller.canFinishGeometry
|
||||||
|
// ? controller.finishGeometry
|
||||||
|
// : null,
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.check, size: 18),
|
||||||
|
label: const Text('Kész'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
37
lib/widgets/map_edit_tools/map_edit_small_status_chip.dart
Normal file
37
lib/widgets/map_edit_tools/map_edit_small_status_chip.dart
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SmallStatusChip extends StatelessWidget {
|
||||||
|
final String text;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
const SmallStatusChip({
|
||||||
|
required this.text,
|
||||||
|
required this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 7,
|
||||||
|
vertical: 3,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.10),
|
||||||
|
borderRadius: BorderRadius.circular(999),
|
||||||
|
border: Border.all(
|
||||||
|
color: color.withOpacity(0.55),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||||
|
color: color,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
height: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SquareToolbarButton extends StatelessWidget {
|
||||||
|
final String tooltip;
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
const SquareToolbarButton({
|
||||||
|
required this.tooltip,
|
||||||
|
required this.icon,
|
||||||
|
required this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Tooltip(
|
||||||
|
message: tooltip,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 42,
|
||||||
|
height: 40,
|
||||||
|
child: OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
minimumSize: const Size(42, 40),
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
|
onPressed: onPressed,
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
89
lib/widgets/map_edit_tools/map_edit_toolbar.dart
Normal file
89
lib/widgets/map_edit_tools/map_edit_toolbar.dart
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
|
import 'map_toolbar_action.dart';
|
||||||
|
import 'map_toolbar_divider.dart';
|
||||||
|
|
||||||
|
class MapEditCompactToolbar extends StatelessWidget {
|
||||||
|
final MapSurveyController controller;
|
||||||
|
|
||||||
|
const MapEditCompactToolbar({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Obx(() {
|
||||||
|
final activeTool = controller.activeEditTool.value;
|
||||||
|
|
||||||
|
return SafeArea(
|
||||||
|
top: false,
|
||||||
|
minimum: const EdgeInsets.fromLTRB(10, 0, 10, 10),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Material(
|
||||||
|
elevation: 8,
|
||||||
|
color: Theme.of(context).colorScheme.surface.withOpacity(0.96),
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 520,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
border: Border.all(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.outline.withOpacity(0.22),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ToolbarAction(
|
||||||
|
icon: Icons.add_location_alt_outlined,
|
||||||
|
label: 'Pont',
|
||||||
|
selected: activeTool == MapEditTool.point,
|
||||||
|
onTap: controller.startPointTool,
|
||||||
|
),
|
||||||
|
ToolbarAction(
|
||||||
|
icon: Icons.polyline_outlined,
|
||||||
|
label: 'Vonal',
|
||||||
|
selected: activeTool == MapEditTool.line,
|
||||||
|
onTap: controller.startLineTool,
|
||||||
|
),
|
||||||
|
ToolbarAction(
|
||||||
|
icon: Icons.border_outer_outlined,
|
||||||
|
label: 'Terület',
|
||||||
|
selected: activeTool == MapEditTool.polygon,
|
||||||
|
onTap: controller.startPolygonTool,
|
||||||
|
),
|
||||||
|
const ToolbarDivider(),
|
||||||
|
ToolbarAction(
|
||||||
|
icon: Icons.list_alt_outlined,
|
||||||
|
label: 'Lista',
|
||||||
|
selected: false,
|
||||||
|
onTap: controller.openFeatureList,
|
||||||
|
),
|
||||||
|
ToolbarAction(
|
||||||
|
icon: Icons.layers_outlined,
|
||||||
|
label: 'Rétegek',
|
||||||
|
selected: false,
|
||||||
|
onTap: controller.openLayerPanel,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
78
lib/widgets/map_edit_tools/map_feature_save_sheet.dart
Normal file
78
lib/widgets/map_edit_tools/map_feature_save_sheet.dart
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
|
import 'color_row.dart';
|
||||||
|
import 'label_field.dart';
|
||||||
|
import 'opacity_slider.dart';
|
||||||
|
import 'save_sheet_actions.dart';
|
||||||
|
import 'sheet_handle.dart';
|
||||||
|
import 'stroke_slider.dart';
|
||||||
|
|
||||||
|
class MapFeatureSaveSheet extends StatelessWidget {
|
||||||
|
final MapSurveyController ctrl;
|
||||||
|
final ScrollController scrollCtrl;
|
||||||
|
const MapFeatureSaveSheet({
|
||||||
|
required this.ctrl,
|
||||||
|
required this.scrollCtrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.15),
|
||||||
|
blurRadius: 16,
|
||||||
|
offset: const Offset(0, -4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: CustomScrollView(
|
||||||
|
controller: scrollCtrl,
|
||||||
|
slivers: [
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Handle
|
||||||
|
SheetHandle(),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Stílus',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ColorRow(ctrl: ctrl),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
OpacitySlider(ctrl: ctrl),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
if (ctrl.activeEditTool.value != MapEditTool.point) ...[
|
||||||
|
StrokeSlider(ctrl: ctrl),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
LabelField(ctrl: ctrl),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
SaveSheetActions(ctrl: ctrl),
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).padding.bottom + 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
lib/widgets/map_edit_tools/map_toolbar_action.dart
Normal file
79
lib/widgets/map_edit_tools/map_toolbar_action.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ToolbarAction extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
final bool selected;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const ToolbarAction({
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
required this.selected,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
final foreground = selected
|
||||||
|
? colorScheme.onPrimaryContainer
|
||||||
|
: colorScheme.onSurfaceVariant;
|
||||||
|
|
||||||
|
final background = selected
|
||||||
|
? colorScheme.primaryContainer.withOpacity(0.90)
|
||||||
|
: Colors.transparent;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 1),
|
||||||
|
child: Tooltip(
|
||||||
|
message: label,
|
||||||
|
waitDuration: const Duration(milliseconds: 500),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
onTap: onTap,
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 160),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
width: 58,
|
||||||
|
height: 48,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: background,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: selected
|
||||||
|
? Border.all(
|
||||||
|
color: colorScheme.primary.withOpacity(0.35),
|
||||||
|
width: 1,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 21,
|
||||||
|
color: foreground,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||||
|
color: foreground,
|
||||||
|
fontWeight:
|
||||||
|
selected ? FontWeight.w800 : FontWeight.w600,
|
||||||
|
height: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
lib/widgets/map_edit_tools/map_toolbar_divider.dart
Normal file
15
lib/widgets/map_edit_tools/map_toolbar_divider.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ToolbarDivider extends StatelessWidget {
|
||||||
|
const ToolbarDivider();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: 1,
|
||||||
|
height: 32,
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
color: Theme.of(context).colorScheme.outline.withOpacity(0.22),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
lib/widgets/map_edit_tools/opacity_slider.dart
Normal file
21
lib/widgets/map_edit_tools/opacity_slider.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
|
import 'labeled_slider.dart';
|
||||||
|
|
||||||
|
class OpacitySlider extends StatelessWidget {
|
||||||
|
final MapSurveyController ctrl;
|
||||||
|
const OpacitySlider({required this.ctrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Obx(() => LabeledSlider(
|
||||||
|
label: 'Átlátszóság',
|
||||||
|
value: ctrl.activeEditOpacity.value,
|
||||||
|
min: 0.1,
|
||||||
|
max: 1.0,
|
||||||
|
display: '${(ctrl.activeEditOpacity.value * 100).round()}%',
|
||||||
|
color: ctrl.activeEditColor.value,
|
||||||
|
onChanged: (v) => ctrl.activeEditOpacity.value = v,
|
||||||
|
));
|
||||||
|
}
|
||||||
51
lib/widgets/map_edit_tools/save_sheet_actions.dart
Normal file
51
lib/widgets/map_edit_tools/save_sheet_actions.dart
Normal file
@ -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';
|
||||||
|
|
||||||
|
class SaveSheetActions extends StatelessWidget {
|
||||||
|
final MapSurveyController ctrl;
|
||||||
|
const SaveSheetActions({required this.ctrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(children: [
|
||||||
|
// ← Vissza — bezárja a sheet-et, folytatja a rajzolást
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
shape:
|
||||||
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.arrow_back, size: 18),
|
||||||
|
label: const Text('Vissza'),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
// Mentés — végleges mentés, mindkét sheet bezárása
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Obx(() => FilledButton.icon(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: ctrl.activeEditColor.value,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10)),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.check, size: 18),
|
||||||
|
label: const Text(
|
||||||
|
'Mentés',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
//Navigator.pop(context); // style sheet bezárás
|
||||||
|
Get.back();
|
||||||
|
await ctrl.finishDraft();
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
lib/widgets/map_edit_tools/sheet_handle.dart
Normal file
18
lib/widgets/map_edit_tools/sheet_handle.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SheetHandle extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 36,
|
||||||
|
height: 4,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.withOpacity(0.35),
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
22
lib/widgets/map_edit_tools/stroke_slider.dart
Normal file
22
lib/widgets/map_edit_tools/stroke_slider.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
|
|
||||||
|
import 'labeled_slider.dart';
|
||||||
|
|
||||||
|
class StrokeSlider extends StatelessWidget {
|
||||||
|
final MapSurveyController ctrl;
|
||||||
|
const StrokeSlider({required this.ctrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Obx(() => LabeledSlider(
|
||||||
|
label: 'Körvonal',
|
||||||
|
value: ctrl.activeEditStrokeWidth.value,
|
||||||
|
min: 0.5,
|
||||||
|
max: 10.0,
|
||||||
|
divisions: 19,
|
||||||
|
display: '${ctrl.activeEditStrokeWidth.value.toStringAsFixed(1)} px',
|
||||||
|
color: ctrl.activeEditColor.value,
|
||||||
|
onChanged: (v) => ctrl.activeEditStrokeWidth.value = v,
|
||||||
|
));
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user