diff --git a/lib/src/blocs/event_bloc.dart b/lib/src/blocs/event_bloc.dart index dedc3eb..a41e2d0 100644 --- a/lib/src/blocs/event_bloc.dart +++ b/lib/src/blocs/event_bloc.dart @@ -2,5 +2,20 @@ import 'dart:async'; import '../resources/firestore_provider.dart'; import '../resources/firebase_storage_provider.dart'; +import '../services/current_selection_service.dart'; -class EventBloc {} +/// A business logic component that manages the state for an event screen. +class EventBloc { + /// An instance of the current selection service. + final CurrentSelectionService _selectionService = currentSelectionService; + + /// The name of the event that's currently selected. + /// + /// Read only. + String get selectedEventName => _selectionService.event.name; + + /// Fetches the tasks for the current user that a part of the currently + /// selected event. + void fetchTasks() {} + void dispose() {} +} diff --git a/lib/src/blocs/home_bloc.dart b/lib/src/blocs/home_bloc.dart index c6e2f76..2be381a 100644 --- a/lib/src/blocs/home_bloc.dart +++ b/lib/src/blocs/home_bloc.dart @@ -17,7 +17,7 @@ class HomeBloc { final AuthService _auth = authService; /// An instance of the firebase repository. - final FirestoreProvider _repository = firestoreProvider; + final FirestoreProvider _firestore = firestoreProvider; /// An instance of the current task service. final CurrentSelectionService _selectionService = currentSelectionService; @@ -90,7 +90,7 @@ class HomeBloc { /// Fetches the tasks for the current user. Future fetchTasks() async { final user = await _auth.currentUser; - _repository.getUserTasks(user.email).pipe(_tasks); + _firestore.getUserTasks(user.email).pipe(_tasks); } /// Returns a future of the avatar url for the current user. @@ -107,17 +107,24 @@ class HomeBloc { /// Marks a task as done in the database. void markTaskAsDone(TaskModel task) async { - _repository.updateTask( + _firestore.updateTask( task.id, done: true, ); } - /// Sets the global current task. - void updateCurrentTask(TaskModel task) { + /// Sets the global selected task. + void updateSelectedTask(TaskModel task) { _selectionService.updateSelectedTask(task); } + /// Updated the global selected event. + void updateSelectedEvent(TaskModel task) async { + final userModel = await _auth.getCurrentUserModel(); + final event = await _firestore.getEvent(userModel.id, eventId: task.event); + _selectionService.updateSelectedEvent(event); + } + /// Updates the serach box text. void updateSearchBoxText(String newText) { _searchBoxText.add(newText); diff --git a/lib/src/blocs/task_bloc.dart b/lib/src/blocs/task_bloc.dart index ef022c4..fcfecd9 100644 --- a/lib/src/blocs/task_bloc.dart +++ b/lib/src/blocs/task_bloc.dart @@ -29,11 +29,16 @@ class TaskBloc extends Object with Validators { /// A subject of task text. final _taskText = BehaviorSubject(); + /// A subject of the text of the current global task. + final _textInitialValue = BehaviorSubject(); + /// The priority of the current task. TaskPriority priority = TaskPriority.high; - /// The text of the current global task. - String get textInitialValue => _selectionService.task.text; + /// The id of the current task. + /// + /// Only to be used if editing an existing task. + String taskId; //Stream getters. /// An observable of the current user model. @@ -50,6 +55,9 @@ class TaskBloc extends Object with Validators { Observable get submitEnabled => Observable.combineLatest2(eventName, taskText, (a, b) => true); + /// An observable of the text of the global selected task. + Observable get textInitialvalue => _textInitialValue.stream; + //Sinks getters. /// Changes the current task event name. Function(String) get changeEventName => _eventName.sink.add; @@ -67,12 +75,13 @@ class TaskBloc extends Object with Validators { } //TODO: Figure out how to update the event and user properties if needed. + // as in the number of pending high tasks for example. /// Saves or updates the current task in the database. Future submit(isEdit) { if (isEdit) { return _firestore.updateTask( - _selectionService.task.id, + taskId, text: _taskText.value, priority: TaskModel.ecodedPriority(priority), ); @@ -97,14 +106,20 @@ class TaskBloc extends Object with Validators { /// Grabs the data from the current global task and pipes it to the local /// streams. void populateWithCurrentTask() { - TaskModel currentTask = _selectionService.task; - if (currentTask != null) { - changeEventName(currentTask.event); - changeTaskText(currentTask.text); - } + // We only want the task that was just selected and stop listening + // after receiving it. + _selectionService.task.take(1).listen( + (TaskModel task) { + _textInitialValue.sink.add(task.text); + changeEventName(task.event); + changeTaskText(task.text); + taskId = task.id; + }, + ); } void dispose() { + _textInitialValue.close(); _taskText.close(); _user.close(); _eventName.close(); diff --git a/lib/src/screens/event_screen.dart b/lib/src/screens/event_screen.dart index 1c346a8..24aaf5d 100644 --- a/lib/src/screens/event_screen.dart +++ b/lib/src/screens/event_screen.dart @@ -1,5 +1,24 @@ import 'package:flutter/material.dart'; -class EventScreen extends StatelessWidget { - Widget build(BuildContext context) {} +import '../blocs/event_bloc.dart'; +import '../widgets/custom_app_bar.dart'; + +class EventScreen extends StatefulWidget { + _EventScreenState createState() => _EventScreenState(); +} + +class _EventScreenState extends State { + final EventBloc bloc = EventBloc(); + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + title: '', + ), + ); + } + + void dispose() { + bloc.dispose(); + super.dispose(); + } } diff --git a/lib/src/screens/home_screen.dart b/lib/src/screens/home_screen.dart index 460a498..fc61bd5 100644 --- a/lib/src/screens/home_screen.dart +++ b/lib/src/screens/home_screen.dart @@ -92,8 +92,11 @@ class _HomeScreenState extends State { child: TaskListTile( task: task, onDone: () => bloc.markTaskAsDone(task), - onEdit: () { - bloc.updateCurrentTask(task); + onEventPressed: () { + bloc.updateSelectedEvent(task); + }, + onEditPressed: () { + bloc.updateSelectedTask(task); Navigator.of(context).pushNamed('editTask/'); }, ), diff --git a/lib/src/screens/task_screen.dart b/lib/src/screens/task_screen.dart index 5ae41e0..58c898a 100644 --- a/lib/src/screens/task_screen.dart +++ b/lib/src/screens/task_screen.dart @@ -26,16 +26,10 @@ class _TaskScreenState extends State { /// An instance of this screen's bloc. final TaskBloc bloc = TaskBloc(); - /// The initial value for the text field. - /// - /// This only gets used whent the screen is set to edit. - String textFieldInitialValue; - initState() { if (widget.isEdit) { bloc.populateWithCurrentTask(); } - textFieldInitialValue = bloc.textInitialValue; super.initState(); } @@ -50,11 +44,21 @@ class _TaskScreenState extends State { padding: const EdgeInsets.only(top: 15.0, left: 20.0, right: 20.0), child: Column( children: [ - BigTextInput( - initialValue: widget.isEdit ? textFieldInitialValue : '', - height: 95, - onChanged: bloc.changeTaskText, - ), + StreamBuilder( + stream: bloc.textInitialvalue, + builder: + (BuildContext context, AsyncSnapshot snapshot) { + String textFieldInitialValue = ''; + if (snapshot.hasData) { + textFieldInitialValue = snapshot.data; + } + return BigTextInput( + initialValue: + widget.isEdit ? textFieldInitialValue : '', + height: 95, + onChanged: bloc.changeTaskText, + ); + }), SizedBox( height: 15, ), diff --git a/lib/src/services/auth_service.dart b/lib/src/services/auth_service.dart index e0341e7..e80fd9e 100644 --- a/lib/src/services/auth_service.dart +++ b/lib/src/services/auth_service.dart @@ -63,6 +63,15 @@ class AuthService { return _googleSignInProvider.signOut(); } + /// Returns the model that represents the current logged in user. + Future getCurrentUserModel() async { + final user = await _googleSignInProvider.getCurrentUser(); + if (user != null) { + return _firestoreProvider.getUser(username: user.email); + } + return null; + } + void dispose() { _user.close(); } diff --git a/lib/src/services/current_selection_service.dart b/lib/src/services/current_selection_service.dart index 7909cba..4a2ccff 100644 --- a/lib/src/services/current_selection_service.dart +++ b/lib/src/services/current_selection_service.dart @@ -1,3 +1,5 @@ +import 'package:rxdart/rxdart.dart'; + import '../models/event_model.dart'; import '../models/task_model.dart'; @@ -7,25 +9,26 @@ import '../models/task_model.dart'; /// can grab the user selection for this service. class CurrentSelectionService { /// The current selected task. - TaskModel _selectedTask; + final _selectedTask = BehaviorSubject(); /// The current selected event. - EventModel _selectedEvent; + final _selectedEvent = BehaviorSubject(); - /// The current selected task. - TaskModel get task => _selectedTask; + /// An observable of the current selected event. + Observable get task => _selectedTask.stream; /// The current selected event. - EventModel get event => _selectedEvent; + Observable get event => _selectedEvent.stream; /// Updates the current selected task. - void updateSelectedTask(TaskModel newTask) { - _selectedTask = newTask; - } + Function(TaskModel) get updateSelectedTask => _selectedTask.sink.add; // Updates the current selected event. - void updateSelectedEvent(EventModel newEvent) { - _selectedEvent = newEvent; + Function(EventModel) get updateSelectedEvent => _selectedEvent.sink.add; + + dispose() { + _selectedEvent.close(); + _selectedTask.close(); } } diff --git a/lib/src/widgets/big_text_input.dart b/lib/src/widgets/big_text_input.dart index 3e5d61d..019ceac 100644 --- a/lib/src/widgets/big_text_input.dart +++ b/lib/src/widgets/big_text_input.dart @@ -22,7 +22,7 @@ class BigTextInput extends StatefulWidget { this.height, this.width, this.elevated = true, - this.initialValue, + this.initialValue = '', }); @override @@ -33,6 +33,13 @@ class _BigTextInputState extends State { /// Custom controller for the text input. TextEditingController _controller; + /// Flag to indicate if the initial value has been set or not. + /// + /// Without this flag the text property of the controller will be set + /// continuously and the cursor inside the text field will return to the + /// origin when tapped. + bool initialValueWasSet = false; + /// Setus up the controller. void initState() { _controller = TextEditingController(text: widget.initialValue); @@ -40,6 +47,17 @@ class _BigTextInputState extends State { super.initState(); } + // Set the text property of the controller if the newly rendered widget + // contains a non empty initial value. It should only be done once, otherwise + // the cursor will continuously return to the start of the input when tapped. + void didUpdateWidget(oldWidget) { + if (widget.initialValue != '' && !initialValueWasSet) { + _controller.text = widget.initialValue; + initialValueWasSet = true; + } + super.didUpdateWidget(oldWidget); + } + /// Calls [onChanged] when updates are sent by the controller. void controllerListener() { widget.onChanged(_controller.text); diff --git a/lib/src/widgets/task_list_tile.dart b/lib/src/widgets/task_list_tile.dart index d6922bd..6d40653 100644 --- a/lib/src/widgets/task_list_tile.dart +++ b/lib/src/widgets/task_list_tile.dart @@ -14,7 +14,7 @@ class TaskListTile extends StatelessWidget { final VoidCallback onDone; /// Function to be called when the "edit" button is pressed. - final VoidCallback onEdit; + final VoidCallback onEditPressed; /// Function to be called when the "event" button is pressed. final VoidCallback onEventPressed; @@ -31,7 +31,7 @@ class TaskListTile extends StatelessWidget { TaskListTile({ @required this.task, this.onDone, - this.onEdit, + this.onEditPressed, this.onEventPressed, }) : assert(task != null); @@ -115,7 +115,7 @@ class TaskListTile extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.end, children: [ ActionButton( - onPressed: onEdit, + onPressed: onEditPressed, text: 'Edit', leadingIconData: Icons.edit, ),