diff --git a/lib/remote/api_manager.dart b/lib/remote/api_manager.dart index cc91a59..d88b8e8 100644 --- a/lib/remote/api_manager.dart +++ b/lib/remote/api_manager.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'package:mentorship_client/failure.dart'; import 'package:mentorship_client/remote/services/auth_service.dart'; +import 'package:mentorship_client/remote/services/comment_service.dart'; import 'package:mentorship_client/remote/services/relation_service.dart'; import 'package:mentorship_client/remote/services/task_service.dart'; import 'package:mentorship_client/remote/services/user_service.dart'; @@ -16,6 +17,7 @@ class ApiManager { final UserService userService = UserService.create(); final RelationService relationService = RelationService.create(); final TaskService taskService = TaskService.create(); + final CommentService commentService = CommentService.create(); ApiManager._internal(); diff --git a/lib/remote/models/comment.dart b/lib/remote/models/comment.dart new file mode 100644 index 0000000..ae28817 --- /dev/null +++ b/lib/remote/models/comment.dart @@ -0,0 +1,17 @@ +import 'package:json_annotation/json_annotation.dart'; +part 'comment.g.dart'; +@JsonSerializable(fieldRename: FieldRename.snake) +class Comment { + final int id; + final int userId; + final int taskId; + final int relationId; + final double creationDate; + final double modificationDate; + final String comment; + + Comment(this.id, this.userId, this.taskId, this.relationId, this.creationDate, + this.modificationDate, this.comment); + factory Comment.fromJson(Map json) => _$CommentFromJson(json); + Map toJson() => _$CommentToJson(this); +} diff --git a/lib/remote/models/comment.g.dart b/lib/remote/models/comment.g.dart new file mode 100644 index 0000000..0de7932 --- /dev/null +++ b/lib/remote/models/comment.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'comment.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Comment _$CommentFromJson(Map json) { + return Comment( + json['id'] as int, + json['user_id'] as int, + json['task_id'] as int, + json['relation_id'] as int, + (json['creation_date'] as num)?.toDouble(), + (json['modification_date'] as num)?.toDouble(), + json['comment'] as String, + ); +} + +Map _$CommentToJson(Comment instance) => { + 'id': instance.id, + 'user_id': instance.userId, + 'task_id': instance.taskId, + 'relation_id': instance.relationId, + 'creation_date': instance.creationDate, + 'modification_date': instance.modificationDate, + 'comment': instance.comment, + }; diff --git a/lib/remote/models/user.dart b/lib/remote/models/user.dart index 40d4d46..4fead77 100644 --- a/lib/remote/models/user.dart +++ b/lib/remote/models/user.dart @@ -58,7 +58,7 @@ class User { ); Map toJson() { - final Map data = new Map(); + final Map data = Map(); data['id'] = this.id; data['username'] = this.username; data['name'] = this.name; diff --git a/lib/remote/repositories/comment_repository.dart b/lib/remote/repositories/comment_repository.dart new file mode 100644 index 0000000..e3337f1 --- /dev/null +++ b/lib/remote/repositories/comment_repository.dart @@ -0,0 +1,55 @@ +import 'package:mentorship_client/remote/api_manager.dart'; +import 'package:mentorship_client/remote/models/comment.dart'; +import 'package:mentorship_client/remote/requests/comment_request.dart'; +import 'package:mentorship_client/remote/responses/custom_response.dart'; + +class CommentRepository { + static final CommentRepository instance = CommentRepository._internal(); + + CommentRepository._internal(); + + Future> getAllComments(int relationId, int taskId) async { + final body = await ApiManager.callSafely( + () => ApiManager.instance.commentService.getAllComments(relationId, taskId)); + List comments = []; + + for (var json in body) { + comments.add(Comment.fromJson(json)); + } + + return comments; + } + + Future newComment( + int relationId, int taskId, CommentRequest commentRequest) async { + final body = await ApiManager.callSafely(() => ApiManager.instance.commentService.newComment( + relationId, + taskId, + commentRequest, + )); + + return CustomResponse.fromJson(body); + } + + Future editComment( + int relationId, int taskId, int commentId, CommentRequest commentRequest) async { + final body = await ApiManager.callSafely(() => ApiManager.instance.commentService.editComment( + relationId, + taskId, + commentId, + commentRequest, + )); + + return CustomResponse.fromJson(body); + } + + Future deleteCommemt(int relationId, int taskId, int commentId) async { + final body = await ApiManager.callSafely(() => ApiManager.instance.commentService.deleteComment( + relationId, + taskId, + commentId, + )); + + return CustomResponse.fromJson(body); + } +} diff --git a/lib/remote/requests/change_password.dart b/lib/remote/requests/change_password.dart index 210590b..2a730d6 100644 --- a/lib/remote/requests/change_password.dart +++ b/lib/remote/requests/change_password.dart @@ -14,7 +14,7 @@ class ChangePassword { } Map toJson() { - final Map data = new Map(); + final Map data = Map(); data["current_password"] = this.currentPassword; data["new_password"] = this.newPassword; return data; diff --git a/lib/remote/requests/comment_request.dart b/lib/remote/requests/comment_request.dart new file mode 100644 index 0000000..895f8db --- /dev/null +++ b/lib/remote/requests/comment_request.dart @@ -0,0 +1,16 @@ +import 'package:flutter/cupertino.dart'; + +class CommentRequest { + String comment; + + CommentRequest({@required this.comment}); + + factory CommentRequest.fromJson(Map json) => + CommentRequest(comment: json["comment"]); + + Map toJson() { + final Map data = Map(); + data['comment'] = this.comment; + return data; + } +} diff --git a/lib/remote/requests/relation_requests.dart b/lib/remote/requests/relation_requests.dart index 9e9ee92..1786a1c 100644 --- a/lib/remote/requests/relation_requests.dart +++ b/lib/remote/requests/relation_requests.dart @@ -19,7 +19,7 @@ class RelationRequest { ); Map toJson() { - final Map data = new Map(); + final Map data = Map(); data['mentor_id'] = this.mentorId; data['mentee_id'] = this.menteeId; data['notes'] = this.notes; diff --git a/lib/remote/requests/task_request.dart b/lib/remote/requests/task_request.dart index 6a50c77..2439018 100644 --- a/lib/remote/requests/task_request.dart +++ b/lib/remote/requests/task_request.dart @@ -9,7 +9,7 @@ class TaskRequest { TaskRequest(description: json["description"]); Map toJson() { - final Map data = new Map(); + final Map data = Map(); data['description'] = this.description; return data; } diff --git a/lib/remote/services/comment_service.chopper.dart b/lib/remote/services/comment_service.chopper.dart new file mode 100644 index 0000000..9c7e37e --- /dev/null +++ b/lib/remote/services/comment_service.chopper.dart @@ -0,0 +1,53 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'comment_service.dart'; + +// ************************************************************************** +// ChopperGenerator +// ************************************************************************** + +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations +class _$CommentService extends CommentService { + _$CommentService([ChopperClient client]) { + if (client == null) return; + this.client = client; + } + + @override + final definitionType = CommentService; + + @override + Future>> getAllComments(int relationId, int taskId) { + final $url = 'mentorship_relation/$relationId/task/$taskId/comments'; + final $request = Request('GET', $url, client.baseUrl); + return client.send, List>($request); + } + + @override + Future>> newComment( + int relationId, int taskId, CommentRequest commentRequest) { + final $url = 'mentorship_relation/$relationId/task/$taskId/comment'; + final $body = commentRequest; + final $request = Request('POST', $url, client.baseUrl, body: $body); + return client.send, Map>($request); + } + + @override + Future>> editComment(int relationId, int taskId, + int commentId, CommentRequest commentRequest) { + final $url = + 'mentorship_relation/$relationId/task/$taskId/comment/$commentId'; + final $body = commentRequest; + final $request = Request('PUT', $url, client.baseUrl, body: $body); + return client.send, Map>($request); + } + + @override + Future>> deleteComment( + int relationId, int taskId, int commentId) { + final $url = + 'mentorship_relation/$relationId/task/$taskId/comment/$commentId'; + final $request = Request('DELETE', $url, client.baseUrl); + return client.send, Map>($request); + } +} diff --git a/lib/remote/services/comment_service.dart b/lib/remote/services/comment_service.dart new file mode 100644 index 0000000..1fe5239 --- /dev/null +++ b/lib/remote/services/comment_service.dart @@ -0,0 +1,52 @@ +import 'package:chopper/chopper.dart'; +import 'package:mentorship_client/constants.dart'; +import 'package:mentorship_client/remote/auth_interceptor.dart'; +import 'package:mentorship_client/remote/requests/comment_request.dart'; + +part 'comment_service.chopper.dart'; + +@ChopperApi(baseUrl: "") +abstract class CommentService extends ChopperService { + @Get(path: "mentorship_relation/{relation_id}/task/{task_id}/comments") + Future>> getAllComments( + @Path("relation_id") int relationId, + @Path("task_id") int taskId, + ); + + @Post(path: "mentorship_relation/{relation_id}/task/{task_id}/comment") + Future>> newComment( + @Path("relation_id") int relationId, + @Path("task_id") int taskId, + @Body() CommentRequest commentRequest, + ); + + @Put(path: "mentorship_relation/{relation_id}/task/{task_id}/comment/{comment_id}") + Future>> editComment( + @Path("relation_id") int relationId, + @Path("task_id") int taskId, + @Path("comment_id") int commentId, + @Body() CommentRequest commentRequest, + ); + + @Delete(path: "mentorship_relation/{relation_id}/task/{task_id}/comment/{comment_id}") + Future>> deleteComment( + @Path("relation_id") int relationId, + @Path("task_id") int taskId, + @Path("comment_id") int commentId, + ); + + static CommentService create() { + final client = ChopperClient( + baseUrl: API_URL, + services: [ + _$CommentService(), + ], + converter: JsonConverter(), + interceptors: [ + HttpLoggingInterceptor(), + AuthInterceptor(), + ]); + + return _$CommentService(client); + } +} diff --git a/lib/screens/comment/bloc/bloc.dart b/lib/screens/comment/bloc/bloc.dart new file mode 100644 index 0000000..47056b3 --- /dev/null +++ b/lib/screens/comment/bloc/bloc.dart @@ -0,0 +1,3 @@ +export 'comment_page_bloc.dart'; +export 'comment_page_event.dart'; +export 'comment_page_state.dart'; diff --git a/lib/screens/comment/bloc/comment_page_bloc.dart b/lib/screens/comment/bloc/comment_page_bloc.dart new file mode 100644 index 0000000..92dc6b9 --- /dev/null +++ b/lib/screens/comment/bloc/comment_page_bloc.dart @@ -0,0 +1,67 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:logging/logging.dart'; +import 'package:mentorship_client/failure.dart'; +import 'package:mentorship_client/remote/models/comment.dart'; +import 'package:mentorship_client/remote/repositories/comment_repository.dart'; +import 'package:mentorship_client/remote/repositories/relation_repository.dart'; +import 'package:mentorship_client/remote/repositories/task_repository.dart'; +import 'package:mentorship_client/remote/responses/custom_response.dart'; +import 'bloc.dart'; + +class CommentPageBloc extends Bloc { + final RelationRepository relationRepository; + final TaskRepository taskRepository; + final CommentRepository commentRepository; + CommentPageBloc(this.relationRepository, this.taskRepository, this.commentRepository) + : super(CommentPageInitial()); + + @override + Stream mapEventToState(CommentPageEvent event) async* { + if (event is CommentPageShowed) { + yield CommentPageLoading(); + try { + List comments = + await commentRepository.getAllComments(event.relation.id, event.taskId); + yield CommentPageSuccess(comments); + } on Failure catch (failure) { + Logger.root.severe("CommentPageBloc: ${failure.message}"); + yield CommentPageFailure(message: failure.message); + } + } + if (event is CommentCreated) { + try { + CustomResponse response = await commentRepository.newComment( + event.relation.id, event.taskId, event.commentRequest); + var comments = await commentRepository.getAllComments(event.relation.id, event.taskId); + yield CommentPageSuccess(comments, message: response.message); + } on Failure catch (failure) { + Logger.root.severe("CommentPageBloc: ${failure.message}"); + yield CommentPageFailure(message: failure.message); + } + } + if (event is CommentEditing) { + try { + CustomResponse response = await commentRepository.editComment( + event.relation.id, event.taskId, event.commentId, event.commentRequest); + var comments = await commentRepository.getAllComments(event.relation.id, event.taskId); + yield CommentPageSuccess(comments, message: response.message); + } on Failure catch (failure) { + Logger.root.severe("CommentPageBloc: ${failure.message}"); + yield CommentPageFailure(message: failure.message); + } + } + if (event is CommentDeleted) { + try { + CustomResponse response = + await commentRepository.deleteCommemt(event.relation.id, event.taskId, event.commentId); + var comments = await commentRepository.getAllComments(event.relation.id, event.taskId); + yield CommentPageSuccess(comments, message: response.message); + } on Failure catch (failure) { + Logger.root.severe("CommentPageBloc: ${failure.message}"); + yield CommentPageFailure(message: failure.message); + } + } + } +} diff --git a/lib/screens/comment/bloc/comment_page_event.dart b/lib/screens/comment/bloc/comment_page_event.dart new file mode 100644 index 0000000..7099bc0 --- /dev/null +++ b/lib/screens/comment/bloc/comment_page_event.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; +import 'package:mentorship_client/remote/models/relation.dart'; +import 'package:mentorship_client/remote/requests/comment_request.dart'; + +abstract class CommentPageEvent extends Equatable { + const CommentPageEvent(); +} + +class CommentPageShowed extends CommentPageEvent { + final Relation relation; + final int taskId; + + CommentPageShowed(this.relation, this.taskId); + @override + List get props => [relation, taskId]; +} + +// class CommentPageRefresh extends CommentPageEvent { +// @override +// List get props => null; +// } + +class CommentCreated extends CommentPageEvent { + final Relation relation; + final int taskId; + final CommentRequest commentRequest; + + CommentCreated(this.relation, this.taskId, this.commentRequest); + + @override + List get props => [relation, taskId, commentRequest]; +} + +class CommentEditing extends CommentPageEvent { + final Relation relation; + final int taskId; + final int commentId; + final CommentRequest commentRequest; + + CommentEditing(this.relation, this.taskId, this.commentId, this.commentRequest); + + @override + List get props => [relation, taskId]; +} + +class CommentDeleted extends CommentPageEvent { + final Relation relation; + final int taskId; + final int commentId; + + CommentDeleted(this.relation, this.taskId, this.commentId); + + @override + List get props => [relation, taskId]; +} diff --git a/lib/screens/comment/bloc/comment_page_state.dart b/lib/screens/comment/bloc/comment_page_state.dart new file mode 100644 index 0000000..8c0271a --- /dev/null +++ b/lib/screens/comment/bloc/comment_page_state.dart @@ -0,0 +1,40 @@ +import 'package:equatable/equatable.dart'; +import 'package:mentorship_client/remote/models/comment.dart'; + +abstract class CommentPageState extends Equatable { + final String message; + + const CommentPageState({this.message}); + + @override + List get props => [message]; +} + +class CommentPageInitial extends CommentPageState { + CommentPageInitial({String message}) : super(message: message); + + @override + List get props => [message]; +} + +class CommentPageLoading extends CommentPageState { + CommentPageLoading({String message}) : super(message: message); +} + +class CommentPageSuccess extends CommentPageState { + // final Relation relation; + // final int taskId; + final List comments; + + CommentPageSuccess(this.comments, {String message}) : super(message: message); + + @override + List get props => [message, comments]; +} + +class CommentPageFailure extends CommentPageState { + CommentPageFailure({String message}) : super(message: message); + + @override + List get props => [message]; +} diff --git a/lib/screens/comment/comments_page.dart b/lib/screens/comment/comments_page.dart new file mode 100644 index 0000000..c04dd41 --- /dev/null +++ b/lib/screens/comment/comments_page.dart @@ -0,0 +1,310 @@ +import 'dart:async'; + +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; +import 'package:mentorship_client/remote/models/relation.dart'; +import 'package:mentorship_client/remote/models/task.dart'; +import 'package:mentorship_client/remote/repositories/comment_repository.dart'; +import 'package:mentorship_client/remote/repositories/relation_repository.dart'; +import 'package:mentorship_client/remote/repositories/task_repository.dart'; +import 'package:mentorship_client/remote/requests/comment_request.dart'; +import 'package:mentorship_client/screens/comment/bloc/bloc.dart'; +import 'package:mentorship_client/widgets/loading_indicator.dart'; +import 'package:toast/toast.dart'; + +class CommentsPage extends StatefulWidget { + final Relation relation; + final Task task; + CommentsPage({this.task, this.relation}); + + @override + _CommentsPageState createState() => _CommentsPageState(); +} + +class _CommentsPageState extends State { + ScrollController _scrollController = ScrollController(); + final _textController = TextEditingController(); + + @override + void dispose() { + _scrollController.dispose(); + _textController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final focus = FocusNode(); + return BlocProvider( + create: (context) => CommentPageBloc( + RelationRepository.instance, + TaskRepository.instance, + CommentRepository.instance, + )..add(CommentPageShowed(widget.relation, widget.task.id)), + child: Scaffold( + appBar: AppBar( + title: Text("Comments"), + ), + body: BlocBuilder( + builder: (context, state) { + if (state is CommentPageSuccess) { + Timer( + Duration(milliseconds: 0), + () => _scrollController.jumpTo(_scrollController.position.maxScrollExtent), + ); + return Stack( + children: [ + state.comments.isEmpty + ? Center( + child: Text("You can add comments about the task here"), + ) + : Padding( + padding: EdgeInsets.only( + top: MediaQuery.of(context).size.height * 0.1, + bottom: MediaQuery.of(context).size.height * 0.08), + child: ListView.separated( + separatorBuilder: (context, index) => Divider(), + controller: _scrollController, + scrollDirection: Axis.vertical, + shrinkWrap: true, + itemCount: state.comments.length, + itemBuilder: (context, i) { + return GestureDetector( + onLongPressStart: (LongPressStartDetails details) { + _showPopupMenu( + context, + details.globalPosition, + _textController, + widget.relation, + widget.task.id, + state.comments[i].id, + state.comments[i].comment, + ); + }, + child: ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + readTimestamp(state.comments[i].creationDate).toString()), + Text(state.comments[i].comment), + ], + ), + ), + ); + }, + ), + ), + Container( + padding: EdgeInsets.all(8), + child: AutoSizeText( + "Task: " + widget.task.description, + style: TextStyle(fontSize: MediaQuery.of(context).size.height * 0.04), + ), + width: double.infinity, + color: Colors.grey[100], + height: MediaQuery.of(context).size.height * 0.1, + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + color: Colors.white, + child: Padding( + padding: EdgeInsets.all(MediaQuery.of(context).size.height * 0.01), + child: TextField( + focusNode: focus, + controller: _textController, + decoration: InputDecoration( + hintText: "Add your comment.", + suffixIcon: IconButton( + icon: Icon(Icons.send), + onPressed: () { + BlocProvider.of(context) + ..add( + CommentCreated( + widget.relation, + widget.task.id, + CommentRequest( + comment: _textController.text, + ), + ), + ); + _textController.clear(); + FocusScope.of(context).requestFocus(FocusNode()); + Toast.show("Sending...", context, duration: 1); + }), + ), + ), + ), + ), + ), + ], + ); + } + if (state is CommentPageFailure) { + return Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(state.message), + RaisedButton( + child: Text("Go Back"), + onPressed: () => Navigator.pop(context), + ), + ], + ), + ); + } + if (state is CommentPageLoading) { + return LoadingIndicator(); + } + return LoadingIndicator(); + }, + ), + ), + ); + } +} + +void _showPopupMenu(context, Offset offset, TextEditingController textEditingController, + Relation relation, int taskId, int commentId, String comment) async { + // ignore: close_sinks + final bloc = BlocProvider.of(context); + + double left = offset.dx; + double top = offset.dy; + await showMenu( + context: context, + position: RelativeRect.fromLTRB(left, top, 100000, 0), + items: [ + PopupMenuItem( + child: MaterialButton( + elevation: 0, + child: Text("Delete comment"), + onPressed: () { + Navigator.pop(context); + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text("Delete comment"), + content: Text("Are you sure you want to delete this comment?"), + actions: [ + FlatButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text("Cancel"), + ), + FlatButton( + child: Text("Delete"), + onPressed: () { + bloc.add( + CommentDeleted( + relation, + taskId, + commentId, + ), + ); + Navigator.pop(context); + Toast.show("Deleting...", context, duration: 1); + }, + ), + ], + ), + ); + }, + ), + ), + PopupMenuItem( + child: MaterialButton( + elevation: 0, + child: Text("Edit comment"), + onPressed: () { + Navigator.pop(context); + _showDialog(context, comment, relation, taskId, commentId); + // FocusScope.of(context).requestFocus(focus); + }, + ), + ), + ], + ); +} + +_showDialog(context, String comment, Relation relation, int taskId, int commentId) async { + TextEditingController editingController = TextEditingController()..text = comment; + // ignore: close_sinks + final bloc = BlocProvider.of(context); + await showDialog( + context: context, + child: AlertDialog( + contentPadding: const EdgeInsets.all(16.0), + content: Row( + children: [ + Expanded( + child: TextField( + controller: editingController, + decoration: InputDecoration(labelText: 'Edit comment'), + ), + ) + ], + ), + actions: [ + FlatButton( + child: const Text('Cancel'), + onPressed: () { + Navigator.pop(context); + }), + FlatButton( + child: Text('Edit'), + onPressed: () { + bloc.add( + CommentEditing( + relation, + taskId, + commentId, + CommentRequest( + comment: editingController.text, + ), + ), + ); + Navigator.pop(context); + Toast.show("Editing...", context, duration: 1); + }, + ) + ], + ), + ); +} + +String readTimestamp(double timestamp) { + var now = DateTime.now(); + var format = DateFormat('HH:mm a'); + var date = DateTime.fromMillisecondsSinceEpoch((timestamp * 1000).toInt()); + var diff = now.difference(date); + var time = ''; + + if (diff.inSeconds <= 0 || + diff.inSeconds > 0 && diff.inMinutes == 0 || + diff.inMinutes > 0 && diff.inHours == 0 || + diff.inHours > 0 && diff.inDays == 0) { + time = format.format(date); + } else if (diff.inDays > 0 && diff.inDays < 7) { + if (diff.inDays == 1) { + time = diff.inDays.toString() + ' day ago'; + } else { + time = diff.inDays.toString() + ' days ago'; + } + } else { + if (diff.inDays == 7) { + time = (diff.inDays / 7).floor().toString() + ' week ago'; + } else { + time = (diff.inDays / 7).floor().toString() + ' weeks ago'; + } + } + + return time; +} diff --git a/lib/screens/home/pages/relation/relation_page.dart b/lib/screens/home/pages/relation/relation_page.dart index 63dc930..b66d5b5 100644 --- a/lib/screens/home/pages/relation/relation_page.dart +++ b/lib/screens/home/pages/relation/relation_page.dart @@ -10,6 +10,7 @@ import 'package:mentorship_client/remote/requests/task_request.dart'; import 'package:mentorship_client/screens/home/bloc/bloc.dart'; import 'package:mentorship_client/screens/home/bloc/home_bloc.dart'; import 'package:mentorship_client/screens/home/pages/relation/bloc/bloc.dart'; +import 'package:mentorship_client/screens/comment/comments_page.dart'; import 'package:mentorship_client/widgets/bold_text.dart'; import 'package:mentorship_client/widgets/loading_indicator.dart'; import 'package:auto_size_text/auto_size_text.dart'; @@ -40,8 +41,7 @@ class _RelationPageState extends State { Tab(text: "Tasks".toUpperCase()), ], ), - body: BlocConsumer( - listener: (context, state) { + body: BlocConsumer(listener: (context, state) { if (state.message != null && state is RelationPageSuccess) { context.showSnackBar(state.message); Navigator.of(context).pop(); @@ -142,10 +142,8 @@ class _RelationPageState extends State { children: [ BoldText("Mentor: ", state.relation.mentor.name), BoldText("Mentee: ", state.relation.mentee.name), - BoldText( - "End date: ", - DateTimeX.fromTimestamp(state.relation.endsOn) - .toDateString()), + BoldText("End date: ", + DateTimeX.fromTimestamp(state.relation.endsOn).toDateString()), BoldText("Notes: ", state.relation.notes), ], ), @@ -155,8 +153,7 @@ class _RelationPageState extends State { ), RaisedButton( color: Theme.of(context).accentColor, - child: Text("Cancel".toUpperCase(), - style: TextStyle(color: Colors.white)), + child: Text("Cancel".toUpperCase(), style: TextStyle(color: Colors.white)), onPressed: () { //ignore: close_sinks final bloc = BlocProvider.of(context); @@ -166,14 +163,12 @@ class _RelationPageState extends State { builder: (context) { return AlertDialog( title: Text("Cancel Relation"), - content: Text( - "Are you sure you want to cancel the relation"), + content: Text("Are you sure you want to cancel the relation"), actions: [ FlatButton( child: Text("Yes"), onPressed: () { - bloc.add(RelationPageCancelledRelation( - state.relation.id)); + bloc.add(RelationPageCancelledRelation(state.relation.id)); Navigator.of(context).pop(); }, ), @@ -236,7 +231,6 @@ class _RelationPageState extends State { ), ), expanded: _buildListView( - context: context, state: state, tasksList: _toDoTasks, @@ -267,8 +261,7 @@ class _RelationPageState extends State { ); } - Widget _buildListView( - {BuildContext context, List tasksList, RelationPageSuccess state}) { + Widget _buildListView({BuildContext context, List tasksList, RelationPageSuccess state}) { return ListView.builder( physics: ClampingScrollPhysics(), shrinkWrap: true, @@ -277,38 +270,30 @@ class _RelationPageState extends State { Task task = tasksList[index]; //ignore: close_sinks final bloc = BlocProvider.of(context); - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - GestureDetector( - onTap: () { - if (!task.isDone) { - bloc.add(TaskCompleted(state.relation, task.id)); - showProgressIndicator(context); - } else - context.toast("Task already achieved."); - }, - child: Checkbox( - value: task.isDone, - ), + return ListTile( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CommentsPage( + relation: state.relation, + task: task, ), - Text(task.description), - ], - ), - IconButton( - icon: Icon( - Icons.delete, - color: Colors.grey[700], ), - onPressed: () { + ); + }, + title: Text( + task.description, + maxLines: 2, + ), + leading: Checkbox( + value: task.isDone, + onChanged: (value) { + if (!task.isDone) { showDialog( context: context, builder: (context) => AlertDialog( - title: Text("Delete task"), - content: Text("Are you sure you want to delete the task?"), + title: Text("Complete Task"), + content: Text("Mark task as completed?"), actions: [ FlatButton( onPressed: () { @@ -317,9 +302,9 @@ class _RelationPageState extends State { child: Text("Cancel"), ), FlatButton( - child: Text("Delete"), + child: Text("Yes"), onPressed: () { - bloc.add(TaskDeleted(state.relation, task.id)); + bloc.add(TaskCompleted(state.relation, task.id)); Navigator.of(context).pop(); showProgressIndicator(context); }, @@ -327,9 +312,41 @@ class _RelationPageState extends State { ], ), ); - }, + } else + context.toast("Task already achieved."); + }, + ), + trailing: IconButton( + icon: Icon( + Icons.delete, + color: Colors.grey[700], ), - ], + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text("Delete task"), + content: Text("Are you sure you want to delete the task?"), + actions: [ + FlatButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text("Cancel"), + ), + FlatButton( + child: Text("Delete"), + onPressed: () { + bloc.add(TaskDeleted(state.relation, task.id)); + Navigator.of(context).pop(); + showProgressIndicator(context); + }, + ), + ], + ), + ); + }, + ), ); }, ); diff --git a/lib/screens/login/login_screen.dart b/lib/screens/login/login_screen.dart index 05f823f..5e0b319 100644 --- a/lib/screens/login/login_screen.dart +++ b/lib/screens/login/login_screen.dart @@ -23,7 +23,7 @@ class LoginScreen extends StatelessWidget { SizedBox(height: 24), Padding( padding: EdgeInsets.symmetric(horizontal: 24), - child: LoginForm(), + child: LoginForm(), ), ], ), diff --git a/lib/screens/send_request/send_request_screen.dart b/lib/screens/send_request/send_request_screen.dart index 6eb9228..f014418 100644 --- a/lib/screens/send_request/send_request_screen.dart +++ b/lib/screens/send_request/send_request_screen.dart @@ -7,7 +7,6 @@ import 'package:mentorship_client/remote/repositories/relation_repository.dart'; import 'package:mentorship_client/remote/requests/relation_requests.dart'; import 'package:mentorship_client/screens/send_request/bloc/bloc.dart'; import 'package:mentorship_client/widgets/loading_indicator.dart'; -import 'package:toast/toast.dart'; class SendRequestScreen extends StatefulWidget { final User otherUser; @@ -30,7 +29,6 @@ class _SendRequestScreenState extends State { @override Widget build(BuildContext context) { //ignore: close_sinks - return BlocProvider( create: (context) => SendRequestBloc(relationRepository: RelationRepository.instance), child: Scaffold( diff --git a/pubspec.lock b/pubspec.lock index dd4a0fe..ff5ded0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -35,7 +35,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.5.0-nullsafety" auto_size_text: dependency: "direct main" description: @@ -49,14 +49,14 @@ packages: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "6.0.1" + version: "6.0.2" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety" bottom_navy_bar: dependency: "direct main" description: @@ -120,13 +120,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.0.9" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0-nullsafety.2" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0-nullsafety" checked_yaml: dependency: transitive description: @@ -161,7 +168,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0-nullsafety" code_builder: dependency: transitive description: @@ -175,7 +182,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" + version: "1.15.0-nullsafety.2" color: dependency: transitive description: @@ -238,7 +245,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.0-nullsafety" fixnum: dependency: transitive description: @@ -373,6 +380,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + json_serializable: + dependency: "direct main" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "3.4.0" logging: dependency: transitive description: @@ -386,14 +400,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10-nullsafety" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0-nullsafety.2" mime: dependency: transitive description: @@ -442,7 +456,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety" pedantic: dependency: transitive description: @@ -531,28 +545,28 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.5" + version: "0.9.6" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0-nullsafety" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety" stream_transform: dependency: transitive description: @@ -566,21 +580,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0-nullsafety" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0-nullsafety" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.16" + version: "0.2.19-nullsafety" timing: dependency: transitive description: @@ -601,7 +615,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0-nullsafety.2" url_launcher: dependency: "direct main" description: @@ -643,7 +657,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0-nullsafety.2" watcher: dependency: transitive description: @@ -673,5 +687,5 @@ packages: source: hosted version: "2.2.0" sdks: - dart: ">=2.7.0 <3.0.0" + dart: ">=2.10.0-0.0.dev <2.10.0" flutter: ">=1.16.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4e2d84c..278ed03 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,8 +33,9 @@ dependencies: expandable: ^4.1.4 auto_size_text: ^2.1.0 flappy_search_bar: ^1.7.2 + json_serializable: ^3.4.0 # Bloc - bloc: ^6.0.1 + bloc: ^6.0.2 flutter_bloc: ^6.0.1 chopper: ^3.0.3