diff --git a/lib/enums/map_survey_mode.dart b/lib/enums/map_survey_mode.dart new file mode 100644 index 0000000..f5b946a --- /dev/null +++ b/lib/enums/map_survey_mode.dart @@ -0,0 +1 @@ +enum MapSurveyMode { browse, measure, stakeout, fieldWalk } 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 abb6cf1..c33e7f1 100644 --- a/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart +++ b/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart @@ -18,23 +18,21 @@ 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/enums/map_survey_mode.dart'; import 'package:terepi_seged/eov/convert_coordinate.dart'; import 'package:terepi_seged/eov/eov.dart'; import 'package:terepi_seged/models/point_to_measure.dart'; import 'package:terepi_seged/models/point_with_description_model.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:terepi_seged/pages/map_survey/presentations/views/measured_points_table_dialog.dart'; +import 'package:terepi_seged/pages/ntrip_settings/presentation/controllers/ntrip_settings_controller.dart'; +import 'package:terepi_seged/pages/ntrip_settings/presentation/views/ntrip_settings_sheet.dart'; import 'package:terepi_seged/services/coord_converter_service.dart'; 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_service.dart'; import 'package:terepi_seged/services/ntrip_service.dart'; -enum MapSurveyMode { - measure, // Bemérés — ahol vagyok, azt rögzítem - stakeout, // Kitűzés — adott ponthoz navigálok, majd rögzítem -} - class MapSurveyController extends GetxController { static MapSurveyController get to => Get.find(); @@ -258,6 +256,77 @@ class MapSurveyController extends GetxController { ); } + String _formatMeter(double? value, [int decimalSpace = 3]) { + if (value == null || value.isNaN || value.isInfinite) { + return '-'; + } + + return '${value.toStringAsFixed(decimalSpace)} m'; + } + + String get verticalAccuracyText { + return _formatMeter(gpsAltitudeError.value); + } + + String get horizontalAccuracyText { + return _formatMeter(max(gpsLatitudeError.value, gpsLongitudeError.value)); + } + + void setMode(MapSurveyMode newMode) { + mode.value = newMode; + + // Itt lehet módhoz kötött állapotokat állítani: + // - alsó panel tartalma + // - térképi tap viselkedés + // - aktív kártyák + // - track indítás/leállítás figyelmeztetés stb. + } + + String get currentModeLabel { + switch (mode.value) { + case MapSurveyMode.browse: + return 'Térkép'; + case MapSurveyMode.measure: + return 'Bemérés'; + case MapSurveyMode.stakeout: + return 'Kitűzés'; + case MapSurveyMode.fieldWalk: + return 'Bejárás'; + } + } + + IconData get currentModeIcon { + switch (mode.value) { + case MapSurveyMode.browse: + return Icons.map; + case MapSurveyMode.measure: + return Icons.add_location_alt; + case MapSurveyMode.stakeout: + return Icons.gps_fixed; + case MapSurveyMode.fieldWalk: + return Icons.hiking; + } + } + + void openNtripsettings() { + if (!Get.isRegistered()) { + Get.put(NtripSettingsController()); + } + + Get.bottomSheet(const NtripSettingsSheet(), + isScrollControlled: true, + backgroundColor: Colors.transparent, + ignoreSafeArea: false); + } + + void showNtripStatus() { + // Get.bottomSheet( + // const NtripStatusSheet(), + // isScrollControlled: true, + // backgroundColor: Colors.transparent, + // ); + } + // ───────────────────────────────────────────────────────────────── // Térkép vezérlők // ───────────────────────────────────────────────────────────────── 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 5dcd840..fc8e1bb 100644 --- a/lib/pages/map_survey/presentations/views/map_survey_view.dart +++ b/lib/pages/map_survey/presentations/views/map_survey_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_map_polywidget/flutter_map_polywidget.dart'; import 'package:get/get.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.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/views/settings_dialog.dart'; import 'package:terepi_seged/utils/rive_utils.dart'; @@ -59,11 +60,11 @@ class MapSurveyView extends GetView { // right: 16, // child: SavePointFab(controller: controller), // ), - Positioned( - bottom: 0, - left: 0, - right: 0, - child: MapBottomPanel(controller: controller)) + // Positioned( + // bottom: 0, + // left: 0, + // right: 0, + // child: MapBottomPanel(controller: controller)) ]); } } diff --git a/lib/pages/ntrip_settings/presentation/controllers/ntrip_settings_controller.dart b/lib/pages/ntrip_settings/presentation/controllers/ntrip_settings_controller.dart new file mode 100644 index 0000000..0c41fc8 --- /dev/null +++ b/lib/pages/ntrip_settings/presentation/controllers/ntrip_settings_controller.dart @@ -0,0 +1,126 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:get/get.dart'; +import 'package:terepi_seged/services/ntrip_service.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class NtripSettingsController extends GetxController { + final formKey = GlobalKey(); + + final hostController = TextEditingController(); + final portController = TextEditingController(); + final mountPointController = TextEditingController(); + final usernameController = TextEditingController(); + final passwordController = TextEditingController(); + + final isPasswordVisible = false.obs; + final autoConnect = false.obs; + final isBusy = false.obs; + + final _secureStorage = const FlutterSecureStorage(); + + @override + void onInit() { + super.onInit(); + loadSettings(); + } + + Future loadSettings() async { + final prefs = await SharedPreferences.getInstance(); + + hostController.text = prefs.getString('ntrip_host') ?? ''; + portController.text = prefs.getInt('ntrip_port')?.toString() ?? '2101'; + mountPointController.text = prefs.getString('ntrip_mountpoint') ?? ''; + usernameController.text = prefs.getString('ntrip_username') ?? ''; + autoConnect.value = prefs.getBool('ntrip_auto_connect') ?? false; + + passwordController.text = + await _secureStorage.read(key: 'ntrip_password') ?? ''; + } + + Future saveSettings() async { + if (!(formKey.currentState?.validate() ?? false)) { + return; + } + + final prefs = await SharedPreferences.getInstance(); + + await prefs.setString('ntrip_host', hostController.text.trim()); + await prefs.setInt( + 'ntrip_port', + int.parse(portController.text.trim()), + ); + await prefs.setString( + 'ntrip_mountpoint', + mountPointController.text.trim(), + ); + await prefs.setString( + 'ntrip_username', + usernameController.text.trim(), + ); + await prefs.setBool( + 'ntrip_auto_connect', + autoConnect.value, + ); + + await _secureStorage.write( + key: 'ntrip_password', + value: passwordController.text, + ); + } + + Future saveAndConnect() async { + if (!(formKey.currentState?.validate() ?? false)) { + return; + } + + try { + isBusy.value = true; + + await saveSettings(); + + // await NtripService.to.connect( + // host: hostController.text.trim(), + // port: int.parse(portController.text.trim()), + // mountPoint: mountPointController.text.trim(), + // username: usernameController.text.trim(), + // password: passwordController.text, + // ); + + Get.back(); + + Get.snackbar( + 'NTRIP', + 'Kapcsolódás elindítva.', + snackPosition: SnackPosition.BOTTOM, + ); + } catch (e) { + Get.snackbar( + 'NTRIP hiba', + e.toString(), + snackPosition: SnackPosition.BOTTOM, + ); + } finally { + isBusy.value = false; + } + } + + Future disconnect() async { + try { + isBusy.value = true; + await NtripService.to.disconnect(); + } finally { + isBusy.value = false; + } + } + + @override + void onClose() { + hostController.dispose(); + portController.dispose(); + mountPointController.dispose(); + usernameController.dispose(); + passwordController.dispose(); + super.onClose(); + } +} diff --git a/lib/pages/ntrip_settings/presentation/views/ntrip_settings_sheet.dart b/lib/pages/ntrip_settings/presentation/views/ntrip_settings_sheet.dart new file mode 100644 index 0000000..f7157e5 --- /dev/null +++ b/lib/pages/ntrip_settings/presentation/views/ntrip_settings_sheet.dart @@ -0,0 +1,216 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:terepi_seged/services/ntrip_service.dart'; + +import '../controllers/ntrip_settings_controller.dart'; + +class NtripSettingsSheet extends GetView { + const NtripSettingsSheet({super.key}); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final bottomInset = MediaQuery.viewInsetsOf(context).bottom; + final screenHeight = MediaQuery.sizeOf(context).height; + + return SafeArea( + top: false, + child: Align( + alignment: Alignment.bottomCenter, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: screenHeight * 0.86, + ), + child: Material( + color: colorScheme.surface, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + clipBehavior: Clip.antiAlias, + child: SingleChildScrollView( + keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, + reverse: true, + padding: EdgeInsets.fromLTRB(16, 12, 16, 20 + bottomInset), + child: Form( + key: controller.formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 42, + height: 4, + margin: const EdgeInsets.only(bottom: 14), + decoration: BoxDecoration( + color: colorScheme.outlineVariant, + borderRadius: BorderRadius.circular(999), + ), + ), + ), + Row( + children: [ + const Icon(Icons.settings_input_antenna), + const SizedBox(width: 8), + Text( + 'NTRIP beállítások', + style: Theme.of(context).textTheme.titleLarge, + ), + ], + ), + const SizedBox(height: 16), + TextFormField( + controller: controller.hostController, + decoration: const InputDecoration( + labelText: 'Caster / host', + hintText: 'pl. caster.example.com', + prefixIcon: Icon(Icons.dns), + ), + textInputAction: TextInputAction.next, + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'A host megadása kötelező.'; + } + return null; + }, + ), + const SizedBox(height: 12), + TextFormField( + controller: controller.portController, + decoration: const InputDecoration( + labelText: 'Port', + hintText: '2101', + prefixIcon: Icon(Icons.tag), + ), + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + validator: (value) { + final port = int.tryParse(value?.trim() ?? ''); + if (port == null || port <= 0 || port > 65535) { + return 'Érvényes portszámot adj meg.'; + } + return null; + }, + ), + const SizedBox(height: 12), + TextFormField( + controller: controller.mountPointController, + decoration: const InputDecoration( + labelText: 'Mountpoint', + hintText: 'pl. BUDAPEST_RTCM32', + prefixIcon: Icon(Icons.place), + ), + textInputAction: TextInputAction.next, + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'A mountpoint megadása kötelező.'; + } + return null; + }, + ), + const SizedBox(height: 12), + TextFormField( + controller: controller.usernameController, + decoration: const InputDecoration( + labelText: 'Felhasználónév', + prefixIcon: Icon(Icons.person), + ), + textInputAction: TextInputAction.next, + ), + const SizedBox(height: 12), + Obx(() { + return TextFormField( + controller: controller.passwordController, + decoration: InputDecoration( + labelText: 'Jelszó', + prefixIcon: const Icon(Icons.lock), + suffixIcon: IconButton( + icon: Icon( + controller.isPasswordVisible.value + ? Icons.visibility_off + : Icons.visibility, + ), + onPressed: () { + controller.isPasswordVisible.value = + !controller.isPasswordVisible.value; + }, + ), + ), + obscureText: !controller.isPasswordVisible.value, + ); + }), + const SizedBox(height: 8), + Obx(() { + return SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Automatikus kapcsolódás'), + subtitle: const Text( + 'Az app indításakor próbáljon NTRIP-re kapcsolódni.', + ), + value: controller.autoConnect.value, + onChanged: (value) { + controller.autoConnect.value = value; + }, + ); + }), + const SizedBox(height: 16), + Obx(() { + final isBusy = controller.isBusy.value; + final isConnected = NtripService.to.isConnected.value; + + return Row( + children: [ + if (isConnected) + Expanded( + child: OutlinedButton.icon( + onPressed: + isBusy ? null : controller.disconnect, + icon: const Icon(Icons.link_off), + label: const Text('Bontás'), + ), + ), + if (isConnected) const SizedBox(width: 12), + Expanded( + child: FilledButton.icon( + onPressed: + isBusy ? null : controller.saveAndConnect, + icon: isBusy + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : const Icon(Icons.play_arrow), + label: Text( + isBusy + ? 'Kapcsolódás...' + : 'Mentés és kapcsolódás', + ), + ), + ), + ], + ); + }), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: TextButton( + onPressed: () async { + await controller.saveSettings(); + Get.back(); + }, + child: const Text('Csak mentés'), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/shell/presentations/controllers/shell_controller.dart b/lib/pages/shell/presentations/controllers/shell_controller.dart index 15e340a..6eb46ae 100644 --- a/lib/pages/shell/presentations/controllers/shell_controller.dart +++ b/lib/pages/shell/presentations/controllers/shell_controller.dart @@ -3,7 +3,7 @@ import 'package:get/get.dart'; class ShellController extends GetxController { static ShellController get to => Get.find(); - final currentIndex = 0.obs; + final currentIndex = 1.obs; final isNavBarVisible = true.obs; static const titles = [ diff --git a/lib/pages/shell/presentations/views/shell_view.dart b/lib/pages/shell/presentations/views/shell_view.dart index ac7295b..12ad0c2 100644 --- a/lib/pages/shell/presentations/views/shell_view.dart +++ b/lib/pages/shell/presentations/views/shell_view.dart @@ -4,6 +4,8 @@ import 'package:terepi_seged/pages/home/presentation/views/home_view.dart'; import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; import 'package:terepi_seged/services/ntrip_service.dart'; import 'package:terepi_seged/widgets/gnss_status_chip.dart'; +import 'package:terepi_seged/widgets/shell_adaptive_appbar.dart'; +import 'package:terepi_seged/widgets/shell_map_appbar.dart'; import '../../../../widgets/app_drawer.dart'; import '../../../map_survey/presentations/views/map_survey_view.dart'; @@ -22,63 +24,36 @@ class ShellView extends GetView { @override Widget build(BuildContext context) { + final mapController = Get.find(); + return Scaffold( - extendBody: true, - extendBodyBehindAppBar: false, - appBar: AppBar( - // Cím reaktívan frissül tab váltáskor - title: Obx(() => Text(controller.currentTitle)), - actions: [ - const GnssStatusChip(), - const SizedBox(width: 6), - Obx(() => controller.currentIndex.value == 1 - ? NtripStatusChip( - isConnected: NtripService.to.isConnected, - onToggle: () { - NtripService.to.isConnected.value - ? NtripService.to.disconnect() - : NtripService.to.connect(); - }, - ) - : const SizedBox.shrink()) - ], - ), + // extendBody: true, + // extendBodyBehindAppBar: false, + resizeToAvoidBottomInset: true, + appBar: ShellMapAppBar(controller: mapController), + // appBar: AppBar( + // // Cím reaktívan frissül tab váltáskor + // title: Obx(() => Text(controller.currentTitle)), + // actions: [ + // const GnssStatusChip(), + // const SizedBox(width: 6), + // Obx(() => controller.currentIndex.value == 1 + // ? NtripStatusChip( + // isConnected: NtripService.to.isConnected, + // onToggle: () { + // NtripService.to.isConnected.value + // ? NtripService.to.disconnect() + // : NtripService.to.connect(); + // }, + // ) + // : const SizedBox.shrink()) + // ], + // ), drawer: const AppDrawer(), body: Obx(() => IndexedStack( index: controller.currentIndex.value, children: _pages, )), - bottomNavigationBar: Obx( - () => AnimatedSize( - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - child: controller.isNavBarVisible.value - ? NavigationBar( - selectedIndex: controller.currentIndex.value, - onDestinationSelected: controller.goToTab, - destinations: const [ - NavigationDestination( - icon: Icon(Icons.map_outlined), - selectedIcon: Icon(Icons.map), - label: 'Térkép', - ), - NavigationDestination( - icon: Icon(Icons.gps_fixed), - label: 'Mérés', - ), - NavigationDestination( - icon: Icon(Icons.route), - label: 'Track', - ), - NavigationDestination( - icon: Icon(Icons.table_chart_outlined), - selectedIcon: Icon(Icons.table_chart), - label: 'Adatok', - ), - ], - ) - : const SizedBox.shrink()), - ), ); } } diff --git a/lib/services/ntrip_service.dart b/lib/services/ntrip_service.dart index 0c2de62..7b415b1 100644 --- a/lib/services/ntrip_service.dart +++ b/lib/services/ntrip_service.dart @@ -200,8 +200,8 @@ class NtripService extends GetxService { 'Accept: */*\r\n' 'Connection: close\r\n' 'Authorization: Basic $auth\r\n' - 'Host:$host\r\n' - 'Ntrip-Version:Ntrip/2.0\r\n' + 'Host: $host\r\n' + 'Ntrip-Version: Ntrip/2.0\r\n' '\r\n'; } @@ -228,7 +228,7 @@ class NtripService extends GetxService { host.value = prefs.getString('ntrip_host') ?? '84.206.45.44'; port.value = prefs.getInt('ntrip_port') ?? 2101; mountpoint.value = prefs.getString('ntrip_mountpoint') ?? 'SGO_RTK3.2'; - username.value = prefs.getString('ntrip_username') ?? 'elgi01'; + username.value = prefs.getString('ntrip_username') ?? 'elgi03'; password.value = prefs.getString('ntrip_password') ?? 'StEfan14'; } diff --git a/lib/widgets/gnss_status_chip.dart b/lib/widgets/gnss_status_chip.dart index 33594e1..f1f9dc8 100644 --- a/lib/widgets/gnss_status_chip.dart +++ b/lib/widgets/gnss_status_chip.dart @@ -19,8 +19,8 @@ import 'gnss_device_picker_dialog.dart'; /// - Kék: DGPS (quality 2) /// - Világoszöld: RTK Float (quality 5) /// - Zöld: RTK Fix (quality 4) ← ideális állapot -class GnssStatusChip extends StatelessWidget { - const GnssStatusChip({super.key}); +class GnssIconStatusChip extends StatelessWidget { + const GnssIconStatusChip({super.key}); @override Widget build(BuildContext context) { @@ -84,6 +84,61 @@ class GnssStatusChip extends StatelessWidget { } } +class GnssTextStatusChip extends StatelessWidget { + const GnssTextStatusChip({super.key}); + + @override + Widget build(BuildContext context) { + return Obx(() { + final connState = GnssService.to.connectionState.value; + final quality = GnssService.to.gpsQuality.value; + final isConn = connState == GnssConnectionState.connected; + final isConnecting = connState == GnssConnectionState.connecting; + final color = _chipColor(isConn, quality); + + return isConnecting + ? Text('') + : Text(_chipLabel(connState, quality, ''), + style: TextStyle( + fontSize: 12, fontWeight: FontWeight.w600, color: color)); + }); + } + + Color _chipColor(bool connected, int quality) { + if (!connected) return Colors.grey; + return switch (quality) { + 4 => Colors.greenAccent, + 5 => Colors.lightGreen, + 2 => Colors.blue, + 1 => Colors.orange, + _ => Colors.orange.shade300, + }; + } + + IconData _chipIcon(bool connected, int quality) { + if (!connected) return Icons.gps_off; + if (quality == 0) return Icons.gps_not_fixed; + return Icons.gps_fixed; + } + + String _chipLabel( + GnssConnectionState state, int quality, String? deviceName) { + return switch (state) { + GnssConnectionState.connecting => 'Kapcsolódás...', + GnssConnectionState.disconnected => + deviceName != null ? '-----' : 'N-----z', + GnssConnectionState.error => 'Hiba', + GnssConnectionState.connected => switch (quality) { + 4 => 'RTK Fix', + 5 => 'RTK Float', + 2 => 'DGPS', + 1 => 'GPS', + _ => 'Fix nélkül', + }, + }; + } +} + /// NTRIP kapcsolat chip az AppBar-ban. /// /// Tapintásra az NTRIP beállítások oldalra navigál. @@ -143,3 +198,38 @@ class NtripStatusChip extends StatelessWidget { }); } } + +class NtripIconStatusChip extends StatelessWidget { + /// NTRIP csatlakozva van-e — a controllerből kapja. + final RxBool isConnected; + + /// Csatlakozás / leválasztás callback. + final VoidCallback? onToggle; + + /// NTRIP beállítások megnyitása. + final VoidCallback? onSettings; + + const NtripIconStatusChip({ + super.key, + required this.isConnected, + this.onToggle, + this.onSettings, + }); + + @override + Widget build(BuildContext context) { + return Obx(() { + final connected = isConnected.value; + final color = connected ? Colors.greenAccent : Colors.grey; + + return GestureDetector( + onTap: onToggle, + onLongPress: onSettings, + child: Icon( + Icons.cell_tower, + color: color, + ), + ); + }); + } +} diff --git a/lib/widgets/map_bottom_panel.dart b/lib/widgets/map_bottom_panel.dart index e48b563..42a871e 100644 --- a/lib/widgets/map_bottom_panel.dart +++ b/lib/widgets/map_bottom_panel.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.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/shell/presentations/controllers/shell_controller.dart'; diff --git a/lib/widgets/map_mode_menu_anchor.dart b/lib/widgets/map_mode_menu_anchor.dart new file mode 100644 index 0000000..d06bda7 --- /dev/null +++ b/lib/widgets/map_mode_menu_anchor.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:get/get_state_manager/get_state_manager.dart'; +import 'package:terepi_seged/enums/map_survey_mode.dart'; +import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; + +class MapModeMenuAnchor extends StatelessWidget { + final MapSurveyController controller; + + const MapModeMenuAnchor({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Obx(() { + return MenuAnchor( + menuChildren: [ + MenuItemButton( + leadingIcon: const Icon(Icons.map), + trailingIcon: controller.mode.value == MapSurveyMode.browse + ? const Icon(Icons.check) + : null, + onPressed: () => controller.setMode(MapSurveyMode.browse), + child: const Text('Térkép')), + MenuItemButton( + leadingIcon: const Icon(Icons.add_location_alt), + trailingIcon: controller.mode.value == MapSurveyMode.measure + ? const Icon(Icons.check) + : null, + onPressed: () => controller.setMode(MapSurveyMode.measure), + child: const Text('Bemérés')), + MenuItemButton( + leadingIcon: const Icon(Icons.gps_fixed), + trailingIcon: controller.mode.value == MapSurveyMode.stakeout + ? const Icon(Icons.check) + : null, + onPressed: () => controller.setMode(MapSurveyMode.stakeout), + child: const Text('Kitűzés')), + MenuItemButton( + leadingIcon: const Icon(Icons.hiking), + trailingIcon: controller.mode.value == MapSurveyMode.fieldWalk + ? const Icon(Icons.check) + : null, + onPressed: () => controller.setMode(MapSurveyMode.fieldWalk), + child: const Text('Bejárás')), + MenuItemButton( + leadingIcon: const Icon(Icons.route), + trailingIcon: controller.mode.value == MapSurveyMode.browse + ? const Icon(Icons.check) + : null, + onPressed: () => controller.setMode(MapSurveyMode.browse), + child: const Text('Track')), + ], + builder: (context, menuController, child) { + return InkWell( + borderRadius: BorderRadius.circular(18), + onTap: () { + if (menuController.isOpen) { + menuController.close(); + } else { + menuController.open(); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 4, + ), + child: Column( + children: [ + Row(mainAxisSize: MainAxisSize.min, children: [ + Icon(controller.currentModeIcon, size: 14), + const SizedBox(width: 1), + Text(controller.currentModeLabel, + style: const TextStyle( + fontSize: 14, fontWeight: FontWeight.w600)), + const Icon(Icons.arrow_drop_down, size: 18), + ]), + Text('Projekt', + style: TextStyle( + fontSize: 11, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w400, + color: colorScheme.onSurfaceVariant)) + ], + ))); + }); + }); + } +} diff --git a/lib/widgets/shell_adaptive_appbar.dart b/lib/widgets/shell_adaptive_appbar.dart new file mode 100644 index 0000000..3967a1c --- /dev/null +++ b/lib/widgets/shell_adaptive_appbar.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:terepi_seged/pages/shell/presentations/controllers/shell_controller.dart'; + +class ShellAdaptiveAppBar extends StatelessWidget + implements PreferredSizeWidget { + final ShellController controller; + + const ShellAdaptiveAppBar({ + super.key, + required this.controller, + }); + + @override + Size get preferredSize => const Size.fromHeight(60); + + @override + Widget build(BuildContext context) { + return AppBar( + title: const Text('Adatook'), + ); + } +} diff --git a/lib/widgets/shell_map_appbar.dart b/lib/widgets/shell_map_appbar.dart new file mode 100644 index 0000000..b05b4a7 --- /dev/null +++ b/lib/widgets/shell_map_appbar.dart @@ -0,0 +1,131 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get_state_manager/get_state_manager.dart'; +import 'package:get/state_manager.dart'; +import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; +import 'package:terepi_seged/pages/shell/presentations/controllers/shell_controller.dart'; +import 'package:terepi_seged/services/ntrip_service.dart'; +import 'package:terepi_seged/widgets/gnss_status_chip.dart'; +import 'package:terepi_seged/widgets/map_mode_menu_anchor.dart'; + +class ShellMapAppBar extends StatelessWidget implements PreferredSizeWidget { + final MapSurveyController controller; + + const ShellMapAppBar({ + super.key, + required this.controller, + }); + + @override + Size get preferredSize => const Size.fromHeight(52); + + @override + Widget build(BuildContext context) { + return AppBar( + toolbarHeight: 60, + leadingWidth: 44, + titleSpacing: 0, + leading: Builder(builder: (context) { + return IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 40, minHeight: 40), + icon: const Icon(Icons.menu), + onPressed: () { + Scaffold.of(context).openDrawer(); + }, + ); + }), + title: Obx(() { + return Row(children: [ + MapModeMenuAnchor(controller: controller), + const SizedBox(width: 2), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row(children: [ + const Text('H:', style: TextStyle(fontSize: 12)), + SizedBox(width: 2), + Text(controller.horizontalAccuracyText, + style: TextStyle( + fontSize: 12, + color: _errorColor(max( + controller.gpsLatitudeError.value, + controller.gpsLongitudeError.value)), + fontWeight: FontWeight.w600, + fontFeatures: const [FontFeature.tabularFigures()])) + ]), + Row(children: [ + const Text('V:', style: TextStyle(fontSize: 12)), + SizedBox(width: 2), + Text(controller.verticalAccuracyText, + style: TextStyle( + fontSize: 12, + color: + _errorColor(controller.gpsAltitudeError.value), + fontWeight: FontWeight.w600, + fontFeatures: const [FontFeature.tabularFigures()])) + ]) + ]), + SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + GnssTextStatusChip(), + Row(children: [ + const Text('V:', style: TextStyle(fontSize: 12)), + SizedBox(width: 2), + Text(controller.verticalAccuracyText, + style: TextStyle( + fontSize: 12, + color: + _errorColor(controller.gpsAltitudeError.value), + fontWeight: FontWeight.w600, + fontFeatures: const [FontFeature.tabularFigures()])) + ]) + ]), + const GnssIconStatusChip(), + const SizedBox(width: 2), + NtripIconStatusChip( + isConnected: NtripService.to.isConnected, + onToggle: () { + NtripService.to.isConnected.value + ? NtripService.to.disconnect() + : NtripService.to.connect(); + }, + onSettings: () { + HapticFeedback.mediumImpact(); + controller.openNtripsettings(); + }), + ]); + }), + actions: [ + PopupMenuButton( + tooltip: 'További funkciók', + icon: const Icon(Icons.more_vert), + onSelected: null, + itemBuilder: (context) => const [ + PopupMenuItem( + value: 1, + child: Text('Koordináták'), + ), + PopupMenuItem( + value: 1, + child: Text('Rétegek'), + ), + ]) + ]); + } + + Color _errorColor(double e) { + if (e < 0.05) return Colors.greenAccent; + if (e < 0.2) return Colors.green; + if (e < 1.0) return Colors.orange; + return Colors.red; + } +}