1
1
import 'package:flutter/material.dart' ;
2
+ import 'package:flutter/scheduler.dart' ;
2
3
import 'package:flutter/services.dart' ;
3
4
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart' ;
4
5
import 'package:intl/intl.dart' ;
@@ -91,12 +92,17 @@ class _LightboxPageLayout extends StatefulWidget {
91
92
const _LightboxPageLayout ({
92
93
required this .routeEntranceAnimation,
93
94
required this .message,
95
+ required this .buildAppBarBottom,
94
96
required this .buildBottomAppBar,
95
97
required this .child,
96
98
});
97
99
98
100
final Animation <double > routeEntranceAnimation;
99
101
final Message message;
102
+
103
+ /// For [AppBar.bottom] .
104
+ final PreferredSizeWidget ? Function (BuildContext context) buildAppBarBottom;
105
+
100
106
final Widget ? Function (
101
107
BuildContext context, Color color, double elevation) buildBottomAppBar;
102
108
final Widget child;
@@ -171,7 +177,8 @@ class _LightboxPageLayoutState extends State<_LightboxPageLayout> {
171
177
172
178
// Make smaller, like a subtitle
173
179
style: themeData.textTheme.titleSmall! .copyWith (color: appBarForegroundColor)),
174
- ])));
180
+ ])),
181
+ bottom: widget.buildAppBarBottom (context));
175
182
}
176
183
177
184
Widget ? bottomAppBar;
@@ -209,17 +216,33 @@ class _ImageLightboxPage extends StatefulWidget {
209
216
required this .routeEntranceAnimation,
210
217
required this .message,
211
218
required this .src,
219
+ required this .thumbnailUrl,
212
220
});
213
221
214
222
final Animation <double > routeEntranceAnimation;
215
223
final Message message;
216
224
final Uri src;
225
+ final Uri ? thumbnailUrl;
217
226
218
227
@override
219
228
State <_ImageLightboxPage > createState () => _ImageLightboxPageState ();
220
229
}
221
230
222
231
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
+
223
246
Widget _buildBottomAppBar (BuildContext context, Color color, double elevation) {
224
247
return BottomAppBar (
225
248
color: color,
@@ -232,19 +255,60 @@ class _ImageLightboxPageState extends State<_ImageLightboxPage> {
232
255
);
233
256
}
234
257
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
+
235
291
@override
236
292
Widget build (BuildContext context) {
237
293
return _LightboxPageLayout (
238
294
routeEntranceAnimation: widget.routeEntranceAnimation,
239
295
message: widget.message,
296
+ buildAppBarBottom: _buildAppBarBottom,
240
297
buildBottomAppBar: _buildBottomAppBar,
241
298
child: SizedBox .expand (
242
299
child: InteractiveViewer (
243
300
child: SafeArea (
244
301
child: LightboxHero (
245
302
message: widget.message,
246
303
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
+ ));
248
312
}
249
313
}
250
314
@@ -457,6 +521,7 @@ class _VideoLightboxPageState extends State<VideoLightboxPage> with PerAccountSt
457
521
return _LightboxPageLayout (
458
522
routeEntranceAnimation: widget.routeEntranceAnimation,
459
523
message: widget.message,
524
+ buildAppBarBottom: (context) => null ,
460
525
buildBottomAppBar: _buildBottomAppBar,
461
526
child: SafeArea (
462
527
child: Center (
@@ -484,6 +549,7 @@ Route<void> getLightboxRoute({
484
549
BuildContext ? context,
485
550
required Message message,
486
551
required Uri src,
552
+ required Uri ? thumbnailUrl,
487
553
required MediaType mediaType,
488
554
}) {
489
555
return AccountPageRouteBuilder (
@@ -500,7 +566,8 @@ Route<void> getLightboxRoute({
500
566
MediaType .image => _ImageLightboxPage (
501
567
routeEntranceAnimation: animation,
502
568
message: message,
503
- src: src),
569
+ src: src,
570
+ thumbnailUrl: thumbnailUrl),
504
571
MediaType .video => VideoLightboxPage (
505
572
routeEntranceAnimation: animation,
506
573
message: message,
0 commit comments