diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 8cb6250..c87df22 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,21 +1,41 @@ PODS: - Flutter (1.0.0) + - fluttertoast (0.0.2): + - Flutter + - Toast + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS - pencil_kit (0.0.1): - Flutter + - Toast (4.0.0) DEPENDENCIES: - Flutter (from `Flutter`) + - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - pencil_kit (from `.symlinks/plugins/pencil_kit/ios`) +SPEC REPOS: + trunk: + - Toast + EXTERNAL SOURCES: Flutter: :path: Flutter + fluttertoast: + :path: ".symlinks/plugins/fluttertoast/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" pencil_kit: :path: ".symlinks/plugins/pencil_kit/ios" SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 pencil_kit: 97b56e7e011e7bddd920311849a2105c4b0b7923 + Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 diff --git a/example/lib/main.dart b/example/lib/main.dart index 838f416..cf10bad 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,5 +1,9 @@ +import 'dart:io'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:pencil_kit/pencil_kit.dart'; void main() { @@ -45,24 +49,91 @@ class _MyAppState extends State { ), ], ), - body: PencilKit( - onPencilKitViewCreated: (controller) => this.controller = controller, - alwaysBounceVertical: false, - alwaysBounceHorizontal: true, - isRulerActive: false, - drawingPolicy: PencilKitIos14DrawingPolicy.anyInput, - onToolPickerVisibilityChanged: (isVisible) { - if (kDebugMode) { - print('isToolPickerVisible $isVisible'); - } - }, - onRulerActiveChanged: (isRulerActive) { - if (kDebugMode) { - print('isRulerActive $isRulerActive'); - } - }, - backgroundColor: Colors.blue.withOpacity(0.1), - isOpaque: false, + body: Column( + children: [ + SingleChildScrollView( + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.save), + onPressed: () async { + final Directory documentDir = await getApplicationDocumentsDirectory(); + final String pathToSave = '${documentDir.path}/drawing'; + try { + await controller.save(uri: pathToSave); + Fluttertoast.showToast( + msg: "Save Success to [$pathToSave]", + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.CENTER, + timeInSecForIosWeb: 1, + backgroundColor: Colors.blueAccent, + textColor: Colors.white, + fontSize: 12.0); + } catch (e) { + Fluttertoast.showToast( + msg: "Save Failed to [$pathToSave]", + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.CENTER, + timeInSecForIosWeb: 1, + backgroundColor: Colors.redAccent, + textColor: Colors.white, + fontSize: 12.0); + } + }, + tooltip: "Save", + ), + IconButton( + icon: const Icon(Icons.download), + onPressed: () async { + final Directory documentDir = await getApplicationDocumentsDirectory(); + final String pathToLoad = '${documentDir.path}/drawing'; + try { + await controller.load(uri: pathToLoad); + Fluttertoast.showToast( + msg: "Load Success from [$pathToLoad]", + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.CENTER, + timeInSecForIosWeb: 1, + backgroundColor: Colors.blueAccent, + textColor: Colors.white, + fontSize: 12.0); + } catch (e) { + Fluttertoast.showToast( + msg: "Load Failed from [$pathToLoad]", + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.CENTER, + timeInSecForIosWeb: 1, + backgroundColor: Colors.redAccent, + textColor: Colors.white, + fontSize: 12.0); + } + }, + tooltip: "Load", + ), + ], + )), + Expanded( + child: PencilKit( + onPencilKitViewCreated: (controller) => this.controller = controller, + alwaysBounceVertical: false, + alwaysBounceHorizontal: true, + isRulerActive: false, + drawingPolicy: PencilKitIos14DrawingPolicy.anyInput, + onToolPickerVisibilityChanged: (isVisible) { + if (kDebugMode) { + print('isToolPickerVisible $isVisible'); + } + }, + onRulerActiveChanged: (isRulerActive) { + if (kDebugMode) { + print('isRulerActive $isRulerActive'); + } + }, + backgroundColor: Colors.blue.withOpacity(0.1), + isOpaque: false, + ), + ), + ], ), ), ); diff --git a/example/pubspec.lock b/example/pubspec.lock index f45a840..a1f2e6d 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" flutter: dependency: "direct main" description: flutter @@ -75,6 +83,19 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c" + url: "https://pub.dev" + source: hosted + version: "8.2.2" lints: dependency: transitive description: @@ -115,6 +136,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" pencil_kit: dependency: "direct main" description: @@ -122,6 +191,14 @@ packages: relative: true source: path version: "1.0.4" + platform: + dependency: transitive + description: + name: platform + sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + url: "https://pub.dev" + source: hosted + version: "3.1.2" plugin_platform_interface: dependency: transitive description: @@ -199,6 +276,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.4-beta" + win32: + dependency: transitive + description: + name: win32 + sha256: c97defd418eef4ec88c0d1652cdce84b9f7b63dd7198e266d06ac1710d527067 + url: "https://pub.dev" + source: hosted + version: "5.0.8" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + url: "https://pub.dev" + source: hosted + version: "1.0.3" sdks: dart: ">=3.1.0-185.0.dev <4.0.0" - flutter: ">=3.0.0" + flutter: ">=3.7.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index a19f985..1178b11 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -31,6 +31,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 + path_provider: ^2.1.1 + fluttertoast: ^8.2.2 dev_dependencies: flutter_test: diff --git a/ios/Classes/FLPencilKit.swift b/ios/Classes/FLPencilKit.swift index 501ef0f..24816f3 100644 --- a/ios/Classes/FLPencilKit.swift +++ b/ios/Classes/FLPencilKit.swift @@ -28,6 +28,10 @@ class FLPencilKitFactory: NSObject, FlutterPlatformViewFactory { } } +enum FLPencilKitError: Error { + case invalidArgument +} + class FLPencilKit: NSObject, FlutterPlatformView { private var _view: UIView private var methodChannel: FlutterMethodChannel @@ -58,21 +62,60 @@ class FLPencilKit: NSObject, FlutterPlatformView { switch call.method { case "clear": pencilKitView.clear() + result(nil) case "redo": pencilKitView.redo() + result(nil) case "undo": pencilKitView.undo() + result(nil) case "show": pencilKitView.show() + result(nil) case "hide": pencilKitView.hide() + result(nil) + case "save": + save(pencilKitView: pencilKitView, call: call, result: result) + case "load": + load(pencilKitView: pencilKitView, call: call, result: result) case "applyProperties": pencilKitView.applyProperties(properties: call.arguments as! [String: Any?]) + result(nil) default: break } } } + + @available(iOS 13, *) + private func save(pencilKitView: PencilKitView, call: FlutterMethodCall, result: FlutterResult) { + do { + let url = try parseUrlFromArgument(call.arguments) + try pencilKitView.save(url: url) + result(url.absoluteString) + } catch { + result(FlutterError(code: "NATIVE_ERROR", message: error.localizedDescription, details: nil)) + } + } + + @available(iOS 13, *) + private func load(pencilKitView: PencilKitView, call: FlutterMethodCall, result: FlutterResult) { + do { + let url = try parseUrlFromArgument(call.arguments) + try pencilKitView.load(url: url) + result(url.absoluteString) + } catch { + result(FlutterError(code: "NATIVE_ERROR", message: error.localizedDescription, details: nil)) + } + } + + private func parseUrlFromArgument(_ arguments: Any?) throws -> URL { + guard let arguments = arguments as? [String], arguments.count == 1 else { + throw FLPencilKitError.invalidArgument + } + return URL(fileURLWithPath: arguments[0]) + } } @available(iOS 13.0, *) @@ -157,6 +200,17 @@ private class PencilKitView: UIView { canvasView.resignFirstResponder() } + func save(url: URL) throws { + let data = canvasView.drawing.dataRepresentation() + try data.write(to: url) + } + + func load(url: URL) throws { + let data = try Data(contentsOf: url) + let drawing = try PKDrawing(data: data) + canvasView.drawing = drawing + } + func applyProperties(properties: [String: Any?]) { if let alwaysBounceVertical = properties["alwaysBounceVertical"] as? Bool { canvasView.alwaysBounceVertical = alwaysBounceVertical diff --git a/lib/src/pencil_kit.dart b/lib/src/pencil_kit.dart index a473dd2..b37df9a 100644 --- a/lib/src/pencil_kit.dart +++ b/lib/src/pencil_kit.dart @@ -8,8 +8,7 @@ import '../pencil_kit.dart'; /// Optional callback invoked when a web view is first created. [controller] is /// the [PencilKitController] for the created pencil kit view. -typedef PencilKitViewCreatedCallback = void Function( - PencilKitController controller); +typedef PencilKitViewCreatedCallback = void Function(PencilKitController controller); enum PencilKitIos14DrawingPolicy { /// if a `PKToolPicker` is visible, respect `UIPencilInteraction.prefersPencilOnlyDrawing`, @@ -138,8 +137,7 @@ class _PencilKitState extends State { viewType: 'plugins.mjstudio/flutter_pencil_kit', creationParamsCodec: const StandardMessageCodec(), onPlatformViewCreated: _onPencilKitPlatformViewCreated, - hitTestBehavior: - widget.hitTestBehavior ?? PlatformViewHitTestBehavior.opaque, + hitTestBehavior: widget.hitTestBehavior ?? PlatformViewHitTestBehavior.opaque, ); } else { return _buildUnAvailable(); @@ -149,8 +147,7 @@ class _PencilKitState extends State { class PencilKitController { PencilKitController._({required int viewId, required this.widget}) - : _channel = - MethodChannel('plugins.mjstudio/flutter_pencil_kit_$viewId') { + : _channel = MethodChannel('plugins.mjstudio/flutter_pencil_kit_$viewId') { _channel.setMethodCallHandler( (MethodCall call) async { if (call.method == 'toolPickerVisibilityDidChange') { @@ -183,13 +180,54 @@ class PencilKitController { }); } + /// Clear all drawing data Future clear() => _channel.invokeMethod('clear'); + /// Redo last action on drawing Future redo() => _channel.invokeMethod('redo'); + /// Undo last action on drawing Future undo() => _channel.invokeMethod('undo'); + /// Show pallete Future show() => _channel.invokeMethod('show'); + /// Hide pallete Future hide() => _channel.invokeMethod('hide'); + + /// Save drawing data into file system. The absolute uri of file in filesystem should be retrieved other library like 'path_provider'. + /// + /// Throws an [Error] if failed + /// + /// Example + /// + /// ```dart + /// final Directory documentDir = await getApplicationDocumentsDirectory(); + /// final String pathToSave = '${documentDir.path}/drawing'; + /// try { + /// await controller.save(uri: pathToSave); + /// // handle success + /// } catch (e) { + /// // handle error + /// } + /// ``` + Future save({required String uri}) => _channel.invokeMethod('save', [uri]); + + /// Load drawing data from file system. The absolute uri of file in filesystem should be retrieved other library like 'path_provider'. + /// + /// Throws an [Error] if failed + /// + /// Example + /// + /// ```dart + /// final Directory documentDir = await getApplicationDocumentsDirectory(); + /// final String pathToLoad = '${documentDir.path}/drawing'; + /// try { + /// await controller.load(uri: pathToLoad); + /// // handle success + /// } catch (e) { + /// // handle error + /// } + /// ``` + Future load({required String uri}) => _channel.invokeMethod('load', [uri]); }