import 'dart:async'; 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 '../services/upload_status_service.dart'; import '../utils.dart' show kTaskListPriorityTransforemer, getImageThumbnailPath; /// A business logic component that manages the state for an event screen. class EventBloc { /// The name of the event being shown. final String eventName; /// An instance of a firestore provider. final FirestoreProvider _firestore = firestoreProvider; /// An instance of the firebase sotrage provider. final FirebaseStorageProvider _storage = storageProvider; /// An instace of the auth service. final AuthService _auth = authService; /// An instance of the upload status service. final UploadStatusService _uploadStatus = uploadStatusService; /// 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>>(); /// A subject of a flag that indicates if there is a snack bar showing. final _snackBarStatus = BehaviorSubject(seedValue: false); /// 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); /// 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; /// An observable of a flag that indicates whether or not a snackBar is /// currently showing. ValueObservable get snackBarStatus => _snackBarStatus.stream; /// An observable of the status of files being uploaded. Observable get uploadStatus => _uploadStatus.status; // 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; /// Updates the snack bar status. Function(bool) get updateSnackBarStatus => _snackBarStatus.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); } // Do not re-fetch the image if it resolved already. 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 { 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; _firestore.getEventObservable(_user.id, _event.id).transform( StreamTransformer>.fromHandlers( handleData: (event, sink) { sink.add(event.media); }, ), ).pipe(_imagesPaths); } /// Marks a task as done in the database. void markTaskAsDone(TaskModel task) async { _firestore.updateTask( task.id, done: true, ); } void dispose() async { await _snackBarStatus.drain(); _snackBarStatus.close(); 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(); } }