freezer/lib/ui/cached_image.dart

220 lines
5.9 KiB
Dart

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<PaletteGenerator> getPaletteGenerator(String url) {
return PaletteGenerator.fromImageProvider(
CachedNetworkImageProvider(url, cacheManager: cacheManager));
}
Future<Color> getPrimaryColor(String url) async {
PaletteGenerator paletteGenerator = await getPaletteGenerator(url);
return paletteGenerator.dominantColor!.color;
}
Future<bool> 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(
{super.key,
required this.url,
this.height,
this.width,
this.circular = false,
this.fullThumb = false,
this.rounded = true});
@override
State<CachedImage> createState() => _CachedImageState();
}
class _CachedImageState extends State<CachedImage> {
@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.circular) {
return ClipOval(
child: child,
);
}
if (widget.rounded) {
return ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
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<ZoomableImageRoute> createState() => _ZoomableImageRouteState();
}
class _ZoomableImageRouteState extends State<ZoomableImageRoute> {
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))),
);
}
}