add saving system with sqlite

This commit is contained in:
lukas-heiligenbrunner 2022-10-29 20:06:20 +02:00
parent 4c54265f89
commit 8ea4dc281e
9 changed files with 309 additions and 33 deletions

View File

@ -8,6 +8,8 @@ class Stroke {
double _miny = double.infinity; double _miny = double.infinity;
double _maxy = double.negativeInfinity; double _maxy = double.negativeInfinity;
final int id;
@override @override
String toString() { String toString() {
return 'Stroke{points: $points}'; return 'Stroke{points: $points}';
@ -17,18 +19,22 @@ class Stroke {
points.add(point); points.add(point);
// update bounding rect // update bounding rect
if (point.point.dx < _minx) _minx = point.point.dx; _updateBoundingRect(point);
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; 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() { Rect getBoundingRect() {
return Rect.fromPoints(Offset(_minx, _miny), Offset(_maxx, _maxy)); return Rect.fromPoints(Offset(_minx, _miny), Offset(_maxx, _maxy));
} }
Stroke.fromPoints(this.points); Stroke.fromPoints(this.points, this.id);
Stroke(); Stroke(this.id);
} }
class Point { class Point {

View File

@ -2,6 +2,7 @@ import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notes/savesystem/note_file.dart';
import 'my_painter.dart'; import 'my_painter.dart';
import 'paint_controller.dart'; import 'paint_controller.dart';
import 'screen_document_mapping.dart'; import 'screen_document_mapping.dart';
@ -11,7 +12,10 @@ import '../tool_bar.dart';
/// Handles input events and draws canvas element /// Handles input events and draws canvas element
class DrawingPage extends StatefulWidget { 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 @override
State<DrawingPage> createState() => _DrawingPageState(); State<DrawingPage> createState() => _DrawingPageState();
@ -22,18 +26,29 @@ class _DrawingPageState extends State<DrawingPage> {
double basezoom = 1.0; double basezoom = 1.0;
Offset offset = const Offset(.0, .0); Offset offset = const Offset(.0, .0);
PaintController controller = PaintController(); late PaintController controller;
late NoteFile noteFile = NoteFile(widget.filePath);
@override @override
void initState() { void initState() {
super.initState(); super.initState();
controller = PaintController(noteFile);
noteFile.init().then((value) => controller.loadStrokesFromFile());
// todo might be weird behaviour if used with short side // todo might be weird behaviour if used with short side
final screenWidth = final screenWidth =
(window.physicalSize.longestSide / window.devicePixelRatio); (window.physicalSize.longestSide / window.devicePixelRatio);
_calcNewPageOffset(const Offset(.0, .0), screenWidth - 45); _calcNewPageOffset(const Offset(.0, .0), screenWidth - 45);
} }
@override
void dispose() {
super.dispose();
noteFile.close();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(

View File

@ -2,7 +2,9 @@ import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:notes/savesystem/line_loader.dart';
import '../savesystem/note_file.dart';
import 'document_types.dart'; import 'document_types.dart';
import 'my_painter.dart'; import 'my_painter.dart';
@ -13,6 +15,10 @@ class PaintController extends ChangeNotifier {
List<Stroke> strokes = []; List<Stroke> strokes = [];
final bool _allowDrawWithFinger = false; final bool _allowDrawWithFinger = false;
PaintController(this.file);
final NoteFile file;
void changePen(Pen pen) { void changePen(Pen pen) {
activePen = pen; activePen = pen;
notifyListeners(); notifyListeners();
@ -40,7 +46,8 @@ class PaintController extends ChangeNotifier {
return thickness; return thickness;
} }
void pointDownEvent(Offset offset, PointerDeviceKind pointer, double tilt) { void pointDownEvent(
Offset offset, PointerDeviceKind pointer, double tilt) async {
if (_allowDrawWithFinger || pointer != PointerDeviceKind.touch) { if (_allowDrawWithFinger || pointer != PointerDeviceKind.touch) {
// todo line drawn on edge where line left page // todo line drawn on edge where line left page
if (!a4Page.contains(offset)) return; if (!a4Page.contains(offset)) return;
@ -48,8 +55,12 @@ class PaintController extends ChangeNotifier {
// todo handle other pens // todo handle other pens
if (activePen != Pen.pen) return; if (activePen != Pen.pen) return;
strokes int strokeid = strokes.isNotEmpty ? strokes.last.id + 1 : 0;
.add(Stroke.fromPoints([Point(offset, _calcTiltedWidth(3.0, tilt))])); strokes.add(Stroke.fromPoints(
[Point(offset, _calcTiltedWidth(3.0, tilt))],
strokes.isNotEmpty ? strokes.last.id + 1 : 0));
file.addStroke(strokeid);
notifyListeners(); notifyListeners();
} }
} }
@ -58,10 +69,11 @@ class PaintController extends ChangeNotifier {
if (activePen == Pen.eraser) return; if (activePen == Pen.eraser) return;
if (_allowDrawWithFinger || pointer != PointerDeviceKind.touch) { 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 // 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 lastStroke.points.add(lastStroke.points.last);
strokes.last.points.add(strokes.last.points.last); file.addPoint(lastStroke.id, lastStroke.points.last);
notifyListeners(); notifyListeners();
} }
} }
@ -83,6 +95,7 @@ class PaintController extends ChangeNotifier {
// check if eraser hit an point within its range // check if eraser hit an point within its range
for (final pt in stroke.points) { for (final pt in stroke.points) {
if (eraserrect.contains(pt.point)) { if (eraserrect.contains(pt.point)) {
file.removeStroke(stroke.id);
strokes.remove(stroke); strokes.remove(stroke);
notifyListeners(); notifyListeners();
return; return;
@ -101,7 +114,9 @@ class PaintController extends ChangeNotifier {
pts.last, pts[pts.length - 2], newWidth); 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; break;
case Pen.highlighter: case Pen.highlighter:
// TODO: Handle this case. // TODO: Handle this case.
@ -113,4 +128,9 @@ class PaintController extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
} }
Future<void> loadStrokesFromFile() async {
strokes = await file.loadStrokes();
notifyListeners();
}
} }

View File

@ -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:flutter/material.dart';
import 'package:notes/drawer_item.dart'; import 'package:notes/drawer_item.dart';
@ -110,7 +110,8 @@ class _CollapseDrawerState extends State<CollapseDrawer>
active: widget.activePage == View.shared, active: widget.activePage == View.shared,
onTap: () => widget.onPageChange(View.shared)), onTap: () => widget.onPageChange(View.shared)),
DrawerItem( DrawerItem(
dta: ItemData('Recycle bin', CupertinoIcons.trash), dta: ItemData(
'Recycle bin', FluentIcons.delete_20_filled),
collapsed: collapsed, collapsed: collapsed,
active: widget.activePage == View.recycle, active: widget.activePage == View.recycle,
onTap: () => widget.onPageChange(View.recycle)), onTap: () => widget.onPageChange(View.recycle)),

View File

@ -1,9 +1,15 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notes/collapse_drawer.dart'; import 'package:notes/collapse_drawer.dart';
import 'package:notes/all_notes_page.dart'; import 'package:notes/all_notes_page.dart';
import 'package:notes/canvas/drawing_page.dart'; import 'package:notes/canvas/drawing_page.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
void main() { void main() {
if (defaultTargetPlatform != TargetPlatform.android &&
defaultTargetPlatform != TargetPlatform.iOS) {
sqfliteFfiInit();
}
runApp(const MyApp()); runApp(const MyApp());
} }
@ -63,10 +69,13 @@ class _MyHomePageState extends State<MyHomePage> {
case View.folders: case View.folders:
return FloatingActionButton( return FloatingActionButton(
onPressed: () { 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( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const DrawingPage(), builder: (context) => DrawingPage(filePath: filename),
), ),
); );
}, },

View File

@ -0,0 +1,47 @@
import 'dart:ui';
import 'note_file.dart';
import '../canvas/document_types.dart';
extension LineLoading on NoteFile {
Future<List<Stroke>> loadStrokes() async {
final query = await db().query('points',
orderBy: 'strokeid',
columns: ['x', 'y', 'thickness', 'strokeid', 'id']);
int strokeid = -1;
List<Stroke> 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<void> addStroke(int newStrokeId) async {
await db().insert(
'strokes', {'color': 0xffffff, 'elevation': 0, 'id': newStrokeId});
}
Future<void> addPoint(int strokeid, Point p) async {
await db().insert('points', {
'x': p.point.dx,
'y': p.point.dy,
'thickness': p.thickness,
'strokeid': strokeid
});
}
Future<void> removeStroke(int id) async {
await db().delete('strokes', where: 'id = $id');
await db().delete('points', where: 'strokeid = $id');
}
}

View File

@ -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<void> 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<void> close() async {
// shrink the db file size
await _db.execute('VACUUM');
_db.close();
}
}

View File

@ -50,13 +50,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.3" 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: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -64,6 +57,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.1" 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: fluentui_system_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -102,6 +109,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.5" version: "0.0.5"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.5"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -151,6 +165,55 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" 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: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -158,6 +221,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.1.0" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -170,6 +254,34 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.0" 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: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -191,6 +303,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
synchronized:
dependency: transitive
description:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0+3"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -212,6 +331,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.2" 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: xml:
dependency: transitive dependency: transitive
description: description:
@ -221,4 +354,4 @@ packages:
version: "6.1.0" version: "6.1.0"
sdks: sdks:
dart: ">=2.18.2 <3.0.0" dart: ">=2.18.2 <3.0.0"
flutter: ">=3.0.0" flutter: ">=3.3.0"

View File

@ -1,9 +1,6 @@
name: notes name: notes
description: A new Flutter project. description: A new Flutter project.
publish_to: 'none'
# 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
# The following defines the version and build number for your application. # The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43 # A version number is three numbers separated by dots, like 1.2.43
@ -32,12 +29,12 @@ dependencies:
flutter: flutter:
sdk: 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 adwaita_icons: ^0.2.1
iconify_flutter: ^0.0.5 iconify_flutter: ^0.0.5
fluentui_system_icons: ^1.1.185 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: dev_dependencies:
flutter_test: flutter_test: