Add hero animation when opening image

This commit is contained in:
Ming Ming 2022-09-07 17:37:50 +08:00
parent fd676e0ac4
commit a3c98267eb
8 changed files with 97 additions and 51 deletions

21
app/lib/flutter_util.dart Normal file
View file

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:nc_photos/entity/file.dart';
class CustomizableMaterialPageRoute extends MaterialPageRoute {
CustomizableMaterialPageRoute({
required super.builder,
super.settings,
super.maintainState,
super.fullscreenDialog,
required this.transitionDuration,
required this.reverseTransitionDuration,
});
@override
final Duration transitionDuration;
@override
final Duration reverseTransitionDuration;
}
String getImageHeroTag(File file) => "imageHero(${file.path})";

View file

@ -18,7 +18,9 @@ const animationDurationNormal = Duration(milliseconds: 250);
const animationDurationLong = Duration(milliseconds: 500);
/// Duration for tab transition animation
const animationDurationTabTransition = Duration(milliseconds: 400);
const animationDurationTabTransition = Duration(milliseconds: 350);
const heroDurationNormal = Duration(milliseconds: 450);
/// Size of the photo/video thumbnails
///

View file

@ -17,6 +17,7 @@ import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/flutter_util.dart' as flutter_util;
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/list_extension.dart';
import 'package:nc_photos/object_extension.dart';
@ -974,6 +975,7 @@ class _ImageListItem extends _FileListItem {
account: account,
previewUrl: previewUrl,
isGif: file.contentType == "image/gif",
heroKey: flutter_util.getImageHeroTag(file),
);
final Account account;

View file

@ -18,6 +18,7 @@ import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/flutter_util.dart' as flutter_util;
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/or_null.dart';
@ -716,6 +717,7 @@ class _ImageListItem extends _FileListItem {
account: account,
previewUrl: previewUrl,
isGif: file.contentType == "image/gif",
heroKey: flutter_util.getImageHeroTag(file),
);
final Account account;

View file

@ -8,6 +8,7 @@ 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.dart' as app;
import 'package:nc_photos/entity/local_file.dart';
import 'package:nc_photos/flutter_util.dart' as flutter_util;
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
import 'package:nc_photos/widget/cached_network_image_mod.dart' as mod;
@ -115,6 +116,8 @@ class _RemoteImageViewerState extends State<RemoteImageViewer> {
onHeightChanged: widget.onHeightChanged,
onZoomStarted: widget.onZoomStarted,
onZoomEnded: widget.onZoomEnded,
child: Hero(
tag: flutter_util.getImageHeroTag(widget.file),
child: mod.CachedNetworkImage(
cacheManager: LargeImageCacheManager.inst,
imageUrl: _getImageUrl(widget.account, widget.file),
@ -133,6 +136,7 @@ class _RemoteImageViewerState extends State<RemoteImageViewer> {
return child;
},
),
),
);
void _onItemLoaded() {

View file

@ -7,6 +7,7 @@ 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/flutter_util.dart'as flutter_util;
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/local_file.dart';
import 'package:nc_photos/k.dart' as k;
@ -65,6 +66,7 @@ class PhotoListImageItem extends PhotoListFileItem {
previewUrl: previewUrl,
isGif: file.contentType == "image/gif",
isFavorite: shouldShowFavoriteBadge && file.isFavorite == true,
heroKey: flutter_util.getImageHeroTag(file),
);
final Account account;
@ -214,10 +216,45 @@ class PhotoListImage extends StatelessWidget {
this.padding = const EdgeInsets.all(2),
this.isGif = false,
this.isFavorite = false,
this.heroKey,
}) : super(key: key);
@override
build(BuildContext context) {
Widget buildPlaceholder() => Center(
child: Icon(
Icons.image_not_supported,
size: 64,
color: Colors.white.withOpacity(.8),
),
);
Widget child;
if (previewUrl == null) {
child = buildPlaceholder();
} else {
child = CachedNetworkImage(
cacheManager: ThumbnailCacheManager.inst,
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,
);
if (heroKey != null) {
child = Hero(
tag: heroKey!,
child: child,
);
}
}
return Padding(
padding: padding,
child: FittedBox(
@ -229,37 +266,7 @@ class PhotoListImage extends StatelessWidget {
// arbitrary size here
constraints: BoxConstraints.tight(const Size(128, 128)),
color: AppTheme.getListItemBackgroundColor(context),
child: previewUrl == null
? Center(
child: Icon(
Icons.image_not_supported,
size: 64,
color: Colors.white.withOpacity(.8),
),
)
: CachedNetworkImage(
cacheManager: ThumbnailCacheManager.inst,
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
// where's the preview???
return Center(
child: Icon(
Icons.image_not_supported,
size: 64,
color: Colors.white.withOpacity(.8),
),
);
},
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
),
child: child,
),
if (isGif)
Container(
@ -296,6 +303,8 @@ class PhotoListImage extends StatelessWidget {
final bool isGif;
final EdgeInsetsGeometry padding;
final bool isFavorite;
// if not null, the image will be contained by a Hero widget
final String? heroKey;
}
class PhotoListVideo extends StatelessWidget {

View file

@ -12,6 +12,7 @@ import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/flutter_util.dart' as flutter_util;
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/share_handler.dart';
@ -385,6 +386,7 @@ class _ImageListItem extends _FileListItem {
account: account,
previewUrl: previewUrl,
isGif: file.contentType == "image/gif",
heroKey: flutter_util.getImageHeroTag(file),
);
final Account account;

View file

@ -14,6 +14,7 @@ import 'package:nc_photos/download_handler.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/flutter_util.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/notified_action.dart';
import 'package:nc_photos/platform/features.dart' as features;
@ -52,8 +53,11 @@ class ViewerArguments {
class Viewer extends StatefulWidget {
static const routeName = "/viewer";
static Route buildRoute(ViewerArguments args) => MaterialPageRoute(
builder: (context) => Viewer.fromArgs(args),
static Route buildRoute(ViewerArguments args) =>
CustomizableMaterialPageRoute(
transitionDuration: k.heroDurationNormal,
reverseTransitionDuration: k.heroDurationNormal,
builder: (_) => Viewer.fromArgs(args),
);
const Viewer({