Extract thumbnail widget

This commit is contained in:
Ming Ming 2022-12-18 14:20:51 +08:00
parent d2d0b44f60
commit ae8156e0b7
8 changed files with 161 additions and 226 deletions

View file

@ -1,15 +1,10 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc/list_album_share_outlier.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/ci_string.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/album.dart';
@ -25,6 +20,7 @@ import 'package:nc_photos/string_extension.dart';
import 'package:nc_photos/use_case/create_share.dart';
import 'package:nc_photos/use_case/remove_share.dart';
import 'package:nc_photos/widget/empty_list_indicator.dart';
import 'package:nc_photos/widget/network_thumbnail.dart';
import 'package:nc_photos/widget/unbounded_list_tile.dart';
import 'package:np_codegen/np_codegen.dart';
@ -260,29 +256,19 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
}
Widget _buildFileThumbnail(File file) {
final Widget child;
if (file_util.isAlbumFile(widget.account, file)) {
child = const Icon(Icons.photo_album, size: 32);
return const SizedBox.square(
dimension: 56,
child: Icon(Icons.photo_album, size: 32),
);
} else {
child = CachedNetworkImage(
cacheManager: ThumbnailCacheManager.inst,
imageUrl: api_util.getFilePreviewUrl(widget.account, file,
width: k.photoThumbSize, height: k.photoThumbSize),
httpHeaders: {
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported, size: 32),
return NetworkRectThumbnail(
account: widget.account,
imageUrl: NetworkRectThumbnail.imageUrlForFile(widget.account, file),
dimension: 56,
errorBuilder: (_) => const Icon(Icons.image_not_supported, size: 32),
);
}
return SizedBox(
width: 56,
height: 56,
child: child,
);
}
String _buildFilename(File file) {

View file

@ -1,10 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:flutter/material.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/widget/network_thumbnail.dart';
class CollectionListSmall extends StatelessWidget {
const CollectionListSmall({
@ -87,24 +84,14 @@ class CollectionListSmall extends StatelessWidget {
Widget _buildCoverImage(BuildContext context) {
Widget buildPlaceholder() => Padding(
padding: const EdgeInsets.all(4),
padding: const EdgeInsets.all(8),
child: fallbackBuilder(context),
);
try {
return FittedBox(
clipBehavior: Clip.hardEdge,
fit: BoxFit.cover,
child: CachedNetworkImage(
cacheManager: ThumbnailCacheManager.inst,
imageUrl: coverUrl,
httpHeaders: {
"Authorization": Api.getAuthorizationHeaderValue(account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
errorWidget: (context, url, error) => buildPlaceholder(),
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
),
return NetworkRectThumbnail(
account: account,
imageUrl: coverUrl,
errorBuilder: (_) => buildPlaceholder(),
);
} catch (_) {
return FittedBox(

View file

@ -0,0 +1,68 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:flutter/material.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/k.dart' as k;
/// A square thumbnail widget for a file
class NetworkRectThumbnail extends StatelessWidget {
const NetworkRectThumbnail({
super.key,
required this.account,
required this.imageUrl,
this.dimension,
required this.errorBuilder,
});
static String imageUrlForFile(Account account, FileDescriptor file) {
return api_util.getFilePreviewUrl(
account,
file,
width: k.photoThumbSize,
height: k.photoThumbSize,
);
}
@override
Widget build(BuildContext context) {
final child = FittedBox(
clipBehavior: Clip.hardEdge,
fit: BoxFit.cover,
child: CachedNetworkImage(
cacheManager: ThumbnailCacheManager.inst,
imageUrl: imageUrl,
// imageUrl: "",
httpHeaders: {
"Authorization": Api.getAuthorizationHeaderValue(account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
errorWidget: (context, __, ___) => SizedBox.square(
dimension: dimension,
child: errorBuilder(context),
),
),
);
if (dimension != null) {
return SizedBox.square(
dimension: dimension,
child: child,
);
} else {
return AspectRatio(
aspectRatio: 1,
child: child,
);
}
}
final Account account;
final String imageUrl;
final double? dimension;
final Widget Function(BuildContext context) errorBuilder;
}

View file

@ -1,17 +1,13 @@
import 'dart:ui';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc/list_face_file.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/compute_queue.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/download_handler.dart';
@ -34,6 +30,7 @@ import 'package:nc_photos/widget/builder/photo_list_item_builder.dart';
import 'package:nc_photos/widget/handler/add_selection_to_album_handler.dart';
import 'package:nc_photos/widget/handler/archive_selection_handler.dart';
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
import 'package:nc_photos/widget/network_thumbnail.dart';
import 'package:nc_photos/widget/photo_list_item.dart';
import 'package:nc_photos/widget/photo_list_util.dart' as photo_list_util;
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
@ -194,32 +191,26 @@ class _PersonBrowserState extends State<PersonBrowser>
Widget _buildFaceImage(BuildContext context) {
Widget cover;
Widget buildPlaceholder() => Padding(
padding: const EdgeInsets.all(8),
child: Icon(
Icons.person,
color: Theme.of(context).listPlaceholderForegroundColor,
),
);
try {
cover = FittedBox(
clipBehavior: Clip.hardEdge,
fit: BoxFit.cover,
child: CachedNetworkImage(
cacheManager: ThumbnailCacheManager.inst,
imageUrl: api_util.getFacePreviewUrl(
widget.account, widget.person.thumbFaceId,
size: k.faceThumbSize),
httpHeaders: {
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
errorWidget: (context, url, error) {
// just leave it empty
return Container();
},
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
cover = NetworkRectThumbnail(
account: widget.account,
imageUrl: api_util.getFacePreviewUrl(
widget.account,
widget.person.thumbFaceId,
size: k.faceThumbSize,
),
errorBuilder: (_) => buildPlaceholder(),
);
} catch (_) {
cover = Icon(
Icons.person,
color: Theme.of(context).listPlaceholderForegroundColor,
size: 24,
cover = FittedBox(
child: buildPlaceholder(),
);
}

View file

@ -1,18 +1,15 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:intl/intl.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/entity/local_file.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/widget/network_thumbnail.dart';
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
import 'package:to_string/to_string.dart';
@ -61,7 +58,7 @@ class PhotoListImageItem extends PhotoListFileItem {
);
@override
buildWidget(BuildContext context) => PhotoListImage(
Widget buildWidget(BuildContext context) => PhotoListImage(
account: account,
previewUrl: previewUrl,
isGif: file.fdMime == "image/gif",
@ -219,33 +216,24 @@ class PhotoListImage extends StatelessWidget {
}) : super(key: key);
@override
build(BuildContext context) {
Widget buildPlaceholder() => Center(
Widget build(BuildContext context) {
Widget buildPlaceholder() => Padding(
padding: const EdgeInsets.all(12),
child: Icon(
Icons.image_not_supported,
size: 64,
color: Theme.of(context).listPlaceholderForegroundColor,
),
);
Widget child;
if (previewUrl == null) {
child = buildPlaceholder();
child = FittedBox(
child: buildPlaceholder(),
);
} else {
child = CachedNetworkImage(
fit: BoxFit.cover,
cacheManager: ThumbnailCacheManager.inst,
child = NetworkRectThumbnail(
account: account,
imageUrl: previewUrl!,
httpHeaders: {
"Authorization": Api.getAuthorizationHeaderValue(account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
errorWidget: (context, url, error) {
// won't work on web because the image is downloaded by
// the cache manager instead
return buildPlaceholder();
},
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
errorBuilder: (_) => buildPlaceholder(),
);
if (heroKey != null) {
child = Hero(
@ -255,42 +243,27 @@ class PhotoListImage extends StatelessWidget {
}
}
return Padding(
padding: padding,
child: FittedBox(
clipBehavior: Clip.hardEdge,
fit: BoxFit.cover,
return IconTheme(
data: const IconThemeData(color: Colors.white),
child: Padding(
padding: padding,
child: Stack(
children: [
Container(
// arbitrary size here
constraints: BoxConstraints.tight(const Size(128, 128)),
color: Theme.of(context).listPlaceholderBackgroundColor,
child: child,
),
if (isGif)
Container(
// arbitrary size here
constraints: BoxConstraints.tight(const Size(128, 128)),
alignment: AlignmentDirectional.topEnd,
padding: const EdgeInsets.symmetric(horizontal: 2),
child: const Icon(
Icons.gif,
size: 36,
color: Colors.white,
),
child: const Icon(Icons.gif, size: 26),
),
if (isFavorite)
Container(
// arbitrary size here
constraints: BoxConstraints.tight(const Size(128, 128)),
alignment: AlignmentDirectional.bottomStart,
padding: const EdgeInsets.all(8),
child: const Icon(
Icons.star,
size: 20,
color: Colors.white,
),
padding: const EdgeInsets.all(6),
child: const Icon(Icons.star, size: 15),
),
],
),
@ -316,62 +289,37 @@ class PhotoListVideo extends StatelessWidget {
}) : super(key: key);
@override
build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(2),
child: FittedBox(
clipBehavior: Clip.hardEdge,
fit: BoxFit.cover,
Widget build(BuildContext context) {
return IconTheme(
data: const IconThemeData(color: Colors.white),
child: Padding(
padding: const EdgeInsets.all(2),
child: Stack(
children: [
Container(
// arbitrary size here
constraints: BoxConstraints.tight(const Size(128, 128)),
color: Theme.of(context).listPlaceholderBackgroundColor,
child: CachedNetworkImage(
cacheManager: ThumbnailCacheManager.inst,
child: NetworkRectThumbnail(
account: account,
imageUrl: previewUrl,
httpHeaders: {
"Authorization": Api.getAuthorizationHeaderValue(account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
errorWidget: (context, url, error) {
// no preview for this video. Normal since video preview is disabled
// by default
return Center(
child: Icon(
Icons.image_not_supported,
size: 64,
color: Theme.of(context).listPlaceholderForegroundColor,
),
);
},
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
errorBuilder: (_) => Padding(
padding: const EdgeInsets.all(12),
child: Icon(
Icons.image_not_supported,
color: Theme.of(context).listPlaceholderForegroundColor,
),
),
),
),
Container(
// arbitrary size here
constraints: BoxConstraints.tight(const Size(128, 128)),
alignment: AlignmentDirectional.topEnd,
padding: const EdgeInsets.all(8),
child: const Icon(
Icons.play_circle_outlined,
size: 24,
color: Colors.white,
),
padding: const EdgeInsets.all(6),
child: const Icon(Icons.play_circle_outlined, size: 17),
),
if (isFavorite)
Container(
// arbitrary size here
constraints: BoxConstraints.tight(const Size(128, 128)),
alignment: AlignmentDirectional.bottomStart,
padding: const EdgeInsets.all(8),
child: const Icon(
Icons.star,
size: 20,
color: Colors.white,
),
padding: const EdgeInsets.all(6),
child: const Icon(Icons.star, size: 15),
),
],
),

View file

@ -1,5 +1,3 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -7,11 +5,9 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc/list_location.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/exception.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
@ -21,6 +17,7 @@ import 'package:nc_photos/theme.dart';
import 'package:nc_photos/use_case/list_location_group.dart';
import 'package:nc_photos/widget/about_geocoding_dialog.dart';
import 'package:nc_photos/widget/collection_list_item.dart';
import 'package:nc_photos/widget/network_thumbnail.dart';
import 'package:nc_photos/widget/place_browser.dart';
import 'package:np_codegen/np_codegen.dart';
@ -307,19 +304,13 @@ class _CountryItemView extends StatelessWidget {
Row(
mainAxisSize: MainAxisSize.min,
children: [
CachedNetworkImage(
cacheManager: ThumbnailCacheManager.inst,
NetworkRectThumbnail(
account: account,
imageUrl: imageUrl,
httpHeaders: {
"Authorization": Api.getAuthorizationHeaderValue(account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
errorWidget: (_, __, ___) => const Padding(
padding: EdgeInsetsDirectional.fromSTEB(8, 8, 0, 8),
errorBuilder: (_) => const Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.location_on),
),
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
),
const SizedBox(width: 8),
Text(text),

View file

@ -1,16 +1,12 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc/search_landing.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/person.dart';
import 'package:nc_photos/exception.dart';
@ -22,6 +18,7 @@ import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/url_launcher_util.dart';
import 'package:nc_photos/use_case/list_location_group.dart';
import 'package:nc_photos/widget/network_thumbnail.dart';
import 'package:nc_photos/widget/people_browser.dart';
import 'package:nc_photos/widget/person_browser.dart';
import 'package:nc_photos/widget/place_browser.dart';
@ -327,7 +324,7 @@ class _LandingPersonItem {
}
class _LandingLocationItem {
_LandingLocationItem({
const _LandingLocationItem({
required this.account,
required this.name,
required this.thumbUrl,
@ -407,20 +404,10 @@ class _LandingItemWidget extends StatelessWidget {
child: fallbackBuilder(context),
);
try {
cover = FittedBox(
clipBehavior: Clip.hardEdge,
fit: BoxFit.cover,
child: CachedNetworkImage(
cacheManager: ThumbnailCacheManager.inst,
imageUrl: coverUrl,
httpHeaders: {
"Authorization": Api.getAuthorizationHeaderValue(account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
errorWidget: (context, url, error) => buildPlaceholder(),
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
),
cover = NetworkRectThumbnail(
account: account,
imageUrl: coverUrl,
errorBuilder: (_) => buildPlaceholder(),
);
} catch (_) {
cover = FittedBox(

View file

@ -1,5 +1,3 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -7,11 +5,8 @@ import 'package:intl/intl.dart';
import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc/list_sharing.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/data_source.dart';
@ -27,6 +22,7 @@ import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/use_case/import_potential_shared_album.dart';
import 'package:nc_photos/widget/album_browser_util.dart' as album_browser_util;
import 'package:nc_photos/widget/empty_list_indicator.dart';
import 'package:nc_photos/widget/network_thumbnail.dart';
import 'package:nc_photos/widget/shared_file_viewer.dart';
import 'package:nc_photos/widget/unbounded_list_tile.dart';
import 'package:np_codegen/np_codegen.dart';
@ -173,22 +169,12 @@ class _SharingBrowserState extends State<SharingBrowser> {
width: _leadingSize,
child: Icon(Icons.folder, size: 32),
)
: CachedNetworkImage(
width: _leadingSize,
height: _leadingSize,
cacheManager: ThumbnailCacheManager.inst,
imageUrl: api_util.getFilePreviewUrl(
widget.account, firstItem.file,
width: k.photoThumbSize, height: k.photoThumbSize),
httpHeaders: {
"Authorization":
Api.getAuthorizationHeaderValue(widget.account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
errorWidget: (context, url, error) =>
const Icon(Icons.folder, size: 32),
: NetworkRectThumbnail(
account: widget.account,
imageUrl: NetworkRectThumbnail.imageUrlForFile(
widget.account, firstItem.file),
dimension: _leadingSize,
errorBuilder: (_) => const Icon(Icons.folder, size: 32),
),
label: shares.first.share.filename,
description: shares.first.share.uidOwner == widget.account.userId
@ -222,21 +208,12 @@ class _SharingBrowserState extends State<SharingBrowser> {
width: _leadingSize,
child: Icon(Icons.photo_album, size: 32),
)
: CachedNetworkImage(
width: _leadingSize,
height: _leadingSize,
cacheManager: ThumbnailCacheManager.inst,
imageUrl: api_util.getFilePreviewUrl(widget.account, cover,
width: k.photoThumbSize, height: k.photoThumbSize),
httpHeaders: {
"Authorization":
Api.getAuthorizationHeaderValue(widget.account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
errorWidget: (context, url, error) =>
const Icon(Icons.photo_album, size: 32),
: NetworkRectThumbnail(
account: widget.account,
imageUrl:
NetworkRectThumbnail.imageUrlForFile(widget.account, cover),
dimension: _leadingSize,
errorBuilder: (_) => const Icon(Icons.photo_album, size: 32),
),
label: firstItem.album.name,
description: shares.first.share.uidOwner == widget.account.userId