|
|
@ -1,12 +1,12 @@ |
|
|
|
part of '../../neon.dart'; |
|
|
|
part of '../../neon.dart'; |
|
|
|
|
|
|
|
|
|
|
|
final _cacheManager = DefaultCacheManager(); |
|
|
|
typedef CacheReviver = FutureOr<Uint8List?> Function(CacheManager cacheManager); |
|
|
|
|
|
|
|
typedef ImageDownloader = FutureOr<Uint8List> Function(); |
|
|
|
typedef APIImageDownloader = Future<Uint8List> Function(); |
|
|
|
typedef CacheWriter = Future<void> Function(CacheManager cacheManager, Uint8List image); |
|
|
|
|
|
|
|
|
|
|
|
class NeonCachedImage extends StatefulWidget { |
|
|
|
class NeonCachedImage extends StatefulWidget { |
|
|
|
const NeonCachedImage._({ |
|
|
|
const NeonCachedImage({ |
|
|
|
required this.getImageFile, |
|
|
|
required this.image, |
|
|
|
required Key super.key, |
|
|
|
required Key super.key, |
|
|
|
this.isSvgHint = false, |
|
|
|
this.isSvgHint = false, |
|
|
|
this.size, |
|
|
|
this.size, |
|
|
@ -15,68 +15,36 @@ class NeonCachedImage extends StatefulWidget { |
|
|
|
this.iconColor, |
|
|
|
this.iconColor, |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
factory NeonCachedImage.url({ |
|
|
|
NeonCachedImage.url({ |
|
|
|
required final String url, |
|
|
|
required final String url, |
|
|
|
final Size? size, |
|
|
|
|
|
|
|
final BoxFit? fit, |
|
|
|
|
|
|
|
final Color? svgColor, |
|
|
|
|
|
|
|
final Color? iconColor, |
|
|
|
|
|
|
|
final Key? key, |
|
|
|
final Key? key, |
|
|
|
}) => |
|
|
|
this.isSvgHint = false, |
|
|
|
NeonCachedImage._( |
|
|
|
this.size, |
|
|
|
getImageFile: () async { |
|
|
|
this.fit, |
|
|
|
final file = await _cacheManager.getSingleFile(url); |
|
|
|
this.svgColor, |
|
|
|
return file.readAsBytes(); |
|
|
|
this.iconColor, |
|
|
|
}, |
|
|
|
}) : image = _getImageFromUrl(url), |
|
|
|
isSvgHint: Uri.parse(url).path.endsWith('.svg'), |
|
|
|
super(key: key ?? Key(url)); |
|
|
|
size: size, |
|
|
|
|
|
|
|
fit: fit, |
|
|
|
|
|
|
|
svgColor: svgColor, |
|
|
|
|
|
|
|
iconColor: iconColor, |
|
|
|
|
|
|
|
key: key ?? Key(url), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
factory NeonCachedImage.api({ |
|
|
|
NeonCachedImage.custom({ |
|
|
|
required final Account account, |
|
|
|
required final ImageDownloader getImage, |
|
|
|
required final String cacheKey, |
|
|
|
required final String cacheKey, |
|
|
|
required final APIImageDownloader download, |
|
|
|
final CacheReviver? reviver, |
|
|
|
final String? etag, |
|
|
|
final CacheWriter? writeCache, |
|
|
|
final Size? size, |
|
|
|
this.isSvgHint = false, |
|
|
|
final BoxFit? fit, |
|
|
|
this.size, |
|
|
|
final Color? svgColor, |
|
|
|
this.fit, |
|
|
|
final Color? iconColor, |
|
|
|
this.svgColor, |
|
|
|
final Key? key, |
|
|
|
this.iconColor, |
|
|
|
}) { |
|
|
|
}) : image = _customImageGetter( |
|
|
|
final realKey = '${account.id}-$cacheKey'; |
|
|
|
reviver, |
|
|
|
return NeonCachedImage._( |
|
|
|
getImage, |
|
|
|
getImageFile: () async { |
|
|
|
writeCache, |
|
|
|
final cacheFile = await _cacheManager.getFileFromCache(realKey); |
|
|
|
cacheKey, |
|
|
|
if (cacheFile != null && cacheFile.validTill.isAfter(DateTime.now())) { |
|
|
|
), |
|
|
|
return cacheFile.file.readAsBytes(); |
|
|
|
super(key: Key(cacheKey)); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final file = await download(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unawaited( |
|
|
|
|
|
|
|
_cacheManager.putFile( |
|
|
|
|
|
|
|
realKey, |
|
|
|
|
|
|
|
file, |
|
|
|
|
|
|
|
maxAge: const Duration(days: 7), |
|
|
|
|
|
|
|
eTag: etag, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return file; |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
size: size, |
|
|
|
|
|
|
|
fit: fit, |
|
|
|
|
|
|
|
svgColor: svgColor, |
|
|
|
|
|
|
|
iconColor: iconColor, |
|
|
|
|
|
|
|
key: key ?? Key(realKey), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final Future<Uint8List> Function() getImageFile; |
|
|
|
final Future<Uint8List> image; |
|
|
|
final bool isSvgHint; |
|
|
|
final bool isSvgHint; |
|
|
|
|
|
|
|
|
|
|
|
final Size? size; |
|
|
|
final Size? size; |
|
|
@ -85,17 +53,60 @@ class NeonCachedImage extends StatefulWidget { |
|
|
|
final Color? svgColor; |
|
|
|
final Color? svgColor; |
|
|
|
final Color? iconColor; |
|
|
|
final Color? iconColor; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static Future<Uint8List> _getImageFromUrl(final String url) async { |
|
|
|
|
|
|
|
final file = await _cacheManager.getSingleFile(url); |
|
|
|
|
|
|
|
return file.readAsBytes(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static Future<Uint8List> _customImageGetter( |
|
|
|
|
|
|
|
final CacheReviver? checkCache, |
|
|
|
|
|
|
|
final ImageDownloader getImage, |
|
|
|
|
|
|
|
final CacheWriter? writeCache, |
|
|
|
|
|
|
|
final String cacheKey, |
|
|
|
|
|
|
|
) async { |
|
|
|
|
|
|
|
final cached = await checkCache?.call(_cacheManager) ?? await _defaultCacheReviver(cacheKey); |
|
|
|
|
|
|
|
if (cached != null) { |
|
|
|
|
|
|
|
return cached; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final data = await getImage(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unawaited(writeCache?.call(_cacheManager, data) ?? _defaultCacheWriter(data, cacheKey)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return data; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static Future<Uint8List?> _defaultCacheReviver(final String cacheKey) async { |
|
|
|
|
|
|
|
final cacheFile = await _cacheManager.getFileFromCache(cacheKey); |
|
|
|
|
|
|
|
if (cacheFile != null && cacheFile.validTill.isAfter(DateTime.now())) { |
|
|
|
|
|
|
|
return cacheFile.file.readAsBytes(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static Future<void> _defaultCacheWriter( |
|
|
|
|
|
|
|
final Uint8List data, |
|
|
|
|
|
|
|
final String cacheKey, |
|
|
|
|
|
|
|
) async { |
|
|
|
|
|
|
|
await _cacheManager.putFile( |
|
|
|
|
|
|
|
cacheKey, |
|
|
|
|
|
|
|
data, |
|
|
|
|
|
|
|
maxAge: const Duration(days: 7), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static final _cacheManager = DefaultCacheManager(); |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
@override |
|
|
|
State<NeonCachedImage> createState() => _NeonCachedImageState(); |
|
|
|
State<NeonCachedImage> createState() => _NeonCachedImageState(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class _NeonCachedImageState extends State<NeonCachedImage> { |
|
|
|
class _NeonCachedImageState extends State<NeonCachedImage> { |
|
|
|
late Future<Uint8List> _future = widget.getImageFile(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
@override |
|
|
|
Widget build(final BuildContext context) => Center( |
|
|
|
Widget build(final BuildContext context) => Center( |
|
|
|
child: FutureBuilder<Uint8List>( |
|
|
|
child: FutureBuilder<Uint8List>( |
|
|
|
future: _future, |
|
|
|
future: widget.image, |
|
|
|
builder: (final context, final fileSnapshot) { |
|
|
|
builder: (final context, final fileSnapshot) { |
|
|
|
if (!fileSnapshot.hasData) { |
|
|
|
if (!fileSnapshot.hasData) { |
|
|
|
return SizedBox( |
|
|
|
return SizedBox( |
|
|
@ -110,10 +121,7 @@ class _NeonCachedImageState extends State<NeonCachedImage> { |
|
|
|
return NeonException( |
|
|
|
return NeonException( |
|
|
|
fileSnapshot.error, |
|
|
|
fileSnapshot.error, |
|
|
|
onRetry: () { |
|
|
|
onRetry: () { |
|
|
|
setState(() { |
|
|
|
setState(() {}); |
|
|
|
// ignore: discarded_futures |
|
|
|
|
|
|
|
_future = widget.getImageFile(); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}, |
|
|
|
}, |
|
|
|
onlyIcon: true, |
|
|
|
onlyIcon: true, |
|
|
|
iconSize: widget.size?.shortestSide, |
|
|
|
iconSize: widget.size?.shortestSide, |
|
|
|