From 0047205ef9856d78f1f641ba0fb4965660183e7e Mon Sep 17 00:00:00 2001 From: rotenaple Date: Wed, 10 Jan 2024 15:03:40 +1030 Subject: [PATCH] #14, #15, misc bug fixes --- lib/csv.dart | 128 +++++++++++++++++++++++++++++++++++++++++++++ lib/main.dart | 62 ++++------------------ lib/pick_path.dart | 60 ++++----------------- 3 files changed, 146 insertions(+), 104 deletions(-) create mode 100644 lib/csv.dart diff --git a/lib/csv.dart b/lib/csv.dart new file mode 100644 index 0000000..a3f886d --- /dev/null +++ b/lib/csv.dart @@ -0,0 +1,128 @@ +import 'dart:io'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; + +class ParseCSV { + String _pathName = "Unnamed Path"; + + Future<(List>, String)> readCSV(String path, String source) async { + String csvData = await _loadCSVData(path, source); + final lines = csvData.split('\n'); + if (kDebugMode) { + print(lines); + } + + return (_processLines(lines), _pathName); + } + + Future _loadCSVData(String path, String source) async { + if (source == "asset") { + return await rootBundle.loadString(path); + } else { + final file = File(path); + return await file.readAsString(); + } + } + + List> _processLines(List lines) { + List> data = []; + for (var line in lines) { + if (_isPathNameLine(line)) { + _pathName = line.substring(2).trim(); + continue; + } + + if (_isCommentOrEmpty(line)) { + continue; + } + + var row = _processRow(line); + if (row != null) { + data.add(row); + } + } + return data; + } + + bool _isPathNameLine(String line) => line.trim().startsWith('##'); + + bool _isCommentOrEmpty(String line) => line.trim().isEmpty || line.trim().startsWith('#'); + + List? _processRow(String line) { + List row = line.split(','); + if (row.length >= 4) { + return [ + row[0], + double.tryParse(row[1]) ?? 0.0, + double.tryParse(row[2]) ?? 0.0, + double.tryParse(row[3]) ?? 0.0 + ]; + } + return null; + } + + Future importCSV() async { + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['csv'], + ); + + if (result != null) { + File file = File(result.files.single.path!); + if (await _validateCSV(file)) { + await _saveFileToAppDirectory(file); + } + } + } + + Future _validateCSV(File file) async { + final input = await file.readAsString(); + final lines = input.split('\n'); + + for (var line in lines) { + if (_isCommentOrEmpty(line)) { + continue; + } + + if (!_isValidCSVRow(line)) { + return false; + } + } + return true; + } + + bool _isValidCSVRow(String line) { + List row = line.split(','); + return row.length == 4 && + _isValidDouble(row[1], -90, 90) && + _isValidDouble(row[2], -180, 180) && + _isValidDouble(row[3], 0, double.infinity); + } + + bool _isValidDouble(String value, double min, double max) { + double? val = double.tryParse(value); + return val != null && val >= min && val <= max; + } + + Future _saveFileToAppDirectory(File file) async { + Directory appDocDir = await getApplicationDocumentsDirectory(); + String baseFilePath = '${appDocDir.path}/${file.uri.pathSegments.last}'; + String newFilePath = baseFilePath; + + int counter = 2; + while (File(newFilePath).existsSync()) { + newFilePath = _appendNumberSuffix(baseFilePath, counter); + counter++; + } + + await file.copy(newFilePath); + } + + String _appendNumberSuffix(String filePath, int counter) { + String extension = filePath.substring(filePath.lastIndexOf('.')); + String pathWithoutExtension = filePath.substring(0, filePath.lastIndexOf('.')); + return '${pathWithoutExtension}_$counter$extension'; + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index c8b9645..6ed9d11 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; -import 'dart:io'; import 'package:gps_path_tracker/location_service.dart'; import 'package:gps_path_tracker/pick_path.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:gps_path_tracker/pick_path.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:gps_path_tracker/time_provider.dart'; +import 'package:gps_path_tracker/csv.dart'; + import 'package:latlong2/latlong.dart'; void main() => runApp(const MyApp()); @@ -52,52 +50,12 @@ class _MyAppState extends State { } Future importData() async { - var returnValue = await ReadCSV().readCSV('assets/pathdata.csv', "asset"); + var returnValue = await ParseCSV().readCSV('assets/pathdata.csv', "asset"); nameLatLngSet = returnValue.$1; _pathName = returnValue.$2; } - Future importCSV() async { - FilePickerResult? result = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['csv'], - ); - - if (result != null) { - File file = File(result.files.single.path!); - - final input = await file.readAsString(); - final lines = input.split('\n'); - bool csvNoErrors = true; - - for (var line in lines) { - if (line.trim().isEmpty || line.trim().startsWith('#')) { - continue; - } - - List row = line.split(','); - if (row.length != 4 || - double.tryParse(row[1]) == null || - double.tryParse(row[1])! < -90 || - double.tryParse(row[1])! > 90 || - double.tryParse(row[2]) == null || - double.tryParse(row[2])! < -180 || - double.tryParse(row[2])! > 180 || - double.tryParse(row[3]) == null || - double.tryParse(row[3])! < 0) { - csvNoErrors = false; - break; - } - } - if (csvNoErrors) { - Directory appDocDir = await getApplicationDocumentsDirectory(); - String appDocPath = appDocDir.path; - final String newFilePath = '$appDocPath/${file.uri.pathSegments.last}'; - await file.copy(newFilePath); - } - } - } void _initLocationStream() { _locationService.initLocationStream((latitude, longitude, speed) { @@ -127,7 +85,7 @@ class _MyAppState extends State { LatLng(nameLatLngSet[_targetIndex][1], nameLatLngSet[_targetIndex][2]); _targetStr = '${nameLatLngSet[_targetIndex][1].toStringAsFixed(7)}, ${nameLatLngSet[_targetIndex][2].toStringAsFixed(7)}'; - _targetName = nameLatLngSet[_targetIndex][0]; + _targetName = "$_targetIndex " + nameLatLngSet[_targetIndex][0]; } void _updateDisplayInfo() { @@ -178,7 +136,7 @@ class _MyAppState extends State { String _calculateEtaDisplay() { if (_currentSpeed > 0.0) { return GetFutureTime() - .getFutureTime((_linearDistance / _currentSpeed * 3.6).toInt()); + .getFutureTime((_estDistance / _currentSpeed * 3.6).toInt()); } else { return "N/A"; } @@ -271,10 +229,8 @@ class _MyAppState extends State { } void processSelectedPath(String path) async { - // Process the file at the given path - // For example, read the CSV, update the state, etc. - - var returnValue = await ReadCSV().readCSV('assets/pathdata.csv', "path"); + var returnValue = await ParseCSV().readCSV(path, "path"); + print(returnValue); var newData = returnValue.$1; _pathName = returnValue.$2; setState(() { @@ -343,7 +299,7 @@ class _MyAppState extends State { Visibility( visible: !isHorizontal, child: const SizedBox( - height: 80, + height: 100, width: 200, ), ), @@ -410,7 +366,7 @@ class _MyAppState extends State { leading: const Icon(Icons.file_copy), title: const Text('Import Custom Path File'), onTap: () { - importCSV(); + ParseCSV().importCSV(); Navigator.pop(context); }, ), diff --git a/lib/pick_path.dart b/lib/pick_path.dart index c474c34..46c0198 100644 --- a/lib/pick_path.dart +++ b/lib/pick_path.dart @@ -1,56 +1,8 @@ import 'dart:io'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; -class ReadCSV { - String _pathName = "Unnamed Path"; - late (Future>>, int) record; - - - Future<(List>, String)> readCSV(String path, String source) async { - String csvData; - if (source == "asset") { - csvData = await rootBundle.loadString(path); - } else { - final file = File(path); - csvData = await file.readAsString(); - } - - final lines = csvData.split('\n'); - if (kDebugMode) { - print(lines); - } - - List> data = []; - for (var line in lines) { - - if (line.trim().startsWith('##')) { - _pathName = line.substring(2).trim(); - continue; - } - - if (line.trim().isEmpty || line.trim().startsWith('#')) { - continue; - } - - List row = line.split(','); - if (row.isNotEmpty && row.length >= 4) { - row = [ - row[0], // Assuming the first column is a String - double.tryParse(row[1]) ?? 0.0, // Latitude - double.tryParse(row[2]) ?? 0.0, // Longitude - double.tryParse(row[3]) ?? 0.0 // Some other numeric value - ]; - data.add(row); - } - } - return (data,_pathName); - } -} - class FileDetails { final File file; final DateTime addedDate; @@ -75,7 +27,9 @@ class PickPath extends StatelessWidget { return fileDetails; } + @override + Widget build(BuildContext context) { return Scaffold( appBar: AppBar( @@ -108,7 +62,8 @@ class PickPath extends StatelessWidget { ); } - Widget buildFileCard(FileDetails fileDetail, String fileName, BuildContext context) { + Widget buildFileCard( + FileDetails fileDetail, String fileName, BuildContext context) { return InkWell( onTap: () { Navigator.pop(context, fileDetail.file.path); @@ -119,8 +74,10 @@ class PickPath extends StatelessWidget { ); } - ListTile buildListTile(FileDetails fileDetail, String fileName, BuildContext context) { - final addedDate = DateFormat('dd MMM yyyy kk:mm').format(fileDetail.addedDate); + ListTile buildListTile( + FileDetails fileDetail, String fileName, BuildContext context) { + final addedDate = + DateFormat('dd MMM yyyy kk:mm').format(fileDetail.addedDate); return ListTile( leading: const Icon(Icons.map), title: Text(fileName), @@ -156,6 +113,7 @@ class PickPath extends StatelessWidget { onPressed: () async { await fileDetail.file.delete(); Navigator.pop(context); + Navigator.pop(context); }, child: const Text('Delete'), ),