Newer
Older
import 'dart:convert';
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';
class AddEventKottasPegel extends StatefulWidget {
const AddEventKottasPegel({Key? key}) : super(key: key);
@override
State<AddEventKottasPegel> createState() => _AddEventKottasPegelPageState();
}
class _AddEventKottasPegelPageState extends State<AddEventKottasPegel> {
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
committed
int measurementStatusId = -1;
List<String> rodColorValues = ['black', 'red', 'blue', 'none'];
int rodColorValuesIdOld = -1;
int rodColorValuesIdNew = -1;
Maximilian Betz
committed
late String angleStatus;
Maximilian Betz
committed
int lengthOld = 0;
int lengthNew = 0;
bool syncGNSSData = true;
bool _addButtonEnabled = true;
bool _eventAdded = false;
bool displayAngle = true;
bool displayOldLength = true;
bool displayNewLength = true;
late StreamSubscription<Position> streamHandler;
late Timer restQueryTimer;
final prefs = SharedPreferences.getInstance(); // Is async
var database = DatabaseInstance();
late OverlayEntry _overlayEntry; //For event creation success notifications
late Timer _overlayCloseTimer;
Maximilian Betz
committed
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
//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;
if(mounted){
setState(() {
//refresh UI on update
});}
}
final EventStoreInstance event = EventStoreInstance();
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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} Long:${position.longitude} Alt:${position.altitude}');
event.currentEvent.latitude = position.latitude.toString();
event.currentEvent.longitude = position.longitude.toString();
event.currentEvent.elevation = position.altitude.toString();
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');
Maximilian Betz
committed
labelEditingController.text = event.currentEvent.label;
Maximilian Betz
committed
angleStatus = angleStatusItems[0];
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');
}
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
/*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;
}
}
if (displayNewLength == true){
if (rodColorValuesIdNew < 0 ||
rodColorValuesIdNew > rodColorValues.length){
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');
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
}
_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;
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
Maximilian Betz
committed
event.currentEvent.urn = 'station:neumayer_iii:snow_level_measure';
event.currentEvent.urnId = 9094;
Maximilian Betz
committed
event.currentEvent.type = 'Deployment';
event.currentEvent.typeId = 187;
Maximilian Betz
committed
//Generate Payload Measurement Data
Map<String, dynamic> data =
{
'status' : measurementStatusItems[measurementStatusId],
Maximilian Betz
committed
};
if(true == addAngle){
data['angle_old'] = angleStatus;
}
if(true == addOldLength) {
data['length_old'] = lengthOld;
data['color_old'] = rodColorValues[rodColorValuesIdOld];
}
if (true == addNewLength) {
data['length_new'] = lengthNew;
data['color_new'] = rodColorValues[rodColorValuesIdNew];
Maximilian Betz
committed
//Add payload as json to data field in db
//event.currentEvent.data = data.toString();
event.currentEvent.data = jsonEncode(data);
Maximilian Betz
committed
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');
Maximilian Betz
committed
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
_resetSelectedFields();
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
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,
event.gnssSync = value;
debugPrint('Switched to:${event.gnssSync}');
// 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,
keyboardType: TextInputType.number,
autovalidateMode: AutovalidateMode.onUserInteraction,
controller: TextEditingController(
"${event.currentEvent.startDate.substring(11,19)} / "
"${event.currentEvent.latitude} / "
"${event.currentEvent.longitude} / "
"${event.currentEvent.elevation}"
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(
Maximilian Betz
committed
controller: labelEditingController,
autovalidateMode: AutovalidateMode.onUserInteraction,
event.currentEvent.label = value;
Maximilian Betz
committed
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(
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
primary: measurementStatusId == 0 ? Colors.black54 : Colors.white12,
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
primary: measurementStatusId == 1 ? Colors.black54 : Colors.white12,
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
primary: measurementStatusId == 2 ? Colors.black54 : Colors.white12,
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
primary: measurementStatusId == 3 ? Colors.black54 : Colors.white12,
measurementStatusId = 3;
setState(() {});
const SizedBox(height: 30.0),
Visibility(
visible: displayAngle,
child:
DropdownButtonFormField(
iconDisabledColor: Colors.green,
Maximilian Betz
committed
value: angleStatus,
isExpanded: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'old angle',
),
items:
angleStatusItems.map((String angle) {
return DropdownMenuItem(
value: angle,
//enabled: displayAngle,
child: Text(angle),
);
}).toList(),
onChanged: (value) {
Maximilian Betz
committed
angleStatus = value.toString();
}
),
),
const SizedBox(height: 15.0),
Visibility(
visible: displayOldLength,
TextFormField(
readOnly: false,
keyboardType: TextInputType.number,
autovalidateMode: AutovalidateMode.onUserInteraction,
inputFormatters:[FilteringTextInputFormatter.allow(RegExp("[0-9]"))],
controller: oldLengthEditingController,
decoration: const InputDecoration(
labelText: 'old length', //Example
border: OutlineInputBorder(),
),
Maximilian Betz
committed
lengthOld = int.parse(value);
setState(() {});
Visibility(
visible: displayOldLength,
child:
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
ElevatedButton(
style: ElevatedButton.styleFrom(
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,
),
onPressed: () {
setState(() {});
},
ElevatedButton(
style: ElevatedButton.styleFrom(
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,
),
onPressed: () {
setState(() {});
},
ElevatedButton(
style: ElevatedButton.styleFrom(
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,
),
onPressed: () {
setState(() {});
},
ElevatedButton(
style: ElevatedButton.styleFrom(
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,
),
onPressed: () {
setState(() {});
},
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();
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();
setState(() {});
},
child: const Text('+2m'),
),
]
),
),
const SizedBox(height: 30.0),
Visibility(
visible: displayNewLength,
TextFormField(
readOnly: false,
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) {
Maximilian Betz
committed
lengthNew = int.parse(value);
setState(() {});
),
),
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: 20.0), //TODO: find a more dynamic solution without a fixed value
primary: rodColorValuesIdNew == 0 ? Colors.black54 : Colors.white12,
),
onPressed: () {
setState(() {});
},
ElevatedButton(
style: ElevatedButton.styleFrom(
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,
),
onPressed: () {
setState(() {});
},
ElevatedButton(
style: ElevatedButton.styleFrom(
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,
),
onPressed: () {
setState(() {});
},
ElevatedButton(
style: ElevatedButton.styleFrom(
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,
),
onPressed: () {
setState(() {});
},
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();
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();
setState(() {});
},
child: const Text('+2m'),
initialValue: event.currentEvent.description,
autovalidateMode: AutovalidateMode.always,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Description'
),
onChanged: (value){
event.currentEvent.description = value;
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,
Maximilian Betz
committed
label: const Text('Save'),
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,
Maximilian Betz
committed
label: const Text('Save'),
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
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)
),
),
],
),
);
}
}