Added preliminary code for the media screen

This commit is contained in:
Mariano Uvalle 2019-04-08 03:03:12 -05:00
parent 02d7dd5be3
commit ec505eb27e
4 changed files with 173 additions and 20 deletions

View file

@ -4,11 +4,14 @@ import 'dart:io';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import '../models/event_model.dart';
import '../models/task_model.dart'; import '../models/task_model.dart';
import '../models/user_model.dart';
import '../resources/firestore_provider.dart'; import '../resources/firestore_provider.dart';
import '../resources/firebase_storage_provider.dart'; import '../resources/firebase_storage_provider.dart';
import '../services/auth_service.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. /// A business logic component that manages the state for an event screen.
class EventBloc { class EventBloc {
@ -27,23 +30,100 @@ class EventBloc {
/// A subject of list of task model. /// A subject of list of task model.
final _tasks = BehaviorSubject<List<TaskModel>>(); final _tasks = BehaviorSubject<List<TaskModel>>();
/// A subject of the list of image paths for this event.
final _imagesPaths = BehaviorSubject<List<String>>();
/// A subject of String paths.
final _imagesFetcher = PublishSubject<String>();
/// A subject of String paths.
final _imagesThumbnailsFetcher = PublishSubject<String>();
/// A subject of a cache that contains the image files.
final _thumbnails = BehaviorSubject<Map<String, Future<File>>>();
/// A subject of a cache that contains the image files.
final _images = BehaviorSubject<Map<String, Future<File>>>();
/// 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<void> _ready;
// Stream getters. // Stream getters.
/// An observable of the tasks linked to the event. /// An observable of the tasks linked to the event.
Observable<List<TaskModel>> get eventTasks => Observable<List<TaskModel>> get eventTasks =>
_tasks.stream.transform(kTaskListPriorityTransforemer); _tasks.stream.transform(kTaskListPriorityTransforemer);
Future<File> get testFile => _storage.getFile( /// An observable of the list of paths of images linked to this event.
'26LVkBPFkHVekVDatitVh6MXrm53/thumb@c64293a0-5970-11e9-d441-03e3ea627257.png'); Observable<List<String>> get imagesPaths => _imagesPaths.stream;
/// An observable of a cache of the images thumbnails files.
Observable<Map<String, Future<File>>> get thumbnails => _thumbnails.stream;
/// An observable of a cache of the images files.
Observable<Map<String, Future<File>>> 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({ EventBloc({
@required this.eventName, @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<void> _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<String, Map<String, Future<File>>> _imagesTransformer({
bool isThumbnail = false,
}) {
final accumulator = (Map<String, Future<File>> cache, String path, _) {
if (isThumbnail) {
path = getImageThumbnailPath(path);
}
if (cache.containsKey(path)) {
return cache;
}
cache[path] = _storage.getFile(path);
return cache;
};
return ScanStreamTransformer(accumulator, <String, Future<File>>{});
}
/// Fetches the tasks for the current user that a part of the currently /// Fetches the tasks for the current user that a part of the currently
/// selected event. /// selected event.
Future<void> fetchTasks() async { Future<void> fetchTasks() async {
final user = await _auth.currentUser; await _ready;
_firestore.getUserTasks(user.email, event: eventName).pipe(_tasks); _firestore.getUserTasks(_user.username, event: eventName).pipe(_tasks);
}
/// Fetches the paths of all the images linked to this event.
Future<void> fetchImagesPaths() async {
await _ready;
_imagesPaths.sink.add(_event.media);
} }
/// Marks a task as done in the database. /// Marks a task as done in the database.
@ -55,6 +135,16 @@ class EventBloc {
} }
void dispose() async { 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(); await _tasks.drain();
_tasks.close(); _tasks.close();
} }

View file

@ -43,7 +43,7 @@ class HomeBloc {
return tasks; return tasks;
}, },
) )
.transform(searchBoxTransformer()) .transform(_searchBoxTransformer())
.transform(kTaskListPriorityTransforemer); .transform(kTaskListPriorityTransforemer);
/// An observable of the current logged in user. /// An observable of the current logged in user.
@ -52,7 +52,7 @@ class HomeBloc {
// TODO: Include the priority in the filtering. // TODO: Include the priority in the filtering.
/// Returns a stream transformer that filters the task with the text from /// Returns a stream transformer that filters the task with the text from
/// the search box. /// the search box.
StreamTransformer<List<TaskModel>, List<TaskModel>> searchBoxTransformer() { StreamTransformer<List<TaskModel>, List<TaskModel>> _searchBoxTransformer() {
return StreamTransformer.fromHandlers( return StreamTransformer.fromHandlers(
handleData: (taskList, sink) { handleData: (taskList, sink) {
sink.add( sink.add(

View file

@ -1,11 +1,13 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../utils.dart' show kBigTextStyle; import '../utils.dart' show kBlueGradient, 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/loading_indicator.dart'; import '../widgets/loading_indicator.dart';
import '../widgets/task_list_tile.dart'; import '../widgets/task_list_tile.dart';
@ -28,6 +30,7 @@ class _EventScreenState extends State<EventScreen>
super.initState(); super.initState();
bloc = EventBloc(eventName: widget.eventName); bloc = EventBloc(eventName: widget.eventName);
bloc.fetchTasks(); bloc.fetchTasks();
bloc.fetchImagesPaths();
_tabController = TabController(vsync: this, length: 2); _tabController = TabController(vsync: this, length: 2);
} }
@ -93,19 +96,72 @@ class _EventScreenState extends State<EventScreen>
); );
} }
// TODO: refactor this.
Widget buildMediaView() { Widget buildMediaView() {
return Center( return StreamBuilder(
child: FutureBuilder( stream: bloc.imagesPaths,
future: bloc.testFile, builder: (BuildContext context, AsyncSnapshot<List<String>> listSnap) {
builder: (BuildContext context, AsyncSnapshot<File> snap) { if (!listSnap.hasData) {
if (!snap.hasData) { return Center(
return Center( child: LoadingIndicator(),
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<Map<String, Future<File>>> 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<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),
);
},
);
},
); );
} },
return Image.file(snap.data); gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
}, crossAxisCount: 3,
), crossAxisSpacing: 5,
mainAxisSpacing: 5,
),
);
},
); );
} }

View file

@ -60,3 +60,10 @@ final StreamTransformer<List<TaskModel>, List<TaskModel>>
.compareTo(TaskModel.ecodedPriority(a.priority))); .compareTo(TaskModel.ecodedPriority(a.priority)));
sink.add(tasksList); sink.add(tasksList);
}); });
/// Gets the path of an image thumbnail from its original path.
String getImageThumbnailPath(String path) {
List<String> tokens = path.split('/');
tokens.last = 'thumb@' + tokens.last;
return tokens.join('/');
}