diff --git a/android/app/build.gradle b/android/app/build.gradle index 4ccf4fb..27ed771 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 33 ndkVersion flutter.ndkVersion compileOptions { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 341736a..eed3fcc 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,9 @@ + + diff --git a/lib/app.dart b/lib/app.dart index c8dad00..d39d257 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -20,9 +20,7 @@ class _AppState extends State { Widget build(BuildContext context) { return ChangeNotifierProvider( create: (ctx) { - final notifier = FileChangeNotifier(); - notifier.loadAllNotes(); - return notifier; + return FileChangeNotifier()..loadAllNotes(); }, child: Scaffold( floatingActionButton: _fab(), @@ -48,27 +46,28 @@ class _AppState extends State { switch (activePage) { case View.all: case View.folders: - return FloatingActionButton( - onPressed: () async { - final now = DateTime.now(); - final name = - 'note-${now.year}_${now.month}_${now.day}-${now.hour}_${now.minute}'; - final filename = '$name.dbnote'; + return Consumer( + builder: (ctx, notifier, child) => FloatingActionButton( + onPressed: () async { + final now = DateTime.now(); + final name = + 'note-${now.year}_${now.month}_${now.day}-${now.hour}_${now.minute}_${now.second}'; + final filename = '$name.dbnote'; - Navigator.push( - context, - MaterialPageRoute( - builder: (ctx) => DrawingPage( - filePath: filename, - name: name, + Navigator.push( + ctx, + MaterialPageRoute( + builder: (ctx) => DrawingPage( + filePath: filename, + name: name, + ), ), - ), - ).then((value) => - Provider.of(context, listen: false) - .loadAllNotes()); - }, - backgroundColor: const Color(0xff3f3f3f), - child: const Icon(Icons.edit_calendar_outlined, color: Colors.orange), + ).then((v) => notifier.loadAllNotes()); + }, + backgroundColor: const Color(0xff3f3f3f), + child: + const Icon(Icons.edit_calendar_outlined, color: Colors.orange), + ), ); default: return Container(); diff --git a/lib/canvas/drawing_page.dart b/lib/canvas/drawing_page.dart index e1765ba..5465646 100644 --- a/lib/canvas/drawing_page.dart +++ b/lib/canvas/drawing_page.dart @@ -5,6 +5,7 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import '../export/export_pdf.dart'; import '../savesystem/note_file.dart'; import '../widgets/icon_material_button.dart'; import '../widgets/tool_bar.dart'; @@ -72,6 +73,7 @@ class _DrawingPageState extends State { color: const Color.fromRGBO(255, 255, 255, .85), onPressed: () { // todo implement + exportPDF(controller.strokes, '${widget.name}.pdf'); }, ), IconMaterialButton( diff --git a/lib/canvas/paint_controller.dart b/lib/canvas/paint_controller.dart index d235e40..3496ac3 100644 --- a/lib/canvas/paint_controller.dart +++ b/lib/canvas/paint_controller.dart @@ -128,6 +128,7 @@ class PaintController extends ChangeNotifier { Point p = Point(offset, newWidth); strokes.last.addPoint(p); + // todo do a batch commit per stroke file.addPoint(strokes.last.id, p); break; case Pen.selector: diff --git a/lib/context/file_change_notifier.dart b/lib/context/file_change_notifier.dart index 3b21794..9ac292c 100644 --- a/lib/context/file_change_notifier.dart +++ b/lib/context/file_change_notifier.dart @@ -12,6 +12,10 @@ class FileChangeNotifier extends ChangeNotifier { Future> loadAllNotes() async { final path = await getSavePath(); + if (!(await path.exists())) { + await path.create(recursive: true); + } + final dta = await path .list() .where((fsentity) => fsentity.path.endsWith('.dbnote')) diff --git a/lib/export/export_pdf.dart b/lib/export/export_pdf.dart new file mode 100644 index 0000000..aeba97e --- /dev/null +++ b/lib/export/export_pdf.dart @@ -0,0 +1,58 @@ +import 'dart:io'; +import 'dart:ui'; + +import 'package:pdf/pdf.dart'; +import 'package:pdf/widgets.dart' as pw; + +import '../canvas/document_types.dart'; +import '../savesystem/path.dart'; + +const _a4width = 210 * PdfPageFormat.mm; +const _a4height = 297 * PdfPageFormat.mm; + +class _StrokePDFPaint extends pw.Widget { + List strokes; + + @override + void layout(pw.Context context, pw.BoxConstraints constraints, + {bool parentUsesSize = false}) { + box = + PdfRect.fromPoints(PdfPoint.zero, const PdfPoint(_a4width, _a4height)); + } + + @override + void paint(pw.Context context) { + super.paint(context); + + for (final stroke in strokes) { + context.canvas.setStrokeColor(PdfColor.fromInt(stroke.color.value)); + for (int i = 0; i < stroke.points.length - 1; i++) { + Offset pt1 = stroke.points[i].point * PdfPageFormat.mm; + pt1 = Offset(pt1.dx, _a4width - pt1.dy); + Offset pt2 = stroke.points[i + 1].point * PdfPageFormat.mm; + pt2 = Offset(pt2.dx, _a4width - pt2.dy); + + context.canvas.setLineWidth(stroke.points[i].thickness); + context.canvas.drawLine(pt1.dx, pt1.dy, pt2.dx, pt2.dy); + context.canvas.strokePath(); + } + } + } + + _StrokePDFPaint(this.strokes); +} + +void exportPDF(List strokes, String name) async { + final pdf = pw.Document(); + + const PdfPageFormat a4 = PdfPageFormat(_a4width, _a4height); + + pdf.addPage(pw.MultiPage( + pageFormat: a4, + build: (context) => [_StrokePDFPaint(strokes)], + )); + + final path = await getSavePath(); + final file = File('${path.path}${Platform.pathSeparator}$name'); + await file.writeAsBytes(await pdf.save(), flush: true); +} diff --git a/lib/main.dart b/lib/main.dart index d5d101a..29c9731 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,15 +1,26 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:sqflite/sqflite.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'app.dart'; -void main() { +void main() async { if (defaultTargetPlatform != TargetPlatform.android && defaultTargetPlatform != TargetPlatform.iOS) { sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; } + + WidgetsFlutterBinding.ensureInitialized(); + + Map statuses = + await [Permission.manageExternalStorage, Permission.storage].request(); + + if (statuses.containsValue(PermissionStatus.denied)) { + // todo some error handling + } + runApp(const MaterialApp(home: App())); } diff --git a/lib/pages/all_notes_page.dart b/lib/pages/all_notes_page.dart index 82739ea..5f001cd 100644 --- a/lib/pages/all_notes_page.dart +++ b/lib/pages/all_notes_page.dart @@ -34,7 +34,9 @@ class _AllNotesPageState extends State { IconMaterialButton( icon: const Icon(Icons.picture_as_pdf_outlined), color: const Color.fromRGBO(255, 255, 255, .85), - onPressed: () {}, + onPressed: () async { + // todo implement pdf import + }, ), IconMaterialButton( icon: const Icon(Icons.search), diff --git a/lib/savesystem/note_file.dart b/lib/savesystem/note_file.dart index e80fb62..20d223b 100644 --- a/lib/savesystem/note_file.dart +++ b/lib/savesystem/note_file.dart @@ -18,11 +18,18 @@ class NoteFile { final path = (await getSavePath()).path + Platform.pathSeparator + filepath; _db = await openDatabase( path, - 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)', - ); + onCreate: (db, version) async { + Batch batch = db.batch(); + + batch.execute('DROP TABLE IF EXISTS strokes;'); + batch.execute('DROP TABLE IF EXISTS points;'); + + batch.execute( + 'CREATE TABLE strokes(id integer primary key autoincrement, color INTEGER, elevation INTEGER)'); + batch.execute( + 'CREATE TABLE points(id integer primary key autoincrement, x INTEGER, y INTEGER, thickness REAL, strokeid INTEGER)'); + await batch.commit(); + return; }, // Set the version. This executes the onCreate function and provides a // path to perform database upgrades and downgrades. diff --git a/lib/savesystem/path.dart b/lib/savesystem/path.dart index af47043..7a38a17 100644 --- a/lib/savesystem/path.dart +++ b/lib/savesystem/path.dart @@ -7,8 +7,11 @@ Future getSavePath() async { Directory dbpath; if (defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS) { + final dir = + (await getExternalStorageDirectory())?.parent.parent.parent.parent ?? + (await getApplicationDocumentsDirectory()); dbpath = Directory( - '${(await getApplicationDocumentsDirectory()).path}${Platform.pathSeparator}notes'); + '${dir.path}${Platform.pathSeparator}Documents${Platform.pathSeparator}notes'); } else { dbpath = Directory( '${(await getApplicationDocumentsDirectory()).path}${Platform.pathSeparator}notes'); diff --git a/pubspec.lock b/pubspec.lock index 3384fbb..a4d1428 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -8,6 +8,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.1" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.2" async: dependency: transitive description: @@ -15,6 +22,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.9.0" + barcode: + dependency: transitive + description: + name: barcode + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.3" boolean_selector: dependency: transitive description: @@ -50,6 +64,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.3" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" fake_async: dependency: transitive description: @@ -109,6 +130,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.5" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.2" js: dependency: transitive description: @@ -221,6 +249,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + pdf: + dependency: "direct main" + description: + name: pdf + url: "https://pub.dartlang.org" + source: hosted + version: "3.8.4" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "10.2.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + url: "https://pub.dartlang.org" + source: hosted + version: "10.2.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.7" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.9.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" petitparser: dependency: transitive description: @@ -256,6 +326,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.0.4" + qr: + dependency: transitive + description: + name: qr + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" sky_engine: dependency: transitive description: flutter @@ -338,6 +415,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.12" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 65cf7ba..f818412 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,8 @@ dependencies: sqflite_common_ffi: ^2.2.0+1 path_provider: ^2.0.11 provider: ^6.0.4 + pdf: ^3.8.4 + permission_handler: ^10.2.0 dev_dependencies: flutter_test: