diff --git a/lib/widgets/album/thumbnail.dart b/lib/widgets/album/thumbnail.dart index aa286816a..043434a51 100644 --- a/lib/widgets/album/thumbnail.dart +++ b/lib/widgets/album/thumbnail.dart @@ -7,7 +7,7 @@ import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/material.dart'; import 'package:transparent_image/transparent_image.dart'; -class Thumbnail extends StatefulWidget { +class Thumbnail extends StatelessWidget { final ImageEntry entry; final double extent; final double devicePixelRatio; @@ -20,10 +20,71 @@ class Thumbnail extends StatefulWidget { }) : super(key: key); @override - ThumbnailState createState() => ThumbnailState(); + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.grey.shade700, + width: 0.5, + ), + ), + child: Stack( + alignment: AlignmentDirectional.bottomStart, + children: [ + ThumbnailImage( + entry: entry, + extent: extent, + devicePixelRatio: devicePixelRatio, + ), + _buildOverlayIcons(), + ], + ), + ); + } + + Widget _buildOverlayIcons() { + final fontSize = min(14.0, (extent / 8).roundToDouble()); + final iconSize = fontSize * 2; + return DefaultTextStyle( + style: TextStyle( + color: Colors.grey[200], + fontSize: fontSize, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (entry.hasGps) GpsIcon(iconSize: iconSize), + if (entry.isGif) + GifIcon(iconSize: iconSize) + else if (entry.isVideo) + VideoIcon( + entry: entry, + iconSize: iconSize, + ), + ], + ), + ); + } } -class ThumbnailState extends State { +class ThumbnailImage extends StatefulWidget { + final ImageEntry entry; + final double extent; + final double devicePixelRatio; + + const ThumbnailImage({ + Key key, + @required this.entry, + @required this.extent, + @required this.devicePixelRatio, + }) : super(key: key); + + @override + State createState() => ThumbnailImageState(); +} + +class ThumbnailImageState extends State { Future _byteLoader; Listenable _entryChangeNotifier; @@ -40,7 +101,7 @@ class ThumbnailState extends State { } @override - void didUpdateWidget(Thumbnail oldWidget) { + void didUpdateWidget(ThumbnailImage oldWidget) { super.didUpdateWidget(oldWidget); if (widget.extent == oldWidget.extent && uri == oldWidget.entry.uri && widget.entry.width == oldWidget.entry.width && widget.entry.height == oldWidget.entry.height && widget.entry.orientationDegrees == oldWidget.entry.orientationDegrees) return; initByteLoader(); @@ -61,84 +122,24 @@ class ThumbnailState extends State { @override Widget build(BuildContext context) { - final fontSize = min(14.0, (widget.extent / 8).roundToDouble()); - final iconSize = fontSize * 2; - return DefaultTextStyle( - style: TextStyle( - color: Colors.grey[200], - fontSize: fontSize, - ), - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey.shade700, - width: 0.5, - ), - ), - child: FutureBuilder( - future: _byteLoader, - builder: (futureContext, AsyncSnapshot snapshot) { - final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage; - return ThumbnailImage(entry: entry, bytes: bytes, iconSize: iconSize); - }), - ), - ); - } -} - -class ThumbnailImage extends StatelessWidget { - final Uint8List bytes; - final ImageEntry entry; - final double iconSize; - - const ThumbnailImage({ - Key key, - @required this.bytes, - @required this.entry, - @required this.iconSize, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Stack( - alignment: AlignmentDirectional.bottomStart, - children: [ - Hero( - tag: entry.uri, - child: LayoutBuilder(builder: (context, constraints) { - // during hero animation back from a fullscreen image, - // the image covers the whole screen (because of the 'fit' prop and the full screen hero constraints) - // so we wrap the image to apply better constraints - final dim = min(constraints.maxWidth, constraints.maxHeight); - return Container( - alignment: Alignment.center, - constraints: BoxConstraints.tight(Size(dim, dim)), - child: bytes.length > 0 - ? Image.memory( + return FutureBuilder( + future: _byteLoader, + builder: (futureContext, AsyncSnapshot snapshot) { + final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage; + return bytes.length > 0 + ? Hero( + tag: entry.uri, + child: LayoutBuilder(builder: (context, constraints) { + final dim = min(constraints.maxWidth, constraints.maxHeight); + return Image.memory( bytes, width: dim, height: dim, fit: BoxFit.cover, - ) - : Icon(Icons.error), - ); - }), - ), - Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (entry.hasGps) GpsIcon(iconSize: iconSize), - if (entry.isGif) - GifIcon(iconSize: iconSize) - else if (entry.isVideo) - VideoIcon( - entry: entry, - iconSize: iconSize, - ), - ], - ), - ], - ); + ); + }), + ) + : Icon(Icons.error); + }); } }