Skip to content

Commit 45fc61d

Browse files
lightbox: Support thumbnail to original image hero transition
Fixes: #799
1 parent 940636a commit 45fc61d

File tree

2 files changed

+69
-3
lines changed

2 files changed

+69
-3
lines changed

lib/widgets/content.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,7 @@ class MessageImage extends StatelessWidget {
587587
context: context,
588588
message: message,
589589
src: resolvedSrcUrl,
590+
thumbnailUrl: resolvedThumbnailUrl,
590591
mediaType: MediaType.image));
591592
},
592593
child: node.loading
@@ -617,6 +618,7 @@ class MessageInlineVideo extends StatelessWidget {
617618
context: context,
618619
message: message,
619620
src: resolvedSrc,
621+
thumbnailUrl: null,
620622
mediaType: MediaType.video));
621623
},
622624
child: Container(

lib/widgets/lightbox.dart

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter/scheduler.dart';
23
import 'package:flutter/services.dart';
34
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
45
import 'package:intl/intl.dart';
@@ -91,12 +92,17 @@ class _LightboxPageLayout extends StatefulWidget {
9192
const _LightboxPageLayout({
9293
required this.routeEntranceAnimation,
9394
required this.message,
95+
required this.buildAppBarBottom,
9496
required this.buildBottomAppBar,
9597
required this.child,
9698
});
9799

98100
final Animation<double> routeEntranceAnimation;
99101
final Message message;
102+
103+
/// For [AppBar.bottom].
104+
final PreferredSizeWidget? Function(BuildContext context) buildAppBarBottom;
105+
100106
final Widget? Function(
101107
BuildContext context, Color color, double elevation) buildBottomAppBar;
102108
final Widget child;
@@ -171,7 +177,8 @@ class _LightboxPageLayoutState extends State<_LightboxPageLayout> {
171177

172178
// Make smaller, like a subtitle
173179
style: themeData.textTheme.titleSmall!.copyWith(color: appBarForegroundColor)),
174-
])));
180+
])),
181+
bottom: widget.buildAppBarBottom(context));
175182
}
176183

177184
Widget? bottomAppBar;
@@ -209,17 +216,30 @@ class _ImageLightboxPage extends StatefulWidget {
209216
required this.routeEntranceAnimation,
210217
required this.message,
211218
required this.src,
219+
required this.thumbnailUrl,
212220
});
213221

214222
final Animation<double> routeEntranceAnimation;
215223
final Message message;
216224
final Uri src;
225+
final Uri? thumbnailUrl;
217226

218227
@override
219228
State<_ImageLightboxPage> createState() => _ImageLightboxPageState();
220229
}
221230

222231
class _ImageLightboxPageState extends State<_ImageLightboxPage> {
232+
double? _loadingProgress;
233+
234+
PreferredSizeWidget? _buildAppBarBottom(BuildContext context) {
235+
if (_loadingProgress == null) {
236+
return null;
237+
}
238+
return PreferredSize(
239+
preferredSize: const Size.fromHeight(4.0),
240+
child: LinearProgressIndicator(minHeight: 4.0, value: _loadingProgress));
241+
}
242+
223243
Widget _buildBottomAppBar(BuildContext context, Color color, double elevation) {
224244
return BottomAppBar(
225245
color: color,
@@ -232,19 +252,60 @@ class _ImageLightboxPageState extends State<_ImageLightboxPage> {
232252
);
233253
}
234254

255+
Widget _frameBuilder(BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
256+
if (widget.thumbnailUrl == null) {
257+
return child;
258+
}
259+
if (frame != null) {
260+
// Image was already available or has finished downloading and
261+
// so it is now available.
262+
return child;
263+
}
264+
return RealmContentNetworkImage(widget.thumbnailUrl!,
265+
filterQuality: FilterQuality.medium);
266+
}
267+
268+
Widget _loadingBuilder(BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
269+
if (widget.thumbnailUrl == null) {
270+
return child;
271+
}
272+
// `loadingProgress` becomes null when Image has finished downloading.
273+
final double? progress;
274+
if (loadingProgress?.expectedTotalBytes == null) {
275+
progress = null;
276+
} else {
277+
progress = loadingProgress!.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!;
278+
}
279+
if (progress != _loadingProgress) {
280+
_loadingProgress = progress;
281+
// This function is called in a build method and setState
282+
// can't be called in a build method, so delay it.
283+
SchedulerBinding.instance.scheduleFrameCallback((_) { if (mounted) setState(() {}); });
284+
}
285+
return child;
286+
}
287+
235288
@override
236289
Widget build(BuildContext context) {
237290
return _LightboxPageLayout(
238291
routeEntranceAnimation: widget.routeEntranceAnimation,
239292
message: widget.message,
293+
buildAppBarBottom: _buildAppBarBottom,
240294
buildBottomAppBar: _buildBottomAppBar,
241295
child: SizedBox.expand(
242296
child: InteractiveViewer(
243297
child: SafeArea(
244298
child: LightboxHero(
245299
message: widget.message,
246300
src: widget.src,
247-
child: RealmContentNetworkImage(widget.src, filterQuality: FilterQuality.medium))))));
301+
child: RealmContentNetworkImage(widget.src,
302+
filterQuality: FilterQuality.medium,
303+
frameBuilder: _frameBuilder,
304+
loadingBuilder: _loadingBuilder),
305+
),
306+
),
307+
),
308+
));
248309
}
249310
}
250311

@@ -457,6 +518,7 @@ class _VideoLightboxPageState extends State<VideoLightboxPage> with PerAccountSt
457518
return _LightboxPageLayout(
458519
routeEntranceAnimation: widget.routeEntranceAnimation,
459520
message: widget.message,
521+
buildAppBarBottom: (context) => null,
460522
buildBottomAppBar: _buildBottomAppBar,
461523
child: SafeArea(
462524
child: Center(
@@ -484,6 +546,7 @@ Route<void> getLightboxRoute({
484546
BuildContext? context,
485547
required Message message,
486548
required Uri src,
549+
required Uri? thumbnailUrl,
487550
required MediaType mediaType,
488551
}) {
489552
return AccountPageRouteBuilder(
@@ -500,7 +563,8 @@ Route<void> getLightboxRoute({
500563
MediaType.image => _ImageLightboxPage(
501564
routeEntranceAnimation: animation,
502565
message: message,
503-
src: src),
566+
src: src,
567+
thumbnailUrl: thumbnailUrl),
504568
MediaType.video => VideoLightboxPage(
505569
routeEntranceAnimation: animation,
506570
message: message,

0 commit comments

Comments
 (0)