diff --git a/example/lib/main.dart b/example/lib/main.dart index b78acc1..500b976 100755 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -248,7 +248,7 @@ class _SynchronizedDisplayState extends State with SingleTi @override void dispose() { - controller.dispose(); + // controller.dispose(); super.dispose(); } diff --git a/lib/src/blurhash.dart b/lib/src/blurhash.dart index 9fdfc65..b9a1cf8 100644 --- a/lib/src/blurhash.dart +++ b/lib/src/blurhash.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:math'; -import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; @@ -78,11 +77,15 @@ Future blurHashDecodeImage({ if (kIsWeb) { // https://github.com/flutter/flutter/issues/45190 - final pixels = await blurHashDecode(blurHash: blurHash, width: width, height: height, punch: punch); + final pixels = await blurHashDecode( + blurHash: blurHash, width: width, height: height, punch: punch); completer.complete(_createBmp(pixels, width, height)); } else { - blurHashDecode(blurHash: blurHash, width: width, height: height, punch: punch).then((pixels) { - ui.decodeImageFromPixels(pixels, width, height, ui.PixelFormat.rgba8888, completer.complete); + blurHashDecode( + blurHash: blurHash, width: width, height: height, punch: punch) + .then((pixels) { + ui.decodeImageFromPixels( + pixels, width, height, ui.PixelFormat.rgba8888, completer.complete); }); } @@ -142,7 +145,8 @@ void _validateBlurHash(String blurHash) { final numX = (sizeFlag % 9) + 1; if (blurHash.length != 4 + 2 * numX * numY) { - throw Exception('blurhash length mismatch: length is ${blurHash.length} but ' + throw Exception( + 'blurhash length mismatch: length is ${blurHash.length} but ' 'it should be ${4 + 2 * numX * numY}'); } } @@ -198,14 +202,16 @@ bool validateBlurhash(String blurhash) { final x = (sizeFlag % 9) + 1; if (blurhash.length != 4 + 2 * x * y) { - debugPrint("blurhash length mismatch: length is ${blurhash.length} but it should be ${4 + 2 * x * y}"); + debugPrint( + "blurhash length mismatch: length is ${blurhash.length} but it should be ${4 + 2 * x * y}"); return false; } return true; } -const _digitCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#\$%*+,-.:;=?@[]^_{|}~"; +const _digitCharacters = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#\$%*+,-.:;=?@[]^_{|}~"; class Style { final String name; @@ -213,7 +219,8 @@ class Style { final ui.Color? stroke; final ui.Color? background; - const Style({required this.name, required this.colors, this.stroke, this.background}); + const Style( + {required this.name, required this.colors, this.stroke, this.background}); } const styles = { diff --git a/lib/src/blurhash_widget.dart b/lib/src/blurhash_widget.dart index aa26ba7..687ddea 100644 --- a/lib/src/blurhash_widget.dart +++ b/lib/src/blurhash_widget.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; @@ -11,6 +12,7 @@ class BlurHash extends StatefulWidget { const BlurHash({ required this.hash, Key? key, + this.imageKey, this.color = Colors.blueGrey, this.imageFit = BoxFit.fill, this.decodingWidth = _DEFAULT_SIZE, @@ -20,6 +22,7 @@ class BlurHash extends StatefulWidget { this.onDisplayed, this.onReady, this.onStarted, + this.onError, this.duration = const Duration(milliseconds: 1000), this.httpHeaders = const {}, this.curve = Curves.easeOut, @@ -40,6 +43,9 @@ class BlurHash extends StatefulWidget { /// Callback when image is downloaded final VoidCallback? onStarted; + /// Calback when image is error + final FutureOr? onError; + /// Hash to decode final String hash; @@ -68,6 +74,9 @@ class BlurHash extends StatefulWidget { /// Network image errorBuilder final ImageErrorWidgetBuilder? errorBuilder; + // Key to use for the widget + final Key? imageKey; + @override BlurHashState createState() => BlurHashState(); } @@ -89,16 +98,16 @@ class BlurHashState extends State { loading = false; } - @override - void didUpdateWidget(BlurHash oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.hash != oldWidget.hash || - widget.image != oldWidget.image || - widget.decodingWidth != oldWidget.decodingWidth || - widget.decodingHeight != oldWidget.decodingHeight) { - _init(); - } - } + // @override + // void didUpdateWidget(BlurHash oldWidget) { + // super.didUpdateWidget(oldWidget); + // if (widget.hash != oldWidget.hash || + // widget.image != oldWidget.image || + // widget.decodingWidth != oldWidget.decodingWidth || + // widget.decodingHeight != oldWidget.decodingHeight) { + // _init(); + // } + // } void _decodeImage() { _image = blurHashDecodeImage( @@ -106,8 +115,8 @@ class BlurHashState extends State { width: widget.decodingWidth, height: widget.decodingHeight, ); - _image.whenComplete(() => widget.onDecoded?.call()); + // .onError((error, stackTrace) {},); } @override @@ -117,125 +126,43 @@ class BlurHashState extends State { clipBehavior: Clip.none, children: [ Positioned.fill( - child: buildBlurHashBackground(), + // child: StreamBuilder( + // stream: Stream.fromFuture(_image), + child: FutureBuilder( + future: _image, + builder: (_, snap) => snap.data != null && + snap.data!.debugDisposed == false && + snap.data!.width > 0 && + snap.data!.height > 0 && + !loading && + snap.hasData && + !snap.hasError + ? Image( + image: UiImage( + snap.data!, + // key: ValueKey(_image.hashCode), + key: widget.key, + ), + fit: widget.imageFit, + errorBuilder: widget.errorBuilder, + ) + : Container(color: widget.color), + ), ), - if (widget.image != null) prepareDisplayedImage(widget.image!), ], ); - - Widget prepareDisplayedImage(String image) => Image.network( - image, - fit: widget.imageFit, - headers: widget.httpHeaders, - errorBuilder: widget.errorBuilder, - loadingBuilder: (context, img, loadingProgress) { - // Download started - if (loading == false) { - loading = true; - widget.onStarted?.call(); - } - - if (loadingProgress == null) { - // Image is now loaded, trigger the event - loaded = true; - widget.onReady?.call(); - return _DisplayImage( - child: img, - duration: widget.duration, - curve: widget.curve, - onCompleted: () => widget.onDisplayed?.call(), - ); - } - - if (loadingProgress.expectedTotalBytes == null || - (loadingProgress.expectedTotalBytes ?? 0) >= 0) { - // return Center( - // child: CircularProgressIndicator( - // value: loadingProgress.expectedTotalBytes != null - // ? loadingProgress.cumulativeBytesLoaded / - // loadingProgress.expectedTotalBytes! - // : null, - // ), - // ); - return const SizedBox(); - } else { - return const SizedBox(); - } - }, - ); - - /// Decode the blurhash then display the resulting Image - Widget buildBlurHashBackground() => FutureBuilder( - future: _image, - builder: (ctx, snap) => snap.hasData - ? Image( - image: UiImage(snap.data!), - fit: widget.imageFit, - errorBuilder: widget.errorBuilder, - ) - : Container(color: widget.color), - ); -} - -// Inner display details & controls -class _DisplayImage extends StatefulWidget { - final Widget child; - final Duration duration; - final Curve curve; - final VoidCallback onCompleted; - - const _DisplayImage({ - required this.child, - this.duration = const Duration(milliseconds: 800), - required this.curve, - required this.onCompleted, - Key? key, - }) : super(key: key); - - @override - _DisplayImageState createState() => _DisplayImageState(); -} - -class _DisplayImageState extends State<_DisplayImage> - with SingleTickerProviderStateMixin { - late Animation opacity; - late AnimationController controller; - - @override - Widget build(BuildContext context) => FadeTransition( - opacity: opacity, - child: widget.child, - ); - - @override - void initState() { - super.initState(); - controller = AnimationController(duration: widget.duration, vsync: this); - final curved = CurvedAnimation(parent: controller, curve: widget.curve); - opacity = Tween(begin: .0, end: 1.0).animate(curved); - controller.forward(); - - curved.addStatusListener(listener); - } - - void listener(AnimationStatus status) { - if (status == AnimationStatus.completed) widget.onCompleted.call(); - } - - @override - void dispose() { - controller.removeStatusListener(listener); - if (controller.isAnimating) controller.stop(); - controller.dispose(); - super.dispose(); - } } class UiImage extends ImageProvider { final ui.Image image; final double scale; + final Key? key; - const UiImage(this.image, {this.scale = 1.0}); + const UiImage( + this.image, { + this.key, + this.scale = 1.0, + }); @override Future obtainKey(ImageConfiguration configuration) =>