From d4813bdce9a945b6666f3f84ebae3359c73c461f Mon Sep 17 00:00:00 2001 From: AYM1607 Date: Mon, 8 Apr 2019 14:48:30 -0500 Subject: [PATCH] Refactored the media view in the event screen, extracted the thumbnail to its own widget ([AsyncThumbnail]) --- lib/src/screens/event_screen.dart | 72 +++++++++++-------------- lib/src/widgets/async_thumbnail.dart | 74 ++++++++++++++++++++++++++ lib/src/widgets/loading_indicator.dart | 10 +++- 3 files changed, 113 insertions(+), 43 deletions(-) create mode 100644 lib/src/widgets/async_thumbnail.dart diff --git a/lib/src/screens/event_screen.dart b/lib/src/screens/event_screen.dart index fa795d0..d6a4058 100644 --- a/lib/src/screens/event_screen.dart +++ b/lib/src/screens/event_screen.dart @@ -2,19 +2,26 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import '../utils.dart' show kBlueGradient, getImageThumbnailPath; +import '../utils.dart' show getImageThumbnailPath; import '../blocs/event_bloc.dart'; import '../models/task_model.dart'; import '../widgets/custom_app_bar.dart'; -import '../widgets/fractionally_screen_sized_box.dart'; +import '../widgets/gradient_touchable_container.dart'; import '../widgets/loading_indicator.dart'; import '../widgets/task_list_tile.dart'; +import '../widgets/async_thumbnail.dart'; +/// A screen that shows all the items linked to an event. class EventScreen extends StatefulWidget { /// The name of the event this screenn is showing. final String eventName; + /// Creates a screen that shows all the items linked to an event. + /// + /// The tasks and images are showed in different page views controlled by a + /// [TabBar]. EventScreen({ @required this.eventName, }); @@ -64,6 +71,7 @@ class _EventScreenState extends State ); } + /// Builds a list of the undone tasks linked to this service. Widget buildTasksListView() { return StreamBuilder( stream: bloc.eventTasks, @@ -96,11 +104,13 @@ class _EventScreenState extends State ); } - // TODO: refactor this. + /// Builds the Page view that contains all the thumnails of the pictures + /// linked to this event. Widget buildMediaView() { return StreamBuilder( stream: bloc.imagesPaths, builder: (BuildContext context, AsyncSnapshot> listSnap) { + // Wait until the images paths have been fetched. if (!listSnap.hasData) { return Center( child: LoadingIndicator(), @@ -112,47 +122,16 @@ class _EventScreenState extends State padding: EdgeInsets.all(10.0), itemBuilder: (BuildContext context, int index) { if (index == 0) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.0), - gradient: kBlueGradient, - ), - ); + return buildAddPictureButton(); } + // Shift the indices since we added a button that's not contained + // in the original paths list. final imagePath = listSnap.data[index - 1]; bloc.fetchThumbnail(imagePath); - return StreamBuilder( - stream: bloc.thumbnails, - builder: (BuildContext context, - AsyncSnapshot>> thumbailsCacheSnap) { - if (!thumbailsCacheSnap.hasData) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.0), - color: Colors.grey, - ), - ); - } - return FutureBuilder( - future: - thumbailsCacheSnap.data[getImageThumbnailPath(imagePath)], - builder: (BuildContext context, - AsyncSnapshot thumbnailFileSnap) { - if (!thumbnailFileSnap.hasData) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.0), - color: Colors.grey, - ), - ); - } - return ClipRRect( - borderRadius: BorderRadius.circular(8.0), - child: Image.file(thumbnailFileSnap.data), - ); - }, - ); - }, + + return AsyncThumbnail( + cacheStream: bloc.thumbnails, + cacheId: getImageThumbnailPath(imagePath), ); }, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( @@ -165,6 +144,17 @@ class _EventScreenState extends State ); } + Widget buildAddPictureButton() { + return GradientTouchableContainer( + radius: 8, + child: Icon( + Icons.camera_alt, + color: Colors.white, + size: 28, + ), + ); + } + void dispose() { bloc.dispose(); super.dispose(); diff --git a/lib/src/widgets/async_thumbnail.dart b/lib/src/widgets/async_thumbnail.dart new file mode 100644 index 0000000..ca7d9c7 --- /dev/null +++ b/lib/src/widgets/async_thumbnail.dart @@ -0,0 +1,74 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; + +import '../widgets/loading_indicator.dart'; + +/// An Widget that displays an image given a stream of a cache and the id for +/// this image. +class AsyncThumbnail extends StatelessWidget { + /// A stream of a cache that maps an image path to the future of its file. + final Observable>> cacheStream; + + /// The id of the image to be displayed. + /// + /// The image will load undefinitely if the the provided id is not contained + /// in the provided cache. + final String cacheId; + + /// Creates a widget that displays an image given a stream of a cache and + /// the id for this image. + /// + /// Neither [cacheStream] nor [cacheId] can be null. + AsyncThumbnail({ + @required this.cacheStream, + @required this.cacheId, + }) : assert(cacheId != null), + assert(cacheStream != null); + + Widget build(BuildContext context) { + return StreamBuilder( + stream: cacheStream, + builder: (BuildContext context, + AsyncSnapshot>> thumbailsCacheSnap) { + // Wait until the images cache has data. + if (!thumbailsCacheSnap.hasData) { + return _buildThumbnailPlaceholder(); + } + + return FutureBuilder( + future: thumbailsCacheSnap.data[cacheId], + builder: + (BuildContext context, AsyncSnapshot thumbnailFileSnap) { + // Wait until the future of the file for this image resolves. + if (!thumbnailFileSnap.hasData) { + return _buildThumbnailPlaceholder(); + } + + return ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.file(thumbnailFileSnap.data), + ); + }, + ); + }, + ); + } + + // TODO: Find a better animation for the placeholder. + Widget _buildThumbnailPlaceholder() { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), + color: Colors.grey, + ), + child: Center( + child: LoadingIndicator( + size: 30, + ), + ), + ); + } +} diff --git a/lib/src/widgets/loading_indicator.dart b/lib/src/widgets/loading_indicator.dart index 91b0c7a..c3b065e 100644 --- a/lib/src/widgets/loading_indicator.dart +++ b/lib/src/widgets/loading_indicator.dart @@ -5,10 +5,16 @@ import 'package:flutter/material.dart'; /// /// Shows an animation of the logo. class LoadingIndicator extends StatelessWidget { + final double size; + + LoadingIndicator({ + this.size = 70, + }); + Widget build(BuildContext context) { return Container( - width: 70, - height: 70, + width: size, + height: size, child: FlareActor( 'assets/animations/loading_animation_looped.flr', animation: 'Flip',