Refactored the media view in the event screen, extracted the thumbnail to its own widget ([AsyncThumbnail])

This commit is contained in:
Mariano Uvalle 2019-04-08 14:48:30 -05:00
parent ec505eb27e
commit d4813bdce9
3 changed files with 113 additions and 43 deletions

View file

@ -2,19 +2,26 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; 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 '../blocs/event_bloc.dart';
import '../models/task_model.dart'; import '../models/task_model.dart';
import '../widgets/custom_app_bar.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/loading_indicator.dart';
import '../widgets/task_list_tile.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 { class EventScreen extends StatefulWidget {
/// The name of the event this screenn is showing. /// The name of the event this screenn is showing.
final String eventName; 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({ EventScreen({
@required this.eventName, @required this.eventName,
}); });
@ -64,6 +71,7 @@ class _EventScreenState extends State<EventScreen>
); );
} }
/// Builds a list of the undone tasks linked to this service.
Widget buildTasksListView() { Widget buildTasksListView() {
return StreamBuilder( return StreamBuilder(
stream: bloc.eventTasks, stream: bloc.eventTasks,
@ -96,11 +104,13 @@ class _EventScreenState extends State<EventScreen>
); );
} }
// TODO: refactor this. /// Builds the Page view that contains all the thumnails of the pictures
/// linked to this event.
Widget buildMediaView() { Widget buildMediaView() {
return StreamBuilder( return StreamBuilder(
stream: bloc.imagesPaths, stream: bloc.imagesPaths,
builder: (BuildContext context, AsyncSnapshot<List<String>> listSnap) { builder: (BuildContext context, AsyncSnapshot<List<String>> listSnap) {
// Wait until the images paths have been fetched.
if (!listSnap.hasData) { if (!listSnap.hasData) {
return Center( return Center(
child: LoadingIndicator(), child: LoadingIndicator(),
@ -112,47 +122,16 @@ class _EventScreenState extends State<EventScreen>
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
if (index == 0) { if (index == 0) {
return Container( return buildAddPictureButton();
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
gradient: kBlueGradient,
),
);
} }
// Shift the indices since we added a button that's not contained
// in the original paths list.
final imagePath = listSnap.data[index - 1]; final imagePath = listSnap.data[index - 1];
bloc.fetchThumbnail(imagePath); bloc.fetchThumbnail(imagePath);
return StreamBuilder(
stream: bloc.thumbnails, return AsyncThumbnail(
builder: (BuildContext context, cacheStream: bloc.thumbnails,
AsyncSnapshot<Map<String, Future<File>>> thumbailsCacheSnap) { cacheId: getImageThumbnailPath(imagePath),
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<File> 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),
);
},
);
},
); );
}, },
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
@ -165,6 +144,17 @@ class _EventScreenState extends State<EventScreen>
); );
} }
Widget buildAddPictureButton() {
return GradientTouchableContainer(
radius: 8,
child: Icon(
Icons.camera_alt,
color: Colors.white,
size: 28,
),
);
}
void dispose() { void dispose() {
bloc.dispose(); bloc.dispose();
super.dispose(); super.dispose();

View file

@ -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<Map<String, Future<File>>> 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<Map<String, Future<File>>> thumbailsCacheSnap) {
// Wait until the images cache has data.
if (!thumbailsCacheSnap.hasData) {
return _buildThumbnailPlaceholder();
}
return FutureBuilder(
future: thumbailsCacheSnap.data[cacheId],
builder:
(BuildContext context, AsyncSnapshot<File> 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,
),
),
);
}
}

View file

@ -5,10 +5,16 @@ import 'package:flutter/material.dart';
/// ///
/// Shows an animation of the logo. /// Shows an animation of the logo.
class LoadingIndicator extends StatelessWidget { class LoadingIndicator extends StatelessWidget {
final double size;
LoadingIndicator({
this.size = 70,
});
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
width: 70, width: size,
height: 70, height: size,
child: FlareActor( child: FlareActor(
'assets/animations/loading_animation_looped.flr', 'assets/animations/loading_animation_looped.flr',
animation: 'Flip', animation: 'Flip',