Skip to content
Snippets Groups Projects
addevent.dart 19.4 KiB
Newer Older
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Maximilian Betz's avatar
Maximilian Betz committed
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';
Maximilian Betz's avatar
Maximilian Betz committed
import 'dart:async';
import 'package:geolocator/geolocator.dart';
Maximilian Betz's avatar
Maximilian Betz committed
class AddEvent extends StatefulWidget {
Maximilian Betz's avatar
Maximilian Betz committed
  const AddEvent({Key? key}) : super(key: key);

Maximilian Betz's avatar
Maximilian Betz committed
  @override
  State<AddEvent> createState() => _AddEventPageState();
}

class _AddEventPageState extends State<AddEvent>  {
  bool syncGNSSData = true;
  late String long = "";
  late String lat = "";
  late String alt = "";
Maximilian Betz's avatar
Maximilian Betz committed
  late double accuracy = 0.0;
  late StreamSubscription<Position> streamHandler;  //For canceling GNSS stream on dispose
  final prefs = SharedPreferences.getInstance();  // Is async
Maximilian Betz's avatar
Maximilian Betz committed
    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) {
Maximilian Betz's avatar
Maximilian Betz committed
          debugPrint('Location permissions are denied');
        }else if(permission == LocationPermission.deniedForever){
Maximilian Betz's avatar
Maximilian Betz committed
          debugPrint('Location permissions are permanently denied');
        }else{
Maximilian Betz's avatar
Maximilian Betz committed
          hasPermission = true;
Maximilian Betz's avatar
Maximilian Betz committed
        hasPermission = true;
Maximilian Betz's avatar
Maximilian Betz committed
      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
              });}
          }
        });
Maximilian Betz's avatar
Maximilian Betz committed
      debugPrint("GPS Service is not enabled, turn on GPS location");
Maximilian Betz's avatar
Maximilian Betz committed
    if(mounted){
      setState(() {
        //refresh the UI
      });}
Maximilian Betz's avatar
Maximilian Betz committed

  @override
  void initState() {
    startGNSS();
Maximilian Betz's avatar
Maximilian Betz committed
    super.initState();
  }
  @override
  void dispose() {

    try {
      streamHandler.cancel();
      debugPrint('Cancel location stream');
    }catch(e){
      debugPrint('Canceling location stream failed');
    }
    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 _validateInput(){
    final EventStoreInstance eventsStore = EventStoreInstance();
    if (RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$').hasMatch(
        eventsStore.currentEvent.label)) {
      if (RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$').hasMatch(
          eventsStore.currentEvent.description)) {
        if(_validateLatitude(eventsStore.currentEvent.latitude)){
          if(_validateLongitude(eventsStore.currentEvent.longitude)){
            if(_validateElevation(eventsStore.currentEvent.elevation)){
              debugPrint('All inputs valid');
              return true;

            }
          }
        }
  void _storeCurrentEvent() {
    final EventStoreInstance eventsStore = EventStoreInstance();
Maximilian Betz's avatar
Maximilian Betz committed
    final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();
    eventsStore.currentEvent.typeId = configuration.getEventIdFromName(eventsStore.currentEvent.type);

    eventsStore.currentEvent.status = "PENDING";
    eventsStore.events.add(
            id: 0,
            urnId: eventsStore.currentEvent.urnId,
            urn: eventsStore.currentEvent.urn,
            label: eventsStore.currentEvent.label,
            type: eventsStore.currentEvent.type,
            typeId: eventsStore.currentEvent.typeId,
            description: eventsStore.currentEvent.description,
            status: eventsStore.currentEvent.status,
            startDate: eventsStore.currentEvent.startDate,
            endDate: eventsStore.currentEvent.endDate,
            latitude: eventsStore.currentEvent.latitude,
            longitude: eventsStore.currentEvent.longitude,
            elevation: eventsStore.currentEvent.elevation
  @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){
Maximilian Betz's avatar
Maximilian Betz committed
      // Update current event coordinates from GNSS stream
      eventsStore.currentEvent.latitude = lat;
      eventsStore.currentEvent.longitude = long;
      eventsStore.currentEvent.elevation = alt;
      var date = DateTime.now().toUtc();
      var isoDate = date.toIso8601String();
      eventsStore.currentEvent.startDate = isoDate;
      eventsStore.currentEvent.endDate = isoDate;
      if(accuracy == 0.0){
        gnssStatusText = "No-Fix";
      else{
        gnssStatusText = "Precision: "+ accuracy.toStringAsFixed(2) +"m";
      }
Maximilian Betz's avatar
Maximilian Betz committed
    }
    else{
Maximilian Betz's avatar
Maximilian Betz committed
      gnssStatusText = "GNSS Disabled"; // Just display existing event coordinates
    if (configuration.initialized == true) {
      return Scaffold(
        appBar: AppBar(title: const Text("Add Event")),
        body: SingleChildScrollView(
          child: Container(
            margin: const EdgeInsets.symmetric(horizontal: 5.0),
            child:
            Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  const SizedBox(height: 15.0),
                  TextFormField(
                    initialValue: eventsStore.currentEvent.label,
                    autovalidateMode: AutovalidateMode.always,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Label',
                    ),
                    onChanged: (value){
                      eventsStore.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),
                  DropdownButtonFormField(
                      value: eventsStore.currentEvent.type,
                      isExpanded: true,
                      decoration: const InputDecoration(
                        border: OutlineInputBorder(),
                        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();
                      }
                  const SizedBox(height: 15.0),
                  DropdownButtonFormField(
                      value: eventsStore.currentEvent.urn,
                      isExpanded: true,
                      decoration: const InputDecoration(
                        border: OutlineInputBorder(),
                        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.urnId =
                            configuration.getDeviceIdFromUrn(value.toString());
                      }
                  ),
                  const SizedBox(height: 15.0),
                  TextFormField(
                    initialValue: eventsStore.currentEvent.description,
                    autovalidateMode: AutovalidateMode.always,
                    decoration: const InputDecoration(
                        border: OutlineInputBorder(),
                        labelText: 'Description'
                    onChanged: (value){
                      eventsStore.currentEvent.description = 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 {
                        //eventsStore.currentEvent.label = value;
                        return null; // Entered Text is valid
                      }
                    },
                  ),
                  const SizedBox(height: 15.0),
                  Row(
                      mainAxisSize: MainAxisSize.max,
                      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.
                                  var isoDate = date.toIso8601String();
                                  eventsStore.currentEvent.startDate = isoDate;
                                  eventsStore.currentEvent.endDate = isoDate;
                                  debugPrint('Date set to : $isoDate');
                                  setState(() {});
                                },
                                currentTime: DateTime.now().toUtc(),
                                locale: LocaleType.en);
                          },
                        ),
                        ),
                        ElevatedButton(
                          onPressed: () {
                            var date = DateTime.now().toUtc();
                            var isoDate = date.toIso8601String();
                            eventsStore.currentEvent.startDate = isoDate;
                            eventsStore.currentEvent.endDate = isoDate;
                            debugPrint('Date set to : ' +
                                eventsStore.currentEvent.endDate.toString());
                            setState(() {});
                          },
                          child: const Text('Now'),
                        ),
                      ]
                  const SizedBox(height: 15.0),
                  TextFormField(
                    readOnly: false,
                    enabled: !syncGNSSData,
                    keyboardType: TextInputType.number,
                    autovalidateMode: AutovalidateMode.onUserInteraction,
                    controller: TextEditingController(
                        text: eventsStore.currentEvent.latitude.toString()),
                    decoration: const InputDecoration(
                      labelText: 'Latitude',
                      border: OutlineInputBorder(),
                    ),
                    onChanged: (value) {
                      eventsStore.currentEvent.latitude = value;
                    },
                    onFieldSubmitted: (value){
                      if (!syncGNSSData) {
                        setState(() {});
                      }
                    },
                    validator: (value) {
                      if (value == "") {
                        return null; // Empty value is allowed
                      }
                      final number = num.tryParse(value!);
                      if (number != null){
                        if (number >= -90.0  && number <= 90.0){
                          return null;  // Latitude valid
                        }
                      return "-90 => Latitude <= +90";
                    },
                  ),
                  const SizedBox(height: 15.0),
                  TextFormField(
                    readOnly: false,
                    enabled: !syncGNSSData,
                    keyboardType: TextInputType.number,
                    autovalidateMode: AutovalidateMode.onUserInteraction,
                    controller: TextEditingController(
                        text: eventsStore.currentEvent.longitude.toString()),
                    decoration: const InputDecoration(
                      labelText: 'Longitude',
                      border: OutlineInputBorder(),
                    ),
                    onChanged: (value) {
                      eventsStore.currentEvent.longitude = value;
                    },
                    onFieldSubmitted: (value){
                      if (!syncGNSSData) {
                        setState(() {});
                      }
                    },
                    validator: (value) {
                      if (value == "") {
                        return null; // Empty value is allowed
                      final number = num.tryParse(value!);
                      if (number != null){
                        if (number >= -180.0  && number <= 180.0){
                          return null;  // Longitude valid
                        }
                      }
                      return "-180 => Longitude <= +180";
                    },

                  const SizedBox(height: 15.0),
                  TextFormField(
                    readOnly: false,
                    enabled: !syncGNSSData,
                    keyboardType: TextInputType.number,
                    autovalidateMode: AutovalidateMode.onUserInteraction,
                    controller: TextEditingController(
                        text: eventsStore.currentEvent.elevation.toString()),
                    decoration: const InputDecoration(
                      labelText: 'Elevation',
                      border: OutlineInputBorder(),
                    ),
                    onChanged: (value) {
                      eventsStore.currentEvent.elevation = value;
                    },
                    onFieldSubmitted: (value){
                      if (!syncGNSSData) {
                        setState(() {});
                      }
                    },
                    validator: (value) {
                      if (value == "") {
                        return null; // Empty value is allowed
                      final number = num.tryParse(value!);
                      if (number != null){
                        return null;  // Elevation valid
                      }
                      return "Only numerical values for elevation in [m]";
                    },
        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
                });
              },
            Container(
                margin: const EdgeInsets.symmetric(vertical: 10.0),
                child: _validateInput() ?
                FloatingActionButton(
                  onPressed: () {
                    if (_validateInput()) {
                      _storeCurrentEvent();
                      HapticFeedback.vibrate();

                      //Update time for next event
                      var date = DateTime.now().toUtc();
                      var isoDate = date.toIso8601String();
                      eventsStore.currentEvent.startDate = isoDate;
                      eventsStore.currentEvent.endDate = isoDate;

                      debugPrint(date.toIso8601String());
                  },
                  tooltip: 'Add Event',
                  child: const Icon(Icons.check),
                ) :
                FloatingActionButton(
                  enableFeedback: false,
                  backgroundColor: Colors.grey,
                  onPressed: () {
                    setState(() {
                      //refresh the UI
                    });
                  },
                  tooltip: 'Correct Inputs before adding the Event',
                  //child: const Icon(Icons.check),
                )
            const SizedBox(width: 10.0),
          ],
        ),
      );
    }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)
            ),
          ],
        ),
      );
    }