From ec505eb27e84fb7c03a6de93a2f68c1fe2dbc78f Mon Sep 17 00:00:00 2001 From: AYM1607 Date: Mon, 8 Apr 2019 03:03:12 -0500 Subject: [PATCH] Added preliminary code for the media screen --- lib/src/blocs/event_bloc.dart | 102 ++++++++++++++++++++++++++++-- lib/src/blocs/home_bloc.dart | 4 +- lib/src/screens/event_screen.dart | 80 +++++++++++++++++++---- lib/src/utils.dart | 7 ++ 4 files changed, 173 insertions(+), 20 deletions(-) diff --git a/lib/src/blocs/event_bloc.dart b/lib/src/blocs/event_bloc.dart index 66ccd8f..69cd55c 100644 --- a/lib/src/blocs/event_bloc.dart +++ b/lib/src/blocs/event_bloc.dart @@ -4,11 +4,14 @@ import 'dart:io'; import 'package:meta/meta.dart'; import 'package:rxdart/rxdart.dart'; +import '../models/event_model.dart'; import '../models/task_model.dart'; +import '../models/user_model.dart'; import '../resources/firestore_provider.dart'; import '../resources/firebase_storage_provider.dart'; import '../services/auth_service.dart'; -import '../utils.dart' show kTaskListPriorityTransforemer; +import '../utils.dart' + show kTaskListPriorityTransforemer, getImageThumbnailPath; /// A business logic component that manages the state for an event screen. class EventBloc { @@ -27,23 +30,100 @@ class EventBloc { /// A subject of list of task model. final _tasks = BehaviorSubject>(); + /// A subject of the list of image paths for this event. + final _imagesPaths = BehaviorSubject>(); + + /// A subject of String paths. + final _imagesFetcher = PublishSubject(); + + /// A subject of String paths. + final _imagesThumbnailsFetcher = PublishSubject(); + + /// A subject of a cache that contains the image files. + final _thumbnails = BehaviorSubject>>(); + + /// A subject of a cache that contains the image files. + final _images = BehaviorSubject>>(); + + /// The event being managed by this bloc. + EventModel _event; + + /// The representation of the current signed in user. + UserModel _user; + + /// Whether the event and user models have been fetched; + Future _ready; // Stream getters. /// An observable of the tasks linked to the event. Observable> get eventTasks => _tasks.stream.transform(kTaskListPriorityTransforemer); - Future get testFile => _storage.getFile( - '26LVkBPFkHVekVDatitVh6MXrm53/thumb@c64293a0-5970-11e9-d441-03e3ea627257.png'); + /// An observable of the list of paths of images linked to this event. + Observable> get imagesPaths => _imagesPaths.stream; + + /// An observable of a cache of the images thumbnails files. + Observable>> get thumbnails => _thumbnails.stream; + + /// An observable of a cache of the images files. + Observable>> get images => _images.stream; + + // Sinks getters. + /// Starts the fetching process for an image given its path. + Function(String) get fetchImage => _imagesFetcher.sink.add; + + /// Starts the fetching process for an image thumbail given its path. + Function(String) get fetchThumbnail => _imagesThumbnailsFetcher.sink.add; EventBloc({ @required this.eventName, - }); + }) { + _ready = _initUserAndEvent(); + _imagesFetcher.transform(_imagesTransformer()).pipe(_images); + _imagesThumbnailsFetcher + .transform(_imagesTransformer(isThumbnail: true)) + .pipe(_thumbnails); + } + + /// Initializes the value for the User and the Event models. + Future _initUserAndEvent() async { + final userModelFuture = _auth.getCurrentUserModel(); + _user = await userModelFuture; + _event = await _firestore.getEvent( + (await userModelFuture).id, + eventName: eventName, + ); + } + + /// Returns a stream transformer that creates a cache map from image storage + /// bucket paths. + ScanStreamTransformer>> _imagesTransformer({ + bool isThumbnail = false, + }) { + final accumulator = (Map> cache, String path, _) { + if (isThumbnail) { + path = getImageThumbnailPath(path); + } + if (cache.containsKey(path)) { + return cache; + } + cache[path] = _storage.getFile(path); + return cache; + }; + + return ScanStreamTransformer(accumulator, >{}); + } /// Fetches the tasks for the current user that a part of the currently /// selected event. Future fetchTasks() async { - final user = await _auth.currentUser; - _firestore.getUserTasks(user.email, event: eventName).pipe(_tasks); + await _ready; + _firestore.getUserTasks(_user.username, event: eventName).pipe(_tasks); + } + + /// Fetches the paths of all the images linked to this event. + Future fetchImagesPaths() async { + await _ready; + _imagesPaths.sink.add(_event.media); } /// Marks a task as done in the database. @@ -55,6 +135,16 @@ class EventBloc { } void dispose() async { + await _imagesThumbnailsFetcher.drain(); + _imagesThumbnailsFetcher.close(); + await _imagesFetcher.drain(); + _imagesFetcher.close(); + await _thumbnails.drain(); + _thumbnails.close(); + await _images.drain(); + _images.close(); + await _imagesPaths.drain(); + _imagesPaths.close(); await _tasks.drain(); _tasks.close(); } diff --git a/lib/src/blocs/home_bloc.dart b/lib/src/blocs/home_bloc.dart index 99b42a1..f5b9678 100644 --- a/lib/src/blocs/home_bloc.dart +++ b/lib/src/blocs/home_bloc.dart @@ -43,7 +43,7 @@ class HomeBloc { return tasks; }, ) - .transform(searchBoxTransformer()) + .transform(_searchBoxTransformer()) .transform(kTaskListPriorityTransforemer); /// An observable of the current logged in user. @@ -52,7 +52,7 @@ class HomeBloc { // TODO: Include the priority in the filtering. /// Returns a stream transformer that filters the task with the text from /// the search box. - StreamTransformer, List> searchBoxTransformer() { + StreamTransformer, List> _searchBoxTransformer() { return StreamTransformer.fromHandlers( handleData: (taskList, sink) { sink.add( diff --git a/lib/src/screens/event_screen.dart b/lib/src/screens/event_screen.dart index 5ee67a3..fa795d0 100644 --- a/lib/src/screens/event_screen.dart +++ b/lib/src/screens/event_screen.dart @@ -1,11 +1,13 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; -import '../utils.dart' show kBigTextStyle; +import '../utils.dart' show kBlueGradient, 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/loading_indicator.dart'; import '../widgets/task_list_tile.dart'; @@ -28,6 +30,7 @@ class _EventScreenState extends State super.initState(); bloc = EventBloc(eventName: widget.eventName); bloc.fetchTasks(); + bloc.fetchImagesPaths(); _tabController = TabController(vsync: this, length: 2); } @@ -93,19 +96,72 @@ class _EventScreenState extends State ); } + // TODO: refactor this. Widget buildMediaView() { - return Center( - child: FutureBuilder( - future: bloc.testFile, - builder: (BuildContext context, AsyncSnapshot snap) { - if (!snap.hasData) { - return Center( - child: LoadingIndicator(), + return StreamBuilder( + stream: bloc.imagesPaths, + builder: (BuildContext context, AsyncSnapshot> listSnap) { + if (!listSnap.hasData) { + return Center( + child: LoadingIndicator(), + ); + } + + return GridView.builder( + itemCount: listSnap.data.length + 1, + padding: EdgeInsets.all(10.0), + itemBuilder: (BuildContext context, int index) { + if (index == 0) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), + gradient: kBlueGradient, + ), + ); + } + 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 Image.file(snap.data); - }, - ), + }, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 5, + mainAxisSpacing: 5, + ), + ); + }, ); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 4ee4692..d757c93 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -60,3 +60,10 @@ final StreamTransformer, List> .compareTo(TaskModel.ecodedPriority(a.priority))); sink.add(tasksList); }); + +/// Gets the path of an image thumbnail from its original path. +String getImageThumbnailPath(String path) { + List tokens = path.split('/'); + tokens.last = 'thumb@' + tokens.last; + return tokens.join('/'); +}