diff --git a/.github/workflows/publish_pub.yml b/.github/workflows/publish_pub.yml new file mode 100644 index 0000000..4944617 --- /dev/null +++ b/.github/workflows/publish_pub.yml @@ -0,0 +1,26 @@ +name: Publish to pub.dev +on: + push: + branches: + - master +jobs: + build: + runs-on: ubuntu-latest + container: + image: google/dart:latest + steps: + - uses: actions/checkout@v1 + - name: Setup credentials + run: | + mkdir -p ~/.pub-cache + cat < ~/.pub-cache/credentials.json + { + "accessToken":"${{ secrets.OAUTH_ACCESS_TOKEN }}", + "refreshToken":"${{ secrets.OAUTH_REFRESH_TOKEN }}", + "tokenEndpoint":"https://accounts.google.com/o/oauth2/token", + "scopes": [ "openid", "https://www.googleapis.com/auth/userinfo.email" ], + "expiration": 1584628470088 + } + EOF + - name: Publish package + run: pub publish -f diff --git a/.gitignore b/.gitignore index e9dc58d..f226b14 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ .pub/ build/ +example/ios/Flutter/flutter_export_environment.sh +example/.flutter-plugins-dependencies +android/.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f3b834e..e300dc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,43 +1,38 @@ -## 1.0.7 +# Changelog -- Updated project to meet changed requirements by Flutter -- Updated third-party dependencies -- Fixes issue #21, #23 +## 1.2.0 +* updated to work with Flutter 1.20+ -## 1.0.6 +## 1.1.6 +* Exposing `ZoomableWidget` from [flutter_advanced_networkimage](https://pub.dartlang.org/packages/flutter_advanced_networkimage) parameters (minScale, zoomSteps, maxScale,panLimit) -- Fixed issue in 1.0.5 on iOS that caused xcode build to fail +## 1.1.5 +* Page controller initial page setting fixed (making able to set initially loaded page) -## 1.0.5 +## 1.1.4 +* Proper android cache cleanup +* iOS build fix -- Fixed bug in iOS where due to caching of Flutter ImageProvider when switching documents old pages would persist -- Added more cases in example demo +## 1.1.3 +- Option to pass in controller `PDFViewer(document: document,controller: PageController())` that you can use to control the pageview rendering the PDF pages. -## 1.0.4 +## 1.1.2 +- Option to preload all pages in memory `PDFViewer(document: document,lazyLoad: false)` -- Refactored PDFdocument.getAllPages() method (Thanks for @SergioBernal8 for PR ) -- Added white background to page (iOS) -- Changed page resolution in iOS to 300 dpi -- Moved tooltips to a proper class +## 1.1.1 +- Option to disable swipe navigation `PDFViewer(document: document,scrollDirection: Aixs.vertical)` +- Option to change scroll axis to vertical or horizontal `PDFViewer(document: document,scrollDirection: Aixs.vertical)` -## 1.0.3 - -- Added white background to page (Android) -- Fixed cocoapods name -- User can now define tooltips and page selection dialog strings -- Tapping on page indicator now prompts to user to page selection dialog -- Added zoom to PDFPage - -## 1.0.2 - -- Bottom appbar no longer appears if PDF has only one page (Thanks for @markathomas for suggesting this ) -- Fixed opening PDF from assets not working. -- Example now opens file from assets +## 1.1.0 +- Removed rxdart dependency +- Upgraded to androidX +- Added support to optional header while loading document from url +- Auto hide picker for 1 page documents ## 1.0.1 - -- Updated readme.md and added screenshots to package +- Swipe control +- Zoom scale up to 5.0 ## 1.0.0 - -- Initial release \ No newline at end of file +- First upgraded version after fork +- Cool new customization features \ No newline at end of file diff --git a/README.md b/README.md index 9c8f116..e294b46 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ -# flutter_plugin_pdf_viewer +# advance_pdf_viewer + +A flutter plugin for handling PDF files. Works on both Android & iOS. Originally forked from (https://github.com/CrossPT/flutter_plugin_pdf_viewer). + +[![Pub Package](https://img.shields.io/pub/v/advance_pdf_viewer.svg?style=flat-square)](https://pub.dartlang.org/packages/advance_pdf_viewer) -A flutter plugin for handling PDF files. Works on both Android & iOS ## Installation -Add *flutter_plugin_pdf_viewer* as a dependency in [your pubspec.yaml file](https://flutter.io/platform-plugins/). +Add *advance_pdf_viewer* as a dependency in [your pubspec.yaml file](https://flutter.io/platform-plugins/). ``` -flutter_plugin_pdf_viewer: any +advance_pdf_viewer: any ``` --- @@ -57,7 +60,7 @@ Use the pre-built PDF Viewer This code produces the following view: -Demo Screenshot 1 +Demo Screenshot 1 --- @@ -66,7 +69,7 @@ This code produces the following view: * Allow password-protected files * ~~Refactor PDFDocument.getAll() method~~ * ~~Increase page resolution~~ -* Add swipe to change page +* ~~Add swipe to change page~~ --- diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs index e889521..5628deb 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,13 @@ +arguments=--scan +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0-20191016123526+0000)) connection.project.dir= eclipse.preferences.version=1 +gradle.user.home= +java.home=/Library/Java/JavaVirtualMachines/jdk-11.0.4.jdk/Contents/Home +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/android/build.gradle b/android/build.gradle index fe588d9..3efafc3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,7 +26,7 @@ android { defaultConfig { minSdkVersion 16 - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'InvalidPackage' diff --git a/android/gradle.properties b/android/gradle.properties index 8bd86f6..53ae0ae 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1 +1,3 @@ +android.enableJetifier=true +android.useAndroidX=true org.gradle.jvmargs=-Xmx1536M diff --git a/android/src/main/java/pt/tribeiro/flutter_plugin_pdf_viewer/FlutterPluginPdfViewerPlugin.java b/android/src/main/java/pt/tribeiro/flutter_plugin_pdf_viewer/FlutterPluginPdfViewerPlugin.java index ffe19f3..9c23001 100644 --- a/android/src/main/java/pt/tribeiro/flutter_plugin_pdf_viewer/FlutterPluginPdfViewerPlugin.java +++ b/android/src/main/java/pt/tribeiro/flutter_plugin_pdf_viewer/FlutterPluginPdfViewerPlugin.java @@ -10,6 +10,7 @@ import java.io.File; import java.io.FileOutputStream; +import java.io.FilenameFilter; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -30,6 +31,7 @@ public class FlutterPluginPdfViewerPlugin implements MethodCallHandler { private HandlerThread handlerThread; private Handler backgroundHandler; private final Object pluginLocker = new Object(); + private final String filePrefix = "FlutterPluginPdfViewer"; /** * Plugin registration. @@ -42,7 +44,7 @@ public static void registerWith(Registrar registrar) { @Override public void onMethodCall(final MethodCall call, final Result result) { - synchronized(pluginLocker){ + synchronized (pluginLocker) { if (backgroundHandler == null) { handlerThread = new HandlerThread("flutterPdfViewer", Process.THREAD_PRIORITY_BACKGROUND); handlerThread.start(); @@ -50,41 +52,42 @@ public void onMethodCall(final MethodCall call, final Result result) { } } final Handler mainThreadHandler = new Handler(); - backgroundHandler.post( + backgroundHandler.post(// new Runnable() { @Override public void run() { switch (call.method) { - case "getNumberOfPages": - final String numResult = getNumberOfPages((String) call.argument("filePath")); - mainThreadHandler.post(new Runnable(){ - @Override - public void run() { - result.success(numResult); - } - }); - break; - case "getPage": - final String pageResult = getPage((String) call.argument("filePath"), (int) call.argument("pageNumber")); - mainThreadHandler.post(new Runnable(){ - @Override - public void run() { - result.success(pageResult); - } - }); - break; - default: - result.notImplemented(); - break; + case "getNumberOfPages": + final String numResult = getNumberOfPages((String) call.argument("filePath")); + mainThreadHandler.post(new Runnable() { + @Override + public void run() { + result.success(numResult); + } + }); + break; + case "getPage": + final String pageResult = getPage((String) call.argument("filePath"), + (int) call.argument("pageNumber")); + mainThreadHandler.post(new Runnable() { + @Override + public void run() { + result.success(pageResult); + } + }); + break; + default: + result.notImplemented(); + break; } } - } - ); + }); } private String getNumberOfPages(String filePath) { File pdf = new File(filePath); try { + clearCacheDir(); PdfRenderer renderer = new PdfRenderer(ParcelFileDescriptor.open(pdf, ParcelFileDescriptor.MODE_READ_ONLY)); Bitmap bitmap; final int pageCount = renderer.getPageCount(); @@ -95,12 +98,39 @@ private String getNumberOfPages(String filePath) { return null; } - private String createTempPreview(Bitmap bmp, String name, int page) { + private boolean clearCacheDir() { + try { + File directory = instance.context().getCacheDir(); + FilenameFilter myFilter = new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.toLowerCase().startsWith(filePrefix.toLowerCase()); + } + }; + File[] files = directory.listFiles(myFilter); + // Log.d("Cache Files", "Size: " + files.length); + for (int i = 0; i < files.length; i++) { + // Log.d("Files", "FileName: " + files[i].getName()); + files[i].delete(); + } + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + private String getFileNameFromPath(String name) { String filePath = name.substring(name.lastIndexOf('/') + 1); - filePath = name.substring(name.lastIndexOf('.')); + filePath = filePath.substring(0, filePath.lastIndexOf('.')); + return String.format("%s-%s", filePrefix, filePath); + } + + private String createTempPreview(Bitmap bmp, String name, int page) { + String fileNameOnly = getFileNameFromPath(name); File file; try { - String fileName = String.format("%s-%d.png", filePath, page); + String fileName = String.format("%s-%d.png", fileNameOnly, page); file = File.createTempFile(fileName, null, instance.context().getCacheDir()); FileOutputStream out = new FileOutputStream(file); bmp.compress(Bitmap.CompressFormat.PNG, 100, out); @@ -113,7 +143,6 @@ private String createTempPreview(Bitmap bmp, String name, int page) { return file.getAbsolutePath(); } - private String getPage(String filePath, int pageNumber) { File pdf = new File(filePath); try { diff --git a/example/android/.settings/org.eclipse.buildship.core.prefs b/example/android/.settings/org.eclipse.buildship.core.prefs index 7338097..5628deb 100644 --- a/example/android/.settings/org.eclipse.buildship.core.prefs +++ b/example/android/.settings/org.eclipse.buildship.core.prefs @@ -1,11 +1,11 @@ -arguments= +arguments=--scan auto.sync=false build.scans.enabled=false -connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(5.4)) +connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0-20191016123526+0000)) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= -java.home= +java.home=/Library/Java/JavaVirtualMachines/jdk-11.0.4.jdk/Contents/Home jvm.arguments= offline.mode=false override.workspace.settings=true diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index ab29832..de2e629 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -38,7 +38,7 @@ android { targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -56,6 +56,6 @@ flutter { dependencies { testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' } diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index c2e1974..e0cb5b2 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ FlutterApplication and put your custom class here. --> runApp(MyApp()); @@ -70,9 +70,51 @@ class _MyAppState extends State { title: const Text('FlutterPluginPDFViewer'), ), body: Center( - child: _isLoading - ? Center(child: CircularProgressIndicator()) - : PDFViewer(document: document)), + child: _isLoading + ? Center(child: CircularProgressIndicator()) + : PDFViewer( + document: document, + zoomSteps: 1, + //uncomment below line to preload all pages + // lazyLoad: false, + // uncomment below line to scroll vertically + // scrollDirection: Axis.vertical, + + //uncomment below code to replace bottom navigation with your own + /* navigationBuilder: + (context, page, totalPages, jumpToPage, animateToPage) { + return ButtonBar( + alignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: Icon(Icons.first_page), + onPressed: () { + jumpToPage()(page: 0); + }, + ), + IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + animateToPage(page: page - 2); + }, + ), + IconButton( + icon: Icon(Icons.arrow_forward), + onPressed: () { + animateToPage(page: page); + }, + ), + IconButton( + icon: Icon(Icons.last_page), + onPressed: () { + jumpToPage(page: totalPages - 1); + }, + ), + ], + ); + }, */ + ), + ), ), ); } diff --git a/example/pubspec.lock b/example/pubspec.lock index 13d20a0..d22b4e9 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,34 +1,55 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + advance_pdf_viewer: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "1.2.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.4.2" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.14.13" convert: dependency: transitive description: @@ -42,7 +63,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.1.3" cupertino_icons: dependency: "direct main" description: @@ -50,39 +71,32 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_advanced_networkimage: + fake_async: dependency: transitive description: - name: flutter_advanced_networkimage + name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "0.5.0" - flutter_cache_manager: + version: "1.1.0" + file: dependency: transitive description: - name: flutter_cache_manager + name: file url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" - flutter_plugin_pdf_viewer: - dependency: "direct dev" - description: - path: ".." - relative: true - source: path - version: "1.0.7" - flutter_svg: + version: "5.2.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: dependency: transitive description: - name: flutter_svg + name: flutter_cache_manager url: "https://pub.dartlang.org" source: hosted - version: "0.13.1" + version: "1.4.1" flutter_test: dependency: "direct dev" description: flutter @@ -94,7 +108,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.0+1" + version: "0.12.2" http_parser: dependency: transitive description: @@ -108,84 +122,105 @@ packages: name: infinite_listview url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1+1" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.5" + version: "0.12.8" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.1.8" numberpicker: dependency: transitive description: name: numberpicker url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.2.1" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" - path_drawing: + version: "1.7.0" + path_provider: dependency: transitive description: - name: path_drawing + name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "0.4.1" - path_parsing: + version: "1.6.11" + path_provider_linux: dependency: transitive description: - name: path_parsing + name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" - path_provider: + version: "0.0.1+2" + path_provider_macos: dependency: transitive description: - name: path_provider + name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "0.0.4" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" - petitparser: + version: "1.9.0" + platform: dependency: transitive description: - name: petitparser + name: platform url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" - quiver: + version: "2.2.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + process: dependency: transitive description: - name: quiver + name: process url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "3.0.13" rxdart: dependency: transitive description: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.21.0" + version: "0.24.1" sky_engine: dependency: transitive description: flutter @@ -197,21 +232,28 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.7.0" sqflite: dependency: transitive description: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.3.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2+1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.9.5" stream_channel: dependency: transitive description: @@ -225,7 +267,7 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" synchronized: dependency: transitive description: @@ -246,21 +288,21 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.5" + version: "0.2.17" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.2.0" uuid: dependency: transitive description: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.2.0" vector_math: dependency: transitive description: @@ -268,13 +310,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - xml: + xdg_directories: dependency: transitive description: - name: xml + name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "3.3.1" + version: "0.1.0" sdks: - dart: ">=2.2.2 <3.0.0" - flutter: ">=1.6.0 <2.0.0" + dart: ">=2.9.0-14.0.dev <3.0.0" + flutter: ">=1.17.0 <2.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 643f674..db5eb5c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,5 +1,5 @@ -name: flutter_plugin_pdf_viewer_example -description: Demonstrates how to use the flutter_plugin_pdf_viewer plugin. +name: advance_pdf_viewer_example +description: Demonstrates how to use the advance_pdf_viewer plugin. publish_to: 'none' environment: @@ -12,13 +12,13 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 + advance_pdf_viewer: + path: ../ dev_dependencies: flutter_test: sdk: flutter - flutter_plugin_pdf_viewer: - path: ../ flutter: uses-material-design: true diff --git a/ios/Classes/FlutterPluginPdfViewerPlugin.m b/ios/Classes/FlutterPluginPdfViewerPlugin.m index 2345bad..5919a46 100644 --- a/ios/Classes/FlutterPluginPdfViewerPlugin.m +++ b/ios/Classes/FlutterPluginPdfViewerPlugin.m @@ -39,9 +39,9 @@ -(NSString *)getNumberOfPages:(NSString *)url } CGPDFDocumentRef SourcePDFDocument = CGPDFDocumentCreateWithURL((__bridge CFURLRef)sourcePDFUrl); size_t numberOfPages = CGPDFDocumentGetNumberOfPages(SourcePDFDocument); - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - NSString *filePathAndDirectory = [documentsDirectory stringByAppendingPathComponent:kDirectory]; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *temporaryDirectory = [paths objectAtIndex:0]; + NSString *filePathAndDirectory = [temporaryDirectory stringByAppendingPathComponent:kDirectory]; NSError *error; // Clear cache folder @@ -76,9 +76,9 @@ -(NSString*)getPage:(NSString *)url ofPage:(size_t)pageNumber } CGPDFDocumentRef SourcePDFDocument = CGPDFDocumentCreateWithURL((__bridge CFURLRef)sourcePDFUrl); size_t numberOfPages = CGPDFDocumentGetNumberOfPages(SourcePDFDocument); - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - NSString *filePathAndDirectory = [documentsDirectory stringByAppendingPathComponent:kDirectory]; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *temporaryDirectory = [paths objectAtIndex:0]; + NSString *filePathAndDirectory = [temporaryDirectory stringByAppendingPathComponent:kDirectory]; NSError *error; if (pageNumber > numberOfPages) { @@ -96,7 +96,7 @@ -(NSString*)getPage:(NSString *)url ofPage:(size_t)pageNumber CGPDFPageRef SourcePDFPage = CGPDFDocumentGetPage(SourcePDFDocument, pageNumber); CGPDFPageRetain(SourcePDFPage); NSString *relativeOutputFilePath = [NSString stringWithFormat:@"%@/%@-%d.png", kDirectory, kFileName, (int)pageNumber]; - NSString *imageFilePath = [documentsDirectory stringByAppendingPathComponent:relativeOutputFilePath]; + NSString *imageFilePath = [temporaryDirectory stringByAppendingPathComponent:relativeOutputFilePath]; CGRect sourceRect = CGPDFPageGetBoxRect(SourcePDFPage, kCGPDFMediaBox); UIGraphicsBeginPDFContextToFile(imageFilePath, sourceRect, nil); // Calculate resolution diff --git a/ios/flutter_plugin_pdf_viewer.podspec b/ios/advance_pdf_viewer.podspec similarity index 93% rename from ios/flutter_plugin_pdf_viewer.podspec rename to ios/advance_pdf_viewer.podspec index 4e1f42b..5786bab 100644 --- a/ios/flutter_plugin_pdf_viewer.podspec +++ b/ios/advance_pdf_viewer.podspec @@ -2,7 +2,7 @@ # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| - s.name = 'flutter_plugin_pdf_viewer' + s.name = 'advance_pdf_viewer' s.version = '1.0.5' s.summary = 'Allows you to generate PNG's of specified pages from a provided PDF file source.' s.description = <<-DESC diff --git a/lib/flutter_plugin_pdf_viewer.dart b/lib/advance_pdf_viewer.dart similarity index 60% rename from lib/flutter_plugin_pdf_viewer.dart rename to lib/advance_pdf_viewer.dart index 77a8af1..8461921 100644 --- a/lib/flutter_plugin_pdf_viewer.dart +++ b/lib/advance_pdf_viewer.dart @@ -1,6 +1,6 @@ -library flutter_plugin_pdf_viewer; +library advance_pdf_viewer; export 'src/document.dart' show PDFDocument; export 'src/page.dart' show PDFPage; -export 'src/viewer.dart' show PDFViewer; +export 'src/viewer.dart' show PDFViewer, IndicatorPosition; export 'src/tooltip.dart' show PDFViewerTooltip; diff --git a/lib/src/document.dart b/lib/src/document.dart index 4844297..8de8eed 100644 --- a/lib/src/document.dart +++ b/lib/src/document.dart @@ -3,9 +3,8 @@ import 'dart:io'; import 'package:flutter/services.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; -import 'package:flutter_plugin_pdf_viewer/src/page.dart'; +import 'package:advance_pdf_viewer/src/page.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:rxdart/rxdart.dart'; class PDFDocument { static const MethodChannel _channel = @@ -13,6 +12,8 @@ class PDFDocument { String _filePath; int count; + List _pages = []; + bool _preloaded = false; /// Load a PDF File from a given File /// @@ -33,9 +34,10 @@ class PDFDocument { /// Load a PDF File from a given URL. /// File is saved in cache /// - static Future fromURL(String url) async { + static Future fromURL(String url, + {Map headers}) async { // Download into cache - File f = await DefaultCacheManager().getSingleFile(url); + File f = await DefaultCacheManager().getSingleFile(url, headers: headers); PDFDocument document = PDFDocument(); document._filePath = f.path; try { @@ -78,20 +80,65 @@ class PDFDocument { /// Load specific page /// /// [page] defaults to `1` and must be equal or above it - Future get({int page = 1}) async { + Future get({ + int page = 1, + final Function(double) onZoomChanged, + final int zoomSteps, + final double minScale, + final double maxScale, + final double panLimit, + }) async { assert(page > 0); + if (_preloaded && _pages.isNotEmpty) return _pages[page - 1]; var data = await _channel .invokeMethod('getPage', {'filePath': _filePath, 'pageNumber': page}); - return new PDFPage(data, page); + return new PDFPage( + data, + page, + onZoomChanged: onZoomChanged, + zoomSteps: zoomSteps, + minScale: minScale, + maxScale: maxScale, + panLimit: panLimit, + ); + } + + Future preloadPages({ + final Function(double) onZoomChanged, + final int zoomSteps, + final double minScale, + final double maxScale, + final double panLimit, + }) async { + int countvar = 1; + await Future.forEach(List(count), (i) async { + final data = await _channel.invokeMethod( + 'getPage', {'filePath': _filePath, 'pageNumber': countvar}); + _pages.add(PDFPage( + data, + countvar, + onZoomChanged: onZoomChanged, + zoomSteps: zoomSteps, + minScale: minScale, + maxScale: maxScale, + panLimit: panLimit, + )); + countvar++; + }); + _preloaded = true; } // Stream all pages - Observable getAll() { + Stream getAll({final Function(double) onZoomChanged}) { return Future.forEach(List(count), (i) async { print(i); final data = await _channel .invokeMethod('getPage', {'filePath': _filePath, 'pageNumber': i}); - return new PDFPage(data, 1); + return new PDFPage( + data, + 1, + onZoomChanged: onZoomChanged, + ); }).asStream(); } } diff --git a/lib/src/page.dart b/lib/src/page.dart index e85e48d..a777c2a 100644 --- a/lib/src/page.dart +++ b/lib/src/page.dart @@ -1,13 +1,26 @@ import 'dart:io'; import 'dart:ui'; +import 'package:advance_pdf_viewer/src/zoomable_widget.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/painting.dart'; -import 'package:flutter_advanced_networkimage/zoomable.dart'; class PDFPage extends StatefulWidget { final String imgPath; final int num; - PDFPage(this.imgPath, this.num); + final Function(double) onZoomChanged; + final int zoomSteps; + final double minScale; + final double maxScale; + final double panLimit; + PDFPage( + this.imgPath, + this.num, { + this.onZoomChanged, + this.zoomSteps = 3, + this.minScale = 1.0, + this.maxScale = 5.0, + this.panLimit = 1.0, + }); @override _PDFPageState createState() => _PDFPageState(); @@ -43,10 +56,11 @@ class _PDFPageState extends State { return Container( decoration: null, child: ZoomableWidget( - zoomSteps: 3, - minScale: 1.0, - panLimit: 0.8, - maxScale: 3.0, + onZoomChanged: widget.onZoomChanged, + zoomSteps: widget.zoomSteps ?? 3, + minScale: widget.minScale ?? 1.0, + panLimit: widget.panLimit ?? 1.0, + maxScale: widget.maxScale ?? 5.0, child: Image(image: provider), )); } diff --git a/lib/src/viewer.dart b/lib/src/viewer.dart index 9326a22..7d5a5f7 100644 --- a/lib/src/viewer.dart +++ b/lib/src/viewer.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_plugin_pdf_viewer/flutter_plugin_pdf_viewer.dart'; +import 'package:advance_pdf_viewer/advance_pdf_viewer.dart'; import 'package:numberpicker/numberpicker.dart'; -import 'tooltip.dart'; enum IndicatorPosition { topLeft, topRight, bottomLeft, bottomRight } @@ -14,17 +13,42 @@ class PDFViewer extends StatefulWidget { final bool showPicker; final bool showNavigation; final PDFViewerTooltip tooltip; + final bool enableSwipeNavigation; + final Axis scrollDirection; + final bool lazyLoad; + final PageController controller; + final int zoomSteps; + final double minScale; + final double maxScale; + final double panLimit; + + final Widget Function( + BuildContext, + int pageNumber, + int totalPages, + void Function({int page}) jumpToPage, + void Function({int page}) animateToPage, + ) navigationBuilder; PDFViewer( {Key key, @required this.document, + this.scrollDirection, + this.lazyLoad = true, this.indicatorText = Colors.white, this.indicatorBackground = Colors.black54, this.showIndicator = true, this.showPicker = true, this.showNavigation = true, + this.enableSwipeNavigation = true, this.tooltip = const PDFViewerTooltip(), - this.indicatorPosition = IndicatorPosition.topRight}) + this.navigationBuilder, + this.controller, + this.indicatorPosition = IndicatorPosition.topRight, + this.zoomSteps, + this.minScale, + this.maxScale, + this.panLimit}) : super(key: key); _PDFViewerState createState() => _PDFViewerState(); @@ -32,47 +56,90 @@ class PDFViewer extends StatefulWidget { class _PDFViewerState extends State { bool _isLoading = true; - int _pageNumber = 1; - int _oldPage = 0; - PDFPage _page; - List _pages = List(); + int _pageNumber; + bool _swipeEnabled = true; + List _pages; + PageController _pageController; + final Duration animationDuration = Duration(milliseconds: 200); + final Curve animationCurve = Curves.easeIn; + + @override + void initState() { + super.initState(); + _pages = List(widget.document.count); + _pageController = widget.controller ?? PageController(); + _pageNumber = _pageController.initialPage + 1; + if (!widget.lazyLoad) + widget.document.preloadPages( + onZoomChanged: onZoomChanged, + zoomSteps: widget.zoomSteps, + minScale: widget.minScale, + maxScale: widget.maxScale, + panLimit: widget.panLimit, + ); + } @override void didChangeDependencies() { super.didChangeDependencies(); - _oldPage = 0; - _pageNumber = 1; + _pageNumber = _pageController.initialPage + 1; _isLoading = true; - _pages.clear(); + _pages = List(widget.document.count); + // _loadAllPages(); _loadPage(); } @override void didUpdateWidget(PDFViewer oldWidget) { super.didUpdateWidget(oldWidget); - _oldPage = 0; - _pageNumber = 1; - _isLoading = true; - _pages.clear(); - _loadPage(); } - _loadPage() async { - setState(() => _isLoading = true); - if (_oldPage == 0) { - _page = await widget.document.get(page: _pageNumber); - } else if (_oldPage != _pageNumber) { - _oldPage = _pageNumber; - _page = await widget.document.get(page: _pageNumber); + onZoomChanged(double scale) { + if (scale != 1.0) { + setState(() { + _swipeEnabled = false; + }); + } else { + setState(() { + _swipeEnabled = true; + }); } - if(this.mounted) { - setState(() => _isLoading = false); + } + + _loadPage() async { + if (_pages[_pageNumber - 1] != null) return; + setState(() { + _isLoading = true; + }); + final data = await widget.document.get( + page: _pageNumber, + onZoomChanged: onZoomChanged, + zoomSteps: widget.zoomSteps, + minScale: widget.minScale, + maxScale: widget.maxScale, + panLimit: widget.panLimit, + ); + _pages[_pageNumber - 1] = data; + if (mounted) { + setState(() { + _isLoading = false; + }); } } + _animateToPage({int page}) { + _pageController.animateToPage(page != null ? page : _pageNumber - 1, + duration: animationDuration, curve: animationCurve); + } + + _jumpToPage({int page}) { + _pageController.jumpToPage(page != null ? page : _pageNumber - 1); + } + Widget _drawIndicator() { Widget child = GestureDetector( - onTap: _pickPage, + onTap: + widget.showPicker && widget.document.count > 1 ? _pickPage : null, child: Container( padding: EdgeInsets.only(top: 4.0, left: 16.0, bottom: 4.0, right: 16.0), @@ -113,7 +180,7 @@ class _PDFViewerState extends State { }).then((int value) { if (value != null) { _pageNumber = value; - _loadPage(); + _jumpToPage(); } }); } @@ -123,13 +190,31 @@ class _PDFViewerState extends State { return Scaffold( body: Stack( children: [ - _isLoading ? Center(child: CircularProgressIndicator()) : _page, + PageView.builder( + physics: _swipeEnabled && widget.enableSwipeNavigation + ? null + : NeverScrollableScrollPhysics(), + onPageChanged: (page) { + setState(() { + _pageNumber = page + 1; + }); + _loadPage(); + }, + scrollDirection: widget.scrollDirection ?? Axis.horizontal, + controller: _pageController, + itemCount: _pages?.length ?? 0, + itemBuilder: (context, index) => _pages[index] == null + ? Center( + child: CircularProgressIndicator(), + ) + : _pages[index], + ), (widget.showIndicator && !_isLoading) ? _drawIndicator() : Container(), ], ), - floatingActionButton: widget.showPicker + floatingActionButton: widget.showPicker && widget.document.count > 1 ? FloatingActionButton( elevation: 4.0, tooltip: widget.tooltip.jump, @@ -141,62 +226,78 @@ class _PDFViewerState extends State { : null, floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, bottomNavigationBar: (widget.showNavigation || widget.document.count > 1) - ? BottomAppBar( - child: new Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: IconButton( - icon: Icon(Icons.first_page), - tooltip: widget.tooltip.first, - onPressed: () { - _pageNumber = 1; - _loadPage(); - }, - ), - ), - Expanded( - child: IconButton( - icon: Icon(Icons.chevron_left), - tooltip: widget.tooltip.previous, - onPressed: () { - _pageNumber--; - if (1 > _pageNumber) { - _pageNumber = 1; - } - _loadPage(); - }, - ), - ), - widget.showPicker - ? Expanded(child: Text('')) - : SizedBox(width: 1), - Expanded( - child: IconButton( - icon: Icon(Icons.chevron_right), - tooltip: widget.tooltip.next, - onPressed: () { - _pageNumber++; - if (widget.document.count < _pageNumber) { - _pageNumber = widget.document.count; - } - _loadPage(); - }, - ), + ? widget.navigationBuilder != null + ? widget.navigationBuilder( + context, + _pageNumber, + widget.document.count, + _jumpToPage, + _animateToPage, + ) + : BottomAppBar( + child: new Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: IconButton( + icon: Icon(Icons.first_page), + tooltip: widget.tooltip.first, + onPressed: _pageNumber == 1 + ? null + : () { + _pageNumber = 1; + _jumpToPage(); + }, + ), + ), + Expanded( + child: IconButton( + icon: Icon(Icons.chevron_left), + tooltip: widget.tooltip.previous, + onPressed: _pageNumber == 1 + ? null + : () { + _pageNumber--; + if (1 > _pageNumber) { + _pageNumber = 1; + } + _animateToPage(); + }, + ), + ), + widget.showPicker + ? Expanded(child: Text('')) + : SizedBox(width: 1), + Expanded( + child: IconButton( + icon: Icon(Icons.chevron_right), + tooltip: widget.tooltip.next, + onPressed: _pageNumber == widget.document.count + ? null + : () { + _pageNumber++; + if (widget.document.count < _pageNumber) { + _pageNumber = widget.document.count; + } + _animateToPage(); + }, + ), + ), + Expanded( + child: IconButton( + icon: Icon(Icons.last_page), + tooltip: widget.tooltip.last, + onPressed: _pageNumber == widget.document.count + ? null + : () { + _pageNumber = widget.document.count; + _jumpToPage(); + }, + ), + ), + ], ), - Expanded( - child: IconButton( - icon: Icon(Icons.last_page), - tooltip: widget.tooltip.last, - onPressed: () { - _pageNumber = widget.document.count; - _loadPage(); - }, - ), - ), - ], - ), - ) + ) : Container(), ); } diff --git a/lib/src/zoomable_widget.dart b/lib/src/zoomable_widget.dart new file mode 100644 index 0000000..ee13468 --- /dev/null +++ b/lib/src/zoomable_widget.dart @@ -0,0 +1,392 @@ +/// originally from https://github.com/mchome/flutter_advanced_networkimage +import 'dart:math'; + +import 'package:flutter/widgets.dart'; + +class ZoomableWidget extends StatefulWidget { + ZoomableWidget({ + Key key, + this.minScale: 0.7, + this.maxScale: 1.4, + this.initialScale: 1.0, + this.initialOffset: Offset.zero, + this.initialRotation: 0.0, + this.enableZoom: true, + this.panLimit: 1.0, + this.singleFingerPan: true, + this.multiFingersPan: true, + this.enableRotate: false, + this.child, + this.onTap, + this.zoomSteps: 0, + this.autoCenter: false, + this.bounceBackBoundary: true, + this.enableFling: true, + this.flingFactor: 1.0, + this.onZoomChanged, + this.resetDuration: const Duration(milliseconds: 250), + this.resetCurve: Curves.easeInOut, + }) : assert(minScale != null), + assert(maxScale != null), + assert(initialScale != null), + assert(initialOffset != null), + assert(initialRotation != null), + assert(enableZoom != null), + assert(panLimit != null), + assert(singleFingerPan != null), + assert(multiFingersPan != null), + assert(enableRotate != null), + assert(zoomSteps != null), + assert(autoCenter != null), + assert(bounceBackBoundary != null), + assert(enableFling != null), + assert(flingFactor != null); + + /// The minimum size for scaling. + final double minScale; + + /// The maximum size for scaling. + final double maxScale; + + /// The initial scale. + final double initialScale; + + /// The initial offset. + final Offset initialOffset; + + /// The initial rotation. + final double initialRotation; + + /// Allow zooming the child widget. + final bool enableZoom; + + /// Allow panning with one finger. + final bool singleFingerPan; + + /// Allow panning with more than one finger. + final bool multiFingersPan; + + /// Allow rotating the [image]. + final bool enableRotate; + + /// Create a boundary with the factor. + final double panLimit; + + /// The child widget that is display. + final Widget child; + + /// Tap callback for this widget. + final VoidCallback onTap; + + /// Allow users to zoom with double tap steps by steps. + final int zoomSteps; + + /// Center offset when zooming to minimum scale. + final bool autoCenter; + + /// Enable the bounce-back boundary. + final bool bounceBackBoundary; + + /// Allow fling child widget after panning. + final bool enableFling; + + /// Greater value create greater fling distance. + final double flingFactor; + + /// When the scale value changed, the callback will be invoked. + final ValueChanged onZoomChanged; + + /// The duration of reset animation. + final Duration resetDuration; + + /// The curve of reset animation. + final Curve resetCurve; + + @override + _ZoomableWidgetState createState() => _ZoomableWidgetState(); +} + +class _ZoomableWidgetState extends State { + final GlobalKey _key = GlobalKey(); + + double _zoom = 1.0; + double _previousZoom = 1.0; + Offset _previousPanOffset = Offset.zero; + Offset _pan = Offset.zero; + Offset _zoomOriginOffset = Offset.zero; + double _rotation = 0.0; + double _previousRotation = 0.0; + + Size _childSize = Size.zero; + Size _containerSize = Size.zero; + + Duration _duration = const Duration(milliseconds: 100); + Curve _curve = Curves.easeOut; + + @override + void initState() { + super.initState(); + _zoom = widget.initialScale; + _pan = widget.initialOffset; + _rotation = widget.initialRotation; + } + + void _onScaleStart(ScaleStartDetails details) { + if (_childSize == Size.zero) { + final RenderBox renderbox = _key.currentContext.findRenderObject(); + _childSize = renderbox.size; + } + setState(() { + _zoomOriginOffset = details.focalPoint; + _previousPanOffset = _pan; + _previousZoom = _zoom; + _previousRotation = _rotation; + }); + } + + void _onScaleUpdate(ScaleUpdateDetails details) { + Size boundarySize = _boundarySize; + + Size _marginSize = const Size(100.0, 100.0); + + _duration = const Duration(milliseconds: 50); + _curve = Curves.easeOut; + + setState(() { + if (widget.enableRotate) + _rotation = (_previousRotation + details.rotation).clamp(-pi, pi); + if (widget.enableZoom && details.scale != 1.0) { + _zoom = (_previousZoom * details.scale) + .clamp(widget.minScale, widget.maxScale); + if (widget.onZoomChanged != null) widget.onZoomChanged(_zoom); + } + }); + + if ((widget.singleFingerPan && details.scale == 1.0) || + (widget.multiFingersPan && details.scale != 1.0)) { + Offset _panRealOffset = (details.focalPoint - + _zoomOriginOffset + + _previousPanOffset * _previousZoom) / + _zoom; + + if (widget.panLimit == 0.0) { + _pan = _panRealOffset; + } else { + Offset _baseOffset = Offset( + _panRealOffset.dx + .clamp(-boundarySize.width / 2, boundarySize.width / 2), + _panRealOffset.dy + .clamp(-boundarySize.height / 2, boundarySize.height / 2), + ); + + Offset _marginOffset = _panRealOffset - _baseOffset; + double _widthFactor = sqrt(_marginOffset.dx.abs()) / _marginSize.width; + double _heightFactor = + sqrt(_marginOffset.dy.abs()) / _marginSize.height; + _marginOffset = Offset( + _marginOffset.dx * _widthFactor * 2, + _marginOffset.dy * _heightFactor * 2, + ); + _pan = _baseOffset + _marginOffset; + } + setState(() {}); + } + } + + void _onScaleEnd(ScaleEndDetails details) { + Size boundarySize = _boundarySize; + + _duration = widget.resetDuration; + _curve = widget.resetCurve; + + final Offset velocity = details.velocity.pixelsPerSecond; + final double magnitude = velocity.distance; + if (magnitude > 800.0 * _zoom && widget.enableFling) { + final Offset direction = velocity / magnitude; + final double distance = (Offset.zero & context.size).shortestSide; + final Offset endOffset = + _pan + direction * distance * widget.flingFactor * 0.5; + _pan = Offset( + endOffset.dx.clamp(-boundarySize.width / 2, boundarySize.width / 2), + endOffset.dy.clamp(-boundarySize.height / 2, boundarySize.height / 2), + ); + } + Offset _clampedOffset = Offset( + _pan.dx.clamp(-boundarySize.width / 2, boundarySize.width / 2), + _pan.dy.clamp(-boundarySize.height / 2, boundarySize.height / 2), + ); + if (_zoom == widget.minScale && widget.autoCenter) { + _clampedOffset = Offset.zero; + } + setState(() => _pan = _clampedOffset); + } + + Size get _boundarySize { + Size _boundarySize = Size( + (_containerSize.width == _childSize.width) + ? (_containerSize.width - _childSize.width / _zoom).abs() + : (_containerSize.width - _childSize.width * _zoom).abs() / _zoom, + (_containerSize.height == _childSize.height) + ? (_containerSize.height - _childSize.height / _zoom).abs() + : (_containerSize.height - _childSize.height * _zoom).abs() / + _zoom, + ) * + widget.panLimit; + + return _boundarySize; + } + + void _handleDoubleTap() { + double _stepLength = 0.0; + + _duration = widget.resetDuration; + _curve = widget.resetCurve; + + if (widget.zoomSteps > 0) + _stepLength = (widget.maxScale - 1.0) / widget.zoomSteps; + + double _tmpZoom = _zoom + _stepLength; + if (_tmpZoom > widget.maxScale || _stepLength == 0.0) _tmpZoom = 1.0; + + setState(() { + _zoom = _tmpZoom; + if (widget.onZoomChanged != null) widget.onZoomChanged(_zoom); + _pan = Offset.zero; + _rotation = 0.0; + _previousZoom = _tmpZoom; + if (_tmpZoom == 1.0) { + _zoomOriginOffset = Offset.zero; + _previousPanOffset = Offset.zero; + } + }); + } + + @override + Widget build(BuildContext context) { + if (widget.child == null) return SizedBox(); + + return CustomMultiChildLayout( + delegate: _ZoomableWidgetLayout(), + children: [ + LayoutId( + id: _ZoomableWidgetLayout.painter, + child: _ZoomableChild( + duration: _duration, + curve: _curve, + zoom: _zoom, + panOffset: _pan, + rotation: _rotation, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + _containerSize = + Size(constraints.maxWidth, constraints.maxHeight); + return Center( + child: Container(key: _key, child: widget.child), + ); + }, + ), + ), + ), + LayoutId( + id: _ZoomableWidgetLayout.gestureContainer, + child: GestureDetector( + child: Container(color: Color(0)), + onScaleStart: _onScaleStart, + onScaleUpdate: _onScaleUpdate, + onScaleEnd: widget.bounceBackBoundary ? _onScaleEnd : null, + onDoubleTap: _handleDoubleTap, + onTap: widget.onTap, + ), + ), + ], + ); + } +} + +class _ZoomableWidgetLayout extends MultiChildLayoutDelegate { + _ZoomableWidgetLayout(); + + static final String gestureContainer = 'gesturecontainer'; + static final String painter = 'painter'; + + @override + void performLayout(Size size) { + layoutChild(gestureContainer, + BoxConstraints.tightFor(width: size.width, height: size.height)); + positionChild(gestureContainer, Offset.zero); + layoutChild(painter, + BoxConstraints.tightFor(width: size.width, height: size.height)); + positionChild(painter, Offset.zero); + } + + @override + bool shouldRelayout(_ZoomableWidgetLayout oldDelegate) => false; +} + +class _ZoomableChild extends ImplicitlyAnimatedWidget { + const _ZoomableChild({ + Duration duration, + Curve curve = Curves.linear, + @required this.zoom, + @required this.panOffset, + @required this.rotation, + @required this.child, + }) : super(duration: duration, curve: curve); + + final double zoom; + final Offset panOffset; + final double rotation; + final Widget child; + + @override + ImplicitlyAnimatedWidgetState createState() => + _ZoomableChildState(); +} + +class _ZoomableChildState extends AnimatedWidgetBaseState<_ZoomableChild> { + DoubleTween _zoom; + OffsetTween _panOffset; + // OffsetTween _zoomOriginOffset; + DoubleTween _rotation; + + @override + void forEachTween(visitor) { + _zoom = visitor( + _zoom, widget.zoom, (dynamic value) => DoubleTween(begin: value)); + _panOffset = visitor(_panOffset, widget.panOffset, + (dynamic value) => OffsetTween(begin: value)); + _rotation = visitor(_rotation, widget.rotation, + (dynamic value) => DoubleTween(begin: value)); + } + + @override + Widget build(BuildContext context) { + return Transform( + alignment: Alignment.center, + origin: Offset(-_panOffset.evaluate(animation).dx, + -_panOffset.evaluate(animation).dy), + transform: Matrix4.identity() + ..translate(_panOffset.evaluate(animation).dx, + _panOffset.evaluate(animation).dy) + ..scale(_zoom.evaluate(animation), _zoom.evaluate(animation)), + child: Transform.rotate( + angle: _rotation.evaluate(animation), + child: widget.child, + ), + ); + } +} + +class DoubleTween extends Tween { + DoubleTween({double begin, double end}) : super(begin: begin, end: end); + + @override + double lerp(double t) => (begin + (end - begin) * t); +} + +class OffsetTween extends Tween { + OffsetTween({Offset begin, Offset end}) : super(begin: begin, end: end); + + @override + Offset lerp(double t) => (begin + (end - begin) * t); +} diff --git a/pubspec.lock b/pubspec.lock index 3a13e9c..177d678 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -8,6 +8,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" charcode: dependency: transitive description: @@ -15,13 +22,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.2" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.14.13" convert: dependency: transitive description: @@ -36,32 +50,25 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.6" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" - flutter_advanced_networkimage: - dependency: "direct main" - description: - name: flutter_advanced_networkimage - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.0" flutter_cache_manager: dependency: "direct main" description: name: flutter_cache_manager url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" - flutter_svg: - dependency: transitive - description: - name: flutter_svg - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.1" + version: "1.4.1" http: dependency: transitive description: @@ -82,49 +89,63 @@ packages: name: infinite_listview url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1+1" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.1.8" numberpicker: dependency: "direct main" description: name: numberpicker url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.2.1" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" - path_drawing: + version: "1.6.4" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.11" + path_provider_linux: dependency: transitive description: - name: path_drawing + name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.4.1" - path_parsing: + version: "0.0.1+2" + path_provider_macos: dependency: transitive description: - name: path_parsing + name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" - path_provider: - dependency: "direct main" + version: "0.0.4" + path_provider_platform_interface: + dependency: transitive description: - name: path_provider + name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.0.1" pedantic: dependency: transitive description: @@ -132,20 +153,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0+1" - petitparser: + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + process: dependency: transitive description: - name: petitparser + name: process url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "3.0.13" rxdart: - dependency: "direct main" + dependency: transitive description: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.21.0" + version: "0.24.1" sky_engine: dependency: transitive description: flutter @@ -164,7 +199,14 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.1.6+1" + version: "1.3.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2+1" string_scanner: dependency: transitive description: @@ -192,7 +234,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.2.0" uuid: dependency: transitive description: @@ -207,13 +249,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - xml: + xdg_directories: dependency: transitive description: - name: xml + name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "3.5.0" + version: "0.1.0" sdks: - dart: ">=2.4.0 <3.0.0" - flutter: ">=1.6.0 <2.0.0" + dart: ">=2.9.0-14.0.dev <3.0.0" + flutter: ">=1.17.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3b97efd..54f4162 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,22 +1,24 @@ -name: flutter_plugin_pdf_viewer +name: advance_pdf_viewer description: A flutter plugin for handling PDF files. Works on both Android & iOS -version: 1.0.7 -author: Tiago Ribeiro -homepage: https://github.com/CrossPT/flutter_plugin_pdf_viewer +version: 1.2.0 +homepage: https://github.com/lohanidamodar/pdf_viewer environment: sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.17.0 <2.0.0" dependencies: flutter: sdk: flutter - flutter_cache_manager: ^1.0.0 - path_provider: ^1.1.0 - rxdart: ^0.21.0 - numberpicker: ^1.0.0 - flutter_advanced_networkimage: ^0.5.0 + flutter_cache_manager: ^1.4.1 + path_provider: ^1.6.11 + numberpicker: ^1.2.1 flutter: plugin: - androidPackage: pt.tribeiro.flutter_plugin_pdf_viewer - pluginClass: FlutterPluginPdfViewerPlugin \ No newline at end of file + platforms: + android: + package: pt.tribeiro.flutter_plugin_pdf_viewer + pluginClass: FlutterPluginPdfViewerPlugin + ios: + pluginClass: FlutterPluginPdfViewerPlugin \ No newline at end of file