177 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Dart
		
	
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Dart
		
	
	
	
| import 'dart:io';
 | |
| import 'dart:math';
 | |
| import 'dart:typed_data';
 | |
| 
 | |
| import 'package:flutter/material.dart';
 | |
| 
 | |
| import 'package:matrix/matrix.dart';
 | |
| 
 | |
| import 'package:extera_next/config/themes.dart';
 | |
| import 'package:extera_next/utils/client_download_content_extension.dart';
 | |
| import 'package:extera_next/utils/matrix_sdk_extensions/matrix_file_extension.dart';
 | |
| import 'package:extera_next/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;
 | |
|   final String? cacheKey;
 | |
|   final Client? client;
 | |
|   final BorderRadius borderRadius;
 | |
| 
 | |
|   const MxcImage({
 | |
|     this.uri,
 | |
|     this.event,
 | |
|     this.width,
 | |
|     this.height,
 | |
|     this.fit,
 | |
|     this.placeholder,
 | |
|     this.isThumbnail = true,
 | |
|     this.animated = false,
 | |
|     this.animationDuration = FluffyThemes.animationDuration,
 | |
|     this.retryDuration = const Duration(seconds: 2),
 | |
|     this.animationCurve = FluffyThemes.animationCurve,
 | |
|     this.thumbnailMethod = ThumbnailMethod.scale,
 | |
|     this.cacheKey,
 | |
|     this.client,
 | |
|     this.borderRadius = BorderRadius.zero,
 | |
|     super.key,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   State<MxcImage> createState() => _MxcImageState();
 | |
| }
 | |
| 
 | |
| class _MxcImageState extends State<MxcImage> {
 | |
|   static final Map<String, Uint8List> _imageDataCache = {};
 | |
|   Uint8List? _imageDataNoCache;
 | |
| 
 | |
|   Uint8List? get _imageData => widget.cacheKey == null
 | |
|       ? _imageDataNoCache
 | |
|       : _imageDataCache[widget.cacheKey];
 | |
| 
 | |
|   set _imageData(Uint8List? data) {
 | |
|     if (data == null) return;
 | |
|     final cacheKey = widget.cacheKey;
 | |
|     cacheKey == null
 | |
|         ? _imageDataNoCache = data
 | |
|         : _imageDataCache[cacheKey] = data;
 | |
|   }
 | |
| 
 | |
|   Future<void> _load() async {
 | |
|     final client =
 | |
|         widget.client ?? widget.event?.room.client ?? Matrix.of(context).client;
 | |
|     final uri = widget.uri;
 | |
|     final event = widget.event;
 | |
| 
 | |
|     if (uri != null) {
 | |
|       final devicePixelRatio = MediaQuery.devicePixelRatioOf(context);
 | |
|       final width = widget.width;
 | |
|       final realWidth = width == null ? null : width * devicePixelRatio;
 | |
|       final height = widget.height;
 | |
|       final realHeight = height == null ? null : height * devicePixelRatio;
 | |
| 
 | |
|       final remoteData = await client.downloadMxcCached(
 | |
|         uri,
 | |
|         width: realWidth,
 | |
|         height: realHeight,
 | |
|         thumbnailMethod: widget.thumbnailMethod,
 | |
|         isThumbnail: widget.isThumbnail,
 | |
|         animated: widget.animated,
 | |
|       );
 | |
|       if (!mounted) return;
 | |
|       setState(() {
 | |
|         _imageData = remoteData;
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     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 {
 | |
|     if (_imageData != null) {
 | |
|       return;
 | |
|     }
 | |
|     try {
 | |
|       await _load();
 | |
|     } on IOException catch (_) {
 | |
|       if (!mounted) return;
 | |
|       await Future.delayed(widget.retryDuration);
 | |
|       _tryLoad(_);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     super.initState();
 | |
|     WidgetsBinding.instance.addPostFrameCallback(_tryLoad);
 | |
|   }
 | |
| 
 | |
|   Widget placeholder(BuildContext context) =>
 | |
|       widget.placeholder?.call(context) ??
 | |
|       Container(
 | |
|         width: widget.width,
 | |
|         height: widget.height,
 | |
|         alignment: Alignment.center,
 | |
|         child: const CircularProgressIndicator.adaptive(strokeWidth: 2),
 | |
|       );
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     final data = _imageData;
 | |
|     final hasData = data != null && data.isNotEmpty;
 | |
| 
 | |
|     return AnimatedSwitcher(
 | |
|         duration: FluffyThemes.animationDuration,
 | |
|         child: hasData
 | |
|             ? ClipRRect(
 | |
|               borderRadius: widget.borderRadius,
 | |
|               child: Image.memory(
 | |
|                 data,
 | |
|                 width: widget.width,
 | |
|                 height: widget.height,
 | |
|                 fit: widget.fit,
 | |
|                 filterQuality: widget.isThumbnail
 | |
|                     ? FilterQuality.low
 | |
|                     : FilterQuality.medium,
 | |
|                 errorBuilder: (context, e, s) {
 | |
|                   Logs().d('Unable to render mxc image', e, s);
 | |
|                   return SizedBox(
 | |
|                     width: widget.width,
 | |
|                     height: widget.height,
 | |
|                     child: Material(
 | |
|                       color: Theme.of(context).colorScheme.surfaceContainer,
 | |
|                       child: Icon(
 | |
|                         Icons.broken_image_outlined,
 | |
|                         size: min(widget.height ?? 64, 64),
 | |
|                         color: Theme.of(context).colorScheme.onSurface,
 | |
|                       ),
 | |
|                     ),
 | |
|                   );
 | |
|                 },
 | |
|               ),
 | |
|             )
 | |
|             : placeholder(context));
 | |
|   }
 | |
| }
 |