import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:freezer/main.dart'; import 'package:freezer/page_routes/fade.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, cacheManager: cacheManager); } Future getPaletteGenerator(String url) { return PaletteGenerator.fromImageProvider( CachedNetworkImageProvider(url, cacheManager: cacheManager)); } 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, cacheManager: cacheManager, 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 StatelessWidget { final String url; final bool rounded; final double? width; final bool enableHero; final Object? heroTag; ZoomableImage({ super.key, required this.url, this.rounded = false, this.width, this.enableHero = true, this.heroTag, }); late final Object? _key = enableHero ? (heroTag ?? UniqueKey()) : null; @override Widget build(BuildContext context) { print('key: $_key'); final image = CachedImage( url: url, rounded: rounded, width: 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(FadePageRoute( builder: (context) => ZoomableImageRoute(imageUrl: url, heroKey: _key), barrierDismissible: true, opaque: false)); }, ); } } class ZoomableImageRoute extends StatefulWidget { final Object? heroKey; final String imageUrl; const ZoomableImageRoute({required this.imageUrl, super.key, this.heroKey}); @override State createState() => _ZoomableImageRouteState(); } class _ZoomableImageRouteState extends State { bool photoViewOpened = true; final controller = PhotoViewController(); final _focusNode = FocusScopeNode(); @override void initState() { controller.outputStateStream.listen(listener); _focusNode.requestFocus(); super.initState(); } @override void dispose() { controller.dispose(); _focusNode.dispose(); super.dispose(); } 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) { return RawKeyboardListener( focusNode: _focusNode, onKey: (event) { if (event is! KeyUpEvent) return; if (event.isKeyPressed(LogicalKeyboardKey.escape)) { Navigator.pop(context); } }, child: PhotoView( imageProvider: CachedNetworkImageProvider(widget.imageUrl, cacheManager: cacheManager), maxScale: 8.0, minScale: 0.2, controller: controller, heroAttributes: widget.heroKey == null ? null : PhotoViewHeroAttributes(tag: widget.heroKey!), backgroundDecoration: const BoxDecoration(color: Color.fromARGB(85, 0, 0, 0))), ); } }