Skip to content
Snippets Groups Projects
addeventkottaspegel.dart 37.8 KiB
Newer Older
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'datamodel.dart';
import 'databaseconnector.dart';
import 'dart:async';
import 'package:geolocator/geolocator.dart';
import 'restdata.dart';

class AddEventKottasPegel extends StatefulWidget {
  const AddEventKottasPegel({Key? key}) : super(key: key);

  @override
  State<AddEventKottasPegel> createState() => _AddEventKottasPegelPageState();
}

class _AddEventKottasPegelPageState extends State<AddEventKottasPegel>  {
  RestData restConnector = RestData();
  List<String> angleStatusItems = ['90° (vertical)', '80°', '70°', '60°', '50°', '40°', '30°',  ];

  //Parameters which shall be added to the measurement event as json string data
  List<String> measurementStatusItems = ['ok', 'broken', 'missing', 'tilted'];
Maximilian Betz's avatar
Maximilian Betz committed
  List<String> rodColorValues = ['black', 'red', 'blue', 'none'];
  int rodColorValuesIdOld = -1;

  int rodColorValuesIdNew = -1;


  int rawMeasurementValue = 0;
  bool syncGNSSData = true;
  bool _addButtonEnabled = true;
  bool _eventAdded = false;

  bool displayAngle = true;
  bool displayOldLength = true;
  bool displayNewLength = true;

  double accuracy = 0.0;
  late StreamSubscription<Position> streamHandler;
  final prefs = SharedPreferences.getInstance();  // Is async
  var database = DatabaseInstance();

  late OverlayEntry _overlayEntry;  //For event creation success notifications
  late Timer _overlayCloseTimer;

  TextEditingController labelEditingController = TextEditingController();

  TextEditingController oldLengthEditingController = TextEditingController();
  TextEditingController newLengthEditingController = TextEditingController();

  Future startRest() async {
    final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();
    final EventStoreInstance event = EventStoreInstance();
    /* Function which starts querying the URL configuration.restRequestUrl  */

    restQueryTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
      // Query data from raspberry pi here
      try{
        Map data = await restConnector.fetchData(configuration.restRequestUrl);
        debugPrint("Rest Query succeeded: $data");
        event.currentEvent.latitude = data['lat'];
        event.currentEvent.longitude = data['lon'];
        event.currentEvent.elevation = data['elevation'].toString();

        var date = DateTime.parse(data['timestamp']);
        var isoDate = date.toIso8601String();
        event.currentEvent.startDate = isoDate; //TODO: test and validate
        event.currentEvent.endDate = isoDate;

        rawMeasurementValue = data['length'];  //in mm  //accept only int values
        accuracy = double.parse(data['precision'].toString());  //accept int and double and string values
      }catch(e){
        //Set some useful error values.  TODO: Ensure that the timeout is not to short otherwise this makes things worse:
        var date = DateTime.now().toUtc();
        var isoDate = date.toIso8601String();
        event.currentEvent.startDate = isoDate;
        event.currentEvent.endDate = isoDate;
        event.currentEvent.latitude = "";
        event.currentEvent.longitude = "";
        event.currentEvent.elevation = "";
        rawMeasurementValue = 0;
        debugPrint("Rest Query failed: $e");
      if(mounted){
        setState(() {
          //refresh UI on update
        });}
  Future startGNSS() async {
    final EventStoreInstance event = EventStoreInstance();
    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) {
Maximilian Betz's avatar
Maximilian Betz committed
          debugPrint('Get Location: Lat:${position.latitude} Long:${position.longitude} Alt:${position.altitude}');
          event.currentEvent.latitude = position.latitude.toString();
          event.currentEvent.longitude = position.longitude.toString();
          event.currentEvent.elevation = position.altitude.toString();
          accuracy = position.accuracy;

          var date = DateTime.now().toUtc();
          var isoDate = date.toIso8601String();
          event.currentEvent.startDate = isoDate;
          event.currentEvent.endDate = isoDate;

          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() {
    final EventStoreInstance event = EventStoreInstance();
    final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();

    // Init displayed parameters with some values to prevent GUI exceptions before the async REST/GNSS data is ready
    event.currentEvent.latitude = '0.0';
    event.currentEvent.longitude = '0.0';
    event.currentEvent.elevation = '0.0';

    var date = DateTime.now().toUtc();
    var isoDate = date.toIso8601String();
    event.currentEvent.startDate = isoDate; //TODO: test and validate
    event.currentEvent.endDate = isoDate;

    if(event.gnssSync){
      // Get data from REST / Raspberry PI
      startRest();
    }else{
      // Get Lat / Long ... from internal GNSS
      startGNSS();
    }

    //Update current event with prefix and cnt
    event.currentEvent.label = configuration.labelConfig.prefix + configuration.labelConfig.cnt.toString().padLeft(3, '0');
    labelEditingController.text = event.currentEvent.label;
    oldLengthEditingController.text = lengthOld.toString();
    newLengthEditingController.text = lengthNew.toString();


    super.initState();
  }

  @override
  void dispose() async {
    try {
      streamHandler.cancel();
      debugPrint('Cancel location stream');
    }catch(e){
      debugPrint('Canceling location stream failed');
    }

    try {
      _overlayEntry.remove();
    }catch(e){
      debugPrint('Dispose error on overlay remove: $e');
    try{
      restQueryTimer.cancel();
    }catch(e){
      debugPrint('Timer cancel error on dispose: $e');
    }


    /*Async update current event configuration to shared preferences*/
    final EventStoreInstance event = EventStoreInstance();
    event.storeToSharedPrefs();

    super.dispose();
  }

  bool _validateLatitude(value){
    if (value == ""){
      return true;  //Empty string is valid
    }
    var number = num.tryParse(value);
    if(number != null){
      if (number >= -90.0  && number <= 90.0){
        return true;  // Latitude valid
      }
    }
    return false;
  }

  bool _validateLongitude(value){
    if (value == ""){
      return true;  //Empty string is valid
    }
    var number = num.tryParse(value);
    if(number != null){
      if (number >= -180.0  && number <= 180.0){
        return true;  // Longitude valid
      }
    }
    return false;
  }

  bool _validateElevation(value){
    if (value == ""){
      return true;  //Empty string is valid
    }
    var number = num.tryParse(value);
    if(number != null){
      return true;  // Any numerical value is valid for elevation
    }
    return false;
  }

  bool _validateCompleteInput(){
    bool flag = true;
    if(displayOldLength == true) {
      if (rodColorValuesIdOld < 0 ||
          rodColorValuesIdOld > rodColorValues.length) {
        flag = false;
      }
Maximilian Betz's avatar
Maximilian Betz committed
      if (lengthOld == 0){
        flag = false;
      }
    }
    if (displayNewLength == true){
      if (rodColorValuesIdNew < 0 ||
          rodColorValuesIdNew > rodColorValues.length){
        flag = false;
      }
Maximilian Betz's avatar
Maximilian Betz committed
      if (lengthNew == 0){
        flag = false;
      }
  bool _validateInput(){
    final EventStoreInstance event = EventStoreInstance();
    if (RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$').hasMatch(
        event.currentEvent.label)) {
      if (RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$').hasMatch(
          event.currentEvent.description) || event.currentEvent.description == '') {
        if(_validateLatitude(event.currentEvent.latitude)){
          if(_validateLongitude(event.currentEvent.longitude)){
            if(_validateElevation(event.currentEvent.elevation)){
              if(measurementStatusId >= 0 && measurementStatusId < measurementStatusItems.length) {
                if (_validateCompleteInput() == true) {
                  return true;
                }
            }
          }
        }
      }
    }
    return false;
  }

  bool _addButtonStatus(){
    if((_validateInput() == true) && (_addButtonEnabled == true)){

      return true;
    }
    return false;
  }

  Future<void> _showResultPopup(BuildContext context, String text, bool error) async {
    OverlayState? overlayState = Overlay.of(context);
    try {
      _overlayEntry.remove(); // Allow only one Overlay Popup at a time
    }catch(e){
      debugPrint('Overlay already removed, during dispose: $e');
    }
    _overlayEntry = OverlayEntry(builder: (context) {
      Color backGroundColor;
      Color textColor;
      if (error == true){
        backGroundColor = Colors.redAccent; //Style for error message
        textColor = Colors.black;
      }
      else {
        backGroundColor = Colors.greenAccent; //Style for notification
        textColor = Colors.black;
      }

      return Stack(
        alignment: Alignment.center,
        children: [
          Positioned(
            // Position at 10% of height from bottom
            bottom: MediaQuery.of(context).size.height * 0.1,
            child: Material(
              borderRadius: BorderRadius.circular(8.0),
              color: backGroundColor, //Some transparency remains
              child:  Container(
                padding: const EdgeInsets.all(5.0), // Space between Text and Bubble
                width: MediaQuery.of(context).size.width * 0.95,
                child:  TextFormField(
                  minLines: 1,
                  maxLines: 5,
                  readOnly: true,
                  autofocus: false,
                  enabled: false,
                  style: TextStyle(color: textColor),
                  controller: TextEditingController(
                    text: text,
                  ),
                ),
              ),
            ),
          ),
        ],
      );
    });

    overlayState?.insert(_overlayEntry);

    try {
      _overlayCloseTimer.cancel(); // Kill old timers
    }catch(e){
      debugPrint('Timer cancel error: $e');
    }

    _overlayCloseTimer = Timer(
      const Duration(seconds: 3),
          () {
        try {
          _overlayEntry.remove(); // Allow only one Overlay Popup. NOTE: Is this a quick an dirty or a proper solution?
        }catch(e){
          debugPrint('Overlay already removed, during dispose: $e');
  void _resetSelectedFields(){
    measurementStatusId = -1;
    rodColorValuesIdOld = -1;
    rodColorValuesIdNew = -1;
    angleStatus = angleStatusItems[0];
    lengthOld = 0;
    lengthNew = 0;
Maximilian Betz's avatar
Maximilian Betz committed

    oldLengthEditingController.text = lengthOld.toString();
    newLengthEditingController.text = lengthNew.toString();
  Future<void> _storeCurrentMeasurementEvent(BuildContext context, bool addAngle, bool addOldLength, bool addNewLength) async {

    final EventStoreInstance event = EventStoreInstance();
    final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();
    //TODO: this is a prototype. URN and ID are hardcoded. Make configurable
    event.currentEvent.urn = 'station:neumayer_iii:snow_level_measure';
    event.currentEvent.urnId = 9094;
    event.currentEvent.type = 'Deployment';
    event.currentEvent.typeId = 187;
    event.currentEvent.status = "PENDING";

    //Generate Payload Measurement Data
    Map<String, dynamic> data =
    {
      'status' : measurementStatusItems[measurementStatusId],
    if(true == addAngle){
      data['angle_old'] = angleStatus;
    }
    if(true == addOldLength) {
      data['length_old'] = lengthOld;
Maximilian Betz's avatar
Maximilian Betz committed
      data['color_old'] = rodColorValues[rodColorValuesIdOld];
    }
    if (true == addNewLength) {
      data['length_new'] = lengthNew;
Maximilian Betz's avatar
Maximilian Betz committed
      data['color_new'] = rodColorValues[rodColorValuesIdNew];
    //Add payload as json to data field in db
    //event.currentEvent.data = data.toString();
    event.currentEvent.data = jsonEncode(data);
    await database.addEvent(event.currentEvent);
    HapticFeedback.vibrate(); //Feedback that adding event succeeded

    //Get next Label prefix, id and event timestamp
    if (configuration.labelConfig.cntUp == true){
      configuration.labelConfig.cnt++;
    }else{
      configuration.labelConfig.cnt--;
    }
    event.currentEvent.label = configuration.labelConfig.prefix + configuration.labelConfig.cnt.toString().padLeft(3, '0');
    labelEditingController.text = event.currentEvent.label; //Trigger label update
    event.currentEvent.description = '';

    var date = DateTime.now().toUtc();
    var isoDate = date.toIso8601String();
    event.currentEvent.startDate = isoDate;
    event.currentEvent.endDate = isoDate;

    // Reset fields for next event
    await event.storeToSharedPrefs();
    await configuration.storeToSharedPrefs();

    _addButtonEnabled = true; //Activate button to add more events
    _eventAdded = true; // Trigger popup in build method

    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    /* Get singletons to access relevant data here.*/
    final EventStoreInstance event = EventStoreInstance();
    final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();
    String switchStatusText = "";
    String switchStatusTextLine2 = "";
    if (true == event.gnssSync){
      switchStatusText = "External REST Data";
      switchStatusText = "Internal GNSS"; // Use internal GNSS source
    if (accuracy == 0.0){
      switchStatusTextLine2 = 'No-Fix';
      switchStatusTextLine2 = "Precision ${accuracy.toStringAsFixed(1)}m";
    // Set visibility parameters
    displayAngle = false;
    displayOldLength = false;
    displayNewLength = false;
    if(measurementStatusId == 0){
      //ok
      displayAngle = true;
      displayOldLength = true;
    }else if (measurementStatusId == 1){
      //broken
      displayNewLength = true;
    }else if (measurementStatusId == 2){
      //missing
      displayNewLength = true;
    }else {
      //tilted / to short
      displayAngle = true;
      displayOldLength = true;
      displayNewLength = true;
    }

    if (true == _eventAdded){
      _eventAdded = false;
      Future.delayed(Duration.zero,() {
        _showResultPopup(context, "Successfully created Event !", false);
      });
    }
    //if (configuration.initialized == true) {
    if (true) {
      return Scaffold(
        appBar: AppBar(
          title: const Text("Kottaspegel"),
          actions: <Widget>[
            Column(
              children: [
                Text(switchStatusText, style: const TextStyle(fontStyle: FontStyle.italic)),
                Text(switchStatusTextLine2, style: const TextStyle(fontStyle: FontStyle.italic)),
              ],
            ),
            Switch(  //Enable showing all or only pending events. Default is to show only pending events
                value: event.gnssSync,
                onChanged: (value) {
                  event.gnssSync = value;
                  debugPrint('Switched to:${event.gnssSync}');
                  accuracy = 0.0;
                    // Get data from rest / raspberry pi
                    streamHandler.cancel();
                    startRest();

                  }else{
                    // Get data from internal GNSS
                    restQueryTimer.cancel();
                    startGNSS();
                    rawMeasurementValue = 0; //Clear Raw Measurement value
        body: SingleChildScrollView(
          child: Container(
            margin: const EdgeInsets.symmetric(horizontal: 5.0),
            child:
            Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  const SizedBox(height: 10.0),
                  TextFormField(
                    readOnly: false,
Maximilian Betz's avatar
Maximilian Betz committed
                    enabled: false,
                    keyboardType: TextInputType.number,
                    autovalidateMode: AutovalidateMode.onUserInteraction,
                    controller: TextEditingController(
Maximilian Betz's avatar
Maximilian Betz committed
                        text: ""
                            "${event.currentEvent.startDate.substring(11,19)} / "
                            "${event.currentEvent.latitude} / "
                            "${event.currentEvent.longitude} / "
                            "${event.currentEvent.elevation}"
Maximilian Betz's avatar
Maximilian Betz committed
                    ),
                    decoration: const InputDecoration(
                      labelText: 'UTC / Lat / Long / Elv ',  //Example
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 15.0),
                  Visibility(
                    visible: event.gnssSync,
                    child:
                    TextFormField(
                      readOnly: false,
                      enabled: false,
                      style: const TextStyle(fontSize: 30.0),
                      keyboardType: TextInputType.number,
                      autovalidateMode: AutovalidateMode.onUserInteraction,
                      controller: TextEditingController(
                          text: "Length: ${rawMeasurementValue.toString()} [mm]"
                      ),
                      decoration: const InputDecoration(
                        labelText: '',  //Example
                        border: OutlineInputBorder(),
                      ),
                    ),
                  ),
                  const SizedBox(height: 15.0),
                  TextFormField(
                    controller: labelEditingController,
                    autovalidateMode: AutovalidateMode.onUserInteraction,

                    decoration: const InputDecoration(
Maximilian Betz's avatar
Maximilian Betz committed
                      border: OutlineInputBorder(),
                      labelText: 'Label',
                    ),
                    onChanged: (value){
                      event.currentEvent.label = value;
                      setState(() {});
                    },
                    validator: (value) {
                      if (!RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$').hasMatch(
                          value!)) {
                        return "Only: a-z , A-Z , _ , 0-9 , ,(Comma) , ( , ) , + , - , . , :";
                      } else {
                        return null; // Entered Text is valid
                      }
                    },
                  ),
                  const SizedBox(height: 15.0),
                  Row(
Maximilian Betz's avatar
Maximilian Betz committed
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: <Widget>[
                        ElevatedButton(
                          style: ElevatedButton.styleFrom(
                            padding: const EdgeInsets.symmetric(vertical: 20.0), //TODO: find a more dynamic solution without a fixed value
Maximilian Betz's avatar
Maximilian Betz committed
                            primary: measurementStatusId == 0 ? Colors.black54 : Colors.white12,
                          ),
                          onPressed: () {
                            measurementStatusId = 0;
                            setState(() {});
                          },
                          child: const Text('Ok'),
                        ),
                        ElevatedButton(
                          style: ElevatedButton.styleFrom(
                            padding: const EdgeInsets.symmetric(vertical: 20.0), //TODO: find a more dynamic solution without a fixed value
Maximilian Betz's avatar
Maximilian Betz committed
                            primary: measurementStatusId == 1 ? Colors.black54 : Colors.white12,
                          ),
                          onPressed: () {
                            measurementStatusId = 1;
                            setState(() {});
                          },
                          child: const Text('broken'),
                        ),
                        ElevatedButton(
                          style: ElevatedButton.styleFrom(
                            padding: const EdgeInsets.symmetric(vertical: 20.0), //TODO: find a more dynamic solution without a fixed value
Maximilian Betz's avatar
Maximilian Betz committed
                            primary: measurementStatusId == 2 ? Colors.black54 : Colors.white12,
                          ),
                          onPressed: () {
                            measurementStatusId = 2;
                            setState(() {});
                          },
                          child: const Text('?'),
                        ),
                        ElevatedButton(
                          style: ElevatedButton.styleFrom(
                            padding: const EdgeInsets.symmetric(vertical: 20.0), //TODO: find a more dynamic solution without a fixed value
Maximilian Betz's avatar
Maximilian Betz committed
                            primary: measurementStatusId == 3 ? Colors.black54 : Colors.white12,
                          ),
                          onPressed: () {
                            measurementStatusId = 3;
                            setState(() {});
Maximilian Betz's avatar
Maximilian Betz committed
                          child: const Text('tilt/short'),
                  const SizedBox(height: 30.0),
                  Visibility(
                    visible: displayAngle,
                    child:
                    DropdownButtonFormField(
                        iconDisabledColor: Colors.green,
                        isExpanded: true,
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                        ),
                        items:
                        angleStatusItems.map((String angle) {
                          return DropdownMenuItem(
                            value: angle,
                            //enabled: displayAngle,
                            child: Text(angle),
                          );
                        }).toList(),
                        onChanged: (value) {
                        }
                    ),
                  ),
                  const SizedBox(height: 15.0),
                  Visibility(
                    visible: displayOldLength,
                    TextFormField(
                      readOnly: false,
Maximilian Betz's avatar
Maximilian Betz committed
                      enabled: true,
                      keyboardType: TextInputType.number,
                      autovalidateMode: AutovalidateMode.onUserInteraction,
                      inputFormatters:[FilteringTextInputFormatter.allow(RegExp("[0-9]"))],
                      controller: oldLengthEditingController,
                      decoration: const InputDecoration(
                        labelText: 'old length',  //Example
                        border: OutlineInputBorder(),
                      ),
                      onChanged: (value) {
                  ),
                  const SizedBox(height: 5.0),
                  Visibility(
                    visible: displayOldLength,
                    child:
                    Row(
Maximilian Betz's avatar
Maximilian Betz committed
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: <Widget>[
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
Maximilian Betz's avatar
Maximilian Betz committed
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0), //TODO: find a more dynamic solution without a fixed value
                              primary: rodColorValuesIdOld == 0 ? Colors.black54 : Colors.white12,
Maximilian Betz's avatar
Maximilian Betz committed
                              rodColorValuesIdOld = 0;
Maximilian Betz's avatar
Maximilian Betz committed
                            child: Text(rodColorValues[0]),
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
Maximilian Betz's avatar
Maximilian Betz committed
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0), //TODO: find a more dynamic solution without a fixed value
                              primary: rodColorValuesIdOld == 1 ? Colors.black54 : Colors.white12,
Maximilian Betz's avatar
Maximilian Betz committed
                              rodColorValuesIdOld = 1;
Maximilian Betz's avatar
Maximilian Betz committed
                            child: Text(rodColorValues[1]),
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
Maximilian Betz's avatar
Maximilian Betz committed
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0), //TODO: find a more dynamic solution without a fixed value
                              primary: rodColorValuesIdOld == 2 ? Colors.black54 : Colors.white12,
Maximilian Betz's avatar
Maximilian Betz committed
                              rodColorValuesIdOld = 2;
Maximilian Betz's avatar
Maximilian Betz committed
                            child: Text(rodColorValues[2]),
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
Maximilian Betz's avatar
Maximilian Betz committed
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0), //TODO: find a more dynamic solution without a fixed value
                              primary: rodColorValuesIdOld == 3 ? Colors.black54 : Colors.white12,
Maximilian Betz's avatar
Maximilian Betz committed
                              rodColorValuesIdOld = 3;
Maximilian Betz's avatar
Maximilian Betz committed
                            child: Text(rodColorValues[3]),
Maximilian Betz's avatar
Maximilian Betz committed
                  const SizedBox(height: 5.0),
                  Visibility(
                    visible: displayOldLength,
                    child:
                    Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: <Widget>[
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 60.0), //TODO: find a more dynamic solution without a fixed value
                              primary: event.gnssSync == true ? Colors.grey : Colors.white54,
                            ),
                            onPressed: () {
                              lengthOld = rawMeasurementValue + 0;
                              oldLengthEditingController.text = lengthOld.toString();
Maximilian Betz's avatar
Maximilian Betz committed
                              setState(() {});
                            },
                            child: const Text('+0m'),
                          ),
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 60.0), //TODO: find a more dynamic solution without a fixed value
                              primary: event.gnssSync == true ? Colors.grey : Colors.white54,
                            ),
                            onPressed: () {
                              lengthOld = rawMeasurementValue + 2000;  //2m offset
                              oldLengthEditingController.text = lengthOld.toString();
Maximilian Betz's avatar
Maximilian Betz committed
                              setState(() {});
                            },
                            child: const Text('+2m'),
                          ),
                        ]
                    ),
                  ),
                  const SizedBox(height: 30.0),
                  Visibility(
                    visible: displayNewLength,
                    TextFormField(
                      readOnly: false,
Maximilian Betz's avatar
Maximilian Betz committed
                      enabled: true,
                      keyboardType: TextInputType.number,
                      autovalidateMode: AutovalidateMode.onUserInteraction,
                      inputFormatters:[FilteringTextInputFormatter.allow(RegExp("[0-9]"))],
                      controller: newLengthEditingController,
                      decoration: const InputDecoration(
                        labelText: 'new length',  //Example
                        border: OutlineInputBorder(),
                      ),
                      onChanged: (value) {
                    ),
                  ),
                  const SizedBox(height: 5.0),
                  Visibility(
                    visible: displayNewLength,
                    child:
                    Row(
Maximilian Betz's avatar
Maximilian Betz committed
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: <Widget>[
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
Maximilian Betz's avatar
Maximilian Betz committed
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0), //TODO: find a more dynamic solution without a fixed value
                              primary: rodColorValuesIdNew == 0 ? Colors.black54 : Colors.white12,
Maximilian Betz's avatar
Maximilian Betz committed
                              rodColorValuesIdNew = 0;
Maximilian Betz's avatar
Maximilian Betz committed
                            child: Text(rodColorValues[0]),
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
Maximilian Betz's avatar
Maximilian Betz committed
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0), //TODO: find a more dynamic solution without a fixed value
                              primary: rodColorValuesIdNew == 1 ? Colors.black54 : Colors.white12,
Maximilian Betz's avatar
Maximilian Betz committed
                              rodColorValuesIdNew = 1;
Maximilian Betz's avatar
Maximilian Betz committed
                            child: Text(rodColorValues[1]),
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
Maximilian Betz's avatar
Maximilian Betz committed
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0), //TODO: find a more dynamic solution without a fixed value
                              primary: rodColorValuesIdNew == 2 ? Colors.black54 : Colors.white12,
Maximilian Betz's avatar
Maximilian Betz committed
                              rodColorValuesIdNew = 2;
Maximilian Betz's avatar
Maximilian Betz committed
                            child: Text(rodColorValues[2]),
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
Maximilian Betz's avatar
Maximilian Betz committed
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0), //TODO: find a more dynamic solution without a fixed value
                              primary: rodColorValuesIdNew == 3 ? Colors.black54 : Colors.white12,
Maximilian Betz's avatar
Maximilian Betz committed
                              rodColorValuesIdNew = 3;
Maximilian Betz's avatar
Maximilian Betz committed
                            child: Text(rodColorValues[3]),
                          ),
                        ]
                    ),
                  ),
                  const SizedBox(height: 5.0),
                  Visibility(
                    visible: displayNewLength,
                    child:
                    Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: <Widget>[
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 60.0), //TODO: find a more dynamic solution without a fixed value
                              primary: event.gnssSync == true ? Colors.grey : Colors.white54,
                            ),
                            onPressed: () {
                              lengthNew = rawMeasurementValue + 0;
                              newLengthEditingController.text = lengthNew.toString();
Maximilian Betz's avatar
Maximilian Betz committed
                              setState(() {});
                            },
                            child: const Text('+0m'),
                          ),
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
                              padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 60.0), //TODO: find a more dynamic solution without a fixed value
                              primary: event.gnssSync == true ? Colors.grey : Colors.white54,
                            ),
                            onPressed: () {
                              lengthNew = rawMeasurementValue + 2000;  //2m offset
                              newLengthEditingController.text = lengthNew.toString();
Maximilian Betz's avatar
Maximilian Betz committed
                              setState(() {});
                            },
                            child: const Text('+2m'),
Maximilian Betz's avatar
Maximilian Betz committed
                  const SizedBox(height: 15.0),
                  TextFormField(
                    initialValue: event.currentEvent.description,
Maximilian Betz's avatar
Maximilian Betz committed
                    autovalidateMode: AutovalidateMode.always,
                    decoration: const InputDecoration(
                        border: OutlineInputBorder(),
                        labelText: 'Description'
                    ),
                    onChanged: (value){
                      event.currentEvent.description = value;
Maximilian Betz's avatar
Maximilian Betz committed
                      setState(() {});
                    },
                    validator: (value) {
                      if (!RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$').hasMatch(
                          value!)) {
                        if(value == ''){
                          return null;  //An empty description is also allowed.
                        }
                        return "Only: a-z , A-Z , _ , 0-9 , ,(Comma) , ( , ) , + , - , . , :";
                      } else {
                        return null; // Entered Text is valid
                      }
                    },
                  ),
                ]
            ),
          ),
        ),
        bottomNavigationBar:
        Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            Container(
              margin: const EdgeInsets.symmetric(vertical: 10.0),
              child: _addButtonStatus() ?

              FloatingActionButton.extended(
                heroTag: null,
                tooltip: 'Create new event in local database',
                icon: null,
                onPressed: () {
                  if (_validateInput()) {
                    _addButtonEnabled = false;  //Disable button until event is stored
                    _storeCurrentMeasurementEvent(context, displayAngle, displayOldLength, displayNewLength);
                  }
                  setState(() {});
                },
              ):
              FloatingActionButton.extended(
                heroTag: null,
                tooltip: 'Input invalid',
                icon: null,
                backgroundColor: Colors.grey,
                onPressed: () {
                },
              ),
            ),
            const SizedBox(width: 5.0),
          ],
        ),
      );
    }else {
      return Scaffold(
        appBar: AppBar(title: const Text("Add Event")),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.end,
          children: <Widget>[
            Container(
              margin: const EdgeInsets.all(10.0),
              child:const Text(
                  'Check Configuration Page for initial setup!',
                  style: TextStyle(fontSize: 20)
              ),
            ),
          ],
        ),
      );
    }
  }