import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; import 'datamodel.dart'; import 'dart:async'; import 'package:geolocator/geolocator.dart'; class AddEvent extends StatefulWidget { const AddEvent({Key? key}) : super(key: key); @override State<AddEvent> createState() => _AddEventPageState(); } class _AddEventPageState extends State<AddEvent> { bool syncGNSSData = true; late String long = ""; late String lat = ""; late String alt = ""; late double accuracy = 0.0; late StreamSubscription<Position> streamHandler; Future startGNSS() async { debugPrint("Check Location Permission"); bool serviceStatus = false; bool hasPermission = false; serviceStatus = await Geolocator.isLocationServiceEnabled(); if(serviceStatus){ LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { debugPrint('Location permissions are denied'); }else if(permission == LocationPermission.deniedForever){ debugPrint('Location permissions are permanently denied'); }else{ hasPermission = true; } }else{ hasPermission = true; } if(hasPermission){ debugPrint('Location permissions granted'); if(mounted){ setState(() { //refresh the UI });} debugPrint('Starting location stream'); streamHandler = Geolocator.getPositionStream( locationSettings: const LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 0, )).listen((Position position) { debugPrint('Get Location: Lat:' + position.latitude.toString() + ' Long:' + position.longitude.toString() + ' Alt:' + position.altitude.toString()); long = position.longitude.toString(); lat = position.latitude.toString(); alt = position.altitude.toString(); accuracy = position.accuracy; if (syncGNSSData == true) { if(mounted){ setState(() { //refresh UI on update });} } }); } }else{ debugPrint("GPS Service is not enabled, turn on GPS location"); } if(mounted){ setState(() { //refresh the UI });} } @override void initState() { startGNSS(); super.initState(); } @override void dispose() { try { streamHandler.cancel(); debugPrint('Cancel location stream'); }catch(e){ debugPrint('Canceling location stream failed'); } super.dispose(); //TODO: only if initialized positionStream.cancel(); } void _storeCurrentEvent() { final EventStoreInstance eventsStore = EventStoreInstance(); eventsStore.currentEvent.status = "PENDING"; eventsStore.events.add( Event( eventsStore.currentEvent.id, eventsStore.currentEvent.urn, eventsStore.currentEvent.label, eventsStore.currentEvent.type, eventsStore.currentEvent.description, eventsStore.currentEvent.status, eventsStore.currentEvent.startDate, eventsStore.currentEvent.endDate, eventsStore.currentEvent.latitude, eventsStore.currentEvent.longitude, eventsStore.currentEvent.elevation )); } //TODO: add field validators for freetext fields. Check allowed characters in sensor.awi.de @override Widget build(BuildContext context) { /* Get singletons to access relevant data here.*/ final EventStoreInstance eventsStore = EventStoreInstance(); final ConfigurationStoreInstance configuration = ConfigurationStoreInstance(); String gnssStatusText = ""; if (true == syncGNSSData){ // Update current event coordinates from GNSS stream eventsStore.currentEvent.latitude = lat; eventsStore.currentEvent.longitude = long; eventsStore.currentEvent.elevation = alt; var date = DateTime.now().toUtc(); eventsStore.currentEvent.startDate = '$date'; eventsStore.currentEvent.endDate = '$date'; if(accuracy == 0.0){ gnssStatusText = "No-Fix"; } else{ gnssStatusText = "Precision: "+ accuracy.toStringAsFixed(2) +"m"; } } else{ gnssStatusText = "GNSS Disabled"; // Just display existing event coordinates } if (configuration.initialized == true) { return Scaffold( appBar: AppBar(title: const Text("Add Event")), body: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ TextFormField( initialValue: eventsStore.currentEvent.label, autovalidateMode: AutovalidateMode.onUserInteraction, decoration: const InputDecoration( labelText: 'Label', errorText: 'Only: a-z , A-Z , _ , 0-9 , ,(Comma) , ( , ) , + , - , . , :' ), onChanged: (value) { eventsStore.currentEvent.label = value; }, validator: (value) { if (!RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$').hasMatch( value!)) { return "Only: a-z , A-Z , _ , 0-9 , ,(Comma) , ( , ) , + , - , . , :"; } else { eventsStore.currentEvent.label = value; return ''; // Entered Text is valid } }, ), DropdownButtonFormField( value: eventsStore.currentEvent.type, isExpanded: true, decoration: const InputDecoration( labelText: 'Event Type', ), items: configuration.eventTypes.map((EventType event) { return DropdownMenuItem( value: event.name, child: Text(event.name), ); }).toList(), onChanged: (value) { eventsStore.currentEvent.type = value.toString(); } ), DropdownButtonFormField( value: eventsStore.currentEvent.urn, isExpanded: true, decoration: const InputDecoration( labelText: 'URN', ), items: configuration.devices.map((Device device) { return DropdownMenuItem( value: device.urn, child: Text(device.urn), ); }).toList(), onChanged: (value) { eventsStore.currentEvent.urn = value.toString(); eventsStore.currentEvent.id = configuration.getDeviceIdFromUrn(value.toString()); } ), TextFormField( initialValue: eventsStore.currentEvent.description, decoration: const InputDecoration( labelText: 'Description' ), onChanged: (value) { eventsStore.currentEvent.description = value; }, ), Row( mainAxisSize: MainAxisSize.min, children: <Widget>[ Flexible(child: TextFormField( controller: TextEditingController( text: eventsStore.currentEvent.startDate), readOnly: true, decoration: const InputDecoration( labelText: 'Timestamp', //helperText: '', //Adds some space below field border: OutlineInputBorder(), ), onTap: () { DatePicker.showDateTimePicker(context, showTitleActions: true, onConfirm: (date) { //Only one field for start and end date. eventsStore.currentEvent.startDate = '$date'; eventsStore.currentEvent.endDate = '$date'; debugPrint('Date set to : $date'); setState(() {}); }, currentTime: DateTime.now().toUtc(), locale: LocaleType.en); }, ), ), ElevatedButton( onPressed: () { var date = DateTime.now().toUtc(); eventsStore.currentEvent.startDate = '$date'; eventsStore.currentEvent.endDate = '$date'; debugPrint('Date set to : ' + eventsStore.currentEvent.endDate.toString()); setState(() {}); }, child: Text('Now'), ), ] ), TextFormField( readOnly: false, enabled: !syncGNSSData, controller: TextEditingController( text: eventsStore.currentEvent.latitude.toString()), decoration: const InputDecoration( labelText: 'Latitude', border: OutlineInputBorder(), ), onChanged: (value) { eventsStore.currentEvent.latitude = value; } ), TextFormField( readOnly: false, enabled: !syncGNSSData, controller: TextEditingController( text: eventsStore.currentEvent.longitude.toString()), decoration: const InputDecoration( labelText: 'Longitude', border: OutlineInputBorder(), ), onChanged: (value) { eventsStore.currentEvent.longitude = value; } ), TextFormField( readOnly: false, enabled: !syncGNSSData, controller: TextEditingController( text: eventsStore.currentEvent.elevation.toString()), decoration: const InputDecoration( labelText: 'Elevation', border: OutlineInputBorder(), ), onChanged: (value) { eventsStore.currentEvent.elevation = value; } ), ]), bottomNavigationBar: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Text(gnssStatusText), const SizedBox(width: 10), Switch( value: syncGNSSData, onChanged: (value) { syncGNSSData = value; debugPrint('Switched to:' + syncGNSSData.toString()); setState(() { //refresh the UI }); }, ), const SizedBox(width: 50), FloatingActionButton( heroTag: null, onPressed: () { _storeCurrentEvent(); HapticFeedback.vibrate(); }, tooltip: 'Add Event', child: const Icon(Icons.check), ), const SizedBox(width: 20), ], ), ); }else { return Scaffold( appBar: AppBar(title: const Text("Add Event")), body: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.end, children: const <Widget>[ Text( ' Visit Configuration first.', style: TextStyle(fontSize: 20) ), ], ), ); } } }