|
|
|
@ -1,10 +1,11 @@
|
|
|
|
|
part of '../neon.dart'; |
|
|
|
|
|
|
|
|
|
final _cacheManager = DefaultCacheManager(); |
|
|
|
|
|
|
|
|
|
class CachedURLImage extends StatelessWidget { |
|
|
|
|
const CachedURLImage({ |
|
|
|
|
required this.url, |
|
|
|
|
required this.requestManager, |
|
|
|
|
required this.client, |
|
|
|
|
required this.account, |
|
|
|
|
this.height, |
|
|
|
|
this.width, |
|
|
|
|
this.fit, |
|
|
|
@ -13,8 +14,7 @@ class CachedURLImage extends StatelessWidget {
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
final String url; |
|
|
|
|
final RequestManager requestManager; |
|
|
|
|
final NextcloudClient client; |
|
|
|
|
final Account account; |
|
|
|
|
|
|
|
|
|
final double? height; |
|
|
|
|
final double? width; |
|
|
|
@ -24,71 +24,59 @@ class CachedURLImage extends StatelessWidget {
|
|
|
|
|
final Color? color; |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
Widget build(final BuildContext context) => SizedBox( |
|
|
|
|
height: height, |
|
|
|
|
width: width, |
|
|
|
|
child: Center( |
|
|
|
|
child: ResultStreamBuilder<Uint8List>( |
|
|
|
|
// TODO: Cache this properly. It was not working in listviews where the old image was still rendered in the same index, until it was scrolled out of view. |
|
|
|
|
stream: requestManager.wrapBytes( |
|
|
|
|
client.id, |
|
|
|
|
'image-${base64.encode(url.codeUnits)}', |
|
|
|
|
() async => (await http.get( |
|
|
|
|
Uri.parse(url), |
|
|
|
|
headers: client.baseHeaders, |
|
|
|
|
)) |
|
|
|
|
.bodyBytes, |
|
|
|
|
preferCache: true, |
|
|
|
|
Widget build(final BuildContext context) => FutureBuilder<File>( |
|
|
|
|
// Really weird false positive |
|
|
|
|
// ignore: discarded_futures |
|
|
|
|
future: _cacheManager.getSingleFile( |
|
|
|
|
url, |
|
|
|
|
headers: account.client.baseHeaders, |
|
|
|
|
), |
|
|
|
|
builder: ( |
|
|
|
|
final context, |
|
|
|
|
final data, |
|
|
|
|
final error, |
|
|
|
|
final loading, |
|
|
|
|
) => |
|
|
|
|
Stack( |
|
|
|
|
children: [ |
|
|
|
|
if (data != null) ...[ |
|
|
|
|
SizedBox( |
|
|
|
|
height: height, |
|
|
|
|
width: width, |
|
|
|
|
child: Builder( |
|
|
|
|
builder: (final context) { |
|
|
|
|
builder: (final context, final fileSnapshot) { |
|
|
|
|
if (fileSnapshot.hasData) { |
|
|
|
|
final content = fileSnapshot.data!.readAsBytesSync(); |
|
|
|
|
|
|
|
|
|
var isSvg = false; |
|
|
|
|
if (Uri.parse(url).path.endsWith('.svg')) { |
|
|
|
|
isSvg = true; |
|
|
|
|
} |
|
|
|
|
if (!isSvg) { |
|
|
|
|
try { |
|
|
|
|
// TODO: Is this safe enough? Research in XML spec if a space is allowed between the < and the tag name |
|
|
|
|
if (utf8.decode(data).contains('<svg')) { |
|
|
|
|
return SvgPicture.memory( |
|
|
|
|
data, |
|
|
|
|
color: color, |
|
|
|
|
); |
|
|
|
|
// TODO: Is this safe enough? |
|
|
|
|
if (utf8.decode(content).contains('<svg')) { |
|
|
|
|
isSvg = true; |
|
|
|
|
} |
|
|
|
|
} catch (_) { |
|
|
|
|
// If the data is not UTF-8 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (isSvg) { |
|
|
|
|
return SvgPicture.memory( |
|
|
|
|
content, |
|
|
|
|
height: height, |
|
|
|
|
width: width, |
|
|
|
|
fit: fit ?? BoxFit.contain, |
|
|
|
|
color: color, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return Image.memory( |
|
|
|
|
data, |
|
|
|
|
content, |
|
|
|
|
height: height, |
|
|
|
|
width: width, |
|
|
|
|
fit: fit, |
|
|
|
|
); |
|
|
|
|
}, |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
], |
|
|
|
|
if (error != null) ...[ |
|
|
|
|
Icon( |
|
|
|
|
} |
|
|
|
|
if (fileSnapshot.hasError) { |
|
|
|
|
return Icon( |
|
|
|
|
Icons.error_outline, |
|
|
|
|
size: height != null && width != null ? min(height!, width!) : height ?? width, |
|
|
|
|
), |
|
|
|
|
], |
|
|
|
|
if (loading) ...[ |
|
|
|
|
SizedBox( |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
return SizedBox( |
|
|
|
|
width: width, |
|
|
|
|
child: const CustomLinearProgressIndicator(), |
|
|
|
|
), |
|
|
|
|
], |
|
|
|
|
], |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
}, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|