217 lines
5.7 KiB
Dart
217 lines
5.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.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);
|
|
}
|
|
|
|
Future<PaletteGenerator> getPaletteGenerator(String url) {
|
|
return PaletteGenerator.fromImageProvider(CachedNetworkImageProvider(url));
|
|
}
|
|
|
|
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(
|
|
{Key? key,
|
|
required this.url,
|
|
this.height,
|
|
this.width,
|
|
this.circular = false,
|
|
this.fullThumb = false,
|
|
this.rounded = false})
|
|
: super(key: key);
|
|
|
|
@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,
|
|
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<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),
|
|
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))),
|
|
);
|
|
}
|
|
}
|