From 1e1a8c38cb2dbeaab66ff71e9317889e5b5ebbfd Mon Sep 17 00:00:00 2001
From: Maximilian Betz <Maximilian.Betz@awi.de>
Date: Fri, 11 Mar 2022 12:42:45 +0100
Subject: [PATCH] enhanced input validation

---
 lib/addevent.dart | 182 ++++++++++++++++++++++++++++++++++++----------
 1 file changed, 142 insertions(+), 40 deletions(-)

diff --git a/lib/addevent.dart b/lib/addevent.dart
index b937338..8a15494 100644
--- a/lib/addevent.dart
+++ b/lib/addevent.dart
@@ -20,8 +20,6 @@ class _AddEventPageState extends State<AddEvent>  {
   late String alt = "";
   late double accuracy = 0.0;
   late StreamSubscription<Position> streamHandler;  //For canceling GNSS stream on dispose
-  bool _labelValidate = false;
-  bool _descriptionValidate = false;
 
   Future startGNSS() async {
     debugPrint("Check Location Permission");
@@ -100,13 +98,58 @@ class _AddEventPageState extends State<AddEvent>  {
     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)) {
-        return true;
+        if(_validateLatitude(eventsStore.currentEvent.latitude)){
+          if(_validateLongitude(eventsStore.currentEvent.longitude)){
+            if(_validateElevation(eventsStore.currentEvent.elevation)){
+              debugPrint('All inputs valid');
+              return true;
+
+            }
+          }
+        }
       }
     }
     return false;
@@ -185,11 +228,8 @@ class _AddEventPageState extends State<AddEvent>  {
                     validator: (value) {
                       if (!RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$').hasMatch(
                           value!)) {
-                        _labelValidate = false;
                         return "Only: a-z , A-Z , _ , 0-9 , ,(Comma) , ( , ) , + , - , . , :";
                       } else {
-                        _labelValidate = true;
-                        //eventsStore.currentEvent.label = value;
                         return null; // Entered Text is valid
                       }
                     },
@@ -249,10 +289,8 @@ class _AddEventPageState extends State<AddEvent>  {
                     validator: (value) {
                       if (!RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$').hasMatch(
                           value!)) {
-                        _descriptionValidate = false;
                         return "Only: a-z , A-Z , _ , 0-9 , ,(Comma) , ( , ) , + , - , . , :";
                       } else {
-                        _descriptionValidate = true;
                         //eventsStore.currentEvent.label = value;
                         return null; // Entered Text is valid
                       }
@@ -302,46 +340,102 @@ class _AddEventPageState extends State<AddEvent>  {
                   ),
                   const SizedBox(height: 15.0),
                   TextFormField(
-                      readOnly: false,
-                      enabled: !syncGNSSData,
-                      controller: TextEditingController(
-                          text: eventsStore.currentEvent.latitude.toString()),
-                      decoration: const InputDecoration(
-                        labelText: 'Latitude',
-                        border: OutlineInputBorder(),
-                      ),
-                      onChanged: (value) {
-                        eventsStore.currentEvent.latitude = value;
+                    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,
-                      controller: TextEditingController(
-                          text: eventsStore.currentEvent.longitude.toString()),
-                      decoration: const InputDecoration(
-                        labelText: 'Longitude',
-                        border: OutlineInputBorder(),
+                    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;
+                    ),
+                    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,
-                      controller: TextEditingController(
-                          text: eventsStore.currentEvent.elevation.toString()),
-                      decoration: const InputDecoration(
-                        labelText: 'Elevation',
-                        border: OutlineInputBorder(),
-                      ),
-                      onChanged: (value) {
-                        eventsStore.currentEvent.elevation = value;
+                    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]";
+                    },
                   ),
                 ]
             ),
@@ -368,8 +462,13 @@ class _AddEventPageState extends State<AddEvent>  {
                 child: _validateInput() ?
                 FloatingActionButton(
                   onPressed: () {
-                    _storeCurrentEvent();
-                    HapticFeedback.vibrate();
+                    if (_validateInput()) {
+                      _storeCurrentEvent();
+                      HapticFeedback.vibrate();
+                    }
+                    else {
+                      setState(() {});
+                    }
                   },
                   tooltip: 'Add Event',
                   child: const Icon(Icons.check),
@@ -378,6 +477,9 @@ class _AddEventPageState extends State<AddEvent>  {
                   enableFeedback: false,
                   backgroundColor: Colors.grey,
                   onPressed: () {
+                    setState(() {
+                      //refresh the UI
+                    });
                   },
                   tooltip: 'Correct Inputs before adding the Event',
                   //child: const Icon(Icons.check),
-- 
GitLab