Skip to content

Commit 016ce48

Browse files
lightbox: Support thumbnail to original image hero transition
Fixes: #799
1 parent 29b3454 commit 016ce48

File tree

2 files changed

+72
-3
lines changed

2 files changed

+72
-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: 70 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,33 @@ 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+
// return const PreferredSize(
236+
// preferredSize: Size.fromHeight(4.0),
237+
// child: LinearProgressIndicator(minHeight: 4.0));
238+
if (_loadingProgress == null) {
239+
return null;
240+
}
241+
return PreferredSize(
242+
preferredSize: const Size.fromHeight(4.0),
243+
child: LinearProgressIndicator(minHeight: 4.0, value: _loadingProgress));
244+
}
245+
223246
Widget _buildBottomAppBar(BuildContext context, Color color, double elevation) {
224247
return BottomAppBar(
225248
color: color,
@@ -232,19 +255,60 @@ class _ImageLightboxPageState extends State<_ImageLightboxPage> {
232255
);
233256
}
234257

258+
Widget _frameBuilder(BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
259+
if (widget.thumbnailUrl == null) {
260+
return child;
261+
}
262+
if (frame != null) {
263+
// Image was already available or has finished downloading and
264+
// so it is now available.
265+
return child;
266+
}
267+
return RealmContentNetworkImage(widget.thumbnailUrl!,
268+
filterQuality: FilterQuality.medium);
269+
}
270+
271+
Widget _loadingBuilder(BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
272+
if (widget.thumbnailUrl == null) {
273+
return child;
274+
}
275+
// `loadingProgress` becomes null when Image has finished downloading.
276+
final double? progress;
277+
if (loadingProgress?.expectedTotalBytes == null) {
278+
progress = null;
279+
} else {
280+
progress = loadingProgress!.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!;
281+
}
282+
if (progress != _loadingProgress) {
283+
_loadingProgress = progress;
284+
// This function is called in a build method and setState
285+
// can't be called in a build method, so delay it.
286+
SchedulerBinding.instance.scheduleFrameCallback((_) { if (mounted) setState(() {}); });
287+
}
288+
return child;
289+
}
290+
235291
@override
236292
Widget build(BuildContext context) {
237293
return _LightboxPageLayout(
238294
routeEntranceAnimation: widget.routeEntranceAnimation,
239295
message: widget.message,
296+
buildAppBarBottom: _buildAppBarBottom,
240297
buildBottomAppBar: _buildBottomAppBar,
241298
child: SizedBox.expand(
242299
child: InteractiveViewer(
243300
child: SafeArea(
244301
child: LightboxHero(
245302
message: widget.message,
246303
src: widget.src,
247-
child: RealmContentNetworkImage(widget.src, filterQuality: FilterQuality.medium))))));
304+
child: RealmContentNetworkImage(widget.src,
305+
filterQuality: FilterQuality.medium,
306+
frameBuilder: _frameBuilder,
307+
loadingBuilder: _loadingBuilder),
308+
),
309+
),
310+
),
311+
));
248312
}
249313
}
250314

@@ -457,6 +521,7 @@ class _VideoLightboxPageState extends State<VideoLightboxPage> with PerAccountSt
457521
return _LightboxPageLayout(
458522
routeEntranceAnimation: widget.routeEntranceAnimation,
459523
message: widget.message,
524+
buildAppBarBottom: (context) => null,
460525
buildBottomAppBar: _buildBottomAppBar,
461526
child: SafeArea(
462527
child: Center(
@@ -484,6 +549,7 @@ Route<void> getLightboxRoute({
484549
BuildContext? context,
485550
required Message message,
486551
required Uri src,
552+
required Uri? thumbnailUrl,
487553
required MediaType mediaType,
488554
}) {
489555
return AccountPageRouteBuilder(
@@ -500,7 +566,8 @@ Route<void> getLightboxRoute({
500566
MediaType.image => _ImageLightboxPage(
501567
routeEntranceAnimation: animation,
502568
message: message,
503-
src: src),
569+
src: src,
570+
thumbnailUrl: thumbnailUrl),
504571
MediaType.video => VideoLightboxPage(
505572
routeEntranceAnimation: animation,
506573
message: message,

0 commit comments

Comments
 (0)