From d965d2e0382670a798052c0cb2803d53d65371e3 Mon Sep 17 00:00:00 2001 From: lnd212 Date: Fri, 5 Nov 2021 18:43:27 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D0=BA?= =?UTF-8?q?=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA,=20=D1=81=D1=82=D1=80=D0=B5?= =?UTF-8?q?=D0=BB=D0=BA=D0=B8=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?=D0=BB=D0=B8=D0=BD=D0=B8=D0=B9,=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=B2=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B5,?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +- cmd/README.md | 2 +- cmd/outTest.txt | 4 +- flutter/lib/curve_painter.dart | 51 +++++-- flutter/lib/pages/drawing_page.dart | 223 ++++++++++++++++++++-------- flutter/lib/src/graph.dart | 167 +++++++++++++++------ flutter/pubspec.lock | 7 + flutter/pubspec.yaml | 1 + 8 files changed, 337 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index 18354f9..132e7d1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ CMD — реализация с коммандным интерфейсом -FLUTTER — реализация с помощью Flutter \ No newline at end of file +FLUTTER — реализация с помощью Flutter + + +Currently in work: Flutter. \ No newline at end of file diff --git a/cmd/README.md b/cmd/README.md index 8f79023..f6fbba2 100644 --- a/cmd/README.md +++ b/cmd/README.md @@ -1 +1 @@ -Application to store graph data. +Dart app to store string data in graph \ No newline at end of file diff --git a/cmd/outTest.txt b/cmd/outTest.txt index 82860c1..bad0c29 100644 --- a/cmd/outTest.txt +++ b/cmd/outTest.txt @@ -2,7 +2,7 @@ НеОриентированный Взвешенный 1: 1|10 2|11 3|10 4|10 -2: 1|11 3|20 -3: 1|10 2|20 +2: 1|11 +3: 4: 1|10 END \ No newline at end of file diff --git a/flutter/lib/curve_painter.dart b/flutter/lib/curve_painter.dart index 9eb30ac..6c2430b 100644 --- a/flutter/lib/curve_painter.dart +++ b/flutter/lib/curve_painter.dart @@ -1,3 +1,4 @@ +import 'package:arrow_path/arrow_path.dart'; import 'package:graphs/src/graph.dart'; import 'package:flutter/material.dart'; import 'dart:math'; @@ -22,17 +23,25 @@ class CurvePainter extends CustomPainter { void drawLine(Canvas canvas, Offset p1, Offset p2) { Paint p = Paint(); p.color = col; - p.strokeWidth = width; + p.strokeWidth = width - 2; canvas.drawLine(p1, p2, p); } void drawDot(Canvas canvas, Offset p1) { var p = Paint(); p.color = col; - p.strokeWidth = width + 1; + p.strokeWidth = width; canvas.drawCircle(p1, rad, p); } + void drawSelfConnect(Canvas canvas, Offset p1) { + var p = Paint(); + p.color = col; + p.strokeWidth = width - 2; + p.style = PaintingStyle.stroke; + canvas.drawCircle(Offset(p1.dx + rad + 20, p1.dy), rad + 20, p); + } + TextSpan _getTextSpan(String s) => TextSpan(text: s, style: textStyle); TextPainter _getTextPainter(String s) => TextPainter( text: _getTextSpan(s), @@ -71,12 +80,37 @@ class CurvePainter extends CustomPainter { return off; } + void drawArrow(Canvas canvas, Offset from, Offset to, + [bool doubleSided = false]) { + Path path; + + // The arrows usually looks better with rounded caps. + Paint paint = Paint() + ..color = Colors.black + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round + ..strokeWidth = width - 1; + + /// Draw a single arrow. + path = Path(); + path.moveTo(from.dx, from.dy); + path.relativeCubicTo(0, 0, 0, 0, to.dx - from.dx, to.dy - from.dy); + path = + ArrowPath.make(path: path, isDoubleSided: doubleSided, tipLength: 13); + canvas.drawPath(path, paint); + } + void drawConnections(Canvas canvas, List dots, Map off) { for (var i in dots) { var list = i.getL(); var beg = off[i.num]; for (var d in list.keys) { - drawLine(canvas, beg!, off[d]!); + if (d == i.num) { + drawSelfConnect(canvas, off[d]!); + } else { + drawArrow(canvas, beg!, off[d]!, !gr.getDoubleSidedBool()); + } } } } @@ -88,7 +122,7 @@ class CurvePainter extends CustomPainter { } else { circleRad = size.width / 3; } - var paint = Paint(); + //var paint = Paint(); //drawLine(canvas, Offset(0, size.height / 2), //Offset(size.width, size.height / 2)); @@ -100,13 +134,8 @@ class CurvePainter extends CustomPainter { canvas, off[i + 1]!, "${gr.getDots()[i].getName()}:[${i + 1}]"); } drawConnections(canvas, gr.getDots(), off); - - paint.color = Colors.blue; - paint.style = PaintingStyle.stroke; - //drawDot(canvas, Offset(size.width / 2, size.height / 2)); - //drawAboveText(canvas, size, "sssssssssssss"); - //_drawTextAt("Assss", Offset(size.width / 2, size.height / 2), canvas); - paint.color = Colors.green; + //drawArrow(canvas, Offset(size.width / 2, size.height / 2), + // Offset(size.width / 2 + 50, size.height / 2 + 200)); } @override diff --git a/flutter/lib/pages/drawing_page.dart b/flutter/lib/pages/drawing_page.dart index 8b3bef5..9f426c5 100644 --- a/flutter/lib/pages/drawing_page.dart +++ b/flutter/lib/pages/drawing_page.dart @@ -11,7 +11,7 @@ Graphs getGraph() { d.add(Dot.fromTwoLists("3", [1, 2], [1, 1])); d.add(Dot.fromTwoLists("Name1", [], [])); d.add(Dot.fromTwoLists("Name2", [], [])); - return Graphs.fromList("1", d, false, false); + return Graphs.fromList("1", d, false, true); } class DrawingPage extends StatefulWidget { @@ -29,6 +29,7 @@ class _DrawingPageState extends State { final _textNumbController = TextEditingController(); final _textDestController = TextEditingController(); final _textLnthController = TextEditingController(); + final _textGrNmController = TextEditingController(); void clearTextControllers() { _textDestController.clear(); @@ -40,29 +41,37 @@ class _DrawingPageState extends State { @override Widget build(BuildContext context) { screenSize = MediaQuery.of(context).size.width; + _textGrNmController.text = data.getName(); return MaterialApp( home: Scaffold( appBar: AppBar( - title: const Text("Graph", - style: TextStyle(fontSize: 30, color: Colors.white)), - toolbarHeight: 110, // Set this height + title: const Align( + alignment: Alignment.topLeft, + child: Text("Graph\n\n", + style: TextStyle( + fontSize: 18, + color: Colors.white, + ))), + toolbarHeight: 110, flexibleSpace: Container( color: Colors.blue, child: Column(children: [ const SizedBox(height: 5), Row(children: [ - addSpaceW(screenSize / 8), + addSpaceW(screenSize / 8 + 21), createButton("\nAdd dot\n", addDotPushed), createInputBox("Dot name", screenSize / 4 - 25, Icons.label, _textNameController), - addSpaceW(20), + addSpaceW(18), createButton("\nDel dot \n", delDotPushed), createInputBox("Dot number", screenSize / 4 - 25, Icons.fiber_manual_record, _textNumbController), ]), addSpaceH(3), Row(children: [ - addSpaceW(screenSize / 8 - 4), + createInputBox( + "Name", screenSize / 8 - 25, null, _textGrNmController), + //addSpaceW(screenSize / 8 - 4), createButton("\nAdd path\n", addPathPushed), createInputBox("Input length", screenSize / 4 - 25, Icons.arrow_right_alt_outlined, _textLnthController), @@ -87,7 +96,12 @@ class _DrawingPageState extends State { child: ButtonBar( mainAxisSize: MainAxisSize.min, children: [ - createButton("Save to file", () {}), + createButton(data.getUseLengthStr(), changeLength), + createButton(data.getDoubleSidedStr(), changeOriented), + /*Text(_textGrNmController.text, + style: + TextStyle(fontSize: 15, color: Colors.blueGrey.shade900)),*/ + createButton("Save to file", fileSaver), createButton("Load from file", fileOpener), ], ), @@ -109,8 +123,29 @@ class _DrawingPageState extends State { ); } - Container createInputBox(String text, double wid, IconData icon, - TextEditingController controller) { + Container createInputBox(String text, double wid, IconData? icon, + TextEditingController? controller) { + if (icon == null) { + return Container( + width: wid, + height: 40, + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), + child: TextField( + controller: controller, + textAlign: TextAlign.center, + onChanged: (name) => data.setName(name), + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(vertical: 10, horizontal: 10), + filled: true, + fillColor: Colors.white, + //prefixIcon: Icon(icon, color: Colors.black), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(40))), + hintStyle: const TextStyle(color: Colors.black38), + hintText: text), + )); + } return Container( width: wid, height: 40, @@ -142,56 +177,46 @@ class _DrawingPageState extends State { void addDotPushed() { //showPopUp("Test", "Test message"); //var inp = int.tryParse(_textNameController.text); - if (_textNameController.text == "") { - showPopUp("Error", "No name in \"Dot name\" box"); - } else { - String? res = data.addIsolated(_textNameController.text); - if (res != null) { - showPopUp("Error", res); - } - } - clearTextControllers(); - } - - void addPathPushed() { - if (_textDestController.text == "") { - showPopUp("Error", "No name in \"Dot name\" box"); - } else { - String? res = data.addIsolated(_textNameController.text); - if (res != null) { - showPopUp("Error", res); - } - } - clearTextControllers(); - } - - void delPathPushed() { - if (_textDestController.text == "") { - showPopUp("Error", "No name in \"Dot name\" box"); - } else { - String? res = data.addIsolated(_textNameController.text); - if (res != null) { - showPopUp("Error", res); - } - } - clearTextControllers(); - } - - void delDotPushed() { - if (_textNumbController.text == "") { - showPopUp("Error", "No number in \"Dot number\" box"); - } else { - int? dot = int.tryParse(_textNumbController.text); - if (dot == null) { - showPopUp("Error", "Can't parse input.\nInts only allowed"); + setState(() { + if (_textNameController.text == "") { + showPopUp("Error", "No name in \"Dot name\" box"); } else { - String? res = data.delDot(dot); + String? res = data.addIsolated(_textNameController.text); if (res != null) { showPopUp("Error", res); } } - } - clearTextControllers(); + clearTextControllers(); + }); + } + + void addPathPushed() { + setState(() { + if (_textNumbController.text == "") { + showPopUp("Error", "No number in \"Dot number\" box"); + } else if (_textDestController.text == "") { + showPopUp("Error", "No name in \"Dot name\" box"); + } else if (_textLnthController.text == "" && data.getUseLengthBool()) { + showPopUp("Error", "No length in \"Input length\" box"); + } else { + int? from = int.tryParse(_textNumbController.text); + int? to = int.tryParse(_textDestController.text); + int? len = int.tryParse(_textLnthController.text); + if (from == null || + to == null || + (len == null && data.getUseLengthBool())) { + showPopUp("Error", + "Can't parse input.\nInts only allowed in \"Dot number\", \"Destination number\" and \"Input length\""); + } else { + if (data.getUseLengthBool() && len == null) len = 0; + String? res = data.addPath(from, to, len!); + if (res != null) { + showPopUp("Error", res); + } + } + } + clearTextControllers(); + }); } void addLenPushed() { @@ -201,6 +226,62 @@ class _DrawingPageState extends State { clearTextControllers(); } + void changeOriented() { + setState(() { + String? res = data.flipUseOrientation(); + if (res != null) showPopUp("Error", res); + }); + } + + void changeLength() { + setState(() { + String? res = data.flipUseLength(); + if (res != null) showPopUp("Error", res); + }); + } + + void delPathPushed() { + setState(() { + if (_textNumbController.text == "") { + showPopUp("Error", "No number in \"Dot number\" box"); + } else if (_textDestController.text == "") { + showPopUp("Error", "No name in \"Dot name\" box"); + } else { + int? from = int.tryParse(_textNumbController.text); + int? to = int.tryParse(_textDestController.text); + if (from == null || to == null) { + showPopUp("Error", + "Can't parse input.\nInts only allowed in \"Dot number\" and \"Destination number\""); + } else { + String? res = data.delPath(from, to); + if (res != null) { + showPopUp("Error", res); + } + } + } + clearTextControllers(); + }); + } + + void delDotPushed() { + setState(() { + if (_textNumbController.text == "") { + showPopUp("Error", "No number in \"Dot number\" box"); + } else { + int? dot = int.tryParse(_textNumbController.text); + if (dot == null) { + showPopUp("Error", "Can't parse input.\nInts only allowed"); + } else { + String? res = data.delDot(dot); + if (res != null) { + showPopUp("Error", res); + } + } + } + clearTextControllers(); + }); + } + void showPopUp(String alertTitle, String err) => showDialog( context: context, builder: (BuildContext context) => AlertDialog( @@ -216,14 +297,34 @@ class _DrawingPageState extends State { ); void fileOpener() async { - FilePickerResult? result = await FilePicker.platform.pickFiles(); + FilePickerResult? result = + await FilePicker.platform.pickFiles(allowedExtensions: ["txt"]); + setState(() { + if (result != null) { + //print(result.files.single.path!); + String? res = data.replaceDataFromFile(result.files.single.path!); + if (res != null) showPopUp("Error", res); + } else { + showPopUp("Error", "No file selected"); + // User canceled the picker + } + }); + } - if (result != null) { - print(result.files.single.path!); - data.replaceDataFromFile(result.files.single.path!); - } else { - showPopUp("Error", "No file selected"); + void fileSaver() async { + String? outputFile = await FilePicker.platform.saveFile( + dialogTitle: 'Please select an output file:', + fileName: 'output-file.txt', + allowedExtensions: ["txt"]); + + if (outputFile == null) { + showPopUp("Error", "Save cancelled"); // User canceled the picker + } else { + if (!outputFile.endsWith(".txt")) { + outputFile += ".txt"; + } + data.printToFile(outputFile); } } diff --git a/flutter/lib/src/graph.dart b/flutter/lib/src/graph.dart index 256e85c..ba04eee 100644 --- a/flutter/lib/src/graph.dart +++ b/flutter/lib/src/graph.dart @@ -1,15 +1,15 @@ import 'dart:io'; class Separators { - final String dotToConnections = ": "; - final String dotToLength = "|"; - final String space = " "; - final String hasLength = "Взвешенный\n"; - final String hasNoLength = "НеВзвешенный\n"; - final String isOriented = "Ориентированный\n"; - final String isNotOriented = "НеОриентированный\n"; - final String nL = "\n"; - final String end = "END"; + static const String dotToConnections = ": "; + static const String dotToLength = "|"; + static const String space = " "; + static const String hasLength = "Взвешенный"; + static const String hasNoLength = "НеВзвешенный"; + static const String isOriented = "Ориентированный"; + static const String isNotOriented = "НеОриентированный"; + static const String nL = "\n"; + static const String end = "END"; Separators(); } @@ -25,6 +25,7 @@ class Dot { String getName() => _name; bool hasConnection(int n) => _ln.containsKey(n); Map getL() => _ln; + int getLength(int x) { if (hasConnection(x)) { return _ln[x]!; @@ -146,15 +147,15 @@ class Graphs { //*********************Add************************ //*********Delete********* - bool delPath(int from, int to) { + String? delPath(int from, int to) { if (from <= 0 || from > _amount || to <= 0 && to > _amount) { - return false; + return "Can't find specified path"; } _dots[from - 1].delPath(to); if (!_oriented) { _dots[to - 1].delPath(from); } - return true; + return null; } String? delDot(int inn) { @@ -175,7 +176,11 @@ class Graphs { return null; } - void flushData() => _dots = []; + void flushData() { + _dots = []; + _amount = 0; + _nameTable = {}; + } //*********Delete********* //******Helper******* @@ -246,50 +251,115 @@ class Graphs { //*****Setters******* void setName(String name) => _name = name; + String? flipUseOrientation() { + if (_amount != 0) { + return "Can change use of orientation only in empty graph"; + } + _oriented = !_oriented; + return null; + } + + String? flipUseLength() { + if (_amount != 0) { + return "Can change use of length only in empty graph"; + } + _useLength = !_useLength; + return null; + } + String? replaceDataFromFile(String path) { - Separators sep = Separators(); File file = File(path); List lines = file.readAsLinesSync(); - _name = lines.removeAt(0); - _oriented = lines.removeAt(0) == sep.isOriented.trim(); - _useLength = lines.removeAt(0) == sep.hasLength.trim(); - _dots = []; + if (lines.length < 3) { + return "Not enough lines in file"; + } + String name = lines.removeAt(0); + bool oriented; + switch (lines.removeAt(0)) { + case Separators.isOriented: + oriented = true; + break; + case Separators.isNotOriented: + oriented = false; + break; + default: + return "Error on parsing \"IsOriented\""; + } + bool useLength; + switch (lines.removeAt(0).trim()) { + case Separators.hasLength: + useLength = true; + break; + case Separators.hasNoLength: + useLength = false; + break; + default: + return "Error on parsing \"HasLength\""; + } + List dots = []; for (var l in lines) { l = l.trimRight(); - if (l != sep.end) { - var spl = l.split(sep.space); + if (l != Separators.end) { + var spl = l.split(Separators.space); List dot = []; List len = []; String name = spl.removeAt(0); name = name.substring(0, name.length - 1); for (var splitted in spl) { if (splitted != "") { - var dt = splitted.split(sep.dotToLength); + var dt = splitted.split(Separators.dotToLength); if (dt.length == 2) { - dot.add(int.parse(dt[0])); - if (_useLength) { - len.add(int.parse(dt[1])); + int? parsed = int.tryParse(dt[0]); + if (parsed == null) { + return "Error while parsing file\nin parsing int in \"${dt[0]}\""; + } + dot.add(parsed); + if (useLength) { + parsed = int.tryParse(dt[1]); + if (parsed == null) { + return "Error while parsing file\nin parsing int in \"${dt[1]}\""; + } + len.add(parsed); } else { len.add(0); } } else if (dt.length == 1) { - dot.add(int.parse(splitted)); + int? parsed = int.tryParse(splitted); + if (parsed == null) { + return "Error while parsing file\nin parsing int in \"$splitted\""; + } + dot.add(parsed); len.add(0); } } } - _dots.add(Dot.fromTwoLists(name, dot, len)); + dots.add(Dot.fromTwoLists(name, dot, len)); } } + _name = name; + _oriented = oriented; + _useLength = useLength; + _dots = dots; _syncNum(); _syncNameTable(); if (!_oriented) _fullFix(); + return null; } //*****Setters******* //*****Getters******* - bool getDoubleSided() => _oriented; - bool getUseLength() => _useLength; + bool getDoubleSidedBool() => _oriented; + String getDoubleSidedStr() { + if (_oriented) return Separators.isOriented; + return Separators.isNotOriented; + } + + bool getUseLengthBool() => _useLength; + String getUseLengthStr() { + if (_useLength) return Separators.hasLength; + return Separators.hasNoLength; + } + List getDots() => _dots; String getName() => _name; String? getNameByNum(int n) => _nameTable[n]; @@ -349,31 +419,34 @@ class Graphs { } void printToFile(String name) { - Separators sep = Separators(); var file = File(name); file.writeAsStringSync("$_name\n"); if (_oriented) { - file.writeAsStringSync(sep.isOriented, mode: FileMode.append); + file.writeAsStringSync("${Separators.isOriented}\n", + mode: FileMode.append); } else { - file.writeAsStringSync(sep.isNotOriented, mode: FileMode.append); + file.writeAsStringSync("${Separators.isNotOriented}\n", + mode: FileMode.append); } if (_useLength) { - file.writeAsStringSync(sep.hasLength, mode: FileMode.append); + file.writeAsStringSync("${Separators.hasLength}\n", + mode: FileMode.append); } else { - file.writeAsStringSync(sep.hasNoLength, mode: FileMode.append); + file.writeAsStringSync("${Separators.hasNoLength}\n", + mode: FileMode.append); } for (int i = 0; i < _amount; i++) { - file.writeAsStringSync((i + 1).toString() + sep.dotToConnections, + file.writeAsStringSync((i + 1).toString() + Separators.dotToConnections, mode: FileMode.append); var d = _dots[i].getL(); for (var j in d.keys) { file.writeAsStringSync( - j.toString() + sep.dotToLength + d[j].toString() + " ", + j.toString() + Separators.dotToLength + d[j].toString() + " ", mode: FileMode.append); } - file.writeAsStringSync(sep.nL, mode: FileMode.append); + file.writeAsStringSync(Separators.nL, mode: FileMode.append); } - file.writeAsStringSync(sep.end, mode: FileMode.append); + file.writeAsStringSync(Separators.end, mode: FileMode.append); } //******Print****** @@ -399,22 +472,22 @@ class Graphs { if (!_oriented) _fullFix(); } Graphs.fromFile(String path) { - Separators sep = Separators(); - File file = File(path); + replaceDataFromFile(path); + /*File file = File(path); List lines = file.readAsLinesSync(); _name = lines.removeAt(0); - _oriented = lines.removeAt(0) == sep.isOriented.trim(); - _useLength = lines.removeAt(0) == sep.hasLength.trim(); + _oriented = lines.removeAt(0) == Separators.isOriented.trim(); + _useLength = lines.removeAt(0) == Separators.hasLength.trim(); _dots = []; for (var l in lines) { - if (l != sep.end) { - var spl = l.split(sep.space); + if (l != Separators.end) { + var spl = l.split(Separators.space); List dot = []; List len = []; String name = spl.removeAt(0); name = name.substring(0, name.length - 1); for (var splitted in spl) { - var dt = splitted.split(sep.dotToLength); + var dt = splitted.split(Separators.dotToLength); if (dt.length == 2) { dot.add(int.parse(dt[0])); if (_useLength) { @@ -432,7 +505,7 @@ class Graphs { } _syncNum(); _syncNameTable(); - if (!_oriented) _fullFix(); + if (!_oriented) _fullFix();*/ } //*******Constructor******** @@ -440,8 +513,8 @@ class Graphs { Graphs.clone(Graphs a) { _name = a.getName(); _dots = a.getDots(); - _oriented = a.getDoubleSided(); - _useLength = a.getUseLength(); + _oriented = a.getDoubleSidedBool(); + _useLength = a.getUseLengthBool(); _amount = _dots.length; _syncNameTable(); } diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 2694ab6..2afd2a3 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -1,6 +1,13 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + arrow_path: + dependency: "direct main" + description: + name: arrow_path + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" async: dependency: transitive description: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 3c41b83..b29645c 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -30,6 +30,7 @@ dependencies: flutter: sdk: flutter file_picker: ^4.2.0 + arrow_path: ^2.0.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.