import 'package:flutter/material.dart'; import 'package:palette_generator/palette_generator.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:photo_view/photo_view.dart'; import 'package:freezer/translations.i18n.dart'; ImagesDatabase imagesDatabase = ImagesDatabase(); class ImagesDatabase { /* !!! Using the wrappers so i don't have to rewrite most of the code, because of migration to cached network image */ void saveImage(String url) { CachedNetworkImageProvider(url); } Future getPaletteGenerator(String url) { return PaletteGenerator.fromImageProvider(CachedNetworkImageProvider(url)); } Future getPrimaryColor(String url) async { PaletteGenerator paletteGenerator = await getPaletteGenerator(url); return paletteGenerator.dominantColor!.color; } Future isDark(String url) async { PaletteGenerator paletteGenerator = await getPaletteGenerator(url); return paletteGenerator.colors.first.computeLuminance() > 0.5 ? false : true; } } class CachedImage extends StatefulWidget { final String url; final double? width; final double? height; final bool circular; final bool fullThumb; final bool rounded; const CachedImage( {Key? key, required this.url, this.height, this.width, this.circular = false, this.fullThumb = false, this.rounded = false}) : super(key: key); @override State createState() => _CachedImageState(); } class _CachedImageState extends State { @override Widget build(BuildContext context) { final Widget child; if (!widget.url.startsWith('http')) { child = Image.asset( widget.url, width: widget.width, height: widget.height, ); } else { child = CachedNetworkImage( fit: BoxFit.scaleDown, imageUrl: widget.url, width: widget.width, height: widget.height, placeholder: (context, url) { if (widget.fullThumb) { return Image.asset( 'assets/cover.jpg', width: widget.width, height: widget.height, ); } return Image.asset('assets/cover_thumb.jpg', width: widget.width, height: widget.height); }, errorWidget: (context, url, error) => Image.asset('assets/cover.jpg', width: widget.width, height: widget.height), ); } if (widget.rounded) { return ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(4.0)), child: child, ); } if (widget.circular) { return ClipOval( child: child, ); } return child; } } class ZoomableImage extends StatefulWidget { final String url; final bool rounded; final double? width; final bool enableHero; final Object? heroTag; const ZoomableImage({ super.key, required this.url, this.rounded = false, this.width, this.enableHero = true, this.heroTag, }); @override State createState() => _ZoomableImageState(); } class _ZoomableImageState extends State { PhotoViewController? controller; bool photoViewOpened = false; late final Object? _key = widget.enableHero ? (widget.heroTag ?? UniqueKey()) : null; @override void initState() { super.initState(); controller = PhotoViewController()..outputStateStream.listen(listener); } // Listener of PhotoView scale changes. Used for closing PhotoView by pinch-in void listener(PhotoViewControllerValue value) { if (value.scale! < 0.16 && photoViewOpened) { Navigator.pop(context); photoViewOpened = false; // to avoid multiple pop() when picture are being scaled out too slowly } } @override Widget build(BuildContext context) { print('key: $_key'); final image = CachedImage( url: widget.url, rounded: widget.rounded, width: widget.width, fullThumb: true, ); final child = _key != null ? Hero( tag: _key!, child: image, ) : image; return GestureDetector( child: Semantics( label: "Album art".i18n, child: child, ), onTap: () { Navigator.of(context).push(PageRouteBuilder( opaque: false, // transparent background pageBuilder: (context, animation, __) { photoViewOpened = true; return FadeTransition( opacity: animation, child: PhotoView( imageProvider: CachedNetworkImageProvider(widget.url), maxScale: 8.0, minScale: 0.2, controller: controller, heroAttributes: _key == null ? null : PhotoViewHeroAttributes(tag: _key!), backgroundDecoration: const BoxDecoration( color: Color.fromARGB(0x90, 0, 0, 0))), ); })); }, ); } }