Browse Source

refactor(neon): Simplify account handling for API and URL images

Signed-off-by: jld3103 <jld3103yt@gmail.com>
pull/969/head
jld3103 1 year ago
parent
commit
a61fd04ac8
No known key found for this signature in database
GPG Key ID: 9062417B9E8EB7B3
  1. 2
      packages/neon/neon/lib/src/widgets/drawer.dart
  2. 174
      packages/neon/neon/lib/src/widgets/image.dart
  3. 4
      packages/neon/neon/lib/src/widgets/unified_search_results.dart
  4. 2
      packages/neon/neon/lib/src/widgets/user_avatar.dart
  5. 2
      packages/neon/neon_news/lib/widgets/articles_view.dart
  6. 2
      packages/neon/neon_news/lib/widgets/feed_icon.dart
  7. 2
      packages/neon/neon_notifications/lib/pages/main.dart
  8. 2
      tool/find-untested-neon-apis.sh

2
packages/neon/neon/lib/src/widgets/drawer.dart

@ -154,7 +154,7 @@ class NeonDrawerHeader extends StatelessWidget {
), ),
), ),
Flexible( Flexible(
child: NeonCachedImage.url( child: NeonUrlImage(
url: theme.logo, url: theme.logo,
), ),
), ),

174
packages/neon/neon/lib/src/widgets/image.dart

@ -33,42 +33,15 @@ typedef ApiImageDownloader = FutureOr<DynamiteResponse<Uint8List, dynamic>> Func
/// fetches. /// fetches.
/// ///
/// See: /// See:
/// * [NeonApiImage] for an image from the [NextcloudClient] /// * [NeonApiImage] for an image widget from an Nextcloud API endpoint.
/// * [NeonUrlImage] for an image widget from an arbitrary URL.
/// * [NeonImageWrapper] for a wrapping widget for images /// * [NeonImageWrapper] for a wrapping widget for images
class NeonCachedImage extends StatefulWidget { class NeonCachedImage extends StatefulWidget {
/// Prints the data from [image] to the canvas.
///
/// The data is not persisted in the cache.
NeonCachedImage({
required final Uint8List image,
required Key super.key,
this.isSvgHint = false,
this.size,
this.fit,
this.svgColorFilter,
this.errorBuilder,
}) : image = Future.value(image);
/// Fetches the image from [url].
///
/// The image is automatically cached.
NeonCachedImage.url({
required final String url,
final Account? account,
final Key? key,
this.isSvgHint = false,
this.size,
this.fit,
this.svgColorFilter,
this.errorBuilder,
}) : image = _getImageFromUrl(url, account),
super(key: key ?? Key(url));
/// Custom image implementation. /// Custom image implementation.
/// ///
/// It is possible to provide custom [reviver] and [writeCache] functions to /// It is possible to provide custom [reviver] and [writeCache] functions to
/// adjust the caching. /// adjust the caching.
NeonCachedImage.custom({ NeonCachedImage({
required final ImageDownloader getImage, required final ImageDownloader getImage,
required final String cacheKey, required final String cacheKey,
final CacheReviver? reviver, final CacheReviver? reviver,
@ -116,20 +89,6 @@ class NeonCachedImage extends StatefulWidget {
/// {@endtemplate} /// {@endtemplate}
final ErrorWidgetBuilder? errorBuilder; final ErrorWidgetBuilder? errorBuilder;
static Future<Uint8List> _getImageFromUrl(final String url, final Account? account) async {
var uri = Uri.parse(url);
if (account != null) {
uri = account.completeUri(uri);
}
final file = await _cacheManager.getSingleFile(
uri.toString(),
headers: account != null && uri.host == account.serverURL.host && account.client.authentications.isNotEmpty
? account.client.authentications.first.headers
: null,
);
return file.readAsBytes();
}
static Future<Uint8List> _customImageGetter( static Future<Uint8List> _customImageGetter(
final CacheReviver? checkCache, final CacheReviver? checkCache,
final ImageDownloader getImage, final ImageDownloader getImage,
@ -232,18 +191,16 @@ class _NeonCachedImageState extends State<NeonCachedImage> {
); );
} }
/// Nextcloud API image. /// A widget painting an Image fetched from the Nextcloud API.
///
/// Extension for [NeonCachedImage] providing a [NextcloudClient] to the caller
/// to retrieve the image.
/// ///
/// See: /// See:
/// * [NeonCachedImage] for a customized image /// * [NeonCachedImage] for a customized image
/// * [NeonUrlImage] for an image widget from an arbitrary URL.
/// * [NeonImageWrapper] for a wrapping widget for images /// * [NeonImageWrapper] for a wrapping widget for images
class NeonApiImage extends StatelessWidget { class NeonApiImage extends StatelessWidget {
/// Creates a new Neon API image with the active account. /// Creates a new Neon API image fetching the image with the currently active account.
/// ///
/// Use [NeonApiImage.custom] to specify fetching the image with a different account. /// See [NeonApiImage.withAccount] to fetch the image using a specific account.
const NeonApiImage({ const NeonApiImage({
required this.getImage, required this.getImage,
required this.cacheKey, required this.cacheKey,
@ -257,10 +214,10 @@ class NeonApiImage extends StatelessWidget {
super.key, super.key,
}) : account = null; }) : account = null;
/// Creates a new Neon API image for a given account. /// Creates a new Neon API image fetching the image with the given [account].
/// ///
/// Use [NeonApiImage] to fetch the image with the currently active account. /// See [NeonApiImage] to fetch the image using the currently active account.
const NeonApiImage.custom({ const NeonApiImage.withAccount({
required this.getImage, required this.getImage,
required this.cacheKey, required this.cacheKey,
required Account this.account, required Account this.account,
@ -274,10 +231,9 @@ class NeonApiImage extends StatelessWidget {
super.key, super.key,
}); });
/// Optional account to use for the request. /// The account to use for the request.
/// ///
/// Defaults to the currently active account in [AccountsBloc.activeAccount]. /// Defaults to the currently active account in [AccountsBloc.activeAccount].
/// Use the [NeonApiImage.custom] constructor to specify a different account.
final Account? account; final Account? account;
/// Image downloader. /// Image downloader.
@ -311,7 +267,7 @@ class NeonApiImage extends StatelessWidget {
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
final account = this.account ?? NeonProvider.of<AccountsBloc>(context).activeAccount.value!; final account = this.account ?? NeonProvider.of<AccountsBloc>(context).activeAccount.value!;
return NeonCachedImage.custom( return NeonCachedImage(
getImage: () async { getImage: () async {
final response = await getImage(account.client); final response = await getImage(account.client);
return response.body; return response.body;
@ -328,13 +284,117 @@ class NeonApiImage extends StatelessWidget {
} }
} }
/// A widget painting an Image fetched from an arbitrary URL.
///
/// See:
/// * [NeonCachedImage] for a customized image
/// * [NeonApiImage] for an image widget from an Nextcloud API endpoint.
/// * [NeonImageWrapper] for a wrapping widget for images
class NeonUrlImage extends StatelessWidget {
/// Creates a new Neon URL image with the active account.
///
/// See [NeonUrlImage.withAccount] for using a specific account.
const NeonUrlImage({
required this.url,
this.reviver,
this.writeCache,
this.isSvgHint = false,
this.size,
this.fit,
this.svgColorFilter,
this.errorBuilder,
super.key,
}) : account = null;
/// Creates a new Neon URL image with the given [account].
///
/// See [NeonUrlImage] for using the active account.
const NeonUrlImage.withAccount({
required this.url,
required Account this.account,
this.reviver,
this.writeCache,
this.isSvgHint = false,
this.size,
this.fit,
this.svgColorFilter,
this.errorBuilder,
super.key,
});
/// The account to use for the request.
///
/// Defaults to the currently active account in [AccountsBloc.activeAccount].
final Account? account;
/// Image url.
final String url;
/// Custom cache reviver function.
final CacheReviver? reviver;
/// Custom cache writer function.
final CacheWriter? writeCache;
/// {@macro NeonCachedImage.svgHint}
final bool isSvgHint;
/// {@macro NeonCachedImage.size}
final Size? size;
/// {@macro NeonCachedImage.fit}
final BoxFit? fit;
/// {@macro NeonCachedImage.svgColorFilter}
final ColorFilter? svgColorFilter;
/// {@macro NeonCachedImage.errorBuilder}
final ErrorWidgetBuilder? errorBuilder;
@override
Widget build(final BuildContext context) {
final account = this.account ?? NeonProvider.of<AccountsBloc>(context).activeAccount.value!;
return NeonCachedImage(
getImage: () async {
final uri = account.completeUri(Uri.parse(url));
final headers = <String, String>{};
// Only send the authentication headers when sending the request to the server of the account
if (uri.toString().startsWith(account.serverURL.toString()) && account.client.authentications.isNotEmpty) {
headers.addAll(account.client.authentications.first.headers);
}
final response = await account.client.executeRawRequest(
'GET',
uri,
headers,
null,
const {200},
);
return response.bytes;
},
cacheKey: '${account.id}-$url',
reviver: reviver,
writeCache: writeCache,
isSvgHint: isSvgHint,
size: size,
fit: fit,
svgColorFilter: svgColorFilter,
errorBuilder: errorBuilder,
);
}
}
/// Nextcloud image wrapper widget. /// Nextcloud image wrapper widget.
/// ///
/// Wraps a child (most commonly an image) into a uniformly styled container. /// Wraps a child (most commonly an image) into a uniformly styled container.
/// ///
/// See: /// See:
/// * [NeonCachedImage] for a customized image /// * [NeonCachedImage] for a customized image
/// * [NeonApiImage] for an image widget from a [NextcloudClient]. /// * [NeonApiImage] for an image widget from an Nextcloud API endpoint.
/// * [NeonUrlImage] for an image widget from an arbitrary URL.
class NeonImageWrapper extends StatelessWidget { class NeonImageWrapper extends StatelessWidget {
/// Creates a new image wrapper. /// Creates a new image wrapper.
const NeonImageWrapper({ const NeonImageWrapper({

4
packages/neon/neon/lib/src/widgets/unified_search_results.dart

@ -110,7 +110,7 @@ class NeonUnifiedSearchResults extends StatelessWidget {
Widget _buildThumbnail(final BuildContext context, final Account account, final core.UnifiedSearchResultEntry entry) { Widget _buildThumbnail(final BuildContext context, final Account account, final core.UnifiedSearchResultEntry entry) {
if (entry.thumbnailUrl.isNotEmpty) { if (entry.thumbnailUrl.isNotEmpty) {
return NeonCachedImage.url( return NeonUrlImage.withAccount(
size: const Size.square(largeIconSize), size: const Size.square(largeIconSize),
url: entry.thumbnailUrl, url: entry.thumbnailUrl,
account: account, account: account,
@ -128,7 +128,7 @@ class NeonUnifiedSearchResults extends StatelessWidget {
final core.UnifiedSearchResultEntry entry, final core.UnifiedSearchResultEntry entry,
) { ) {
if (entry.icon.startsWith('/')) { if (entry.icon.startsWith('/')) {
return NeonCachedImage.url( return NeonUrlImage.withAccount(
size: Size.square(IconTheme.of(context).size!), size: Size.square(IconTheme.of(context).size!),
url: entry.icon, url: entry.icon,
account: account, account: account,

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

@ -59,7 +59,7 @@ class _UserAvatarState extends State<NeonUserAvatar> {
radius: size / 2, radius: size / 2,
backgroundColor: widget.backgroundColor, backgroundColor: widget.backgroundColor,
child: ClipOval( child: ClipOval(
child: NeonApiImage.custom( child: NeonApiImage.withAccount(
account: widget.account, account: widget.account,
cacheKey: 'avatar-${widget.username}-$brightness$pixelSize', cacheKey: 'avatar-${widget.username}-$brightness$pixelSize',
getImage: (final client) async => switch (brightness) { getImage: (final client) async => switch (brightness) {

2
packages/neon/neon_news/lib/widgets/articles_view.dart

@ -117,7 +117,7 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
), ),
), ),
if (article.mediaThumbnail != null) ...[ if (article.mediaThumbnail != null) ...[
NeonCachedImage.url( NeonUrlImage(
url: article.mediaThumbnail!, url: article.mediaThumbnail!,
size: const Size(100, 50), size: const Size(100, 50),
fit: BoxFit.cover, fit: BoxFit.cover,

2
packages/neon/neon_news/lib/widgets/feed_icon.dart

@ -20,7 +20,7 @@ class NewsFeedIcon extends StatelessWidget {
size: Size.square(size), size: Size.square(size),
borderRadius: borderRadius, borderRadius: borderRadius,
child: faviconLink != null && faviconLink.isNotEmpty child: faviconLink != null && faviconLink.isNotEmpty
? NeonCachedImage.url( ? NeonUrlImage(
url: faviconLink, url: faviconLink,
size: Size.square(size), size: Size.square(size),
) )

2
packages/neon/neon_notifications/lib/pages/main.dart

@ -81,7 +81,7 @@ class _NotificationsMainPageState extends State<NotificationsMainPage> {
) )
: SizedBox.fromSize( : SizedBox.fromSize(
size: const Size.square(largeIconSize), size: const Size.square(largeIconSize),
child: NeonCachedImage.url( child: NeonUrlImage(
url: notification.icon!, url: notification.icon!,
size: const Size.square(largeIconSize), size: const Size.square(largeIconSize),
svgColorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn), svgColorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),

2
tool/find-untested-neon-apis.sh

@ -4,7 +4,7 @@ cd "$(dirname "$0")/.."
function find_apis() { function find_apis() {
path="$1" path="$1"
grep -r "$path" --include "*\.dart" -e "client\.[^.]*.[^(]*(" -oh | sed "s/^client\.//" | sed "s/($//" | sed "s/Raw$//" | sort | uniq grep -r "$path" --include "*\.dart" -e "client\.[^.]*.[^(]*(" -oh | sed "s/^client\.//" | sed "s/($//" | sed "s/Raw$//" | grep -v "^executeRawRequest" | sort | uniq
} }
used_apis=("$(find_apis "packages/neon")") used_apis=("$(find_apis "packages/neon")")

Loading…
Cancel
Save