From 8ea4dc281efcb63aef41447e2f1cb7dd97179907 Mon Sep 17 00:00:00 2001 From: lukas-heiligenbrunner Date: Sat, 29 Oct 2022 20:06:20 +0200 Subject: [PATCH] add saving system with sqlite --- lib/canvas/document_types.dart | 18 ++-- lib/canvas/drawing_page.dart | 19 +++- lib/canvas/paint_controller.dart | 34 +++++-- lib/collapse_drawer.dart | 5 +- lib/main.dart | 11 ++- lib/savesystem/line_loader.dart | 47 ++++++++++ lib/savesystem/note_file.dart | 48 ++++++++++ pubspec.lock | 149 +++++++++++++++++++++++++++++-- pubspec.yaml | 11 +-- 9 files changed, 309 insertions(+), 33 deletions(-) create mode 100644 lib/savesystem/line_loader.dart create mode 100644 lib/savesystem/note_file.dart diff --git a/lib/canvas/document_types.dart b/lib/canvas/document_types.dart index 4034096..d89aafc 100644 --- a/lib/canvas/document_types.dart +++ b/lib/canvas/document_types.dart @@ -8,6 +8,8 @@ class Stroke { double _miny = double.infinity; double _maxy = double.negativeInfinity; + final int id; + @override String toString() { return 'Stroke{points: $points}'; @@ -17,18 +19,22 @@ class Stroke { points.add(point); // update bounding rect - if (point.point.dx < _minx) _minx = point.point.dx; - if (point.point.dx > _maxx) _maxx = point.point.dx; - if (point.point.dy < _miny) _miny = point.point.dy; - if (point.point.dy > _maxy) _maxy = point.point.dy; + _updateBoundingRect(point); + } + + void _updateBoundingRect(Point p) { + if (p.point.dx < _minx) _minx = p.point.dx; + if (p.point.dx > _maxx) _maxx = p.point.dx; + if (p.point.dy < _miny) _miny = p.point.dy; + if (p.point.dy > _maxy) _maxy = p.point.dy; } Rect getBoundingRect() { return Rect.fromPoints(Offset(_minx, _miny), Offset(_maxx, _maxy)); } - Stroke.fromPoints(this.points); - Stroke(); + Stroke.fromPoints(this.points, this.id); + Stroke(this.id); } class Point { diff --git a/lib/canvas/drawing_page.dart b/lib/canvas/drawing_page.dart index 5d22948..e6a59c9 100644 --- a/lib/canvas/drawing_page.dart +++ b/lib/canvas/drawing_page.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'dart:ui'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; +import 'package:notes/savesystem/note_file.dart'; import 'my_painter.dart'; import 'paint_controller.dart'; import 'screen_document_mapping.dart'; @@ -11,7 +12,10 @@ import '../tool_bar.dart'; /// Handles input events and draws canvas element class DrawingPage extends StatefulWidget { - const DrawingPage({Key? key}) : super(key: key); + // path to the .dbnote file + final String filePath; + + const DrawingPage({Key? key, required this.filePath}) : super(key: key); @override State createState() => _DrawingPageState(); @@ -22,18 +26,29 @@ class _DrawingPageState extends State { double basezoom = 1.0; Offset offset = const Offset(.0, .0); - PaintController controller = PaintController(); + late PaintController controller; + late NoteFile noteFile = NoteFile(widget.filePath); @override void initState() { super.initState(); + controller = PaintController(noteFile); + noteFile.init().then((value) => controller.loadStrokesFromFile()); + // todo might be weird behaviour if used with short side final screenWidth = (window.physicalSize.longestSide / window.devicePixelRatio); _calcNewPageOffset(const Offset(.0, .0), screenWidth - 45); } + @override + void dispose() { + super.dispose(); + + noteFile.close(); + } + @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/canvas/paint_controller.dart b/lib/canvas/paint_controller.dart index e466b38..27dcb85 100644 --- a/lib/canvas/paint_controller.dart +++ b/lib/canvas/paint_controller.dart @@ -2,7 +2,9 @@ import 'dart:math'; import 'dart:ui'; import 'package:flutter/foundation.dart'; +import 'package:notes/savesystem/line_loader.dart'; +import '../savesystem/note_file.dart'; import 'document_types.dart'; import 'my_painter.dart'; @@ -13,6 +15,10 @@ class PaintController extends ChangeNotifier { List strokes = []; final bool _allowDrawWithFinger = false; + PaintController(this.file); + + final NoteFile file; + void changePen(Pen pen) { activePen = pen; notifyListeners(); @@ -40,7 +46,8 @@ class PaintController extends ChangeNotifier { return thickness; } - void pointDownEvent(Offset offset, PointerDeviceKind pointer, double tilt) { + void pointDownEvent( + Offset offset, PointerDeviceKind pointer, double tilt) async { if (_allowDrawWithFinger || pointer != PointerDeviceKind.touch) { // todo line drawn on edge where line left page if (!a4Page.contains(offset)) return; @@ -48,8 +55,12 @@ class PaintController extends ChangeNotifier { // todo handle other pens if (activePen != Pen.pen) return; - strokes - .add(Stroke.fromPoints([Point(offset, _calcTiltedWidth(3.0, tilt))])); + int strokeid = strokes.isNotEmpty ? strokes.last.id + 1 : 0; + strokes.add(Stroke.fromPoints( + [Point(offset, _calcTiltedWidth(3.0, tilt))], + strokes.isNotEmpty ? strokes.last.id + 1 : 0)); + file.addStroke(strokeid); + notifyListeners(); } } @@ -58,10 +69,11 @@ class PaintController extends ChangeNotifier { if (activePen == Pen.eraser) return; if (_allowDrawWithFinger || pointer != PointerDeviceKind.touch) { - if (strokes.last.points.length <= 1) { + final lastStroke = strokes.last; + if (lastStroke.points.length <= 1) { // if the line consists only of one point (point) add endpoint as the same to allow drawing a line - // todo maybe solve this in custompainter in future - strokes.last.points.add(strokes.last.points.last); + lastStroke.points.add(lastStroke.points.last); + file.addPoint(lastStroke.id, lastStroke.points.last); notifyListeners(); } } @@ -83,6 +95,7 @@ class PaintController extends ChangeNotifier { // check if eraser hit an point within its range for (final pt in stroke.points) { if (eraserrect.contains(pt.point)) { + file.removeStroke(stroke.id); strokes.remove(stroke); notifyListeners(); return; @@ -101,7 +114,9 @@ class PaintController extends ChangeNotifier { pts.last, pts[pts.length - 2], newWidth); } - strokes.last.addPoint(Point(offset, newWidth)); + Point p = Point(offset, newWidth); + strokes.last.addPoint(p); + file.addPoint(strokes.last.id, p); break; case Pen.highlighter: // TODO: Handle this case. @@ -113,4 +128,9 @@ class PaintController extends ChangeNotifier { notifyListeners(); } } + + Future loadStrokesFromFile() async { + strokes = await file.loadStrokes(); + notifyListeners(); + } } diff --git a/lib/collapse_drawer.dart b/lib/collapse_drawer.dart index ecefbe8..d9eab0a 100644 --- a/lib/collapse_drawer.dart +++ b/lib/collapse_drawer.dart @@ -1,4 +1,4 @@ -import 'package:flutter/cupertino.dart'; +import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:notes/drawer_item.dart'; @@ -110,7 +110,8 @@ class _CollapseDrawerState extends State active: widget.activePage == View.shared, onTap: () => widget.onPageChange(View.shared)), DrawerItem( - dta: ItemData('Recycle bin', CupertinoIcons.trash), + dta: ItemData( + 'Recycle bin', FluentIcons.delete_20_filled), collapsed: collapsed, active: widget.activePage == View.recycle, onTap: () => widget.onPageChange(View.recycle)), diff --git a/lib/main.dart b/lib/main.dart index 4d470ce..0b6d052 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,15 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:notes/collapse_drawer.dart'; import 'package:notes/all_notes_page.dart'; import 'package:notes/canvas/drawing_page.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; void main() { + if (defaultTargetPlatform != TargetPlatform.android && + defaultTargetPlatform != TargetPlatform.iOS) { + sqfliteFfiInit(); + } runApp(const MyApp()); } @@ -63,10 +69,13 @@ class _MyHomePageState extends State { case View.folders: return FloatingActionButton( onPressed: () { + //final now = DateTime.now(); + //String filename = 'note-${now.year}_${now.month}_${now.day}-${now.hour}_${now.minute}.dbnote'; + String filename = 'test.dbnote'; Navigator.push( context, MaterialPageRoute( - builder: (context) => const DrawingPage(), + builder: (context) => DrawingPage(filePath: filename), ), ); }, diff --git a/lib/savesystem/line_loader.dart b/lib/savesystem/line_loader.dart new file mode 100644 index 0000000..2b3c041 --- /dev/null +++ b/lib/savesystem/line_loader.dart @@ -0,0 +1,47 @@ +import 'dart:ui'; + +import 'note_file.dart'; +import '../canvas/document_types.dart'; + +extension LineLoading on NoteFile { + Future> loadStrokes() async { + final query = await db().query('points', + orderBy: 'strokeid', + columns: ['x', 'y', 'thickness', 'strokeid', 'id']); + int strokeid = -1; + List strokes = []; + + for (final i in query) { + final int csid = i['strokeid'] as int; + if (csid != strokeid) { + strokeid = csid; + strokes.add(Stroke(strokeid)); + } + final Point p = Point( + Offset(i['x'] as double, i['y'] as double), i['thickness'] as double); + strokes.last.points.add(p); + } + + return strokes; + } + + // create new stroke in file and return strokeid + Future addStroke(int newStrokeId) async { + await db().insert( + 'strokes', {'color': 0xffffff, 'elevation': 0, 'id': newStrokeId}); + } + + Future addPoint(int strokeid, Point p) async { + await db().insert('points', { + 'x': p.point.dx, + 'y': p.point.dy, + 'thickness': p.thickness, + 'strokeid': strokeid + }); + } + + Future removeStroke(int id) async { + await db().delete('strokes', where: 'id = $id'); + await db().delete('points', where: 'strokeid = $id'); + } +} diff --git a/lib/savesystem/note_file.dart b/lib/savesystem/note_file.dart new file mode 100644 index 0000000..bc50219 --- /dev/null +++ b/lib/savesystem/note_file.dart @@ -0,0 +1,48 @@ +import 'package:flutter/foundation.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; + +class NoteFile { + late Database _db; + String filename; + + Database db() { + return _db; + } + + NoteFile(this.filename); + + Future init() async { + String dbpath = filename; + if (defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS) { + dbpath = '${await getDatabasesPath()}/$filename'; + } else { + // Change the default factory + databaseFactory = databaseFactoryFfi; + } + + _db = await openDatabase( + dbpath, + onCreate: (db, version) { + return db.execute( + 'CREATE TABLE strokes(id integer primary key autoincrement, color INTEGER, elevation INTEGER);' + 'CREATE TABLE points(id integer primary key autoincrement, x INTEGER, y INTEGER, thickness REAL, strokeid INTEGER)', + ); + }, + // Set the version. This executes the onCreate function and provides a + // path to perform database upgrades and downgrades. + version: 1, + ); + } + + void delete() { + // todo remove db file + } + + Future close() async { + // shrink the db file size + await _db.execute('VACUUM'); + _db.close(); + } +} diff --git a/pubspec.lock b/pubspec.lock index 6fe185f..efe9770 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -50,13 +50,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.3" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" fake_async: dependency: transitive description: @@ -64,6 +57,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.4" fluentui_system_icons: dependency: "direct main" description: @@ -102,6 +109,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.5" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.5" lints: dependency: transitive description: @@ -151,6 +165,55 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.20" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" petitparser: dependency: transitive description: @@ -158,6 +221,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.1.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" sky_engine: dependency: transitive description: flutter @@ -170,6 +254,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.9.0" + sqflite: + dependency: "direct main" + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0+2" + sqflite_common_ffi: + dependency: "direct main" + description: + name: sqflite_common_ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0+1" + sqlite3: + dependency: transitive + description: + name: sqlite3 + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.1" stack_trace: dependency: transitive description: @@ -191,6 +303,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0+3" term_glyph: dependency: transitive description: @@ -212,6 +331,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+2" xml: dependency: transitive description: @@ -221,4 +354,4 @@ packages: version: "6.1.0" sdks: dart: ">=2.18.2 <3.0.0" - flutter: ">=3.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2a6f796..1865381 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,6 @@ name: notes description: A new Flutter project. - -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -32,12 +29,12 @@ dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 adwaita_icons: ^0.2.1 iconify_flutter: ^0.0.5 fluentui_system_icons: ^1.1.185 + sqflite: ^2.2.0+2 + sqflite_common_ffi: ^2.2.0+1 + path_provider: ^2.0.11 dev_dependencies: flutter_test: