Newer
Older
import 'package:flutter/material.dart';
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'databaseconnector.dart';
import 'dart:async';
import 'package:geolocator/geolocator.dart';
const AddEvent({Key? key}) : super(key: key);
@override
State<AddEvent> createState() => _AddEventPageState();
}
class _AddEventPageState extends State<AddEvent> {
late String long = "";
late String lat = "";
late String alt = "";
late StreamSubscription<Position> streamHandler; //For canceling GNSS stream on dispose
final prefs = SharedPreferences.getInstance(); // Is async
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) {
}else if(permission == LocationPermission.deniedForever){
debugPrint('Location permissions are permanently denied');
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(mounted){
setState(() {
//refresh UI on update
});}
}
});
debugPrint("GPS Service is not enabled, turn on GPS location");
setState(() {
//refresh the UI
});}
@override
try {
streamHandler.cancel();
debugPrint('Cancel location stream');
}catch(e){
debugPrint('Canceling location stream failed');
}
/*Async update current event configuration to shared preferences*/
final EventStoreInstance event = EventStoreInstance();
event.storeToSharedPrefs();
super.dispose();
}
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
140
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){
if (RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$').hasMatch(
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)){
}
}
return false;
}
bool _addButtonStatus(){
if((_validateInput() == true) && (_addButtonEnabled == true)){
return true;
}
return false;
}
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
void _showAddSuccessOverlay(BuildContext context) async {
// Declaring and Initializing OverlayState
// and OverlayEntry objects
OverlayState? overlayState = Overlay.of(context);
OverlayEntry overlayEntry;
overlayEntry = OverlayEntry(builder: (context) {
// You can return any widget you like
// here to be displayed on the Overlay
return Positioned(
top: MediaQuery.of(context).padding.top,
right: MediaQuery.of(context).padding.right,
child: Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.all(3.0), //a view pixel space to top and right
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)
)
),
child: const Padding(
padding: EdgeInsets.all(10.0),
child: Text(
'Created Event!',
//style: TextStyle(
// color: Colors.black, fontWeight: FontWeight.bold),
),
),
),
),
),
),
);
});
// Inserting the OverlayEntry into the Overlay
overlayState?.insert(overlayEntry);
// Awaiting for 3 seconds
await Future.delayed(const Duration(seconds: 3));
// Removing the OverlayEntry from the Overlay
overlayEntry.remove();
}
Future<void> _storeCurrentEvent(BuildContext context) async {
final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();
event.currentEvent.typeId = configuration.getEventIdFromName(event.currentEvent.type);
event.currentEvent.status = "PENDING";
await database.addEvent(event.currentEvent);
HapticFeedback.vibrate(); //Feedback that adding event succeeded
_addButtonEnabled = true; //Activate button for add more events
_showAddSuccessOverlay(context); //Show pop up to indicated adding event succeeded.
//Update timestamp in UI
var date = DateTime.now().toUtc();
var isoDate = date.toIso8601String();
event.currentEvent.startDate = isoDate;
event.currentEvent.endDate = isoDate;
@override
Widget build(BuildContext context) {
/* Get singletons to access relevant data here.*/
final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();
// Update current event coordinates from GNSS stream
eventStore.currentEvent.latitude = lat;
eventStore.currentEvent.longitude = long;
eventStore.currentEvent.elevation = alt;
var isoDate = date.toIso8601String();
eventStore.currentEvent.startDate = isoDate;
eventStore.currentEvent.endDate = isoDate;
if(accuracy == 0.0){
gnssStatusText = "No-Fix";
else{
gnssStatusText = "Precision: "+ accuracy.toStringAsFixed(2) +"m";
}
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(
autovalidateMode: AutovalidateMode.always,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Label',
),
onChanged: (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(
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) {
),
const SizedBox(height: 15.0),
DropdownButtonFormField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
items:
configuration.devices.map((Device device) {
return DropdownMenuItem(
value: device.urn,
child: Text(device.urn),
);
}).toList(),
onChanged: (value) {
eventStore.currentEvent.urn = value.toString();
eventStore.currentEvent.urnId =
configuration.getDeviceIdFromUrn(value.toString());
}
),
const SizedBox(height: 15.0),
TextFormField(
autovalidateMode: AutovalidateMode.always,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Description'
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
}
},
),
const SizedBox(height: 15.0),
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(child:
TextFormField(
controller: TextEditingController(

Maximilian Betz
committed
text: eventStore.currentEvent.startDate.substring(0, 19) + 'Z' //Do not show microseconds
),
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();
eventStore.currentEvent.startDate = isoDate;
eventStore.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();
eventStore.currentEvent.startDate = isoDate;
eventStore.currentEvent.endDate = isoDate;
setState(() {});
},
child: const Text('Now'),
),
]
const SizedBox(height: 15.0),
TextFormField(
keyboardType: TextInputType.number,
autovalidateMode: AutovalidateMode.onUserInteraction,
controller: TextEditingController(
decoration: const InputDecoration(
labelText: 'Latitude',
border: OutlineInputBorder(),
),
onChanged: (value) {
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
}
),
const SizedBox(height: 15.0),
TextFormField(
keyboardType: TextInputType.number,
autovalidateMode: AutovalidateMode.onUserInteraction,
controller: TextEditingController(
decoration: const InputDecoration(
labelText: 'Longitude',
border: OutlineInputBorder(),
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(
keyboardType: TextInputType.number,
autovalidateMode: AutovalidateMode.onUserInteraction,
controller: TextEditingController(
decoration: const InputDecoration(
labelText: 'Elevation',
border: OutlineInputBorder(),
),
onChanged: (value) {
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(
onChanged: (value) {
eventStore.gnssSync = value;
debugPrint('Switched to:' + eventStore.gnssSync.toString());
setState(() {
//refresh the UI
});
},
const SizedBox(width: 50),
Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
FloatingActionButton(
onPressed: () {
_addButtonEnabled = false; //Disable button until event is stored
_storeCurrentEvent(context);
},
tooltip: 'Add Event',
child: const Icon(Icons.check),
) :
FloatingActionButton(
enableFeedback: false,
backgroundColor: Colors.grey,
onPressed: () {
//child: const Icon(Icons.check),
)
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)
),
),
],
),
);
}
//TODO: The app shall prefill the label with a configurable prefix and optionally a running number. E.g. prefix: (PS122.3-4_) running number: 1 label = PS122.3-4_1