import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:matrix/matrix.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions.dart/matrix_file_extension.dart'; import 'package:fluffychat/widgets/matrix.dart'; class MxcImage extends StatefulWidget { final Uri? uri; final Event? event; final double? width; final double? height; final BoxFit? fit; final bool isThumbnail; final bool animated; final Duration retryDuration; final Duration animationDuration; final Curve animationCurve; final ThumbnailMethod thumbnailMethod; final Widget Function(BuildContext context)? placeholder; const MxcImage({ this.uri, this.event, this.width, this.height, this.fit, this.placeholder, this.isThumbnail = true, this.animated = false, this.animationDuration = const Duration(milliseconds: 200), this.retryDuration = const Duration(seconds: 2), this.animationCurve = Curves.linear, this.thumbnailMethod = ThumbnailMethod.scale, Key? key, }) : super(key: key); @override State createState() => _MxcImageState(); } class _MxcImageState extends State { Uint8List? _imageData; bool? _isCached; Future _load() async { final client = Matrix.of(context).client; final uri = widget.uri; final event = widget.event; if (uri != null) { final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; final width = widget.width; final realWidth = width == null ? null : width * devicePixelRatio; final height = widget.height; final realHeight = height == null ? null : height * devicePixelRatio; final httpUri = widget.isThumbnail ? uri.getThumbnail( client, width: realWidth, height: realHeight, animated: widget.animated, method: widget.thumbnailMethod, ) : uri.getDownloadLink(client); final storeKey = widget.isThumbnail ? httpUri : uri; if (_isCached == null) { final cachedData = await client.database?.getFile(storeKey); if (cachedData != null) { if (!mounted) return; setState(() { _imageData = cachedData; _isCached = true; }); return; } _isCached = false; } final remoteData = await http.get(httpUri).then((r) => r.bodyBytes); if (!mounted) return; setState(() { _imageData = remoteData; }); await client.database?.storeFile(storeKey, remoteData, 0); } if (event != null) { final data = await event.downloadAndDecryptAttachment( getThumbnail: widget.isThumbnail, ); if (data.detectFileType is MatrixImageFile) { if (!mounted) return; setState(() { _imageData = data.bytes; }); return; } } } void _tryLoad(_) async { try { await _load(); } catch (_) { if (!mounted) return; await Future.delayed(widget.retryDuration); _tryLoad(_); } } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback(_tryLoad); } @override Widget build(BuildContext context) { final data = _imageData; return AnimatedCrossFade( duration: widget.animationDuration, crossFadeState: data == null ? CrossFadeState.showFirst : CrossFadeState.showSecond, firstChild: widget.placeholder?.call(context) ?? const Center( child: CircularProgressIndicator.adaptive(), ), secondChild: data == null ? Container() : Image.memory( data, width: widget.width, height: widget.height, fit: widget.fit, ), ); } }