diff --git a/CHANGELOG.md b/CHANGELOG.md index a8e1709..b4c4fa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [1.0.0] - 2021-03-10 +* Support null-safefty +* Use open source [native_pdf_view](https://pub.dev/packages/native_pdf_view) package instead of commercial package +* Improved video controls + ## [0.2.0] - 2021-02-02 * A `MediaProvider` can now also have a description that can be useful when sharing the media to other apps. * The hero animation now works also for text media. diff --git a/lib/enough_media.dart b/lib/enough_media.dart index 3b46062..4639c02 100644 --- a/lib/enough_media.dart +++ b/lib/enough_media.dart @@ -9,6 +9,6 @@ export 'preview/all_preview.dart'; export 'widget_registry.dart'; export 'package:flutter_sound_lite/flutter_sound.dart'; -export 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; +export 'package:native_pdf_view/native_pdf_view.dart'; export 'package:video_player/video_player.dart'; export 'package:chewie/chewie.dart'; diff --git a/lib/interactive/audio_interactive_media.dart b/lib/interactive/audio_interactive_media.dart index 8f64cfb..e3df948 100644 --- a/lib/interactive/audio_interactive_media.dart +++ b/lib/interactive/audio_interactive_media.dart @@ -7,8 +7,8 @@ import '../media_provider.dart'; class AudioInteractiveMedia extends StatefulWidget { final MediaProvider mediaProvider; AudioInteractiveMedia({ - Key key, - @required this.mediaProvider, + Key? key, + required this.mediaProvider, }) : super(key: key); @override @@ -16,14 +16,13 @@ class AudioInteractiveMedia extends StatefulWidget { } class _AudioInteractiveMediaState extends State { - String name; - FlutterSoundPlayer audioPlayer; - Track track; + late String name; + late Track track; @override void initState() { final provider = widget.mediaProvider; - name = provider.name ?? ''; + name = provider.name; if (provider is MemoryMediaProvider) { track = Track(dataBuffer: provider.data, trackTitle: name); } else if (provider is UrlMediaProvider) { diff --git a/lib/interactive/image_interactive_media.dart b/lib/interactive/image_interactive_media.dart index ef9b722..d09409f 100644 --- a/lib/interactive/image_interactive_media.dart +++ b/lib/interactive/image_interactive_media.dart @@ -5,7 +5,7 @@ import 'package:photo_view/photo_view.dart'; /// Displays zoomable images class ImageInteractiveMedia extends StatelessWidget { final MediaProvider mediaProvider; - const ImageInteractiveMedia({Key key, @required this.mediaProvider}) + const ImageInteractiveMedia({Key? key, required this.mediaProvider}) : super(key: key); @override diff --git a/lib/interactive/pdf_interactive_media.dart b/lib/interactive/pdf_interactive_media.dart index 8657f8d..0d880fd 100644 --- a/lib/interactive/pdf_interactive_media.dart +++ b/lib/interactive/pdf_interactive_media.dart @@ -1,12 +1,11 @@ import 'package:enough_media/media_provider.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; +import 'package:native_pdf_view/native_pdf_view.dart'; /// Displays PDFs class PdfInteractiveMedia extends StatefulWidget { final MediaProvider mediaProvider; - PdfInteractiveMedia({Key key, @required this.mediaProvider}) + PdfInteractiveMedia({Key? key, required this.mediaProvider}) : super(key: key); @override @@ -14,63 +13,56 @@ class PdfInteractiveMedia extends StatefulWidget { } class _PdfInteractiveMediaState extends State { - PdfViewerController pdfViewerController; - OverlayEntry overlayEntry; + late PdfController pdfController; @override void initState() { - pdfViewerController = PdfViewerController(); + pdfController = PdfController(document: initDocument(widget.mediaProvider)); super.initState(); } - @override - Widget build(BuildContext context) { - final provider = widget.mediaProvider; + Future initDocument(final MediaProvider provider) { if (provider is MemoryMediaProvider) { - return SfPdfViewer.memory( - provider.data, - controller: pdfViewerController, - onTextSelectionChanged: onTextSelectionChanged, - ); - } else if (provider is UrlMediaProvider) { - return SfPdfViewer.network( - provider.url, - controller: pdfViewerController, - onTextSelectionChanged: onTextSelectionChanged, - ); + return PdfDocument.openData(provider.data); + // } else if (provider is UrlMediaProvider) { + // provider.url + // ); } else if (provider is AssetMediaProvider) { - return SfPdfViewer.asset( - provider.assetName, - controller: pdfViewerController, - onTextSelectionChanged: onTextSelectionChanged, - ); + return PdfDocument.openAsset(provider.assetName); } else { throw StateError('Unsupported media provider $provider'); } } - void onTextSelectionChanged(PdfTextSelectionChangedDetails details) { - if (details.selectedText == null && overlayEntry != null) { - overlayEntry.remove(); - overlayEntry = null; - } else if (details.selectedText != null && overlayEntry == null) { - final OverlayState overlayState = Overlay.of(context); - overlayEntry = OverlayEntry( - builder: (context) => Positioned( - top: details.globalSelectedRegion.center.dy - 55, - left: details.globalSelectedRegion.bottomLeft.dx, - child: RaisedButton( - child: Text('Copy', style: TextStyle(fontSize: 17)), - onPressed: () { - Clipboard.setData(ClipboardData(text: details.selectedText)); - pdfViewerController.clearSelection(); - }, - color: Colors.white, - elevation: 10, - ), - ), - ); - overlayState.insert(overlayEntry); - } + @override + Widget build(BuildContext context) { + return PdfView( + controller: pdfController, + ); } + + // void onTextSelectionChanged(PdfTextSelectionChangedDetails details) { + // if (details.selectedText == null && overlayEntry != null) { + // overlayEntry.remove(); + // overlayEntry = null; + // } else if (details.selectedText != null && overlayEntry == null) { + // final OverlayState overlayState = Overlay.of(context); + // overlayEntry = OverlayEntry( + // builder: (context) => Positioned( + // top: details.globalSelectedRegion.center.dy - 55, + // left: details.globalSelectedRegion.bottomLeft.dx, + // child: RaisedButton( + // child: Text('Copy', style: TextStyle(fontSize: 17)), + // onPressed: () { + // Clipboard.setData(ClipboardData(text: details.selectedText)); + // pdfViewerController.clearSelection(); + // }, + // color: Colors.white, + // elevation: 10, + // ), + // ), + // ); + // overlayState.insert(overlayEntry); + // } + // } } diff --git a/lib/interactive/video_interactive_media.dart b/lib/interactive/video_interactive_media.dart index 3801269..4980eb1 100644 --- a/lib/interactive/video_interactive_media.dart +++ b/lib/interactive/video_interactive_media.dart @@ -8,16 +8,17 @@ import '../media_provider.dart'; class VideoInteractiveMedia extends StatefulWidget { final MediaProvider mediaProvider; - VideoInteractiveMedia({Key key, this.mediaProvider}) : super(key: key); + VideoInteractiveMedia({Key? key, required this.mediaProvider}) + : super(key: key); @override _VideoInteractiveMediaState createState() => _VideoInteractiveMediaState(); } class _VideoInteractiveMediaState extends State { - VideoPlayerController _videoController; - ChewieController _chewieController; - Future _loader; + late VideoPlayerController _videoController; + late ChewieController _chewieController; + late Future _loader; @override void initState() { @@ -26,10 +27,8 @@ class _VideoInteractiveMediaState extends State { } void dispose() { - if (_videoController != null) { - _videoController.dispose(); - _chewieController.dispose(); - } + _videoController.dispose(); + _chewieController.dispose(); super.dispose(); } @@ -81,12 +80,9 @@ class _VideoInteractiveMediaState extends State { case ConnectionState.waiting: case ConnectionState.active: return Center(child: CircularProgressIndicator()); - break; case ConnectionState.done: return buildVideo(); - break; } - return Text('loading video...'); }, ); } diff --git a/lib/interactive_media_widget.dart b/lib/interactive_media_widget.dart index d5224a1..49b2dca 100644 --- a/lib/interactive_media_widget.dart +++ b/lib/interactive_media_widget.dart @@ -6,10 +6,10 @@ import 'package:flutter/material.dart'; class InteractiveMediaWidget extends StatelessWidget { final MediaProvider mediaProvider; final bool useRegistry; - final Object heroTag; + final Object? heroTag; const InteractiveMediaWidget({ - Key key, - @required this.mediaProvider, + Key? key, + required this.mediaProvider, this.useRegistry = false, this.heroTag, }) : super(key: key); @@ -17,7 +17,7 @@ class InteractiveMediaWidget extends StatelessWidget { @override Widget build(BuildContext context) { if (heroTag != null) { - return Hero(tag: heroTag, child: _buildMedia()); + return Hero(tag: heroTag!, child: _buildMedia()); } return _buildMedia(); } @@ -27,7 +27,7 @@ class InteractiveMediaWidget extends StatelessWidget { if (useRegistry) { final registry = WidgetRegistry(); if (registry.resolveInteractive != null) { - final resolved = registry.resolveInteractive(provider); + final resolved = registry.resolveInteractive!(provider); if (resolved != null) { return resolved; } diff --git a/lib/media_provider.dart b/lib/media_provider.dart index 9f2155d..6ec7799 100644 --- a/lib/media_provider.dart +++ b/lib/media_provider.dart @@ -8,10 +8,10 @@ class MediaProvider { final String mediaType; /// The size in bytes, if known - final int size; + final int? size; /// Description, can be useful when sharing this media - final String description; + final String? description; /// Creates a new media provider with the given [name], [mediaType] like application/pdf, [size] in bytes and [description]. MediaProvider(this.name, this.mediaType, this.size, {this.description}); @@ -46,7 +46,7 @@ class UrlMediaProvider extends MediaProvider { /// Creates a new URL media provider with the given [name], [mediaType] and [url]. /// Optionally specify the [size] in bytes and the [description]. UrlMediaProvider(String name, String mediaType, this.url, - {int size, String description}) + {int? size, String? description}) : super(name, mediaType, size, description: description); } @@ -58,7 +58,7 @@ class MemoryMediaProvider extends MediaProvider { /// Creates a new meemory media provider with the given [name], [mediaType] and [data]. /// Optionally specify the [description]. MemoryMediaProvider(String name, String mediaType, this.data, - {String description}) + {String? description}) : super(name, mediaType, data.length, description: description); } @@ -70,13 +70,13 @@ class AssetMediaProvider extends MediaProvider { /// Creates a new asset media provider with the given [name], [mediaType] and [assetName]. /// Optionally specify the [size] in bytes and the [description]. AssetMediaProvider(String name, String mediaType, this.assetName, - {String description}) + {String? description}) : super(name, mediaType, null, description: description); } class TextMediaProvider extends MediaProvider { final String text; TextMediaProvider(String name, String mediaType, this.text, - {String description}) + {String? description}) : super(name, mediaType, null, description: description); } diff --git a/lib/preview/image_preview.dart b/lib/preview/image_preview.dart index 884a2d8..cae9c5f 100644 --- a/lib/preview/image_preview.dart +++ b/lib/preview/image_preview.dart @@ -5,11 +5,11 @@ import 'package:flutter/material.dart'; class ImagePreview extends StatelessWidget { final MediaProvider mediaProvider; final BoxFit fit; - final double width; - final double height; + final double? width; + final double? height; const ImagePreview( - {Key key, - @required this.mediaProvider, + {Key? key, + required this.mediaProvider, this.fit = BoxFit.cover, this.width, this.height}) diff --git a/lib/preview_media_widget.dart b/lib/preview_media_widget.dart index 5aeb0fa..52f0dcd 100644 --- a/lib/preview_media_widget.dart +++ b/lib/preview_media_widget.dart @@ -17,32 +17,32 @@ class PreviewMediaWidget extends StatefulWidget { final bool useRegistry; /// The width of the preview media - final double width; + final double? width; /// The height of the preview media - final double height; + final double? height; /// Optional delegate to switch to (typically fullscreen) interactive mode - final Future Function(InteractiveMediaWidget) showInteractiveDelegate; + final Future Function(InteractiveMediaWidget)? showInteractiveDelegate; /// Optional fallback widget that is shown when an unsupported media is encountered - final Widget fallbackWidget; + final Widget? fallbackWidget; /// Optional list of context menu entries. /// /// When the `showInteractiveDelegate` is defined, then the context menu is shown after a long press. /// When the `showInteractiveDelegate` is not defined, then the context menu is shown after a press. /// Note that additionally the onContextMenuSelected delegate needs to be specified - final List contextMenuEntries; + final List? contextMenuEntries; /// Handler for context menu - final Function(MediaProvider mediaProvider, dynamic entry) + final Function(MediaProvider mediaProvider, dynamic entry)? onContextMenuSelected; /// Creates a new media preview PreviewMediaWidget({ - Key key, - @required this.mediaProvider, + Key? key, + required this.mediaProvider, this.width, this.height, this.showInteractiveDelegate, @@ -75,7 +75,7 @@ class _PreviewMediaWidgetState extends State { Widget _buildInteractiveDelegateButton() { void Function() onPressed; - void Function() onLongPressed; + void Function()? onLongPressed; if (widget.showInteractiveDelegate != null) { onPressed = _showInteractiveDelegate; if (this.widget.contextMenuEntries?.isNotEmpty ?? false) { @@ -103,23 +103,23 @@ class _PreviewMediaWidgetState extends State { useRegistry: widget.useRegistry, heroTag: widget.mediaProvider, ); - widget.showInteractiveDelegate(interactive); + widget.showInteractiveDelegate!(interactive); } void _showContextMenu() async { final selectedValue = await showMenu( - items: widget.contextMenuEntries, + items: widget.contextMenuEntries!, context: context, position: _getPosition((widget.width ?? 60) / 2), ); if (selectedValue != null && widget.onContextMenuSelected != null) { - widget.onContextMenuSelected(widget.mediaProvider, selectedValue); + widget.onContextMenuSelected!(widget.mediaProvider, selectedValue); } } - RelativeRect _getPosition(double shift) { + RelativeRect _getPosition(double? shift) { shift ??= 60; - final rb = context.findRenderObject() as RenderBox; + final rb = context.findRenderObject() as RenderBox?; final offset = rb?.localToGlobal(Offset.zero); double left = (offset?.dx ?? 120) + shift; double top = (offset?.dy ?? 120) + shift; @@ -153,7 +153,7 @@ class _PreviewMediaWidgetState extends State { ); } if (widget.fallbackWidget != null) { - return widget.fallbackWidget; + return widget.fallbackWidget!; } return Container( width: widget.width, @@ -162,12 +162,12 @@ class _PreviewMediaWidgetState extends State { ); } - Widget resolveFromRegistry() { + Widget? resolveFromRegistry() { final registry = WidgetRegistry(); final provider = widget.mediaProvider; if (registry.resolvePreview != null) { final resolved = - registry.resolvePreview(provider, widget.width, widget.height); + registry.resolvePreview!(provider, widget.width, widget.height); if (resolved != null) { return resolved; } diff --git a/lib/widget_registry.dart b/lib/widget_registry.dart index 893fc7e..5c44ecf 100644 --- a/lib/widget_registry.dart +++ b/lib/widget_registry.dart @@ -3,17 +3,17 @@ import 'package:enough_media/media_provider.dart'; import 'package:flutter/widgets.dart'; class WidgetRegistry { - static WidgetRegistry _instance; + static WidgetRegistry _instance = WidgetRegistry._internal(); final previewRegistry = - {}; + {}; final interactiveRegistry = {}; - Widget Function(MediaProvider, double width, double height) resolvePreview; - Widget Function(MediaProvider) resolveInteractive; + Widget? Function(MediaProvider, double? width, double? height)? + resolvePreview; + Widget? Function(MediaProvider)? resolveInteractive; WidgetRegistry._internal(); factory WidgetRegistry() { - _instance ??= WidgetRegistry._internal(); return _instance; } } diff --git a/pubspec.yaml b/pubspec.yaml index 85ddb6a..15ebc2d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,21 +1,21 @@ name: enough_media description: Extensible collection of media viewers for preview and interactive experiences. -version: 0.2.0 +version: 1.0.0 homepage: https://github.com/enough-software/enough_media environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.17.0" + sdk: ">=2.12.0 <3.0.0" + flutter: ">=2.0.0" dependencies: flutter: sdk: flutter - syncfusion_flutter_pdfviewer: ^18.4.46-beta - photo_view: ^0.10.3 - flutter_sound_lite: ^7.7.0+1 - video_player: ^2.0.0 - chewie: ^1.0.0 - path_provider: ^1.6.27 + native_pdf_view: '>=4.0.0 <6.0.0' + photo_view: '>=0.11.1 <2.0.0' + flutter_sound_lite: '>=8.0.0+2 <10.0.0' + video_player: '>=2.0.0 <4.0.0' + chewie: '>=1.0.0 <3.0.0' + path_provider: '>=2.0.1 <4.0.0' dev_dependencies: