Browse Source

neon, neon_files: make NeonCachedImage easily extendable

The new FilePreviewImage now also uses the mimetype as as svgHint
pull/432/head
Nikolas Rimikis 1 year ago
parent
commit
4f0009939e
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 146
      packages/neon/neon/lib/src/widgets/cached_image.dart
  2. 11
      packages/neon/neon/lib/src/widgets/user_avatar.dart
  3. 109
      packages/neon/neon_files/lib/widgets/file_preview.dart

146
packages/neon/neon/lib/src/widgets/cached_image.dart

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

11
packages/neon/neon/lib/src/widgets/user_avatar.dart

@ -43,7 +43,7 @@ class _UserAvatarState extends State<NeonUserAvatar> {
@override
Widget build(final BuildContext context) => LayoutBuilder(
builder: (final context, final constraints) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final brightness = Theme.of(context).brightness;
size = constraints.constrain(Size.square(widget.size)).shortestSide;
final pixelSize = (size * MediaQuery.of(context).devicePixelRatio).toInt();
return Stack(
@ -53,11 +53,10 @@ class _UserAvatarState extends State<NeonUserAvatar> {
radius: size / 2,
backgroundColor: widget.backgroundColor,
child: ClipOval(
child: NeonCachedImage.api(
account: widget.account,
cacheKey: 'avatar-${widget.username}-${isDark ? 'dark' : 'light'}$pixelSize',
download: () async {
if (isDark) {
child: NeonCachedImage.custom(
cacheKey: '${widget.account.id}-avatar-${widget.username}-$brightness$pixelSize',
getImage: () async {
if (brightness == Brightness.dark) {
return widget.account.client.core.getDarkAvatar(
userId: widget.username,
size: pixelSize,

109
packages/neon/neon_files/lib/widgets/file_preview.dart

@ -21,39 +21,14 @@ class FilePreview extends StatelessWidget {
final BorderRadius? borderRadius;
final bool withBackground;
int get width => size.width.toInt();
int get height => size.height.toInt();
@override
Widget build(final BuildContext context) {
final color = this.color ?? Theme.of(context).colorScheme.primary;
return SizedBox.fromSize(
size: size,
child: StreamBuilder<bool?>(
stream: bloc.options.showPreviewsOption.stream,
builder: (final context, final showPreviewsSnapshot) {
if ((showPreviewsSnapshot.data ?? false) && (details.hasPreview ?? false)) {
final account = Provider.of<AccountsBloc>(context, listen: false).activeAccount.value!;
final child = NeonCachedImage.api(
account: account,
cacheKey: 'preview-${details.path.join('/')}-$width-$height',
etag: details.etag,
download: () async => account.client.core.getPreview(
file: details.path.join('/'),
x: width,
y: height,
),
);
if (withBackground) {
return NeonImageWrapper(
color: Colors.white,
borderRadius: borderRadius,
child: child,
);
}
return child;
}
child: Builder(
builder: (final context) {
if (details.isDirectory) {
return Icon(
MdiIcons.folder,
@ -62,13 +37,83 @@ class FilePreview extends StatelessWidget {
);
}
return FileIcon(
details.name,
color: color,
size: size.shortestSide,
return OptionBuilder<bool>(
option: bloc.options.showPreviewsOption,
builder: (final context, final showPreviewsSnapshot) {
if (showPreviewsSnapshot && (details.hasPreview ?? false)) {
final account = Provider.of<AccountsBloc>(context, listen: false).activeAccount.value!;
final child = FilePreviewImage(
account: account,
file: details,
size: size,
);
if (withBackground) {
return NeonImageWrapper(
color: Colors.white,
borderRadius: borderRadius,
child: child,
);
}
return child;
}
return FileIcon(
details.name,
color: color,
size: size.shortestSide,
);
},
);
},
),
);
}
}
class FilePreviewImage extends NeonCachedImage {
factory FilePreviewImage({
required final Account account,
required final FileDetails file,
required final Size size,
}) {
final width = size.width.toInt();
final height = size.height.toInt();
final path = file.path.join('/');
final cacheKey = '${account.id}-preview-$path-$width-$height';
return FilePreviewImage._(
account: account,
file: file,
size: size,
cacheKey: cacheKey,
path: path,
width: width,
height: height,
);
}
FilePreviewImage._({
required final Account account,
required final FileDetails file,
required Size super.size,
required super.cacheKey,
required final String path,
required final int width,
required final int height,
}) : super.custom(
getImage: () async => account.client.core.getPreview(
file: path,
x: width,
y: height,
),
writeCache: (final cacheManager, final data) async {
await cacheManager.putFile(
cacheKey,
data,
maxAge: const Duration(days: 7),
eTag: file.etag,
);
},
isSvgHint: file.mimeType?.contains('svg') ?? false,
);
}

Loading…
Cancel
Save