implement pdf export
handle correct storage management for android
This commit is contained in:
commit
65c851a416
@ -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 {
|
||||
|
@ -1,6 +1,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="eu.heili.notes">
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<application
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:label="notes"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
|
19
lib/app.dart
19
lib/app.dart
@ -20,9 +20,7 @@ class _AppState extends State<App> {
|
||||
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<App> {
|
||||
switch (activePage) {
|
||||
case View.all:
|
||||
case View.folders:
|
||||
return FloatingActionButton(
|
||||
return Consumer<FileChangeNotifier>(
|
||||
builder: (ctx, notifier, child) => FloatingActionButton(
|
||||
onPressed: () async {
|
||||
final now = DateTime.now();
|
||||
final name =
|
||||
'note-${now.year}_${now.month}_${now.day}-${now.hour}_${now.minute}';
|
||||
'note-${now.year}_${now.month}_${now.day}-${now.hour}_${now.minute}_${now.second}';
|
||||
final filename = '$name.dbnote';
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
ctx,
|
||||
MaterialPageRoute(
|
||||
builder: (ctx) => DrawingPage(
|
||||
filePath: filename,
|
||||
name: name,
|
||||
),
|
||||
),
|
||||
).then((value) =>
|
||||
Provider.of<FileChangeNotifier>(context, listen: false)
|
||||
.loadAllNotes());
|
||||
).then((v) => notifier.loadAllNotes());
|
||||
},
|
||||
backgroundColor: const Color(0xff3f3f3f),
|
||||
child: const Icon(Icons.edit_calendar_outlined, color: Colors.orange),
|
||||
child:
|
||||
const Icon(Icons.edit_calendar_outlined, color: Colors.orange),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return Container();
|
||||
|
@ -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<DrawingPage> {
|
||||
color: const Color.fromRGBO(255, 255, 255, .85),
|
||||
onPressed: () {
|
||||
// todo implement
|
||||
exportPDF(controller.strokes, '${widget.name}.pdf');
|
||||
},
|
||||
),
|
||||
IconMaterialButton(
|
||||
|
@ -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:
|
||||
|
@ -12,6 +12,10 @@ class FileChangeNotifier extends ChangeNotifier {
|
||||
|
||||
Future<List<NoteTileData>> 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'))
|
||||
|
58
lib/export/export_pdf.dart
Normal file
58
lib/export/export_pdf.dart
Normal file
@ -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<Stroke> 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<Stroke> 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);
|
||||
}
|
@ -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<Permission, PermissionStatus> statuses =
|
||||
await [Permission.manageExternalStorage, Permission.storage].request();
|
||||
|
||||
if (statuses.containsValue(PermissionStatus.denied)) {
|
||||
// todo some error handling
|
||||
}
|
||||
|
||||
runApp(const MaterialApp(home: App()));
|
||||
}
|
||||
|
@ -34,7 +34,9 @@ class _AllNotesPageState extends State<AllNotesPage> {
|
||||
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),
|
||||
|
@ -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.
|
||||
|
@ -7,8 +7,11 @@ Future<Directory> 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');
|
||||
|
84
pubspec.lock
84
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:
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user