import 'dart:math'; import 'dart:typed_data'; import 'package:aves/model/image_decode_service.dart'; import 'package:aves/model/image_entry.dart'; import 'package:flutter/material.dart'; import 'package:transparent_image/transparent_image.dart'; class Thumbnail extends StatefulWidget { final ImageEntry entry; final double extent; final double devicePixelRatio; const Thumbnail({ Key key, @required this.entry, @required this.extent, @required this.devicePixelRatio, }) : super(key: key); @override ThumbnailState createState() => ThumbnailState(); } class ThumbnailState extends State { Future _byteLoader; ImageEntry get entry => widget.entry; String get uri => widget.entry.uri; @override void initState() { super.initState(); initByteLoader(); } @override void didUpdateWidget(Thumbnail oldWidget) { super.didUpdateWidget(oldWidget); if (uri == oldWidget.entry.uri && widget.extent == oldWidget.extent) return; initByteLoader(); } initByteLoader() { final dim = (widget.extent * widget.devicePixelRatio).round(); _byteLoader = ImageDecodeService.getImageBytes(widget.entry, dim, dim); } @override void dispose() { ImageDecodeService.cancelGetImageBytes(uri); super.dispose(); } @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 Stack( alignment: AlignmentDirectional.bottomStart, children: [ Hero( tag: 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( bytes, width: dim, height: dim, fit: BoxFit.cover, ) : Icon(Icons.error), ); }), ), if (entry.isVideo) VideoTag( entry: entry, iconSize: iconSize, ) else if (entry.isGif) Icon( Icons.gif, size: iconSize, ), ], ); }), ), ); } } class VideoTag extends StatelessWidget { final ImageEntry entry; final double iconSize; const VideoTag({Key key, this.entry, this.iconSize}) : super(key: key); @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.all(1), padding: EdgeInsets.only(right: iconSize / 4), decoration: BoxDecoration( color: Color(0xBB000000), borderRadius: BorderRadius.all( Radius.circular(iconSize), ), ), child: Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon( Icons.play_circle_outline, size: iconSize, ), SizedBox(width: 2), Text(entry.durationText) ], ), ); } }