GnssService osztály és refraktorálás a app_drawer miatt, shellview bevezetés.

This commit is contained in:
torok.istvan 2026-05-11 13:50:40 +02:00
parent f1a3753f36
commit af5d9d2c0b
18 changed files with 1048 additions and 192 deletions

View File

@ -4,6 +4,8 @@ import 'package:get/get.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:terepi_seged/routes/app_pages.dart'; import 'package:terepi_seged/routes/app_pages.dart';
import 'package:terepi_seged/services/coord_converter_service.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<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -16,6 +18,8 @@ Future<void> main() async {
await Get.putAsync<CoordConverterService>( await Get.putAsync<CoordConverterService>(
() => CoordConverterService().init()); () => CoordConverterService().init());
Get.put(GnssDeviceService());
Get.put(GnssService());
runApp(const MyApp()); runApp(const MyApp());
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:terepi_seged/pages/home/presentation/controllers/home_controller.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/pages/home/presentation/widgets/big_button_widget.dart';
import 'package:terepi_seged/widgets/app_drawer.dart';
class HomeView extends GetView<HomeViewController> { class HomeView extends GetView<HomeViewController> {
const HomeView({Key? key}) : super(key: key); const HomeView({Key? key}) : super(key: key);
@ -10,195 +11,179 @@ class HomeView extends GetView<HomeViewController> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
color: Colors.white, color: Colors.white,
child: Scaffold( child: Column(children: [
extendBody: true, Column(
backgroundColor: Colors.black.withOpacity(0.05), children: const [
appBar: AppBar( Padding(
elevation: 0.0, padding: EdgeInsets.only(top: 10.0),
title: const Text('Kezdőlap'), child: Center(
leading: IconButton( child: Text("Kezdőlap"),
icon: const Icon(Icons.arrow_back_ios), ),
color: Colors.white,
onPressed: () {
Get.back();
},
), ),
// 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( Padding(
children: const [ padding: const EdgeInsets.symmetric(vertical: 10.0),
Padding( child: Row(
padding: EdgeInsets.only(top: 10.0), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
child: Center( children: [
child: Text("Kezdőlap"), BigButtonWidget(
), iconData: Icons.navigation,
), label: "Navigáció",
// TextButton( onPressed: () async {
// onPressed: () async { Get.toNamed('/navigation');
// Get.toNamed('/map'); // ScaffoldMessenger.of(context)
// }, // .showSnackBar(const SnackBar(
// child: const Text('Map')), // content: Text(
// TextButton( // "Fejlesztlés alatt",
// onPressed: () async { // style: TextStyle(fontWeight: FontWeight.bold),
// Get.toNamed('/bluetooth_test'); // ),
// }, // backgroundColor: Colors.black54,
// 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,
// ));
},
),
],
), ),
), BigButtonWidget(
Padding( iconData: Icons.message,
padding: const EdgeInsets.symmetric(vertical: 10.0), label: "Üzenetek",
child: Row( onPressed: () async {
mainAxisAlignment: MainAxisAlignment.spaceEvenly, // Get.toNamed("/navigation");
children: [ ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
BigButtonWidget( content: Text(
iconData: Icons.navigation, "Fejlesztlés alatt",
label: "Navigáció", style: TextStyle(fontWeight: FontWeight.bold),
onPressed: () async { ),
Get.toNamed('/navigation'); backgroundColor: Colors.black54,
// 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( Padding(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, padding: const EdgeInsets.symmetric(vertical: 10.0),
children: [ child: Row(
BigButtonWidget( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
iconData: Icons.house, children: [
label: "Ingatlanok", BigButtonWidget(
onPressed: () async { iconData: Icons.house,
// Get.toNamed('/map_test'); label: "Ingatlanok",
ScaffoldMessenger.of(context) onPressed: () async {
.showSnackBar(const SnackBar( // Get.toNamed('/map_test');
content: Text( ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
"Fejlesztlés alatt", content: Text(
style: TextStyle(fontWeight: FontWeight.bold), "Fejlesztlés alatt",
), style: TextStyle(fontWeight: FontWeight.bold),
backgroundColor: Colors.black54, ),
)); 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,
// ));
},
),
],
), ),
), BigButtonWidget(
Row( iconData: Icons.edit_road,
mainAxisAlignment: MainAxisAlignment.center, label: "Track",
children: [ onPressed: () async {
Obx( Get.toNamed("/tracking");
() => Text( // ScaffoldMessenger.of(context)
"Verzió: ${controller.packageInfo.value.version}+${controller.packageInfo.value.buildNumber}", // .showSnackBar(const SnackBar(
style: const TextStyle(fontSize: 12)), // 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)),
) )
]))); ],
)
]));
} }
} }

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -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<ShellController> {
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',
),
],
)),
);
}
}

View File

@ -98,6 +98,7 @@ class StartPageController extends GetxController {
void _departStartPage() async { void _departStartPage() async {
await Future.delayed(const Duration(seconds: 5)); await Future.delayed(const Duration(seconds: 5));
Get.offAllNamed(Routes.HOME); //Get.offAllNamed(Routes.HOME);
Get.offAllNamed(Routes.SHELL);
} }
} }

View File

@ -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/bindings/map_test_bindings.dart';
import '../pages/map_test/presentation/views/map_test_view.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'; part 'app_routes.dart';
// ignore: avoid_classes_with_only_static_members // ignore: avoid_classes_with_only_static_members
@ -46,6 +49,11 @@ class AppPages {
name: Routes.STARTPAGE, name: Routes.STARTPAGE,
page: () => const StartPage(), page: () => const StartPage(),
binding: StartPageBinding()), binding: StartPageBinding()),
GetPage(
name: Routes.SHELL,
page: () => const ShellView(),
binding: ShellBinding(),
),
GetPage( GetPage(
name: Routes.MAP, page: () => const MapView(), binding: MapBinding()), name: Routes.MAP, page: () => const MapView(), binding: MapBinding()),
GetPage( GetPage(

View File

@ -19,4 +19,7 @@ abstract class Routes {
static const TRACKING = '/tracking'; static const TRACKING = '/tracking';
static const MAPADDPOINTDIALOG = "/map_add_point_dialog"; static const MAPADDPOINTDIALOG = "/map_add_point_dialog";
static const LOGIN = '/login';
static const SHELL = '/shell';
} }

View File

@ -2,6 +2,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'gnss_connection.dart'; import 'gnss_connection.dart';
import 'gnss_device_service.dart';
// Nordic UART Service a legelterjedtebb BLE UART profil // Nordic UART Service a legelterjedtebb BLE UART profil
const _nusServiceUuid = '6e400001-b5b3-f393-e0a9-e50e24dcca9e'; const _nusServiceUuid = '6e400001-b5b3-f393-e0a9-e50e24dcca9e';

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import 'gnss_connection.dart'; import 'gnss_connection.dart';
import 'gnss_device_service.dart';
class BtSerialGnssConnection implements GnssConnection { class BtSerialGnssConnection implements GnssConnection {
@override @override

View File

@ -1,6 +1,6 @@
// lib/services/gnss/gnss_connection.dart // lib/services/gnss/gnss_connection.dart
enum GnssConnectionType { btSerial, ble } import 'gnss_device_service.dart';
enum GnssConnectionState { disconnected, connecting, connected, error } enum GnssConnectionState { disconnected, connecting, connected, error }

View File

@ -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<String, dynamic> toJson() => {
'address': address,
'name': name,
'type': type.name,
};
factory GnssDevice.fromJson(Map<String, dynamic> 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<GnssDevice>();
// Elérhető eszközök listái
final pairedBtDevices = <GnssDevice>[].obs;
final scannedBleDevices = <GnssDevice>[].obs;
final isScanning = false.obs;
@override
Future<void> onInit() async {
super.onInit();
await _loadSelectedDevice();
await loadPairedBtDevices();
}
// Perzisztencia
Future<void> _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<void> 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<void> 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<void> 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<void> 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;
}
}

View File

@ -6,6 +6,7 @@ import '../../gnss_sentences/gngga.dart';
import 'gnss_connection.dart'; import 'gnss_connection.dart';
import 'bt_serial_gnss_connection.dart'; import 'bt_serial_gnss_connection.dart';
import 'ble_gnss_connection.dart'; import 'ble_gnss_connection.dart';
import 'gnss_device_service.dart';
class GnssService extends GetxService { class GnssService extends GetxService {
static GnssService get to => Get.find(); static GnssService get to => Get.find();
@ -101,4 +102,24 @@ class GnssService extends GetxService {
_disconnect(); _disconnect();
super.onClose(); super.onClose();
} }
// Eszközváltás a GnssDevicePickerDialog hívja
Future<void> 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
}
}
} }

285
lib/widgets/app_drawer.dart Normal file
View File

@ -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<PackageInfo>(
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,
),
),
);
}
}

View File

@ -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<void> 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),
),
);
}
}

View File

@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake)
# https://github.com/flutter/flutter/issues/57146. # https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 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 === # === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
@ -92,7 +97,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E env COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT} ${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG> ${FLUTTER_TARGET_PLATFORM} $<CONFIG>
VERBATIM VERBATIM
) )
add_custom_target(flutter_assemble DEPENDS add_custom_target(flutter_assemble DEPENDS

View File

@ -20,6 +20,13 @@ add_executable(${BINARY_NAME} WIN32
# that need different build settings. # that need different build settings.
apply_standard_settings(${BINARY_NAME}) 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. # Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")

View File

@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico"
// Version // Version
// //
#ifdef 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_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else #else
#define VERSION_AS_NUMBER 1,0,0 #define VERSION_AS_NUMBER 1,0,0,0
#endif #endif
#ifdef FLUTTER_BUILD_NAME #if defined(FLUTTER_VERSION)
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME #define VERSION_AS_STRING FLUTTER_VERSION
#else #else
#define VERSION_AS_STRING "1.0.0" #define VERSION_AS_STRING "1.0.0"
#endif #endif