diff --git a/lib/main.dart b/lib/main.dart index fb6f6de..12bd2ae 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,8 @@ import 'package:get/get.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:terepi_seged/routes/app_pages.dart'; import 'package:terepi_seged/services/coord_converter_service.dart'; +import 'package:terepi_seged/services/gnss/gnss_device_service.dart'; +import 'package:terepi_seged/services/gnss/gnss_service.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -16,6 +18,8 @@ Future main() async { await Get.putAsync( () => CoordConverterService().init()); + Get.put(GnssDeviceService()); + Get.put(GnssService()); runApp(const MyApp()); } diff --git a/lib/pages/home/presentation/views/home_view.dart b/lib/pages/home/presentation/views/home_view.dart index a3ebd13..300e6a9 100644 --- a/lib/pages/home/presentation/views/home_view.dart +++ b/lib/pages/home/presentation/views/home_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:terepi_seged/pages/home/presentation/controllers/home_controller.dart'; import 'package:terepi_seged/pages/home/presentation/widgets/big_button_widget.dart'; +import 'package:terepi_seged/widgets/app_drawer.dart'; class HomeView extends GetView { const HomeView({Key? key}) : super(key: key); @@ -10,195 +11,179 @@ class HomeView extends GetView { Widget build(BuildContext context) { return Container( color: Colors.white, - child: Scaffold( - extendBody: true, - backgroundColor: Colors.black.withOpacity(0.05), - appBar: AppBar( - elevation: 0.0, - title: const Text('Kezdőlap'), - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios), - color: Colors.white, - onPressed: () { - Get.back(); - }, + child: Column(children: [ + Column( + children: const [ + Padding( + padding: EdgeInsets.only(top: 10.0), + child: Center( + child: Text("Kezdőlap"), + ), ), + // TextButton( + // onPressed: () async { + // Get.toNamed('/map'); + // }, + // child: const Text('Map')), + // TextButton( + // onPressed: () async { + // Get.toNamed('/bluetooth_test'); + // }, + // child: const Text('Bluetooth test')), + // TextButton( + // onPressed: () async { + // Get.toNamed('/socket_test'); + // }, + // child: const Text('Socket teszt')), + // TextButton( + // onPressed: () async { + // Get.toNamed('/rtcm_test'); + // }, + // child: const Text('RTCM teszt')) + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + onPressed: () async { + Get.toNamed("/field_trip"); + }, + child: Text('Field trip')), + // TextButton( + // onPressed: () async { + // Get.toNamed("/tracking"); + // }, + // child: Text('Tracking')), + TextButton( + onPressed: () async { + Get.toNamed("/map"); + }, + child: Text('Kitűzés')) + ])), + Padding( + padding: const EdgeInsets.only(top: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // BigButtonWidget( + // iconData: Icons.flag, + // label: "Kitűzés", + // onPressed: () async { + // Get.toNamed('/map'); + // }, + // ), + BigButtonWidget( + iconData: Icons.flag, + label: "Bemérés", + onPressed: () async { + Get.toNamed('/map_survey'); + }, + ), + BigButtonWidget( + iconData: Icons.monitor_heart, + label: "Mérés", + onPressed: () async { + Get.toNamed("/measured_data"); + // ScaffoldMessenger.of(context) + // .showSnackBar(const SnackBar( + // content: Text( + // "Fejlesztlés alatt", + // style: TextStyle(fontWeight: FontWeight.bold), + // ), + // backgroundColor: Colors.black54, + // )); + }, + ), + ], ), - body: Column(children: [ - Column( - children: const [ - Padding( - padding: EdgeInsets.only(top: 10.0), - child: Center( - child: Text("Kezdőlap"), - ), - ), - // TextButton( - // onPressed: () async { - // Get.toNamed('/map'); - // }, - // child: const Text('Map')), - // TextButton( - // onPressed: () async { - // Get.toNamed('/bluetooth_test'); - // }, - // child: const Text('Bluetooth test')), - // TextButton( - // onPressed: () async { - // Get.toNamed('/socket_test'); - // }, - // child: const Text('Socket teszt')), - // TextButton( - // onPressed: () async { - // Get.toNamed('/rtcm_test'); - // }, - // child: const Text('RTCM teszt')) - ], - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - onPressed: () async { - Get.toNamed("/field_trip"); - }, - child: Text('Field trip')), - // TextButton( - // onPressed: () async { - // Get.toNamed("/tracking"); - // }, - // child: Text('Tracking')), - TextButton( - onPressed: () async { - Get.toNamed("/map"); - }, - child: Text('Kitűzés')) - ])), - Padding( - padding: const EdgeInsets.only(top: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - // BigButtonWidget( - // iconData: Icons.flag, - // label: "Kitűzés", - // onPressed: () async { - // Get.toNamed('/map'); - // }, - // ), - BigButtonWidget( - iconData: Icons.flag, - label: "Bemérés", - onPressed: () async { - Get.toNamed('/map_survey'); - }, - ), - BigButtonWidget( - iconData: Icons.monitor_heart, - label: "Mérés", - onPressed: () async { - Get.toNamed("/measured_data"); - // ScaffoldMessenger.of(context) - // .showSnackBar(const SnackBar( - // content: Text( - // "Fejlesztlés alatt", - // style: TextStyle(fontWeight: FontWeight.bold), - // ), - // backgroundColor: Colors.black54, - // )); - }, - ), - ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + BigButtonWidget( + iconData: Icons.navigation, + label: "Navigáció", + onPressed: () async { + Get.toNamed('/navigation'); + // ScaffoldMessenger.of(context) + // .showSnackBar(const SnackBar( + // content: Text( + // "Fejlesztlés alatt", + // style: TextStyle(fontWeight: FontWeight.bold), + // ), + // backgroundColor: Colors.black54, + // )); + }, ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - BigButtonWidget( - iconData: Icons.navigation, - label: "Navigáció", - onPressed: () async { - Get.toNamed('/navigation'); - // ScaffoldMessenger.of(context) - // .showSnackBar(const SnackBar( - // content: Text( - // "Fejlesztlés alatt", - // style: TextStyle(fontWeight: FontWeight.bold), - // ), - // backgroundColor: Colors.black54, - // )); - }, - ), - BigButtonWidget( - iconData: Icons.message, - label: "Üzenetek", - onPressed: () async { - // Get.toNamed("/navigation"); - ScaffoldMessenger.of(context) - .showSnackBar(const SnackBar( - content: Text( - "Fejlesztlés alatt", - style: TextStyle(fontWeight: FontWeight.bold), - ), - backgroundColor: Colors.black54, - )); - }, - ), - ], + BigButtonWidget( + iconData: Icons.message, + label: "Üzenetek", + onPressed: () async { + // Get.toNamed("/navigation"); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text( + "Fejlesztlés alatt", + style: TextStyle(fontWeight: FontWeight.bold), + ), + backgroundColor: Colors.black54, + )); + }, ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - BigButtonWidget( - iconData: Icons.house, - label: "Ingatlanok", - onPressed: () async { - // Get.toNamed('/map_test'); - ScaffoldMessenger.of(context) - .showSnackBar(const SnackBar( - content: Text( - "Fejlesztlés alatt", - style: TextStyle(fontWeight: FontWeight.bold), - ), - backgroundColor: Colors.black54, - )); - }, - ), - BigButtonWidget( - iconData: Icons.edit_road, - label: "Track", - onPressed: () async { - Get.toNamed("/tracking"); - // ScaffoldMessenger.of(context) - // .showSnackBar(const SnackBar( - // content: Text( - // "Fejlesztlés alatt", - // style: TextStyle(fontWeight: FontWeight.bold), - // ), - // backgroundColor: Colors.black54, - // )); - }, - ), - ], + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + BigButtonWidget( + iconData: Icons.house, + label: "Ingatlanok", + onPressed: () async { + // Get.toNamed('/map_test'); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text( + "Fejlesztlés alatt", + style: TextStyle(fontWeight: FontWeight.bold), + ), + backgroundColor: Colors.black54, + )); + }, ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Obx( - () => Text( - "Verzió: ${controller.packageInfo.value.version}+${controller.packageInfo.value.buildNumber}", - style: const TextStyle(fontSize: 12)), - ) - ], + BigButtonWidget( + iconData: Icons.edit_road, + label: "Track", + onPressed: () async { + Get.toNamed("/tracking"); + // ScaffoldMessenger.of(context) + // .showSnackBar(const SnackBar( + // content: Text( + // "Fejlesztlés alatt", + // style: TextStyle(fontWeight: FontWeight.bold), + // ), + // backgroundColor: Colors.black54, + // )); + }, + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Obx( + () => Text( + "Verzió: ${controller.packageInfo.value.version}+${controller.packageInfo.value.buildNumber}", + style: const TextStyle(fontSize: 12)), ) - ]))); + ], + ) + ])); } } diff --git a/lib/pages/shell/bindings/shell_binding.dart b/lib/pages/shell/bindings/shell_binding.dart new file mode 100644 index 0000000..e71376f --- /dev/null +++ b/lib/pages/shell/bindings/shell_binding.dart @@ -0,0 +1,15 @@ +import 'package:get/get.dart'; +import 'package:terepi_seged/pages/home/presentation/controllers/home_controller.dart'; +import 'package:terepi_seged/pages/map/presentation/controllers/map_controller.dart'; + +import '../presentations/controllers/shell_controller.dart'; + +class ShellBinding extends Bindings { + @override + void dependencies() { + // TODO: implement dependencies + Get.put(ShellController()); + Get.put(HomeViewController()); + Get.put(MapViewController()); + } +} diff --git a/lib/pages/shell/presentations/controllers/shell_controller.dart b/lib/pages/shell/presentations/controllers/shell_controller.dart new file mode 100644 index 0000000..7d3bc5a --- /dev/null +++ b/lib/pages/shell/presentations/controllers/shell_controller.dart @@ -0,0 +1,27 @@ +import 'package:get/get.dart'; + +class ShellController extends GetxController { + static ShellController get to => Get.find(); + + final currentIndex = 0.obs; + + static const titles = [ + 'Térkép', + 'Pontmérés', + 'Nyomvonal', + 'Adatok', + ]; + + String get currentTitle => titles[currentIndex.value]; + + void goToTab(int index) { + if (index < 0 || index >= titles.length) return; + currentIndex.value = index; + } + + // Shortcutok — a Drawerből hívhatók + void goToMap() => goToTab(0); + void goToSurvey() => goToTab(1); + void goToTracking() => goToTab(2); + void goToData() => goToTab(3); +} diff --git a/lib/pages/shell/presentations/views/shell_view.dart b/lib/pages/shell/presentations/views/shell_view.dart new file mode 100644 index 0000000..6827a3c --- /dev/null +++ b/lib/pages/shell/presentations/views/shell_view.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:terepi_seged/pages/home/presentation/views/home_view.dart'; + +import '../../../../widgets/app_drawer.dart'; +import '../../../map_survey/presentations/views/mapsurvey_view.dart'; +import '../controllers/shell_controller.dart'; + +class ShellView extends GetView { + const ShellView({super.key}); + + static const _pages = [ + HomeView(), + //MapView(), + MapSurveyView(), + //TrackingView(), + //DataView(), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + // Cím reaktívan frissül tab váltáskor + title: Obx(() => Text(controller.currentTitle)), + actions: [ + //Obx(() => _GnssStatusChip()), + const SizedBox(width: 8), + ], + ), + drawer: const AppDrawer(), + body: Obx(() => IndexedStack( + index: controller.currentIndex.value, + children: _pages, + )), + bottomNavigationBar: Obx(() => 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', + ), + ], + )), + ); + } +} diff --git a/lib/pages/start/presentation/controllers/start_page_controller.dart b/lib/pages/start/presentation/controllers/start_page_controller.dart index 98d1dfe..0855b35 100644 --- a/lib/pages/start/presentation/controllers/start_page_controller.dart +++ b/lib/pages/start/presentation/controllers/start_page_controller.dart @@ -98,6 +98,7 @@ class StartPageController extends GetxController { void _departStartPage() async { await Future.delayed(const Duration(seconds: 5)); - Get.offAllNamed(Routes.HOME); + //Get.offAllNamed(Routes.HOME); + Get.offAllNamed(Routes.SHELL); } } diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 67a44dc..f5ddf1b 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -28,6 +28,9 @@ import 'package:terepi_seged/pages/tracking/presentation/views/tracking_view.dar import '../pages/map_test/bindings/map_test_bindings.dart'; import '../pages/map_test/presentation/views/map_test_view.dart'; +import '../pages/shell/bindings/shell_binding.dart'; +import '../pages/shell/presentations/views/shell_view.dart'; + part 'app_routes.dart'; // ignore: avoid_classes_with_only_static_members @@ -46,6 +49,11 @@ class AppPages { name: Routes.STARTPAGE, page: () => const StartPage(), binding: StartPageBinding()), + GetPage( + name: Routes.SHELL, + page: () => const ShellView(), + binding: ShellBinding(), + ), GetPage( name: Routes.MAP, page: () => const MapView(), binding: MapBinding()), GetPage( diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 49663b5..57551b0 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -19,4 +19,7 @@ abstract class Routes { static const TRACKING = '/tracking'; static const MAPADDPOINTDIALOG = "/map_add_point_dialog"; + + static const LOGIN = '/login'; + static const SHELL = '/shell'; } diff --git a/lib/services/gnss/ble_gnss_connection.dart b/lib/services/gnss/ble_gnss_connection.dart index d222d0d..1b95289 100644 --- a/lib/services/gnss/ble_gnss_connection.dart +++ b/lib/services/gnss/ble_gnss_connection.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'gnss_connection.dart'; +import 'gnss_device_service.dart'; // Nordic UART Service — a legelterjedtebb BLE UART profil const _nusServiceUuid = '6e400001-b5b3-f393-e0a9-e50e24dcca9e'; diff --git a/lib/services/gnss/bt_serial_gnss_connection.dart b/lib/services/gnss/bt_serial_gnss_connection.dart index c7c62b0..bbd8980 100644 --- a/lib/services/gnss/bt_serial_gnss_connection.dart +++ b/lib/services/gnss/bt_serial_gnss_connection.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import 'gnss_connection.dart'; +import 'gnss_device_service.dart'; class BtSerialGnssConnection implements GnssConnection { @override diff --git a/lib/services/gnss/gnss_connection.dart b/lib/services/gnss/gnss_connection.dart index d0683ad..4815dc3 100644 --- a/lib/services/gnss/gnss_connection.dart +++ b/lib/services/gnss/gnss_connection.dart @@ -1,6 +1,6 @@ // lib/services/gnss/gnss_connection.dart -enum GnssConnectionType { btSerial, ble } +import 'gnss_device_service.dart'; enum GnssConnectionState { disconnected, connecting, connected, error } diff --git a/lib/services/gnss/gnss_device_service.dart b/lib/services/gnss/gnss_device_service.dart new file mode 100644 index 0000000..1f588e2 --- /dev/null +++ b/lib/services/gnss/gnss_device_service.dart @@ -0,0 +1,145 @@ +// lib/services/gnss_device_service.dart +import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +enum GnssConnectionType { btSerial, ble, phoneGps } + +class GnssDevice { + final String address; + final String name; + final GnssConnectionType type; + final int? rssi; + + const GnssDevice({ + required this.address, + required this.name, + required this.type, + this.rssi, + }); + + String get typeLabel => switch (type) { + GnssConnectionType.btSerial => 'BT Serial', + GnssConnectionType.ble => 'BLE', + GnssConnectionType.phoneGps => 'Telefon GPS', + }; + + Map toJson() => { + 'address': address, + 'name': name, + 'type': type.name, + }; + + factory GnssDevice.fromJson(Map j) => GnssDevice( + address: j['address'] as String, + name: j['name'] as String, + type: GnssConnectionType.values + .byName(j['type'] as String? ?? 'btSerial'), + ); +} + +class GnssDeviceService extends GetxService { + static GnssDeviceService get to => Get.find(); + + // Kiválasztott eszköz — minden controller innen olvassa + final selectedDevice = Rxn(); + + // Elérhető eszközök listái + final pairedBtDevices = [].obs; + final scannedBleDevices = [].obs; + final isScanning = false.obs; + + @override + Future onInit() async { + super.onInit(); + await _loadSelectedDevice(); + await loadPairedBtDevices(); + } + + // ── Perzisztencia ──────────────────────────────────────────────── + + Future _loadSelectedDevice() async { + final prefs = await SharedPreferences.getInstance(); + final address = prefs.getString('gnss_address'); + final name = prefs.getString('gnss_name'); + final type = prefs.getString('gnss_type') ?? 'btSerial'; + + if (address != null && name != null) { + selectedDevice.value = GnssDevice( + address: address, + name: name, + type: GnssConnectionType.values.byName(type), + ); + } + } + + Future selectDevice(GnssDevice device) async { + selectedDevice.value = device; + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('gnss_address', device.address); + await prefs.setString('gnss_name', device.name); + await prefs.setString('gnss_type', device.type.name); + } + + Future clearDevice() async { + selectedDevice.value = null; + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('gnss_address'); + await prefs.remove('gnss_name'); + await prefs.remove('gnss_type'); + } + + // ── BT Serial — párosított eszközök ───────────────────────────── + + Future loadPairedBtDevices() async { + try { + final bonded = await FlutterBluetoothSerial.instance.getBondedDevices(); + pairedBtDevices.value = bonded + .where((d) => d.address != null && d.name != null) + .map((d) => GnssDevice( + address: d.address, + name: d.name ?? d.address, + type: GnssConnectionType.btSerial, + )) + .toList(); + } catch (_) {} + } + + // ── BLE — aktív keresés ────────────────────────────────────────── + + Future startBleScan() async { + if (isScanning.value) return; + scannedBleDevices.clear(); + isScanning.value = true; + + try { + await FlutterBluePlus.startScan( + timeout: const Duration(seconds: 8), + ); + + FlutterBluePlus.scanResults.listen((results) { + final devices = results + .where((r) => r.device.platformName.isNotEmpty) + .map((r) => GnssDevice( + address: r.device.remoteId.str, + name: r.device.platformName, + type: GnssConnectionType.ble, + rssi: r.rssi, + )) + .toList(); + scannedBleDevices.value = devices; + }); + + await Future.delayed(const Duration(seconds: 8)); + } finally { + await FlutterBluePlus.stopScan(); + isScanning.value = false; + } + } + + void stopBleScan() { + FlutterBluePlus.stopScan(); + isScanning.value = false; + } +} diff --git a/lib/services/gnss/gnss_service.dart b/lib/services/gnss/gnss_service.dart index 06c6def..7e2fd54 100644 --- a/lib/services/gnss/gnss_service.dart +++ b/lib/services/gnss/gnss_service.dart @@ -6,6 +6,7 @@ import '../../gnss_sentences/gngga.dart'; import 'gnss_connection.dart'; import 'bt_serial_gnss_connection.dart'; import 'ble_gnss_connection.dart'; +import 'gnss_device_service.dart'; class GnssService extends GetxService { static GnssService get to => Get.find(); @@ -101,4 +102,24 @@ class GnssService extends GetxService { _disconnect(); super.onClose(); } + +// ── Eszközváltás — a GnssDevicePickerDialog hívja ─────────────── + + Future onDeviceChanged(GnssDevice device) async { + switch (device.type) { + case GnssConnectionType.btSerial: + await connectBtSerial(device.address); + + case GnssConnectionType.ble: + await connectBle(device.address); + + case GnssConnectionType.phoneGps: + // Külső GPS kapcsolat lezárása ha volt + await _disconnect(); + connectionState.value = GnssConnectionState.disconnected; + activeConnectionType.value = GnssConnectionType.phoneGps; + // A telefon GPS kezelése a map_controller _startPhoneGps()-ben van + // itt csak jelezzük hogy phone módra váltottunk + } + } } diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart new file mode 100644 index 0000000..c5cf898 --- /dev/null +++ b/lib/widgets/app_drawer.dart @@ -0,0 +1,285 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +import '../services/gnss/gnss_connection.dart'; +import '../services/gnss/gnss_device_service.dart'; +import '../services/gnss/gnss_service.dart'; +import 'gnss_device_picker_dialog.dart'; + +class AppDrawer extends StatelessWidget { + const AppDrawer({super.key}); + + @override + Widget build(BuildContext context) { + return Drawer( + // Közvetlenül Column a Drawer-ben — a Drawer maga korlátozott + // magasságú (képernyő teljes magassága), így Expanded működik. + // SafeArea csak a Header és Footer részeken kell. + child: Column( + children: [ + // ── 1. Fejléc — SafeArea csak felül ───────────────────── + SafeArea( + bottom: false, + child: _DrawerHeader(), + ), + + const Divider(height: 1), + + // ── 2. Scrollozható lista — Expanded tölti ki a helyet ── + Expanded( + child: ListView( + padding: EdgeInsets.zero, + children: [ + // Projekt + ListTile( + leading: const Icon(Icons.folder_outlined), + title: const Text('Aktív projekt'), + subtitle: const Text( + 'Nincs projekt', + style: TextStyle(fontSize: 12), + ), + trailing: const Icon(Icons.arrow_forward_ios, size: 14), + onTap: () { + Get.back(); + // Get.to(() => const ProjectPickerView()); + }, + ), + + // GNSS eszköz + Obx(() { + final device = GnssDeviceService.to.selectedDevice.value; + return ListTile( + leading: Icon( + device?.type == GnssConnectionType.ble + ? Icons.bluetooth_searching + : device?.type == GnssConnectionType.phoneGps + ? Icons.phone_android + : Icons.bluetooth, + ), + title: const Text('GNSS eszköz'), + subtitle: Text( + device?.name ?? 'Nincs kiválasztva', + style: const TextStyle(fontSize: 12), + ), + // Kapcsolat státusz ikon — saját Obx, nem egymásba ágyazva + trailing: Obx(() { + final connected = GnssService.to.connectionState.value == + GnssConnectionState.connected; + return Icon( + connected ? Icons.circle : Icons.circle_outlined, + size: 12, + color: connected ? Colors.green : Colors.grey, + ); + }), + onTap: () { + Get.back(); + GnssDevicePickerDialog.show(); + }, + ); + }), + + const Divider(height: 24), + + // ── 3. Beállítások ───────────────────────────────── + const _SectionLabel('Beállítások'), + + ListTile( + leading: const Icon(Icons.cell_tower), + title: const Text('NTRIP'), + onTap: () { + Get.back(); + // Get.to(() => const NtripSettingsView()); + }, + ), + + ListTile( + leading: const Icon(Icons.map_outlined), + title: const Text('Alaptérkép'), + onTap: () { + Get.back(); + // _showBasemapPicker(context); + }, + ), + + ListTile( + leading: const Icon(Icons.straighten), + title: const Text('Koordináta-rendszer'), + subtitle: const Text( + 'EOV', + style: TextStyle(fontSize: 12), + ), + onTap: () { + Get.back(); + // Get.to(() => const DisplaySettingsView()); + }, + ), + + ListTile( + leading: const Icon(Icons.volume_up_outlined), + title: const Text('Hang és rezgés'), + onTap: () { + Get.back(); + // Get.to(() => const FeedbackSettingsView()); + }, + ), + + const Divider(height: 24), + + // ── 4. Admin (kommentezve, később aktiválható) ────── + // Obx(() => AuthService.to.isAdmin + // ? Column(children: [...]) + // : const SizedBox.shrink(), + // ), + ], + ), + ), + + // ── 5. Lábléc — SafeArea csak alul ────────────────────── + const Divider(height: 1), + SafeArea( + top: false, + child: _DrawerFooter(), + ), + ], + ), + ); + } +} + +// ── Fejléc ─────────────────────────────────────────────────────────── +// Nincs Obx — amíg AuthService ki van kommentelve, nincs reaktív adat. +// Ha AuthService bekötésre kerül, akkor kell visszatenni az Obx-et. + +class _DrawerHeader extends StatelessWidget { + const _DrawerHeader(); + + @override + Widget build(BuildContext context) { + // TODO: Obx(() { final user = AuthService.to.currentUser.value; ... }) + // amikor az AuthService be lesz kötve. + return Container( + padding: const EdgeInsets.fromLTRB(16, 20, 16, 16), + child: Row(children: [ + CircleAvatar( + radius: 22, + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + child: Text( + '?', // user?.fullName[0].toUpperCase() ?? '?' + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Ismeretlen felhasználó', + // user?.fullName ?? 'Ismeretlen felhasználó' + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + 'Vendég', // user?.role.label ?? '' + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.secondary, + ), + ), + ], + ), + ), + ]), + ); + } +} + +// ── Lábléc ─────────────────────────────────────────────────────────── + +class _DrawerFooter extends StatelessWidget { + const _DrawerFooter(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row(children: [ + // App verzió + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (_, snap) => Text( + snap.hasData ? 'v${snap.data!.version}' : '', + style: TextStyle( + fontSize: 11, + color: Colors.grey.shade500, + ), + ), + ), + const Spacer(), + // Kijelentkezés + TextButton.icon( + icon: const Icon(Icons.logout, size: 16), + label: const Text('Kilépés'), + style: TextButton.styleFrom( + foregroundColor: Colors.red.shade400, + ), + onPressed: () { + Get.back(); + _confirmSignOut(context); + }, + ), + ]), + ); + } + + void _confirmSignOut(BuildContext context) { + Get.dialog(AlertDialog( + title: const Text('Kijelentkezés'), + content: const Text('Biztosan ki szeretnél jelentkezni?'), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('Mégse'), + ), + FilledButton( + onPressed: () { + Get.back(); + // AuthService.to.signOut(); + }, + child: const Text('Kilépés'), + ), + ], + )); + } +} + +// ── Szekció felirat ────────────────────────────────────────────────── + +class _SectionLabel extends StatelessWidget { + final String text; + const _SectionLabel(this.text); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 4), + child: Text( + text.toUpperCase(), + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: Colors.grey.shade500, + letterSpacing: 0.8, + ), + ), + ); + } +} diff --git a/lib/widgets/gnss_device_picker_dialog.dart b/lib/widgets/gnss_device_picker_dialog.dart new file mode 100644 index 0000000..ccb4acf --- /dev/null +++ b/lib/widgets/gnss_device_picker_dialog.dart @@ -0,0 +1,286 @@ +// lib/widgets/gnss_device_picker_dialog.dart +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../services/gnss/gnss_device_service.dart'; +import '../services/gnss/gnss_service.dart'; + +class GnssDevicePickerDialog extends StatelessWidget { + const GnssDevicePickerDialog({super.key}); + + static Future show() => Get.dialog( + const GnssDevicePickerDialog(), + barrierDismissible: true, + ); + + @override + Widget build(BuildContext context) { + final svc = GnssDeviceService.to; + final screenHeight = MediaQuery.of(context).size.height; + + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + // A dialog max a képernyő 80%-át foglalja el + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: screenHeight * 0.80, + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ── Fejléc — fix, nem scrollozódik ────────────────── + Row(children: [ + const Icon(Icons.sensors), + const SizedBox(width: 10), + const Expanded( + child: Text( + 'GNSS eszköz kiválasztása', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + IconButton( + icon: const Icon(Icons.close, size: 20), + onPressed: () => Get.back(), + ), + ]), + + const Divider(height: 24), + + // ── Scrollozható tartalom ──────────────────────────── + Flexible( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Telefon GPS opció + _DeviceTile( + device: const GnssDevice( + address: 'phone', + name: 'Telefon GPS', + type: GnssConnectionType.phoneGps, + ), + ), + + const SizedBox(height: 12), + + // BT Serial — párosított eszközök + _SectionHeader( + title: 'Bluetooth Serial (párosított)', + trailing: TextButton.icon( + icon: const Icon(Icons.refresh, size: 16), + label: const Text('Frissítés'), + onPressed: svc.loadPairedBtDevices, + ), + ), + + Obx(() { + if (svc.pairedBtDevices.isEmpty) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + 'Nincs párosított BT eszköz.', + style: + TextStyle(color: Colors.grey, fontSize: 13), + ), + ); + } + return Column( + children: svc.pairedBtDevices + .map((d) => _DeviceTile(device: d)) + .toList(), + ); + }), + + const SizedBox(height: 12), + + // BLE — keresett eszközök + _SectionHeader( + title: 'Bluetooth LE', + trailing: Obx( + () => svc.isScanning.value + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + width: 14, + height: 14, + child: CircularProgressIndicator( + strokeWidth: 2), + ), + const SizedBox(width: 6), + TextButton( + onPressed: svc.stopBleScan, + child: const Text('Stop'), + ), + ], + ) + : TextButton.icon( + icon: const Icon(Icons.search, size: 16), + label: const Text('Keresés'), + onPressed: svc.startBleScan, + ), + ), + ), + + Obx(() { + if (svc.isScanning.value && + svc.scannedBleDevices.isEmpty) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + 'Keresés folyamatban...', + style: + TextStyle(color: Colors.grey, fontSize: 13), + ), + ); + } + if (svc.scannedBleDevices.isEmpty) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + 'Kattints a "Keresés" gombra.', + style: + TextStyle(color: Colors.grey, fontSize: 13), + ), + ); + } + return Column( + children: svc.scannedBleDevices + .map((d) => _DeviceTile(device: d)) + .toList(), + ); + }), + + // Kis padding alulra a scrollozás végén + const SizedBox(height: 8), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} + +// ── Eszköz sor ────────────────────────────────────────────────────── + +class _DeviceTile extends StatelessWidget { + final GnssDevice device; + const _DeviceTile({required this.device}); + + @override + Widget build(BuildContext context) { + final svc = GnssDeviceService.to; + + return Obx(() { + final isSelected = svc.selectedDevice.value?.address == device.address; + + return ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + leading: CircleAvatar( + radius: 18, + backgroundColor: isSelected + ? Colors.blue.withOpacity(0.15) + : Colors.grey.withOpacity(0.1), + child: Icon( + _iconFor(device.type), + size: 18, + color: isSelected ? Colors.blue : Colors.grey, + ), + ), + title: Text( + device.name, + style: TextStyle( + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + fontSize: 14, + ), + ), + subtitle: Row(children: [ + _TypeChip(device.typeLabel), + if (device.rssi != null) ...[ + const SizedBox(width: 6), + Text( + '${device.rssi} dBm', + style: const TextStyle(fontSize: 11, color: Colors.grey), + ), + ], + if (device.type != GnssConnectionType.phoneGps) + Padding( + padding: const EdgeInsets.only(left: 6), + child: Text( + device.address, + style: const TextStyle(fontSize: 10, color: Colors.grey), + ), + ), + ]), + trailing: isSelected + ? const Icon(Icons.check_circle, color: Colors.blue) + : null, + onTap: () async { + await svc.selectDevice(device); + Get.back(); + await GnssService.to.onDeviceChanged(device); + }, + ); + }); + } + + IconData _iconFor(GnssConnectionType type) => switch (type) { + GnssConnectionType.btSerial => Icons.bluetooth, + GnssConnectionType.ble => Icons.bluetooth_searching, + GnssConnectionType.phoneGps => Icons.phone_android, + }; +} + +// ── Szekció fejléc ─────────────────────────────────────────────────── + +class _SectionHeader extends StatelessWidget { + final String title; + final Widget? trailing; + const _SectionHeader({required this.title, this.trailing}); + + @override + Widget build(BuildContext context) { + return Row(children: [ + Text( + title, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: Colors.grey.shade600, + letterSpacing: 0.5, + ), + ), + const Spacer(), + if (trailing != null) trailing!, + ]); + } +} + +// ── Típus chip ─────────────────────────────────────────────────────── + +class _TypeChip extends StatelessWidget { + final String label; + const _TypeChip(this.label); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + label, + style: const TextStyle(fontSize: 10, color: Colors.blue), + ), + ); + } +} diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index 930d207..903f489 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt index b9e550f..17411a8 100644 --- a/windows/runner/CMakeLists.txt +++ b/windows/runner/CMakeLists.txt @@ -20,6 +20,13 @@ add_executable(${BINARY_NAME} WIN32 # that need different build settings. apply_standard_settings(${BINARY_NAME}) +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + # Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index cf2feee..87247d4 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else -#define VERSION_AS_NUMBER 1,0,0 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif