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"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
compileSdkVersion 33
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="eu.heili.notes">
|
package="eu.heili.notes">
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<application
|
<application
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:label="notes"
|
android:label="notes"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
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) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return ChangeNotifierProvider(
|
||||||
create: (ctx) {
|
create: (ctx) {
|
||||||
final notifier = FileChangeNotifier();
|
return FileChangeNotifier()..loadAllNotes();
|
||||||
notifier.loadAllNotes();
|
|
||||||
return notifier;
|
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
floatingActionButton: _fab(),
|
floatingActionButton: _fab(),
|
||||||
@ -48,27 +46,28 @@ class _AppState extends State<App> {
|
|||||||
switch (activePage) {
|
switch (activePage) {
|
||||||
case View.all:
|
case View.all:
|
||||||
case View.folders:
|
case View.folders:
|
||||||
return FloatingActionButton(
|
return Consumer<FileChangeNotifier>(
|
||||||
|
builder: (ctx, notifier, child) => FloatingActionButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final name =
|
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';
|
final filename = '$name.dbnote';
|
||||||
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
ctx,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (ctx) => DrawingPage(
|
builder: (ctx) => DrawingPage(
|
||||||
filePath: filename,
|
filePath: filename,
|
||||||
name: name,
|
name: name,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).then((value) =>
|
).then((v) => notifier.loadAllNotes());
|
||||||
Provider.of<FileChangeNotifier>(context, listen: false)
|
|
||||||
.loadAllNotes());
|
|
||||||
},
|
},
|
||||||
backgroundColor: const Color(0xff3f3f3f),
|
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:
|
default:
|
||||||
return Container();
|
return Container();
|
||||||
|
@ -5,6 +5,7 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../export/export_pdf.dart';
|
||||||
import '../savesystem/note_file.dart';
|
import '../savesystem/note_file.dart';
|
||||||
import '../widgets/icon_material_button.dart';
|
import '../widgets/icon_material_button.dart';
|
||||||
import '../widgets/tool_bar.dart';
|
import '../widgets/tool_bar.dart';
|
||||||
@ -72,6 +73,7 @@ class _DrawingPageState extends State<DrawingPage> {
|
|||||||
color: const Color.fromRGBO(255, 255, 255, .85),
|
color: const Color.fromRGBO(255, 255, 255, .85),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// todo implement
|
// todo implement
|
||||||
|
exportPDF(controller.strokes, '${widget.name}.pdf');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconMaterialButton(
|
IconMaterialButton(
|
||||||
|
@ -128,6 +128,7 @@ class PaintController extends ChangeNotifier {
|
|||||||
|
|
||||||
Point p = Point(offset, newWidth);
|
Point p = Point(offset, newWidth);
|
||||||
strokes.last.addPoint(p);
|
strokes.last.addPoint(p);
|
||||||
|
// todo do a batch commit per stroke
|
||||||
file.addPoint(strokes.last.id, p);
|
file.addPoint(strokes.last.id, p);
|
||||||
break;
|
break;
|
||||||
case Pen.selector:
|
case Pen.selector:
|
||||||
|
@ -12,6 +12,10 @@ class FileChangeNotifier extends ChangeNotifier {
|
|||||||
|
|
||||||
Future<List<NoteTileData>> loadAllNotes() async {
|
Future<List<NoteTileData>> loadAllNotes() async {
|
||||||
final path = await getSavePath();
|
final path = await getSavePath();
|
||||||
|
if (!(await path.exists())) {
|
||||||
|
await path.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
final dta = await path
|
final dta = await path
|
||||||
.list()
|
.list()
|
||||||
.where((fsentity) => fsentity.path.endsWith('.dbnote'))
|
.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/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite/sqflite.dart';
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
|
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
if (defaultTargetPlatform != TargetPlatform.android &&
|
if (defaultTargetPlatform != TargetPlatform.android &&
|
||||||
defaultTargetPlatform != TargetPlatform.iOS) {
|
defaultTargetPlatform != TargetPlatform.iOS) {
|
||||||
sqfliteFfiInit();
|
sqfliteFfiInit();
|
||||||
databaseFactory = databaseFactoryFfi;
|
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()));
|
runApp(const MaterialApp(home: App()));
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,9 @@ class _AllNotesPageState extends State<AllNotesPage> {
|
|||||||
IconMaterialButton(
|
IconMaterialButton(
|
||||||
icon: const Icon(Icons.picture_as_pdf_outlined),
|
icon: const Icon(Icons.picture_as_pdf_outlined),
|
||||||
color: const Color.fromRGBO(255, 255, 255, .85),
|
color: const Color.fromRGBO(255, 255, 255, .85),
|
||||||
onPressed: () {},
|
onPressed: () async {
|
||||||
|
// todo implement pdf import
|
||||||
|
},
|
||||||
),
|
),
|
||||||
IconMaterialButton(
|
IconMaterialButton(
|
||||||
icon: const Icon(Icons.search),
|
icon: const Icon(Icons.search),
|
||||||
|
@ -18,11 +18,18 @@ class NoteFile {
|
|||||||
final path = (await getSavePath()).path + Platform.pathSeparator + filepath;
|
final path = (await getSavePath()).path + Platform.pathSeparator + filepath;
|
||||||
_db = await openDatabase(
|
_db = await openDatabase(
|
||||||
path,
|
path,
|
||||||
onCreate: (db, version) {
|
onCreate: (db, version) async {
|
||||||
return db.execute(
|
Batch batch = db.batch();
|
||||||
'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)',
|
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
|
// Set the version. This executes the onCreate function and provides a
|
||||||
// path to perform database upgrades and downgrades.
|
// path to perform database upgrades and downgrades.
|
||||||
|
@ -7,8 +7,11 @@ Future<Directory> getSavePath() async {
|
|||||||
Directory dbpath;
|
Directory dbpath;
|
||||||
if (defaultTargetPlatform == TargetPlatform.android ||
|
if (defaultTargetPlatform == TargetPlatform.android ||
|
||||||
defaultTargetPlatform == TargetPlatform.iOS) {
|
defaultTargetPlatform == TargetPlatform.iOS) {
|
||||||
|
final dir =
|
||||||
|
(await getExternalStorageDirectory())?.parent.parent.parent.parent ??
|
||||||
|
(await getApplicationDocumentsDirectory());
|
||||||
dbpath = Directory(
|
dbpath = Directory(
|
||||||
'${(await getApplicationDocumentsDirectory()).path}${Platform.pathSeparator}notes');
|
'${dir.path}${Platform.pathSeparator}Documents${Platform.pathSeparator}notes');
|
||||||
} else {
|
} else {
|
||||||
dbpath = Directory(
|
dbpath = Directory(
|
||||||
'${(await getApplicationDocumentsDirectory()).path}${Platform.pathSeparator}notes');
|
'${(await getApplicationDocumentsDirectory()).path}${Platform.pathSeparator}notes');
|
||||||
|
84
pubspec.lock
84
pubspec.lock
@ -8,6 +8,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1"
|
version: "0.2.1"
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.2"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -15,6 +22,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.9.0"
|
version: "2.9.0"
|
||||||
|
barcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: barcode
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.3"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -50,6 +64,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.3"
|
version: "0.0.3"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -109,6 +130,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.5"
|
version: "0.0.5"
|
||||||
|
image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -221,6 +249,48 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
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:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -256,6 +326,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.4"
|
version: "6.0.4"
|
||||||
|
qr:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: qr
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -338,6 +415,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.12"
|
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:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -36,6 +36,8 @@ dependencies:
|
|||||||
sqflite_common_ffi: ^2.2.0+1
|
sqflite_common_ffi: ^2.2.0+1
|
||||||
path_provider: ^2.0.11
|
path_provider: ^2.0.11
|
||||||
provider: ^6.0.4
|
provider: ^6.0.4
|
||||||
|
pdf: ^3.8.4
|
||||||
|
permission_handler: ^10.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user