From 02a6cd763879f4f42b072565d2312a0ac30b1cc0 Mon Sep 17 00:00:00 2001 From: susmonteiro Date: Thu, 29 Sep 2022 15:20:58 +0100 Subject: [PATCH 01/73] create team-management branch --- frontend/lib/routes/teams/TeamsTable.dart | 135 ++++++++++++---------- 1 file changed, 75 insertions(+), 60 deletions(-) diff --git a/frontend/lib/routes/teams/TeamsTable.dart b/frontend/lib/routes/teams/TeamsTable.dart index 6f2093d5..87d83e08 100644 --- a/frontend/lib/routes/teams/TeamsTable.dart +++ b/frontend/lib/routes/teams/TeamsTable.dart @@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:frontend/components/eventNotifier.dart'; +import 'package:frontend/components/router.dart'; import 'package:frontend/main.dart'; import 'package:frontend/models/member.dart'; import 'package:frontend/components/ListViewCard.dart'; @@ -37,73 +38,87 @@ class _TeamTableState extends State Widget build(BuildContext context) { super.build(context); - return NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), + return Scaffold( + body: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), + ), ), - ), - ], - body: FutureBuilder( - future: Future.wait([ - _teamService.getTeams( - event: Provider.of(context).event.id) - ]), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.hasError) { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ], + body: FutureBuilder( + future: Future.wait([ + _teamService.getTeams( + event: Provider.of(context).event.id) + ]), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('An error has occured. Please contact the admins'), - duration: Duration(seconds: 4), - ), - ); - return Center( - child: Icon( - Icons.error, - size: 200, - )); - } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('An error has occured. Please contact the admins'), + duration: Duration(seconds: 4), + ), + ); + return Center( + child: Icon( + Icons.error, + size: 200, + )); + } - List> data = snapshot.data as List>; + List> data = snapshot.data as List>; - List tms = data[0] as List; + List tms = data[0] as List; - tms.sort((a, b) => a.name!.compareTo(b.name!)); + tms.sort((a, b) => a.name!.compareTo(b.name!)); - return RefreshIndicator( - onRefresh: () { - return Future.delayed(Duration.zero, () { - setState(() {}); - }); - }, - child: ListView.builder( - itemCount: tms.length, - itemBuilder: (context, index) => - TeamMemberRow(team: tms[index]), - addAutomaticKeepAlives: true, - physics: const AlwaysScrollableScrollPhysics(), - ), - ); - } else { - return Shimmer.fromColors( - baseColor: Colors.grey[400]!, - highlightColor: Colors.white, - child: ListView.builder( - itemCount: 5, - itemBuilder: (context, index) => TeamMemberRow.fake(), - addAutomaticKeepAlives: true, - physics: const BouncingScrollPhysics( - parent: AlwaysScrollableScrollPhysics()), - ), - ); - } + return RefreshIndicator( + onRefresh: () { + return Future.delayed(Duration.zero, () { + setState(() {}); + }); + }, + child: ListView.builder( + itemCount: tms.length, + itemBuilder: (context, index) => + TeamMemberRow(team: tms[index]), + addAutomaticKeepAlives: true, + physics: const AlwaysScrollableScrollPhysics(), + ), + ); + } else { + return Shimmer.fromColors( + baseColor: Colors.grey[400]!, + highlightColor: Colors.white, + child: ListView.builder( + itemCount: 5, + itemBuilder: (context, index) => TeamMemberRow.fake(), + addAutomaticKeepAlives: true, + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics()), + ), + ); + } + }, + ), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, + floatingActionButton: FloatingActionButton.extended( + onPressed: () { + Navigator.pushNamed( + context, + Routes.AddSpeaker, + ); }, + label: const Text('Create New Team'), + icon: const Icon(Icons.person_add), + backgroundColor: Colors.indigo, ), ); } From 7dee224cab258c3b3f8497910fec5643495f3020 Mon Sep 17 00:00:00 2001 From: susmonteiro Date: Sat, 1 Oct 2022 15:28:39 +0100 Subject: [PATCH 02/73] Form to create new team --- frontend/lib/routes/teams/TeamsTable.dart | 45 +++++++++++++++++------ 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/frontend/lib/routes/teams/TeamsTable.dart b/frontend/lib/routes/teams/TeamsTable.dart index 87d83e08..864ae340 100644 --- a/frontend/lib/routes/teams/TeamsTable.dart +++ b/frontend/lib/routes/teams/TeamsTable.dart @@ -1,10 +1,5 @@ -import 'dart:html'; - -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:frontend/components/eventNotifier.dart'; -import 'package:frontend/components/router.dart'; import 'package:frontend/main.dart'; import 'package:frontend/models/member.dart'; import 'package:frontend/components/ListViewCard.dart'; @@ -110,18 +105,46 @@ class _TeamTableState extends State ), floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, floatingActionButton: FloatingActionButton.extended( - onPressed: () { - Navigator.pushNamed( - context, - Routes.AddSpeaker, - ); - }, + onPressed: showCreateTeamDialog, label: const Text('Create New Team'), icon: const Icon(Icons.person_add), backgroundColor: Colors.indigo, ), ); } + + showCreateTeamDialog() { + String name = ""; + return showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text("Create New Team"), + content: TextField( + onChanged: (value) { + name = value; + }, + decoration: const InputDecoration( + hintText: "Insert the name of the new team"), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, "Cancel"), + child: const Text("Cancel"), + ), + TextButton( + onPressed: () => createTeam(name), + child: const Text("Create"), + ), + ], + ), + ); + } + + void createTeam(String name) async { + await _teamService.createTeam(name); + setState(() {}); + Navigator.pop(context, "Create"); + } } class TeamMemberRow extends StatelessWidget { From 888f1b1f93ef44d15a420aef5ed421c0ea1cd138 Mon Sep 17 00:00:00 2001 From: susmonteiro Date: Sat, 1 Oct 2022 15:49:01 +0100 Subject: [PATCH 03/73] Change dark theme color of button --- frontend/lib/routes/teams/TeamsTable.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/lib/routes/teams/TeamsTable.dart b/frontend/lib/routes/teams/TeamsTable.dart index 864ae340..ce9f98b0 100644 --- a/frontend/lib/routes/teams/TeamsTable.dart +++ b/frontend/lib/routes/teams/TeamsTable.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:frontend/components/deckTheme.dart'; import 'package:frontend/components/eventNotifier.dart'; import 'package:frontend/main.dart'; import 'package:frontend/models/member.dart'; @@ -105,11 +106,12 @@ class _TeamTableState extends State ), floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, floatingActionButton: FloatingActionButton.extended( - onPressed: showCreateTeamDialog, - label: const Text('Create New Team'), - icon: const Icon(Icons.person_add), - backgroundColor: Colors.indigo, - ), + onPressed: showCreateTeamDialog, + label: const Text('Create New Team'), + icon: const Icon(Icons.person_add), + backgroundColor: Provider.of(context).isDark + ? Colors.grey[500] + : Colors.indigo), ); } From 4f011db3a476deda097483fd24c641e82aa47c8d Mon Sep 17 00:00:00 2001 From: susmonteiro Date: Sat, 1 Oct 2022 16:56:55 +0100 Subject: [PATCH 04/73] Create speed dial on team screen --- frontend/lib/routes/member/MemberScreen.dart | 15 ++- frontend/lib/routes/teams/TeamScreen.dart | 104 +++++++++++++------ frontend/pubspec.yaml | 3 +- 3 files changed, 85 insertions(+), 37 deletions(-) diff --git a/frontend/lib/routes/member/MemberScreen.dart b/frontend/lib/routes/member/MemberScreen.dart index 86ffebad..d41e4a66 100644 --- a/frontend/lib/routes/member/MemberScreen.dart +++ b/frontend/lib/routes/member/MemberScreen.dart @@ -186,7 +186,8 @@ class _MemberBannerState extends State { class DisplayParticipations extends StatefulWidget { final Member member; final bool small; - const DisplayParticipations({Key? key, required this.member, required this.small}) + const DisplayParticipations( + {Key? key, required this.member, required this.small}) : super(key: key); @override @@ -201,7 +202,8 @@ class _DisplayParticipationsState extends State { @override void initState() { super.initState(); - this.memberParticipations = memberService.getMemberParticipations(widget.member.id); + this.memberParticipations = + memberService.getMemberParticipations(widget.member.id); } @override @@ -210,7 +212,8 @@ class _DisplayParticipationsState extends State { future: memberParticipations, builder: (context, snapshot) { if (snapshot.hasData) { - List memParticipations = snapshot.data as List; + List memParticipations = + snapshot.data as List; return Scaffold( backgroundColor: Color.fromRGBO(186, 196, 242, 0.1), @@ -218,7 +221,11 @@ class _DisplayParticipationsState extends State { padding: EdgeInsets.symmetric(horizontal: 32), physics: BouncingScrollPhysics(), children: memParticipations.reversed - .map((e) => MemberPartCard(event: e.event!, role: e.role!, team: e.team!, small: widget.small)) + .map((e) => MemberPartCard( + event: e.event!, + role: e.role!, + team: e.team!, + small: widget.small)) .toList(), ), ); diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index f5bd2470..2a4110ef 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -9,6 +9,7 @@ import 'package:frontend/routes/meeting/MeetingCard.dart'; import 'package:frontend/routes/member/MemberScreen.dart'; import 'package:frontend/services/meetingService.dart'; import 'package:frontend/services/teamService.dart'; +import 'package:flutter_speed_dial/flutter_speed_dial.dart'; class TeamScreen extends StatefulWidget { final Team team; @@ -108,36 +109,38 @@ class _DisplayMeetingState extends State { @override Widget build(BuildContext context) { - List> _futureMeetings = widget.meetingsIds! - .map((m) => _meetingService.getMeeting(m)) - .toList(); + List> _futureMeetings = + widget.meetingsIds!.map((m) => _meetingService.getMeeting(m)).toList(); return Scaffold( backgroundColor: Color.fromRGBO(186, 196, 242, 0.1), - body: (widget.meetingsIds == null) ? Container() : FutureBuilder( - future: Future.wait(_futureMeetings), - builder: (context, snapshot){ - if (snapshot.hasData) { - List meetings = snapshot.data as List; + body: (widget.meetingsIds == null) + ? Container() + : FutureBuilder( + future: Future.wait(_futureMeetings), + builder: (context, snapshot) { + if (snapshot.hasData) { + List meetings = snapshot.data as List; - return ListView( - padding: EdgeInsets.symmetric(horizontal: 32), - physics: BouncingScrollPhysics(), - scrollDirection: Axis.vertical, - children: - meetings.map((e) => MeetingCard(meeting: e!)).toList()); - } else { - return Container( - child: Center( - child: Container( - height: 50, - width: 50, - child: CircularProgressIndicator(), - ), - ), - ); - } - }), + return ListView( + padding: EdgeInsets.symmetric(horizontal: 32), + physics: BouncingScrollPhysics(), + scrollDirection: Axis.vertical, + children: meetings + .map((e) => MeetingCard(meeting: e!)) + .toList()); + } else { + return Container( + child: Center( + child: Container( + height: 50, + width: 50, + child: CircularProgressIndicator(), + ), + ), + ); + } + }), floatingActionButton: FloatingActionButton.extended( onPressed: () {}, label: const Text('Add Meetings'), @@ -152,6 +155,48 @@ class DisplayMembers extends StatelessWidget { final List members; const DisplayMembers({Key? key, required this.members}) : super(key: key); + SpeedDial buildSpeedDial() { + return SpeedDial( + animatedIcon: AnimatedIcons.menu_close, + animatedIconTheme: IconThemeData(size: 28.0), + backgroundColor: Colors.indigo, + visible: true, + curve: Curves.bounceInOut, + children: [ + SpeedDialChild( + child: Icon(Icons.delete, color: Colors.white), + backgroundColor: Color(0xff5C7FF2), + // TODO + onTap: () => print('Delete this team'), + label: 'Delete Team', + labelStyle: + TextStyle(fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.edit, color: Colors.white), + backgroundColor: Color(0xff5C7FF2), + // TODO + onTap: () => print('Edit the name of this team'), + label: 'Edit Team', + labelStyle: + TextStyle(fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.person, color: Colors.white), + backgroundColor: Color(0xff5C7FF2), + // TODO + onTap: () => print('Edit Members of this team'), + label: 'Edit Members', + labelStyle: + TextStyle(fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + ], + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -161,12 +206,7 @@ class DisplayMembers extends StatelessWidget { physics: BouncingScrollPhysics(), scrollDirection: Axis.vertical, children: members.map((e) => ShowMember(member: e!)).toList()), - floatingActionButton: FloatingActionButton.extended( - onPressed: () {}, - label: const Text('Edit Members'), - icon: const Icon(Icons.edit), - backgroundColor: Color(0xff5C7FF2), - ), + floatingActionButton: buildSpeedDial(), ); } } diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 0209af5b..5493a2d7 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -23,6 +23,7 @@ environment: dependencies: flutter: sdk: flutter + flutter_speed_dial: google_sign_in: ^5.0.7 http: ^0.13.3 flutter_dotenv: ^5.0.0 @@ -40,7 +41,7 @@ dependencies: collection: ^1.15.0 image: ^3.0.5 shimmer: ^2.0.0 - timeago: ^3.1.0 + timeago: ^3.1.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.3 From 9ccc2922ff9abee6f2f70803fc5d3d165d932604 Mon Sep 17 00:00:00 2001 From: susmonteiro Date: Sat, 1 Oct 2022 17:10:42 +0100 Subject: [PATCH 05/73] Change speed dial --- frontend/lib/routes/teams/TeamScreen.dart | 36 +++++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index 2a4110ef..b118902e 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -159,36 +159,46 @@ class DisplayMembers extends StatelessWidget { return SpeedDial( animatedIcon: AnimatedIcons.menu_close, animatedIconTheme: IconThemeData(size: 28.0), - backgroundColor: Colors.indigo, + backgroundColor: Color(0xff5C7FF2), visible: true, curve: Curves.bounceInOut, children: [ SpeedDialChild( - child: Icon(Icons.delete, color: Colors.white), - backgroundColor: Color(0xff5C7FF2), + child: Icon(Icons.person_remove, color: Colors.white), + backgroundColor: Colors.indigo, // TODO - onTap: () => print('Delete this team'), - label: 'Delete Team', + onTap: () => print('Remove Members'), + label: 'Remove Members', labelStyle: TextStyle(fontWeight: FontWeight.w500, color: Colors.white), labelBackgroundColor: Colors.black, ), SpeedDialChild( - child: Icon(Icons.edit, color: Colors.white), - backgroundColor: Color(0xff5C7FF2), + child: Icon(Icons.person_add, color: Colors.white), + backgroundColor: Colors.indigo, // TODO - onTap: () => print('Edit the name of this team'), - label: 'Edit Team', + onTap: () => print('Add Members'), + label: 'Add Members', + labelStyle: + TextStyle(fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.delete, color: Colors.white), + backgroundColor: Colors.indigo, + // TODO + onTap: () => print('Delete Team'), + label: 'Delete Team', labelStyle: TextStyle(fontWeight: FontWeight.w500, color: Colors.white), labelBackgroundColor: Colors.black, ), SpeedDialChild( - child: Icon(Icons.person, color: Colors.white), - backgroundColor: Color(0xff5C7FF2), + child: Icon(Icons.edit, color: Colors.white), + backgroundColor: Colors.indigo, // TODO - onTap: () => print('Edit Members of this team'), - label: 'Edit Members', + onTap: () => print('Edit Team'), + label: 'Edit Team', labelStyle: TextStyle(fontWeight: FontWeight.w500, color: Colors.white), labelBackgroundColor: Colors.black, From ba5f3d3fb5a70689fe268fcc654a82d400570065 Mon Sep 17 00:00:00 2001 From: susmonteiro Date: Sat, 1 Oct 2022 17:33:34 +0100 Subject: [PATCH 06/73] Edit team name #261 --- frontend/lib/models/team.dart | 4 +- frontend/lib/routes/teams/TeamScreen.dart | 148 ++++++++++++++-------- 2 files changed, 96 insertions(+), 56 deletions(-) diff --git a/frontend/lib/models/team.dart b/frontend/lib/models/team.dart index d401b1e4..90fb9fff 100644 --- a/frontend/lib/models/team.dart +++ b/frontend/lib/models/team.dart @@ -57,7 +57,7 @@ class TeamPublic { class Team { final String? id; - final String? name; + String? name; final List? members; final List? meetings; @@ -71,7 +71,7 @@ class Team { id: json['id'], name: json['name'], members: members.map((e) => TeamMember.fromJson(e)).toList(), - meetings: meetings.length == 0? [] : meetings as List, + meetings: meetings.length == 0 ? [] : meetings as List, ); } diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index b118902e..a111905d 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -25,7 +25,7 @@ class TeamScreen extends StatefulWidget { class _TeamScreen extends State with SingleTickerProviderStateMixin { late final TabController _tabController; - TeamService teamService = new TeamService(); + TeamService _teamService = new TeamService(); _TeamScreen({Key? key}); @@ -47,6 +47,97 @@ class _TeamScreen extends State setState(() {}); } + SpeedDial buildSpeedDial() { + return SpeedDial( + animatedIcon: AnimatedIcons.menu_close, + animatedIconTheme: IconThemeData(size: 28.0), + backgroundColor: Color(0xff5C7FF2), + visible: true, + curve: Curves.bounceInOut, + children: [ + SpeedDialChild( + child: Icon(Icons.person_remove, color: Colors.white), + backgroundColor: Colors.indigo, + // TODO + onTap: () => print('Remove Members'), + label: 'Remove Members', + labelStyle: + TextStyle(fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.person_add, color: Colors.white), + backgroundColor: Colors.indigo, + // TODO + onTap: () => print('Add Members'), + label: 'Add Members', + labelStyle: + TextStyle(fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.delete, color: Colors.white), + backgroundColor: Colors.indigo, + // TODO + onTap: () => print('Delete Team'), + label: 'Delete Team', + labelStyle: + TextStyle(fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.edit, color: Colors.white), + backgroundColor: Colors.indigo, + // TODO + onTap: () => showEditTeamDialog(), + label: 'Edit Team', + labelStyle: + TextStyle(fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + ], + ); + } + + showEditTeamDialog() { + String name = widget.team.name ?? ""; + return showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text("Edit Team"), + content: TextFormField( + initialValue: name, + onChanged: (value) { + name = value; + }, + decoration: const InputDecoration(hintText: "New name for the team"), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, "Cancel"), + child: const Text("Cancel"), + ), + TextButton( + onPressed: () => editTeam(widget.team.id, name), + child: const Text("Update"), + ), + ], + ), + ); + } + + void editTeam(String? id, String name) async { + if (id == null) { + // TODO do something + return; + } + final response = await _teamService.updateTeam(id, name); + setState(() { + widget.team.name = response?.name ?? "Empty name"; + }); + Navigator.pop(context, "Update"); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -83,6 +174,8 @@ class _TeamScreen extends State )) ])), ), + // TODO should only appear in Members tab? + floatingActionButton: buildSpeedDial(), ); } } @@ -155,58 +248,6 @@ class DisplayMembers extends StatelessWidget { final List members; const DisplayMembers({Key? key, required this.members}) : super(key: key); - SpeedDial buildSpeedDial() { - return SpeedDial( - animatedIcon: AnimatedIcons.menu_close, - animatedIconTheme: IconThemeData(size: 28.0), - backgroundColor: Color(0xff5C7FF2), - visible: true, - curve: Curves.bounceInOut, - children: [ - SpeedDialChild( - child: Icon(Icons.person_remove, color: Colors.white), - backgroundColor: Colors.indigo, - // TODO - onTap: () => print('Remove Members'), - label: 'Remove Members', - labelStyle: - TextStyle(fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - SpeedDialChild( - child: Icon(Icons.person_add, color: Colors.white), - backgroundColor: Colors.indigo, - // TODO - onTap: () => print('Add Members'), - label: 'Add Members', - labelStyle: - TextStyle(fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - SpeedDialChild( - child: Icon(Icons.delete, color: Colors.white), - backgroundColor: Colors.indigo, - // TODO - onTap: () => print('Delete Team'), - label: 'Delete Team', - labelStyle: - TextStyle(fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - SpeedDialChild( - child: Icon(Icons.edit, color: Colors.white), - backgroundColor: Colors.indigo, - // TODO - onTap: () => print('Edit Team'), - label: 'Edit Team', - labelStyle: - TextStyle(fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - ], - ); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -216,7 +257,6 @@ class DisplayMembers extends StatelessWidget { physics: BouncingScrollPhysics(), scrollDirection: Axis.vertical, children: members.map((e) => ShowMember(member: e!)).toList()), - floatingActionButton: buildSpeedDial(), ); } } From fb7cf11abad1a90155b05bad9c214f10d04f594c Mon Sep 17 00:00:00 2001 From: susmonteiro Date: Sat, 1 Oct 2022 18:00:33 +0100 Subject: [PATCH 07/73] Remove team #261 --- frontend/lib/routes/teams/TeamScreen.dart | 36 +++++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index a111905d..0e2a9486 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -78,8 +78,7 @@ class _TeamScreen extends State SpeedDialChild( child: Icon(Icons.delete, color: Colors.white), backgroundColor: Colors.indigo, - // TODO - onTap: () => print('Delete Team'), + onTap: () => showDeleteTeamDialog(), label: 'Delete Team', labelStyle: TextStyle(fontWeight: FontWeight.w500, color: Colors.white), @@ -88,7 +87,6 @@ class _TeamScreen extends State SpeedDialChild( child: Icon(Icons.edit, color: Colors.white), backgroundColor: Colors.indigo, - // TODO onTap: () => showEditTeamDialog(), label: 'Edit Team', labelStyle: @@ -126,6 +124,27 @@ class _TeamScreen extends State ); } + showDeleteTeamDialog() { + final String name = widget.team.name ?? "team"; + return showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text("Remove Team"), + content: Text("Are you sure you want to delete \"$name\"?"), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, "No"), + child: const Text("No"), + ), + TextButton( + onPressed: () => deleteTeam(widget.team.id), + child: const Text("Yes"), + ), + ], + ), + ); + } + void editTeam(String? id, String name) async { if (id == null) { // TODO do something @@ -138,6 +157,17 @@ class _TeamScreen extends State Navigator.pop(context, "Update"); } + void deleteTeam(String? id) async { + if (id == null) { + // TODO do something + return; + } + final response = await _teamService.deleteTeam(id); + Navigator.pop(context, "Update"); + // TODO when going back to TeamTable, it should be refreshed so that the deleted team is not shown. What is the best option to do this? + Navigator.pop(context); + } + @override Widget build(BuildContext context) { return Scaffold( From 7ff6f5025b68e784d312b368a204ab4640f738c0 Mon Sep 17 00:00:00 2001 From: JoaoGomes24 Date: Wed, 19 Oct 2022 16:25:00 +0100 Subject: [PATCH 08/73] begin work on team management --- frontend/lib/routes/teams/TeamScreen.dart | 146 +++++++++++++++++++++- frontend/lib/services/teamService.dart | 2 +- 2 files changed, 142 insertions(+), 6 deletions(-) diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index 0e2a9486..ed450966 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -9,8 +9,16 @@ import 'package:frontend/routes/meeting/MeetingCard.dart'; import 'package:frontend/routes/member/MemberScreen.dart'; import 'package:frontend/services/meetingService.dart'; import 'package:frontend/services/teamService.dart'; +import 'package:frontend/services/memberService.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; +final Map roles = { + "MEMBER": "Member", + "TEAMLEADER": "Team Leader", + "COORDINATOR": "Coordinator", + "ADMIN": "Administrator" +}; + class TeamScreen extends StatefulWidget { final Team team; final List members; @@ -58,8 +66,7 @@ class _TeamScreen extends State SpeedDialChild( child: Icon(Icons.person_remove, color: Colors.white), backgroundColor: Colors.indigo, - // TODO - onTap: () => print('Remove Members'), + onTap: () => showRemoveMemberDialog(), label: 'Remove Members', labelStyle: TextStyle(fontWeight: FontWeight.w500, color: Colors.white), @@ -68,9 +75,8 @@ class _TeamScreen extends State SpeedDialChild( child: Icon(Icons.person_add, color: Colors.white), backgroundColor: Colors.indigo, - // TODO - onTap: () => print('Add Members'), - label: 'Add Members', + onTap: () => showAddMemberDialog(), + label: 'Add Member', labelStyle: TextStyle(fontWeight: FontWeight.w500, color: Colors.white), labelBackgroundColor: Colors.black, @@ -145,6 +151,136 @@ class _TeamScreen extends State ); } + showRemoveMemberDialog() { + String memberId = ""; + return showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text("Remove Team Member"), + content: TextFormField( + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a Id'; + } + return null; + }, + onChanged: (value) { + memberId = value; + }, + decoration: const InputDecoration(hintText: "Team Member Id"), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, "Cancel"), + child: const Text("Cancel"), + ), + TextButton( + onPressed: () => removeTeamMember(widget.team.id, memberId), + child: const Text("Delete"), + ), + ], + ), + ); + } + + showAddMemberDialog() { + String memberId = ""; + String memberRole=""; + return showDialog( + context: context, + builder: (BuildContext context){ + return AlertDialog( + title: Text("Add Member"), + content: Padding( + padding: const EdgeInsets.all(4.0), + child: Form( + child: Column( + children:[ + DropdownButtonFormField( + validator: (value) { + if (value == null) { + return 'Please enter the kind of the meeting'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.category), + labelText: "MemberId *", + ), + items: members.map((Member member) { + return new DropdownMenuItem(value: member.id, child: Text(member.name)); + }).toList(), + onChanged: (newValue) { + // do other stuff with _category + setState(() => memberId = newValue.toString()); + }), + DropdownButtonFormField( + validator: (value) { + if (value == null) { + return 'Please enter the kind of the meeting'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.category), + labelText: "Role *", + ), + items: roles.keys.map((String role) { + return new DropdownMenuItem(value: role, child: Text(role)); + }).toList(), + onChanged: (newValue) { + // do other stuff with _category + setState(() => memberRole = newValue.toString()); + }), + ], + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, "Cancel"), + child: const Text("Cancel"), + ), + TextButton( + onPressed: () => addMember(widget.team.id, memberId, memberRole), + child: const Text("Add"), + ), + ], + ); + } + ); + } + + + void addMember(String? id, String memberId, String memberRole) async { + if (id == null) { + // TODO do something + return; + } + + MemberService _memberService = MemberService(); + Member? _member; + + _member = await _memberService.getMember(memberId); + + + final response = await _teamService.addTeamMember(id, _member, memberRole); + setState(() { + widget.members = response?.members; + }); + Navigator.pop(context, "Add"); + } + + void removeTeamMember(String? id, String memberId) async { + if (id == null) { + // TODO do something + return; + } + final response = await _teamService.deleteTeamMember(id, memberId); + Navigator.pop(context, "Delete"); + Navigator.pop(context); + } + void editTeam(String? id, String name) async { if (id == null) { // TODO do something diff --git a/frontend/lib/services/teamService.dart b/frontend/lib/services/teamService.dart index 804d5107..ba6e0186 100644 --- a/frontend/lib/services/teamService.dart +++ b/frontend/lib/services/teamService.dart @@ -97,7 +97,7 @@ class TeamService extends Service { } } - Future addTeamMember(String id, String member, String role) async { + Future addTeamMember(String id, Member member, String role) async { var body = { "member": member, "role": role, From 4a70a9ed7916dbeee495047e75ed1ac555494195 Mon Sep 17 00:00:00 2001 From: Pedro Maximino Date: Thu, 20 Oct 2022 16:12:59 +0100 Subject: [PATCH 09/73] fix: Fix team service and fetching members --- frontend/lib/models/member.dart | 1 + frontend/lib/routes/teams/TeamScreen.dart | 154 +++++++++++----------- frontend/lib/services/teamService.dart | 10 +- 3 files changed, 82 insertions(+), 83 deletions(-) diff --git a/frontend/lib/models/member.dart b/frontend/lib/models/member.dart index b297e15f..521d1119 100644 --- a/frontend/lib/models/member.dart +++ b/frontend/lib/models/member.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:frontend/models/contact.dart'; +import 'package:frontend/models/team.dart'; class Member { final String id; diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index ed450966..bac68cf5 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:frontend/components/ListViewCard.dart'; +import 'package:frontend/main.dart'; import 'package:frontend/models/meeting.dart'; import 'package:frontend/models/member.dart'; import 'package:frontend/models/team.dart'; @@ -21,7 +22,7 @@ final Map roles = { class TeamScreen extends StatefulWidget { final Team team; - final List members; + List members; TeamScreen({Key? key, required this.team, required this.members}) : super(key: key); @@ -159,14 +160,14 @@ class _TeamScreen extends State title: const Text("Remove Team Member"), content: TextFormField( validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a Id'; - } - return null; - }, - onChanged: (value) { - memberId = value; - }, + if (value == null || value.isEmpty) { + return 'Please enter a Id'; + } + return null; + }, + onChanged: (value) { + memberId = value; + }, decoration: const InputDecoration(hintText: "Team Member Id"), ), actions: [ @@ -185,73 +186,73 @@ class _TeamScreen extends State showAddMemberDialog() { String memberId = ""; - String memberRole=""; + String memberRole = ""; return showDialog( - context: context, - builder: (BuildContext context){ - return AlertDialog( - title: Text("Add Member"), - content: Padding( - padding: const EdgeInsets.all(4.0), - child: Form( - child: Column( - children:[ - DropdownButtonFormField( - validator: (value) { - if (value == null) { - return 'Please enter the kind of the meeting'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.category), - labelText: "MemberId *", - ), - items: members.map((Member member) { - return new DropdownMenuItem(value: member.id, child: Text(member.name)); - }).toList(), - onChanged: (newValue) { - // do other stuff with _category - setState(() => memberId = newValue.toString()); - }), - DropdownButtonFormField( - validator: (value) { - if (value == null) { - return 'Please enter the kind of the meeting'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.category), - labelText: "Role *", - ), - items: roles.keys.map((String role) { - return new DropdownMenuItem(value: role, child: Text(role)); - }).toList(), - onChanged: (newValue) { - // do other stuff with _category - setState(() => memberRole = newValue.toString()); - }), - ], + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text("Add Member"), + content: Padding( + padding: const EdgeInsets.all(4.0), + child: Form( + child: Column( + children: [ + DropdownButtonFormField( + validator: (value) { + if (value == null) { + return 'Please select one member'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.category), + labelText: "MemberId *", + ), + items: widget.members.map((Member? member) { + return new DropdownMenuItem( + value: member!.id, child: Text(member.name)); + }).toList(), + onChanged: (newValue) { + // do other stuff with _category + setState(() => memberId = newValue.toString()); + }), + DropdownButtonFormField( + validator: (value) { + if (value == null) { + return 'Please select one role'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.category), + labelText: "Role *", + ), + items: roles.keys.map((String role) { + return new DropdownMenuItem( + value: role, child: Text(role)); + }).toList(), + onChanged: (newValue) { + setState(() => memberRole = newValue.toString()); + }), + ], + ), ), ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, "Cancel"), - child: const Text("Cancel"), - ), - TextButton( - onPressed: () => addMember(widget.team.id, memberId, memberRole), - child: const Text("Add"), - ), - ], - ); - } - ); + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, "Cancel"), + child: const Text("Cancel"), + ), + TextButton( + onPressed: () => + addMember(widget.team.id, memberId, memberRole), + child: const Text("Add"), + ), + ], + ); + }); } - void addMember(String? id, String memberId, String memberRole) async { if (id == null) { // TODO do something @@ -259,14 +260,11 @@ class _TeamScreen extends State } MemberService _memberService = MemberService(); - Member? _member; - - _member = await _memberService.getMember(memberId); - - - final response = await _teamService.addTeamMember(id, _member, memberRole); + await _teamService.addTeamMember(id, memberId, memberRole); + var members = await _memberService.getMembers( + event: App.localStorage.getInt("event")); setState(() { - widget.members = response?.members; + widget.members = members; }); Navigator.pop(context, "Add"); } diff --git a/frontend/lib/services/teamService.dart b/frontend/lib/services/teamService.dart index ba6e0186..7d749747 100644 --- a/frontend/lib/services/teamService.dart +++ b/frontend/lib/services/teamService.dart @@ -97,9 +97,9 @@ class TeamService extends Service { } } - Future addTeamMember(String id, Member member, String role) async { + Future addTeamMember(String id, String memberId, String role) async { var body = { - "member": member, + "member": memberId, "role": role, }; @@ -118,14 +118,14 @@ class TeamService extends Service { } Future updateTeamMemberRole( - String id, String memberID, Member member, String role) async { + String id, String memberId, String role) async { var body = { - "member": member, + "member": memberId, "role": role, }; Response response = - await dio.put(baseURL + '/$id' + '/members' + '/$memberID', data: body); + await dio.put(baseURL + '/$id' + '/members' + '/$memberId', data: body); try { return Team.fromJson(json.decode(response.data!)); From 18c94f493c9bbde20a5780c14894d50e580103e8 Mon Sep 17 00:00:00 2001 From: Pedro Maximino Date: Thu, 20 Oct 2022 16:13:42 +0100 Subject: [PATCH 10/73] fix: Remove useless comment --- frontend/lib/routes/teams/TeamScreen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index bac68cf5..2d0c0955 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -213,7 +213,6 @@ class _TeamScreen extends State value: member!.id, child: Text(member.name)); }).toList(), onChanged: (newValue) { - // do other stuff with _category setState(() => memberId = newValue.toString()); }), DropdownButtonFormField( From ed9423a9bcb018f59b37da9f32e5f18f3f0b8b2f Mon Sep 17 00:00:00 2001 From: Pedro Maximino Date: Thu, 20 Oct 2022 16:14:29 +0100 Subject: [PATCH 11/73] feat: Add TODO comment --- frontend/lib/routes/teams/TeamScreen.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index 2d0c0955..6b36115d 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -208,6 +208,7 @@ class _TeamScreen extends State icon: const Icon(Icons.category), labelText: "MemberId *", ), + // TODO: We need to fetch the event members somewhere and then see which of those are not part of the team already. items: widget.members.map((Member? member) { return new DropdownMenuItem( value: member!.id, child: Text(member.name)); From 0ac0d945ab51c43cba14ed8c526d2caa28d04ae5 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Thu, 20 Oct 2022 17:59:28 +0100 Subject: [PATCH 12/73] first commit --- frontend/lib/routes/HomeScreen.dart | 24 ++ .../lib/routes/session/AddSessionForm.dart | 238 ++++++++++++++++++ .../lib/routes/session/EditSessionForm.dart | 237 +++++++++++++++++ frontend/lib/routes/session/SessionCard.dart | 233 +++++++++++++++++ frontend/lib/routes/session/SessionPage.dart | 96 +++++++ .../lib/routes/session/SessionsNotifier.dart | 34 +++ frontend/lib/services/sessionService.dart | 36 +++ 7 files changed, 898 insertions(+) create mode 100644 frontend/lib/routes/session/AddSessionForm.dart create mode 100644 frontend/lib/routes/session/EditSessionForm.dart create mode 100644 frontend/lib/routes/session/SessionCard.dart create mode 100644 frontend/lib/routes/session/SessionPage.dart create mode 100644 frontend/lib/routes/session/SessionsNotifier.dart diff --git a/frontend/lib/routes/HomeScreen.dart b/frontend/lib/routes/HomeScreen.dart index 58109c0b..a8143dd8 100644 --- a/frontend/lib/routes/HomeScreen.dart +++ b/frontend/lib/routes/HomeScreen.dart @@ -11,6 +11,8 @@ import 'package:frontend/routes/teams/TeamsTable.dart'; import 'package:frontend/services/authService.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:provider/provider.dart'; +import 'package:frontend/routes/session/SessionPage.dart'; + class HomeScreen extends StatefulWidget { HomeScreen({Key? key}) : super(key: key); @@ -93,6 +95,9 @@ class _HomeScreenState extends State { Center( child: MeetingPage(), ), + Center( + child: SessionPage(), + ) ], ), ), @@ -186,6 +191,20 @@ class _HomeScreenState extends State { } }); } + + case 5: + { + return FloatingActionButton.extended( + onPressed: () { + Navigator.pushNamed( + context, + Routes.AddMeeting, + ); + }, + label: const Text('Create New Session'), + icon: const Icon(Icons.add), + ); + } } } } @@ -237,6 +256,11 @@ class CustomNavBar extends StatelessWidget { icon: Icon( Icons.meeting_room, )), + BottomNavigationBarItem( + label: 'Sessions', + icon: Icon( + Icons.co_present, + )), ], onTap: onTapped, ); diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart new file mode 100644 index 00000000..8bfcff36 --- /dev/null +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -0,0 +1,238 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/components/appbar.dart'; +import 'package:frontend/models/session.dart'; +import 'package:frontend/routes/session/SessionsNotifier.dart'; +import 'package:frontend/services/sessionService.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class AddSessionForm extends StatefulWidget { + AddSessionForm({Key? key}) : super(key: key); + + @override + _AddSessionFormState createState() => _AddSessionFormState(); +} + +const kinds = ["TALK", "PRESENTATION", "WORKSHOP"]; + +class _AddSessionFormState extends State { + final _formKey = GlobalKey(); + final _titleController = TextEditingController(); + final _placeController = TextEditingController(); + final _beginDateController = TextEditingController(); + final _endDateController = TextEditingController(); + final _sessionService = SessionService(); + + DateTime? dateTime; + DateTime? _begin; + DateTime? _end; + + String _kind = ""; + + void _submit() async { + if (_formKey.currentState!.validate()) { + var title = _titleController.text; + var place = _placeController.text; + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Uploading')), + ); + + Session? s = await _sessionService.createSession( + _begin!.toUtc(), _end!.toUtc(), place, _kind, title); + if (s != null) { + SessionsNotifier notifier = + Provider.of(context, listen: false); + notifier.add(s); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + action: SnackBarAction( + label: 'Undo', + onPressed: () { + _sessionService.deleteSession(s.id); + notifier.remove(s); + }, + ), + ), + ); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + } + Navigator.pop(context); + } + } + + Future _selectDateTime(BuildContext context, bool isBegin) async { + final datePicker = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2000), + lastDate: DateTime(2025), + ); + + final timePicker = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true), + child: child!, + ); + }); + + if (datePicker != null && timePicker != null) { + if (isBegin) { + _begin = DateTime(datePicker.year, datePicker.month, datePicker.day, + timePicker.hour, timePicker.minute); + } else { + _end = DateTime(datePicker.year, datePicker.month, datePicker.day, + timePicker.hour, timePicker.minute); + } + } + } + + String getDateTime(DateTime dateTime) { + return DateFormat('yyyy-MM-dd HH:mm').format(dateTime); + } + + Widget _buildForm() { + return Form( + key: _formKey, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _titleController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a title'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.title), + labelText: "Title *", + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _placeController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a place'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.place), + labelText: "Place *", + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _beginDateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a beggining date'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "Begin Date *", + ), + readOnly: true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, true); + String formattedDate = getDateTime(_begin!); + + setState(() { + _beginDateController.text = formattedDate; + }); + }, + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _endDateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter an ending date'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "End Date *", + ), + readOnly: true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, false); + String formattedDate = getDateTime(_end!); + + setState(() { + _endDateController.text = formattedDate; + }); + }, + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: DropdownButtonFormField( + validator: (value) { + if (value == null) { + return 'Please enter the kind of session'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.category), + labelText: "Kind *", + ), + items: kinds.map((String kind) { + return new DropdownMenuItem(value: kind, child: Text(kind)); + }).toList(), + onChanged: (newValue) { + // do other stuff with _category + setState(() => _kind = newValue.toString()); + })), + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () => _submit(), + child: const Text('Submit'), + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + CustomAppBar appBar = CustomAppBar( + disableEventChange: true, + ); + return Scaffold( + body: Stack(children: [ + Container( + margin: EdgeInsets.fromLTRB(0, appBar.preferredSize.height, 0, 0), + child: _buildForm()), + appBar, + ]), + ); + } +} diff --git a/frontend/lib/routes/session/EditSessionForm.dart b/frontend/lib/routes/session/EditSessionForm.dart new file mode 100644 index 00000000..eac960bc --- /dev/null +++ b/frontend/lib/routes/session/EditSessionForm.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/models/session.dart'; +import 'package:frontend/routes/session/SessionsNotifier.dart'; +import 'package:frontend/services/sessionService.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class EditSessionForm extends StatefulWidget { + final Session session; + EditSessionForm({Key? key, required this.session}) : super(key: key); + + @override + _EditSessionFormState createState() => _EditSessionFormState(); +} + +const kinds = ["TALK", "PRESENTATION", "WORKSHOP"]; + +class _EditSessionFormState extends State { + final _formKey = GlobalKey(); + late TextEditingController _titleController; + late TextEditingController _placeController; + late TextEditingController _beginDateController; + late TextEditingController _endDateController; + final _sessionService = SessionService(); + + late DateTime _begin; + late DateTime _end; + late String _kind; + + @override + void initState() { + super.initState(); + _titleController = TextEditingController(text: widget.session.title); + _placeController = TextEditingController(text: widget.session.place); + _beginDateController = + TextEditingController(text: getDateTime(widget.session.begin)); + _endDateController = + TextEditingController(text: getDateTime(widget.session.end)); + _begin = widget.session.begin; + _end = widget.session.end; + _kind = widget.session.kind; + } + + String getDateTime(DateTime dateTime) { + return DateFormat('yyyy-MM-dd HH:mm').format(dateTime); + } + + void _submit() async { + if (_formKey.currentState!.validate()) { + var title = _titleController.text; + var place = _placeController.text; + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Uploading')), + ); + + // Session? s = await _sessionService.updateSession( + // widget.session.id, _begin.toUtc(), _end.toUtc(), place, _kind, title); + + // if (s != null) { + // SessionsNotifier notifier = + // Provider.of(context, listen: false); + // notifier.edit(s); + + // ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text('Done'), + // duration: Duration(seconds: 2), + // ), + // ); + // Navigator.pop(context); + // } else { + // ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar(content: Text('An error occured.')), + // ); + // } + } + } + + Future _selectDateTime(BuildContext context, bool isBegin) async { + final datePicker = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2000), + lastDate: DateTime(2025), + ); + + final timePicker = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true), + child: child!, + ); + }); + + if (datePicker != null && timePicker != null) { + if (isBegin) { + _begin = DateTime(datePicker.year, datePicker.month, datePicker.day, + timePicker.hour, timePicker.minute); + } else { + _end = DateTime(datePicker.year, datePicker.month, datePicker.day, + timePicker.hour, timePicker.minute); + } + } + } + + Widget _buildForm() { + return Form( + key: _formKey, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _titleController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a title'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.title), + labelText: "Title *", + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _placeController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a place'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.place), + labelText: "Place *", + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _beginDateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a beggining date'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "Begin Date *", + ), + readOnly: true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, true); + String formattedDate = getDateTime(_begin); + + setState(() { + _beginDateController.text = formattedDate; + }); + }, + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _endDateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter an ending date'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "End Date *", + ), + readOnly: true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, false); + String formattedDate = getDateTime(_end); + + setState(() { + _endDateController.text = formattedDate; + }); + }, + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: DropdownButtonFormField( + validator: (value) { + if (value == null) { + return 'Please enter the kind of session'; + } + return null; + }, + // Transforming TEAM or COMPANY or EVENT into Team or Company or Event + value: + "${widget.session.kind[0].toUpperCase()}${widget.session.kind.substring(1).toLowerCase()}", + decoration: const InputDecoration( + icon: const Icon(Icons.category), + labelText: "Kind *", + ), + items: kinds.map((String kind) { + return new DropdownMenuItem(value: kind, child: Text(kind)); + }).toList(), + onChanged: (newValue) { + // do other stuff with _category + setState(() => _kind = newValue.toString()); + })), + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () => _submit(), + child: const Text('Submit'), + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return _buildForm(); + } +} diff --git a/frontend/lib/routes/session/SessionCard.dart b/frontend/lib/routes/session/SessionCard.dart new file mode 100644 index 00000000..26462d02 --- /dev/null +++ b/frontend/lib/routes/session/SessionCard.dart @@ -0,0 +1,233 @@ +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:frontend/components/blurryDialog.dart'; +import 'package:frontend/main.dart'; +import 'package:frontend/models/session.dart'; +import 'package:frontend/routes/session/EditSessionForm.dart'; +import 'package:frontend/routes/session/SessionsNotifier.dart'; +import 'package:frontend/services/authService.dart'; +import 'package:frontend/services/sessionService.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class SessionCard extends StatelessWidget { + final Session session; + final _sessionService = SessionService(); + + SessionCard({Key? key, required this.session}) : super(key: key); + + void _editSessionModal(context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) { + return FractionallySizedBox( + heightFactor: 0.7, + child: Container( + child: EditSessionForm(session: session), + )); + }, + ); + } + + void _deleteSessionDialog(context, id) { + showDialog( + context: context, + builder: (BuildContext context) { + return BlurryDialog('Warning', + 'Are you sure you want to delete session ${session.title}?', () { + _deleteSession(context, id); + }); + }, + ); + } + + void _deleteSession(context, id) async { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Deleting')), + ); + + Session? s = await _sessionService.deleteSession(id); + if (s != null) { + SessionsNotifier notifier = + Provider.of(context, listen: false); + notifier.remove(s); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + ), + ); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + } + } + + Widget _buildSessionCard(BuildContext context) { + double _dateCardWidth = 120.0, + _dateFontSize = 30.0, + _titleFontSize = 23.0, + _placeDateFontSize = 20.0, + _cardMargin = 25.0, + _dateMargins = 25.0, + _iconsMargin = 8.0, + _titleUpBottomMargin = 20.0, + _titleLeftMargin = 15.0; + return LayoutBuilder(builder: (context, constraints) { + if (constraints.maxWidth < App.SIZE) { + _dateCardWidth = 50.0; + _dateFontSize = 14.0; + _titleFontSize = 16.0; + _placeDateFontSize = 14.0; + } + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + margin: EdgeInsets.all(_cardMargin), + child: Stack(children: [ + Positioned.fill( + child: Container( + alignment: Alignment.centerLeft, + // This child will fill full height, replace it with your leading widget + child: Container( + width: _dateCardWidth, + child: Column( + children: [ + Container( + margin: EdgeInsets.only(top: _dateMargins), + child: Text( + DateFormat.d().format(session.begin), + style: TextStyle( + color: Colors.white, fontSize: _dateFontSize), + ), + ), + Container( + margin: EdgeInsets.only(bottom: _dateMargins), + child: Text( + DateFormat.MMM().format(session.begin).toUpperCase(), + style: TextStyle( + color: Colors.white, fontSize: _dateFontSize), + ), + ), + ], + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(5.0), + topLeft: Radius.circular(5.0)), + image: DecorationImage( + image: AssetImage("assets/banner_background.png"), + fit: BoxFit.fill, + ), + ), + ), + ), + ), + Row( + children: [ + SizedBox(width: _dateCardWidth), + Expanded( + child: Container( + margin: EdgeInsets.only( + top: _titleUpBottomMargin, + bottom: _titleUpBottomMargin, + left: _titleLeftMargin), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + session.title, + style: TextStyle(fontSize: _titleFontSize), + textAlign: TextAlign.left, + ), + ), + Container( + child: Text( + session.place, + style: TextStyle( + color: Colors.grey, fontSize: _placeDateFontSize), + textAlign: TextAlign.left, + ), + ), + Container( + padding: EdgeInsets.only(top: 5.0), + child: Text( + DateFormat.jm().format(session.begin.toLocal()) + + ' - ' + + DateFormat.jm().format(session.end.toLocal()), + style: TextStyle( + color: Colors.grey, fontSize: _placeDateFontSize), + textAlign: TextAlign.left, + ), + ), + ], + ), + ), + ), + Expanded( + child: Padding( + padding: EdgeInsets.all(_iconsMargin), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + onPressed: () { + _editSessionModal(context); + }, + icon: Icon(Icons.edit), + color: const Color(0xff5c7ff2)), + FutureBuilder( + future: + Provider.of(context).role, + builder: (context, snapshot) { + if (snapshot.hasData) { + Role r = snapshot.data as Role; + + if (r == Role.ADMIN || + r == Role.COORDINATOR) { + return IconButton( + onPressed: () => _deleteSessionDialog( + context, session.id), + icon: Icon(Icons.delete), + color: Colors.red); + } else { + return Container(); + } + } else { + return Container(); + } + }) + ]), + ])), + ), + ], + ), + ]), + ); + }); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + print("go to session page"); //TODO + }, + child: _buildSessionCard(context)); + } +} diff --git a/frontend/lib/routes/session/SessionPage.dart b/frontend/lib/routes/session/SessionPage.dart new file mode 100644 index 00000000..9ba2b56f --- /dev/null +++ b/frontend/lib/routes/session/SessionPage.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/main.dart'; +import 'package:frontend/models/session.dart'; +import 'package:frontend/routes/session/SessionCard.dart'; +import 'package:frontend/routes/session/SessionsNotifier.dart'; +import 'package:frontend/services/sessionService.dart'; +import 'package:provider/provider.dart'; + +class SessionPage extends StatelessWidget { + const SessionPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + child: SessionList(), + ); + } +} + +class SessionList extends StatefulWidget { + const SessionList({Key? key}) : super(key: key); + + @override + _SessionListState createState() => _SessionListState(); +} + +class _SessionListState extends State + with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { + final SessionService _service = SessionService(); + late final Future> _sessions; + late final TabController _tabController; + + @override + void initState() { + _sessions = _service.getSessions(); + _tabController = TabController(length: 2, vsync: this); + super.initState(); + } + + @override + // TODO: implement wantKeepAlive + bool get wantKeepAlive => true; + + @override + Widget build(BuildContext context) { + super.build(context); + return FutureBuilder( + future: _sessions, + builder: (context, snapshot) { + if (snapshot.hasData) { + SessionsNotifier notifier = Provider.of(context); + + notifier.sessions = snapshot.data as List; + + return LayoutBuilder(builder: (context, constraints) { + bool small = constraints.maxWidth < App.SIZE; + return Column( + children: [ + TabBar( + isScrollable: small, + controller: _tabController, + tabs: [ + Tab(text: 'Upcoming'), + Tab(text: 'Past'), + ], + ), + Consumer( + builder: (context, cart, child) { + return Expanded( + child: TabBarView(controller: _tabController, children: [ + ListView( + children: notifier + .getUpcoming() + .map((e) => SessionCard(session: e)) + .toList(), + ), + ListView( + children: notifier + .getPast() + .map((e) => SessionCard(session: e)) + .toList(), + ), + ]), + ); + }, + ), + ], + ); + }); + } else { + return CircularProgressIndicator(); + } + }, + ); + } +} diff --git a/frontend/lib/routes/session/SessionsNotifier.dart b/frontend/lib/routes/session/SessionsNotifier.dart new file mode 100644 index 00000000..7839980a --- /dev/null +++ b/frontend/lib/routes/session/SessionsNotifier.dart @@ -0,0 +1,34 @@ +import 'package:flutter/cupertino.dart'; +import 'package:frontend/models/session.dart'; + +class SessionsNotifier extends ChangeNotifier { + List sessions; + + SessionsNotifier({required this.sessions}); + + List getUpcoming() { + return sessions.where((s) => DateTime.now().isBefore(s.begin)).toList(); + } + + List getPast() { + return sessions.where((s) => DateTime.now().isAfter(s.begin)).toList(); + } + + void add(Session s) { + sessions.add(s); + notifyListeners(); + } + + void remove(Session s) { + sessions.removeWhere((session) => s.id == session.id); + notifyListeners(); + } + + void edit(Session s) { + int index = sessions.indexWhere((session) => s.id == session.id); + if (index != -1) { + sessions[index] = s; + notifyListeners(); + } + } +} diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index ce743f2d..e80d9261 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -42,6 +42,42 @@ class SessionService extends Service { } } + Future createSession(DateTime begin, DateTime end, String place, + String kind, String title) async { + var body = { + "begin": begin.toIso8601String(), + "end": end.toIso8601String(), + "place": place, + "title": title, + "kind": kind.toUpperCase() + }; + + Response response = await dio.post("/sessions", data: body); + + try { + return Session.fromJson(json.decode(response.data!)); + } on SocketException { + throw DeckException('No Internet connection'); + } on HttpException { + throw DeckException('Not found'); + } on FormatException { + throw DeckException('Wrong format'); + } + } + + Future deleteSession(String id) async { + Response response = await dio.delete("/sessions/" + id); + try { + return Session.fromJson(json.decode(response.data!)); + } on SocketException { + throw DeckException('No Internet connection'); + } on HttpException { + throw DeckException('Not found'); + } on FormatException { + throw DeckException('Wrong format'); + } + } + Future updateSession(Session session) async { var body = session.toJson(); From 70822e00a9eb44bce66a6ed59f3cdab0a3654ad1 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Thu, 20 Oct 2022 18:13:13 +0100 Subject: [PATCH 13/73] fixed place as optional --- frontend/lib/routes/session/SessionCard.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/lib/routes/session/SessionCard.dart b/frontend/lib/routes/session/SessionCard.dart index 26462d02..398ca941 100644 --- a/frontend/lib/routes/session/SessionCard.dart +++ b/frontend/lib/routes/session/SessionCard.dart @@ -153,7 +153,7 @@ class SessionCard extends StatelessWidget { ), Container( child: Text( - session.place, + session.place ?? 'another value', style: TextStyle( color: Colors.grey, fontSize: _placeDateFontSize), textAlign: TextAlign.left, From f189f5d5ce9d7073c5066ec5001d329e0dd06822 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Fri, 21 Oct 2022 15:08:47 +0100 Subject: [PATCH 14/73] fixed createSession in session service --- frontend/lib/components/router.dart | 6 +++++ frontend/lib/models/session.dart | 12 +++++----- frontend/lib/routes/HomeScreen.dart | 2 +- .../lib/routes/session/AddSessionForm.dart | 24 +++++++++++++++++++ frontend/lib/services/eventService.dart | 2 +- frontend/lib/services/sessionService.dart | 2 +- 6 files changed, 39 insertions(+), 9 deletions(-) diff --git a/frontend/lib/components/router.dart b/frontend/lib/components/router.dart index 84613572..610c8b41 100644 --- a/frontend/lib/components/router.dart +++ b/frontend/lib/components/router.dart @@ -10,6 +10,8 @@ import 'package:frontend/routes/member/AddMemberForm.dart'; import 'package:frontend/routes/member/MemberListWidget.dart'; import 'package:frontend/routes/speaker/SpeakerListWidget.dart'; import 'package:frontend/routes/speaker/AddSpeakerForm.dart'; +import 'package:frontend/routes/session/AddSessionForm.dart'; + class Routes { static const String BaseRoute = '/'; @@ -22,6 +24,8 @@ class Routes { static const String ShowAllMembers = '/all/members'; static const String AddMember = '/add/member'; static const String AddMeeting = '/add/meeting'; + static const String AddSession = '/add/session'; + } Route generateRoute(RouteSettings settings) { @@ -46,6 +50,8 @@ Route generateRoute(RouteSettings settings) { return SlideRoute(page: AddMemberForm()); case Routes.AddMeeting: return SlideRoute(page: AddMeetingForm()); + case Routes.AddSession: + return SlideRoute(page: AddSessionForm()); default: return MaterialPageRoute(builder: (context) => UnknownScreen()); } diff --git a/frontend/lib/models/session.dart b/frontend/lib/models/session.dart index 650310f0..492b1a88 100644 --- a/frontend/lib/models/session.dart +++ b/frontend/lib/models/session.dart @@ -1,7 +1,7 @@ import 'dart:convert'; class Session { - final String? id; + final String id; final DateTime begin; final DateTime end; final String title; @@ -9,12 +9,12 @@ class Session { final String? place; final String kind; final String? companyId; - final List? speakersIds; + final String? speaker; final String? videoURL; final SessionTickets? tickets; Session({ - this.id, + required this.id, required this.begin, required this.end, required this.title, @@ -22,7 +22,7 @@ class Session { this.place, required this.kind, this.companyId, - this.speakersIds, + this.speaker, this.videoURL, this.tickets, }); @@ -37,7 +37,7 @@ class Session { place: json['place'], kind: json['kind'], companyId: json['company'], - speakersIds: json['speaker'], + speaker: json['speaker'], videoURL: json['videoURL'], tickets: SessionTickets.fromJson(json['tickets']), ); @@ -52,7 +52,7 @@ class Session { 'place': place, 'kind': kind, 'company': companyId, - 'speaker': speakersIds, + 'speaker': speaker, 'videoURL': videoURL, 'tickets': tickets?.toJson(), }; diff --git a/frontend/lib/routes/HomeScreen.dart b/frontend/lib/routes/HomeScreen.dart index a8143dd8..c64c6695 100644 --- a/frontend/lib/routes/HomeScreen.dart +++ b/frontend/lib/routes/HomeScreen.dart @@ -198,7 +198,7 @@ class _HomeScreenState extends State { onPressed: () { Navigator.pushNamed( context, - Routes.AddMeeting, + Routes.AddSession, ); }, label: const Text('Create New Session'), diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 8bfcff36..e8fc6378 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -21,6 +21,7 @@ class _AddSessionFormState extends State { final _placeController = TextEditingController(); final _beginDateController = TextEditingController(); final _endDateController = TextEditingController(); + final _speakerController = TextEditingController(); final _sessionService = SessionService(); DateTime? dateTime; @@ -33,6 +34,7 @@ class _AddSessionFormState extends State { if (_formKey.currentState!.validate()) { var title = _titleController.text; var place = _placeController.text; + var speaker = _speakerController.text; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Uploading')), @@ -41,6 +43,9 @@ class _AddSessionFormState extends State { Session? s = await _sessionService.createSession( _begin!.toUtc(), _end!.toUtc(), place, _kind, title); if (s != null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('HERE')), + ); SessionsNotifier notifier = Provider.of(context, listen: false); notifier.add(s); @@ -61,6 +66,9 @@ class _AddSessionFormState extends State { ), ); } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('BL')), + ); ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( @@ -209,6 +217,22 @@ class _AddSessionFormState extends State { // do other stuff with _category setState(() => _kind = newValue.toString()); })), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _speakerController, + validator: (value) { + if ((value == null || value.isEmpty) && _kind == "TALK") { + return 'Please enter a speaker'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.star), + labelText: "Speaker *", + ), + ), + ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( diff --git a/frontend/lib/services/eventService.dart b/frontend/lib/services/eventService.dart index 075773f8..bca3819b 100644 --- a/frontend/lib/services/eventService.dart +++ b/frontend/lib/services/eventService.dart @@ -281,7 +281,7 @@ class EventService extends Service { }; if (s.kind == 'TALK') { - body['speaker'] = json.encode(s.speakersIds); + body['speaker'] = json.encode(s.speaker); } else { body['company'] = s.companyId; } diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index e80d9261..0cd7e846 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -52,7 +52,7 @@ class SessionService extends Service { "kind": kind.toUpperCase() }; - Response response = await dio.post("/sessions", data: body); + Response response = await dio.post("/events/sessions", data: body); try { return Session.fromJson(json.decode(response.data!)); From b3fa302644c523dbdcdd96662300451d91619616 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sat, 22 Oct 2022 21:09:13 +0100 Subject: [PATCH 15/73] added description --- frontend/lib/models/session.dart | 4 ++-- .../lib/routes/session/AddSessionForm.dart | 22 +++++++++++++++++-- frontend/lib/services/sessionService.dart | 6 +++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/frontend/lib/models/session.dart b/frontend/lib/models/session.dart index 492b1a88..b38f5ee8 100644 --- a/frontend/lib/models/session.dart +++ b/frontend/lib/models/session.dart @@ -5,7 +5,7 @@ class Session { final DateTime begin; final DateTime end; final String title; - final String? description; + final String description; final String? place; final String kind; final String? companyId; @@ -18,7 +18,7 @@ class Session { required this.begin, required this.end, required this.title, - this.description, + required this.description, this.place, required this.kind, this.companyId, diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index e8fc6378..554e5524 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -22,6 +22,7 @@ class _AddSessionFormState extends State { final _beginDateController = TextEditingController(); final _endDateController = TextEditingController(); final _speakerController = TextEditingController(); + final _descriptionController = TextEditingController(); final _sessionService = SessionService(); DateTime? dateTime; @@ -35,13 +36,14 @@ class _AddSessionFormState extends State { var title = _titleController.text; var place = _placeController.text; var speaker = _speakerController.text; + var description = _descriptionController.text; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Uploading')), ); - Session? s = await _sessionService.createSession( - _begin!.toUtc(), _end!.toUtc(), place, _kind, title); + Session? s = await _sessionService.createSession(_begin!.toUtc(), + _end!.toUtc(), place, _kind, title, description, speaker); if (s != null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('HERE')), @@ -133,6 +135,22 @@ class _AddSessionFormState extends State { ), ), ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _descriptionController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a description'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.title), + labelText: "Description *", + ), + ), + ), Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 0cd7e846..0a2a7b18 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -43,13 +43,15 @@ class SessionService extends Service { } Future createSession(DateTime begin, DateTime end, String place, - String kind, String title) async { + String kind, String title, String description, String speaker) async { var body = { "begin": begin.toIso8601String(), "end": end.toIso8601String(), "place": place, "title": title, - "kind": kind.toUpperCase() + "kind": kind.toUpperCase(), + "description": description, + "speaker": speaker }; Response response = await dio.post("/events/sessions", data: body); From e3c2d204ef6ade9422ff8c5224a5f335b6370a32 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sat, 22 Oct 2022 21:18:33 +0100 Subject: [PATCH 16/73] modified description icon --- frontend/lib/routes/session/AddSessionForm.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 554e5524..4c6aa22c 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -146,7 +146,7 @@ class _AddSessionFormState extends State { return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.title), + icon: const Icon(Icons.description), labelText: "Description *", ), ), From 31e202b2e48221b60b8a77272842b5c711d88e08 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sat, 22 Oct 2022 21:49:20 +0100 Subject: [PATCH 17/73] added optional videourl --- .../lib/routes/session/AddSessionForm.dart | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 4c6aa22c..a291820d 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -13,7 +13,7 @@ class AddSessionForm extends StatefulWidget { _AddSessionFormState createState() => _AddSessionFormState(); } -const kinds = ["TALK", "PRESENTATION", "WORKSHOP"]; +const kinds = ["Talk", "Presentation", "Workshop"]; class _AddSessionFormState extends State { final _formKey = GlobalKey(); @@ -23,6 +23,8 @@ class _AddSessionFormState extends State { final _endDateController = TextEditingController(); final _speakerController = TextEditingController(); final _descriptionController = TextEditingController(); + final _companyController = TextEditingController(); + final _videoURLController = TextEditingController(); final _sessionService = SessionService(); DateTime? dateTime; @@ -251,6 +253,26 @@ class _AddSessionFormState extends State { ), ), ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _companyController, + decoration: const InputDecoration( + icon: const Icon(Icons.business), + labelText: "Company *", + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _videoURLController, + decoration: const InputDecoration( + icon: const Icon(Icons.video_call), + labelText: "VideoURL *", + ), + ), + ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( From ef884fa1bbeb490fb0a59c9c56cb52acaa91a3b1 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sat, 22 Oct 2022 22:12:19 +0100 Subject: [PATCH 18/73] Company and Speaker are shown according to session kind --- .../lib/routes/session/AddSessionForm.dart | 71 ++++++++++--------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index a291820d..d135c187 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -153,22 +153,6 @@ class _AddSessionFormState extends State { ), ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _placeController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a place'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.place), - labelText: "Place *", - ), - ), - ), Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( @@ -234,32 +218,53 @@ class _AddSessionFormState extends State { return new DropdownMenuItem(value: kind, child: Text(kind)); }).toList(), onChanged: (newValue) { - // do other stuff with _category setState(() => _kind = newValue.toString()); })), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_kind == "Talk") + ? TextFormField( + controller: _speakerController, + validator: (value) { + if (_kind == "Talk") { + if (value == null || value.isEmpty) { + return 'Please enter a speaker'; + } + return null; + } + }, + decoration: const InputDecoration( + icon: const Icon(Icons.place), + labelText: "Speaker *", + ), + ) + : null, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_kind == "Workshop" || _kind == "Presentation") + ? TextFormField( + controller: _companyController, + decoration: const InputDecoration( + icon: const Icon(Icons.business), + labelText: "Company *", + ), + ) + : null, + ), Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - controller: _speakerController, + controller: _placeController, validator: (value) { - if ((value == null || value.isEmpty) && _kind == "TALK") { - return 'Please enter a speaker'; + if (value == null || value.isEmpty) { + return 'Please enter a place'; } return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.star), - labelText: "Speaker *", - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _companyController, - decoration: const InputDecoration( - icon: const Icon(Icons.business), - labelText: "Company *", + icon: const Icon(Icons.place), + labelText: "Place ", ), ), ), @@ -269,7 +274,7 @@ class _AddSessionFormState extends State { controller: _videoURLController, decoration: const InputDecoration( icon: const Icon(Icons.video_call), - labelText: "VideoURL *", + labelText: "VideoURL", ), ), ), From 4444015bc17541b5c2391d30ab00cba02b8538f1 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sat, 22 Oct 2022 22:22:41 +0100 Subject: [PATCH 19/73] Place is optional + updated speaker icon --- .../lib/routes/session/AddSessionForm.dart | 11 ++---- frontend/lib/services/sessionService.dart | 39 +++++++++++++------ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index d135c187..041de5f2 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -39,13 +39,14 @@ class _AddSessionFormState extends State { var place = _placeController.text; var speaker = _speakerController.text; var description = _descriptionController.text; + var company = _companyController.text; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Uploading')), ); Session? s = await _sessionService.createSession(_begin!.toUtc(), - _end!.toUtc(), place, _kind, title, description, speaker); + _end!.toUtc(), place, _kind, title, description, speaker, company); if (s != null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('HERE')), @@ -234,7 +235,7 @@ class _AddSessionFormState extends State { } }, decoration: const InputDecoration( - icon: const Icon(Icons.place), + icon: const Icon(Icons.speaker), labelText: "Speaker *", ), ) @@ -256,12 +257,6 @@ class _AddSessionFormState extends State { padding: const EdgeInsets.all(8.0), child: TextFormField( controller: _placeController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a place'; - } - return null; - }, decoration: const InputDecoration( icon: const Icon(Icons.place), labelText: "Place ", diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 0a2a7b18..12d7421a 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -42,17 +42,34 @@ class SessionService extends Service { } } - Future createSession(DateTime begin, DateTime end, String place, - String kind, String title, String description, String speaker) async { - var body = { - "begin": begin.toIso8601String(), - "end": end.toIso8601String(), - "place": place, - "title": title, - "kind": kind.toUpperCase(), - "description": description, - "speaker": speaker - }; + Future createSession( + DateTime begin, + DateTime end, + String place, + String kind, + String title, + String description, + String? speaker, + String? company) async { + var body = kind == "TALK" + ? { + "begin": begin.toIso8601String(), + "end": end.toIso8601String(), + "place": place, + "title": title, + "kind": kind.toUpperCase(), + "description": description, + "speaker": speaker + } + : { + "begin": begin.toIso8601String(), + "end": end.toIso8601String(), + "place": place, + "title": title, + "kind": kind.toUpperCase(), + "description": description, + "company": speaker + }; Response response = await dio.post("/events/sessions", data: body); From aa1e3fef9ef6978569066e9825803788dd271943 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sat, 22 Oct 2022 22:53:29 +0100 Subject: [PATCH 20/73] Fixed Sessions Provider --- frontend/lib/main.dart | 4 ++++ frontend/lib/routes/HomeScreen.dart | 35 ++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index be386298..d665bb0a 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -4,6 +4,7 @@ import 'package:frontend/components/deckTheme.dart'; import 'package:frontend/components/eventNotifier.dart'; import 'package:frontend/routes/company/CompanyTableNotifier.dart'; import 'package:frontend/routes/meeting/MeetingsNotifier.dart'; +import 'package:frontend/routes/session/SessionsNotifier.dart'; import 'package:frontend/routes/speaker/speakerNotifier.dart'; import 'package:frontend/models/event.dart'; import 'package:frontend/services/authService.dart'; @@ -35,6 +36,9 @@ Future main() async { ChangeNotifierProvider( create: (_) => MeetingsNotifier(meetings: []), ), + ChangeNotifierProvider( + create: (_) => SessionsNotifier(sessions: []), + ), ChangeNotifierProvider( create: (_) => AuthService(), ), diff --git a/frontend/lib/routes/HomeScreen.dart b/frontend/lib/routes/HomeScreen.dart index c64c6695..ef20ec83 100644 --- a/frontend/lib/routes/HomeScreen.dart +++ b/frontend/lib/routes/HomeScreen.dart @@ -13,7 +13,6 @@ import 'package:google_sign_in/google_sign_in.dart'; import 'package:provider/provider.dart'; import 'package:frontend/routes/session/SessionPage.dart'; - class HomeScreen extends StatefulWidget { HomeScreen({Key? key}) : super(key: key); @@ -194,16 +193,30 @@ class _HomeScreenState extends State { case 5: { - return FloatingActionButton.extended( - onPressed: () { - Navigator.pushNamed( - context, - Routes.AddSession, - ); - }, - label: const Text('Create New Session'), - icon: const Icon(Icons.add), - ); + return FutureBuilder( + future: Provider.of(context).role, + builder: (context, snapshot) { + if (snapshot.hasData) { + Role r = snapshot.data as Role; + + if (r == Role.ADMIN || r == Role.COORDINATOR) { + return FloatingActionButton.extended( + onPressed: () { + Navigator.pushNamed( + context, + Routes.AddSession, + ); + }, + label: const Text('Create New Session'), + icon: const Icon(Icons.add), + ); + } else { + return Container(); + } + } else { + return Container(); + } + }); } } } From 77a477ba1619f8bd52a0d2abe0ad739c14363d81 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sun, 23 Oct 2022 00:07:58 +0100 Subject: [PATCH 21/73] Started adding serach when adding speaker to a session --- .../lib/routes/session/AddSessionForm.dart | 311 +++++++++++------- 1 file changed, 184 insertions(+), 127 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 041de5f2..7f3eb920 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:frontend/components/appbar.dart'; import 'package:frontend/models/session.dart'; +import 'package:frontend/models/speaker.dart'; import 'package:frontend/routes/session/SessionsNotifier.dart'; import 'package:frontend/services/sessionService.dart'; +import 'package:frontend/services/speakerService.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; @@ -27,6 +29,10 @@ class _AddSessionFormState extends State { final _videoURLController = TextEditingController(); final _sessionService = SessionService(); + late Future> speakers; + + SpeakerService speakerService = new SpeakerService(); + DateTime? dateTime; DateTime? _begin; DateTime? _end; @@ -120,171 +126,222 @@ class _AddSessionFormState extends State { Widget _buildForm() { return Form( key: _formKey, - child: Column( - children: [ - Padding( + child: Column(children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _titleController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a title'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.title), + labelText: "Title *", + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _descriptionController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a description'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.description), + labelText: "Description *", + ), + ), + ), + Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - controller: _titleController, + controller: _beginDateController, validator: (value) { if (value == null || value.isEmpty) { - return 'Please enter a title'; + return 'Please enter a beggining date'; } return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.title), - labelText: "Title *", + icon: const Icon(Icons.calendar_today), + labelText: "Begin Date *", ), - ), - ), - Padding( + readOnly: true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, true); + String formattedDate = getDateTime(_begin!); + + setState(() { + _beginDateController.text = formattedDate; + }); + }, + )), + Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - controller: _descriptionController, + controller: _endDateController, validator: (value) { if (value == null || value.isEmpty) { - return 'Please enter a description'; + return 'Please enter an ending date'; } return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.description), - labelText: "Description *", + icon: const Icon(Icons.calendar_today), + labelText: "End Date *", ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _beginDateController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a beggining date'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "Begin Date *", - ), - readOnly: true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, true); - String formattedDate = getDateTime(_begin!); + readOnly: true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, false); + String formattedDate = getDateTime(_end!); - setState(() { - _beginDateController.text = formattedDate; - }); - }, - )), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _endDateController, + setState(() { + _endDateController.text = formattedDate; + }); + }, + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: DropdownButtonFormField( validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter an ending date'; + if (value == null) { + return 'Please enter the kind of session'; } return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "End Date *", + icon: const Icon(Icons.category), + labelText: "Kind *", ), - readOnly: true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, false); - String formattedDate = getDateTime(_end!); - - setState(() { - _endDateController.text = formattedDate; - }); - }, - )), - Padding( - padding: const EdgeInsets.all(8.0), - child: DropdownButtonFormField( + items: kinds.map((String kind) { + return new DropdownMenuItem(value: kind, child: Text(kind)); + }).toList(), + onChanged: (newValue) { + setState(() => _kind = newValue.toString()); + })), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_kind == "Talk") + ? TextFormField( + controller: _speakerController, validator: (value) { - if (value == null) { - return 'Please enter the kind of session'; + if (_kind == "Talk") { + if (value == null || value.isEmpty) { + return 'Please enter a speaker'; + } + return null; } - return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.category), - labelText: "Kind *", + icon: const Icon(Icons.star), + labelText: "Speaker *", ), - items: kinds.map((String kind) { - return new DropdownMenuItem(value: kind, child: Text(kind)); - }).toList(), - onChanged: (newValue) { - setState(() => _kind = newValue.toString()); - })), - Padding( - padding: const EdgeInsets.all(8.0), - child: (_kind == "Talk") - ? TextFormField( - controller: _speakerController, - validator: (value) { - if (_kind == "Talk") { - if (value == null || value.isEmpty) { - return 'Please enter a speaker'; - } - return null; - } - }, - decoration: const InputDecoration( - icon: const Icon(Icons.speaker), - labelText: "Speaker *", - ), - ) - : null, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: (_kind == "Workshop" || _kind == "Presentation") - ? TextFormField( - controller: _companyController, - decoration: const InputDecoration( - icon: const Icon(Icons.business), - labelText: "Company *", - ), - ) - : null, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _placeController, - decoration: const InputDecoration( - icon: const Icon(Icons.place), - labelText: "Place ", - ), + onChanged: (newQuery) { + setState(() {}); + if (_speakerController.text.length > 1) { + this.speakers = speakerService.getSpeakers( + name: _speakerController.text); + } + }) + : null, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_kind == "Workshop" || _kind == "Presentation") + ? TextFormField( + controller: _companyController, + decoration: const InputDecoration( + icon: const Icon(Icons.business), + labelText: "Company *", + ), + ) + : null, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _placeController, + decoration: const InputDecoration( + icon: const Icon(Icons.place), + labelText: "Place ", ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _videoURLController, - decoration: const InputDecoration( - icon: const Icon(Icons.video_call), - labelText: "VideoURL", - ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _videoURLController, + decoration: const InputDecoration( + icon: const Icon(Icons.video_call), + labelText: "VideoURL", ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: ElevatedButton( - onPressed: () => _submit(), - child: const Text('Submit'), - ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () => _submit(), + child: const Text('Submit'), ), - ], - ), + ), + ...getResults(MediaQuery.of(context).size.height / 2) + ]), ); } + List getResults(double height) { + if (_speakerController.text.length > 1) { + return [ + Container( + decoration: new BoxDecoration( + color: Theme.of(context).cardColor, + ), + child: FutureBuilder( + future: Future.wait([this.speakers]), + builder: (context, snapshot) { + if (snapshot.hasData) { + List data = snapshot.data as List; + + List speaksMatched = data[0] as List; + return searchResults(speaksMatched, height); + } else { + return Center(child: CircularProgressIndicator()); + } + })) + ]; + } else { + return []; + } + } + + Widget searchResults(List speakers, double listHeight) { + List results = getListCards(speakers); + return Container( + constraints: BoxConstraints(maxHeight: listHeight), + child: ListView.builder( + shrinkWrap: true, + itemCount: results.length, + itemBuilder: (BuildContext context, int index) { + return results[index]; + })); + } + + List getListCards(List speakers) { + List results = []; + if (speakers.length != 0) { + results.addAll(speakers.map( + (e) => SearchResultWidget(speaker: e, index: speakers.indexOf(e)))); + } + return results; + } + @override Widget build(BuildContext context) { CustomAppBar appBar = CustomAppBar( From fc3d76c62daa7d92169ad41137404fdd80cf1951 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sun, 23 Oct 2022 02:11:47 +0100 Subject: [PATCH 22/73] suggesting speakers --- .../lib/routes/session/AddSessionForm.dart | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 7f3eb920..228b886a 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -23,7 +23,7 @@ class _AddSessionFormState extends State { final _placeController = TextEditingController(); final _beginDateController = TextEditingController(); final _endDateController = TextEditingController(); - final _speakerController = TextEditingController(); + var _speakerController = TextEditingController(); final _descriptionController = TextEditingController(); final _companyController = TextEditingController(); final _videoURLController = TextEditingController(); @@ -252,6 +252,7 @@ class _AddSessionFormState extends State { }) : null, ), + ...getResults(MediaQuery.of(context).size.height / 3), Padding( padding: const EdgeInsets.all(8.0), child: (_kind == "Workshop" || _kind == "Presentation") @@ -291,7 +292,6 @@ class _AddSessionFormState extends State { child: const Text('Submit'), ), ), - ...getResults(MediaQuery.of(context).size.height / 2) ]), ); } @@ -336,12 +336,49 @@ class _AddSessionFormState extends State { List getListCards(List speakers) { List results = []; if (speakers.length != 0) { - results.addAll(speakers.map( - (e) => SearchResultWidget(speaker: e, index: speakers.indexOf(e)))); + results.addAll(speakers + .map((e) => SpeakerSearch(speaker: e, index: speakers.indexOf(e)))); } return results; } + SpeakerSearch({required Speaker speaker, required int index}) { + return InkWell( + onTap: () { + _speakerController.text = speaker.id; + setState(() {}); + }, + child: Center( + child: ListTile( + leading: CircleAvatar( + foregroundImage: NetworkImage(getImageURL(speaker)), + backgroundImage: AssetImage( + 'assets/noImage.png', + ), + ), + title: Text(getName(speaker)), + ), + )); + } + + String getImageURL(Speaker speaker) { + if (speaker != null) { + return speaker!.imgs!.internal!; + } else { + //ERROR case + return ""; + } + } + + String getName(Speaker speaker) { + if (speaker != null) { + return speaker!.name; + } else { + //ERROR case + return ""; + } + } + @override Widget build(BuildContext context) { CustomAppBar appBar = CustomAppBar( From e9afae9a150145851626421daec609d0948ed22e Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Tue, 25 Oct 2022 16:57:51 +0100 Subject: [PATCH 23/73] Added tickets checkbox --- frontend/lib/models/session.dart | 8 ++-- .../lib/routes/session/AddSessionForm.dart | 40 +++++++++++++++---- frontend/lib/services/eventService.dart | 4 +- frontend/lib/services/sessionService.dart | 8 ++-- 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/frontend/lib/models/session.dart b/frontend/lib/models/session.dart index b38f5ee8..ed33bedc 100644 --- a/frontend/lib/models/session.dart +++ b/frontend/lib/models/session.dart @@ -9,7 +9,7 @@ class Session { final String? place; final String kind; final String? companyId; - final String? speaker; + final List? speakersIds; final String? videoURL; final SessionTickets? tickets; @@ -22,7 +22,7 @@ class Session { this.place, required this.kind, this.companyId, - this.speaker, + this.speakersIds, this.videoURL, this.tickets, }); @@ -37,7 +37,7 @@ class Session { place: json['place'], kind: json['kind'], companyId: json['company'], - speaker: json['speaker'], + speakersIds: json['speaker'], videoURL: json['videoURL'], tickets: SessionTickets.fromJson(json['tickets']), ); @@ -52,7 +52,7 @@ class Session { 'place': place, 'kind': kind, 'company': companyId, - 'speaker': speaker, + 'speaker': speakersIds, 'videoURL': videoURL, 'tickets': tickets?.toJson(), }; diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 228b886a..2dcc40e0 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -38,6 +38,7 @@ class _AddSessionFormState extends State { DateTime? _end; String _kind = ""; + bool value = false; void _submit() async { if (_formKey.currentState!.validate()) { @@ -51,8 +52,17 @@ class _AddSessionFormState extends State { const SnackBar(content: Text('Uploading')), ); - Session? s = await _sessionService.createSession(_begin!.toUtc(), - _end!.toUtc(), place, _kind, title, description, speaker, company); + List speakersIds = [speaker]; + + Session? s = await _sessionService.createSession( + _begin!.toUtc(), + _end!.toUtc(), + place, + _kind, + title, + description, + speakersIds, + company); if (s != null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('HERE')), @@ -277,12 +287,26 @@ class _AddSessionFormState extends State { ), Padding( padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _videoURLController, - decoration: const InputDecoration( - icon: const Icon(Icons.video_call), - labelText: "VideoURL", - ), + child: Row( + children: [ + SizedBox( + width: 10, + ), //SizedBox + Text( + 'Add tickets to this session', + //style: TextStyle(fontSize: 17.0), + ), //Text + SizedBox(width: 10), //SizedBox + /** Checkbox Widget **/ + Checkbox( + value: this.value, + onChanged: (value) { + setState(() { + this.value = value!; + }); + }, + ), //Checkbox + ], //[] ), ), Padding( diff --git a/frontend/lib/services/eventService.dart b/frontend/lib/services/eventService.dart index bca3819b..485407bb 100644 --- a/frontend/lib/services/eventService.dart +++ b/frontend/lib/services/eventService.dart @@ -280,8 +280,8 @@ class EventService extends Service { 'place': s.place, }; - if (s.kind == 'TALK') { - body['speaker'] = json.encode(s.speaker); + if (s.kind == 'Talk') { + body['speaker'] = json.encode(s.speakersIds); } else { body['company'] = s.companyId; } diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 12d7421a..27de944d 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -49,9 +49,9 @@ class SessionService extends Service { String kind, String title, String description, - String? speaker, + List? speakersIds, String? company) async { - var body = kind == "TALK" + var body = kind == "Talk" ? { "begin": begin.toIso8601String(), "end": end.toIso8601String(), @@ -59,7 +59,7 @@ class SessionService extends Service { "title": title, "kind": kind.toUpperCase(), "description": description, - "speaker": speaker + "speaker": speakersIds } : { "begin": begin.toIso8601String(), @@ -68,7 +68,7 @@ class SessionService extends Service { "title": title, "kind": kind.toUpperCase(), "description": description, - "company": speaker + "company": "62eb938a34a18caadf832709" }; Response response = await dio.post("/events/sessions", data: body); From 76a1b243433561f9f7746cd7fd542d084e8a63f8 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Tue, 25 Oct 2022 19:53:05 +0100 Subject: [PATCH 24/73] Tickets with slider and dates --- .../lib/routes/session/AddSessionForm.dart | 137 ++++++++++++++++-- 1 file changed, 127 insertions(+), 10 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 2dcc40e0..f54a763d 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -16,6 +16,7 @@ class AddSessionForm extends StatefulWidget { } const kinds = ["Talk", "Presentation", "Workshop"]; +const List ticketOptions = [Text('Yes'), Text('No')]; class _AddSessionFormState extends State { final _formKey = GlobalKey(); @@ -27,6 +28,9 @@ class _AddSessionFormState extends State { final _descriptionController = TextEditingController(); final _companyController = TextEditingController(); final _videoURLController = TextEditingController(); + final _maxTicketsController = TextEditingController(); + final _ticketBeginDateController = TextEditingController(); + final _ticketEndDateController = TextEditingController(); final _sessionService = SessionService(); late Future> speakers; @@ -39,6 +43,9 @@ class _AddSessionFormState extends State { String _kind = ""; bool value = false; + final List _ticketSelection = [false, true]; + bool _yes = false; + double _currentSliderValue = 0; void _submit() async { if (_formKey.currentState!.validate()) { @@ -287,17 +294,49 @@ class _AddSessionFormState extends State { ), Padding( padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - SizedBox( - width: 10, - ), //SizedBox + child: Column( + children: [ + TextField( + decoration: const InputDecoration( + icon: const Icon(Icons.airplane_ticket), + labelText: "Add tickets to this session"), + ), + Text('Add tickets'), + const SizedBox(height: 5), + ToggleButtons( + onPressed: (int index) { + setState(() { + // The button that is tapped is set to true, and the others to false. + for (int i = 0; i < _ticketSelection.length; i++) { + _ticketSelection[i] = i == index; + _yes = i != index; + } + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: Color.fromARGB(255, 63, 81, 181), + selectedColor: Color.fromARGB(255, 63, 81, 181), + fillColor: Color.fromARGB(255, 150, 164, 243), + color: Color.fromARGB(255, 0, 0, 0), + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 80.0, + ), + isSelected: _ticketSelection, + children: ticketOptions, + ), + IconButton( + icon: const Icon(Icons.airplane_ticket), + tooltip: 'Add tickets to this session', + onPressed: () { + setState(() {}); + }, + ), Text( 'Add tickets to this session', //style: TextStyle(fontSize: 17.0), - ), //Text - SizedBox(width: 10), //SizedBox - /** Checkbox Widget **/ + ), + // SizedBox(width: 10), //SizedBox Checkbox( value: this.value, onChanged: (value) { @@ -305,8 +344,86 @@ class _AddSessionFormState extends State { this.value = value!; }); }, - ), //Checkbox - ], //[] + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_yes == true) + ? TextFormField( + controller: _maxTicketsController, + decoration: const InputDecoration( + icon: const Icon(Icons.airplane_ticket), + labelText: "Maximum number of tickets *", + ), + ) + : null, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_yes == true) + ? TextFormField( + controller: _ticketBeginDateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a beggining date for ticket availability *'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "Ticket availability begin date *", + ), + readOnly: + true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, true); + String formattedDate = getDateTime(_begin!); + setState(() { + _beginDateController.text = formattedDate; + }); + }, + ) + : null), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_yes == true) + ? TextFormField( + controller: _ticketBeginDateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter an end date for ticket availability *'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "Ticket availability end date *", + ), + readOnly: + true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, true); + String formattedDate = getDateTime(_begin!); + setState(() { + _beginDateController.text = formattedDate; + }); + }, + ) + : null), + Padding( + padding: const EdgeInsets.all(8.0), + child: Slider( + value: _currentSliderValue, + max: 100, + divisions: 5, + label: _currentSliderValue.round().toString(), + onChanged: (double value) { + setState(() { + _currentSliderValue = value; + }); + }, ), ), Padding( From a170bf9501a89cd821dc3de24d53626c923df1d0 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Tue, 25 Oct 2022 22:36:54 +0100 Subject: [PATCH 25/73] Tickets with switch button --- .../lib/routes/session/AddSessionForm.dart | 173 ++++++++++-------- 1 file changed, 95 insertions(+), 78 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index f54a763d..b377b557 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -46,6 +46,7 @@ class _AddSessionFormState extends State { final List _ticketSelection = [false, true]; bool _yes = false; double _currentSliderValue = 0; + bool _ticketsOn = false; void _submit() async { if (_formKey.currentState!.validate()) { @@ -292,77 +293,84 @@ class _AddSessionFormState extends State { ), ), ), + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: Column( + // children: [ + // TextField( + // decoration: const InputDecoration( + // icon: const Icon(Icons.airplane_ticket), + // labelText: "Add tickets to this session"), + // ), + // Text('Add tickets'), + // const SizedBox(height: 5), + // ToggleButtons( + // onPressed: (int index) { + // setState(() { + // // The button that is tapped is set to true, and the others to false. + // for (int i = 0; i < _ticketSelection.length; i++) { + // _ticketSelection[i] = i == index; + // _yes = i != index; + // } + // }); + // }, + // borderRadius: const BorderRadius.all(Radius.circular(8)), + // selectedBorderColor: Color.fromARGB(255, 63, 81, 181), + // selectedColor: Color.fromARGB(255, 63, 81, 181), + // fillColor: Color.fromARGB(255, 150, 164, 243), + // color: Color.fromARGB(255, 0, 0, 0), + // constraints: const BoxConstraints( + // minHeight: 40.0, + // minWidth: 80.0, + // ), + // isSelected: _ticketSelection, + // children: ticketOptions, + // ), + // Text( + // 'Add tickets to this session', + // //style: TextStyle(fontSize: 17.0), + // ), + // // SizedBox(width: 10), //SizedBox + // Checkbox( + // value: this.value, + // onChanged: (value) { + // setState(() { + // this.value = value!; + // }); + // }, + // ), + // ], + // ), + // ), Padding( padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - TextField( - decoration: const InputDecoration( - icon: const Icon(Icons.airplane_ticket), - labelText: "Add tickets to this session"), - ), - Text('Add tickets'), - const SizedBox(height: 5), - ToggleButtons( - onPressed: (int index) { - setState(() { - // The button that is tapped is set to true, and the others to false. - for (int i = 0; i < _ticketSelection.length; i++) { - _ticketSelection[i] = i == index; - _yes = i != index; - } - }); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: Color.fromARGB(255, 63, 81, 181), - selectedColor: Color.fromARGB(255, 63, 81, 181), - fillColor: Color.fromARGB(255, 150, 164, 243), - color: Color.fromARGB(255, 0, 0, 0), - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 80.0, - ), - isSelected: _ticketSelection, - children: ticketOptions, - ), - IconButton( - icon: const Icon(Icons.airplane_ticket), - tooltip: 'Add tickets to this session', - onPressed: () { - setState(() {}); - }, - ), - Text( - 'Add tickets to this session', - //style: TextStyle(fontSize: 17.0), - ), - // SizedBox(width: 10), //SizedBox - Checkbox( - value: this.value, - onChanged: (value) { - setState(() { - this.value = value!; - }); - }, - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: (_yes == true) - ? TextFormField( - controller: _maxTicketsController, - decoration: const InputDecoration( - icon: const Icon(Icons.airplane_ticket), - labelText: "Maximum number of tickets *", - ), + child: (_ticketsOn == true) + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Maximum number of tickets *", + style: TextStyle(fontSize: 17.0), + textAlign: TextAlign.right, + ), + Slider( + value: _currentSliderValue, + max: 100, + divisions: 100, + label: _currentSliderValue.round().toString(), + onChanged: (double value) { + setState(() { + _currentSliderValue = value; + }); + }, + ), + ], ) : null, ), Padding( padding: const EdgeInsets.all(8.0), - child: (_yes == true) + child: (_ticketsOn == true) ? TextFormField( controller: _ticketBeginDateController, validator: (value) { @@ -388,7 +396,7 @@ class _AddSessionFormState extends State { : null), Padding( padding: const EdgeInsets.all(8.0), - child: (_yes == true) + child: (_ticketsOn == true) ? TextFormField( controller: _ticketBeginDateController, validator: (value) { @@ -413,19 +421,28 @@ class _AddSessionFormState extends State { ) : null), Padding( - padding: const EdgeInsets.all(8.0), - child: Slider( - value: _currentSliderValue, - max: 100, - divisions: 5, - label: _currentSliderValue.round().toString(), - onChanged: (double value) { - setState(() { - _currentSliderValue = value; - }); - }, - ), - ), + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Text( + "Add tickets *", + style: TextStyle(fontSize: 17.0), + textAlign: TextAlign.right, + ), + Switch( + onChanged: (bool value) { + setState(() { + _ticketsOn = value; + }); + }, + value: _ticketsOn, + activeColor: Color.fromARGB(255, 19, 214, 77), + activeTrackColor: Color.fromARGB(255, 97, 233, 138), + inactiveThumbColor: Color.fromARGB(255, 216, 30, 30), + inactiveTrackColor: Color.fromARGB(255, 245, 139, 139), + ), + ], + )), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( From e4c4696a8d2f24d1a1ae92f0773b0c59fb4624f3 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 26 Oct 2022 00:08:43 +0100 Subject: [PATCH 26/73] End and begin date for tickets --- .../lib/routes/session/AddSessionForm.dart | 125 +++++++----------- 1 file changed, 47 insertions(+), 78 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index b377b557..c26b69ac 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -293,64 +293,56 @@ class _AddSessionFormState extends State { ), ), ), - // Padding( - // padding: const EdgeInsets.all(8.0), - // child: Column( - // children: [ - // TextField( - // decoration: const InputDecoration( - // icon: const Icon(Icons.airplane_ticket), - // labelText: "Add tickets to this session"), - // ), - // Text('Add tickets'), - // const SizedBox(height: 5), - // ToggleButtons( - // onPressed: (int index) { - // setState(() { - // // The button that is tapped is set to true, and the others to false. - // for (int i = 0; i < _ticketSelection.length; i++) { - // _ticketSelection[i] = i == index; - // _yes = i != index; - // } - // }); - // }, - // borderRadius: const BorderRadius.all(Radius.circular(8)), - // selectedBorderColor: Color.fromARGB(255, 63, 81, 181), - // selectedColor: Color.fromARGB(255, 63, 81, 181), - // fillColor: Color.fromARGB(255, 150, 164, 243), - // color: Color.fromARGB(255, 0, 0, 0), - // constraints: const BoxConstraints( - // minHeight: 40.0, - // minWidth: 80.0, - // ), - // isSelected: _ticketSelection, - // children: ticketOptions, - // ), - // Text( - // 'Add tickets to this session', - // //style: TextStyle(fontSize: 17.0), - // ), - // // SizedBox(width: 10), //SizedBox - // Checkbox( - // value: this.value, - // onChanged: (value) { - // setState(() { - // this.value = value!; - // }); - // }, - // ), - // ], - // ), - // ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Icon( + Icons.receipt, + color: Color.fromARGB(255, 124, 123, 123), + size: 25.0, + ), + // Container( + // child: Text( + // "Session Ticket", + // style: TextStyle(fontSize: 32), + // ), + // decoration: BoxDecoration( + // color: Color.fromARGB(255, 124, 123, 123), + // borderRadius: BorderRadius.circular(100), + // ), + // ), + Text( + "Add tickets ", + style: TextStyle( + fontSize: 17.0, + color: Color.fromARGB(255, 102, 101, 101)), + textAlign: TextAlign.right, + ), + Switch( + onChanged: (bool value) { + setState(() { + _ticketsOn = value; + }); + }, + value: _ticketsOn, + activeColor: Color.fromARGB(255, 19, 214, 77), + activeTrackColor: Color.fromARGB(255, 97, 233, 138), + inactiveThumbColor: Color.fromARGB(255, 216, 30, 30), + inactiveTrackColor: Color.fromARGB(255, 245, 139, 139), + ), + ], + )), Padding( padding: const EdgeInsets.all(8.0), child: (_ticketsOn == true) - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, + ? Row( children: [ Text( "Maximum number of tickets *", - style: TextStyle(fontSize: 17.0), + style: TextStyle( + fontSize: 17.0, + color: Color.fromARGB(255, 102, 101, 101)), textAlign: TextAlign.right, ), Slider( @@ -389,7 +381,7 @@ class _AddSessionFormState extends State { await _selectDateTime(context, true); String formattedDate = getDateTime(_begin!); setState(() { - _beginDateController.text = formattedDate; + _ticketBeginDateController.text = formattedDate; }); }, ) @@ -398,7 +390,7 @@ class _AddSessionFormState extends State { padding: const EdgeInsets.all(8.0), child: (_ticketsOn == true) ? TextFormField( - controller: _ticketBeginDateController, + controller: _ticketEndDateController, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter an end date for ticket availability *'; @@ -415,34 +407,11 @@ class _AddSessionFormState extends State { await _selectDateTime(context, true); String formattedDate = getDateTime(_begin!); setState(() { - _beginDateController.text = formattedDate; + _ticketEndDateController.text = formattedDate; }); }, ) : null), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Text( - "Add tickets *", - style: TextStyle(fontSize: 17.0), - textAlign: TextAlign.right, - ), - Switch( - onChanged: (bool value) { - setState(() { - _ticketsOn = value; - }); - }, - value: _ticketsOn, - activeColor: Color.fromARGB(255, 19, 214, 77), - activeTrackColor: Color.fromARGB(255, 97, 233, 138), - inactiveThumbColor: Color.fromARGB(255, 216, 30, 30), - inactiveTrackColor: Color.fromARGB(255, 245, 139, 139), - ), - ], - )), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( From bf6c404bbbe72913c5644926d193fa6f62b7cf4e Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 26 Oct 2022 00:26:45 +0100 Subject: [PATCH 27/73] Fixed datePicker for tickets --- .../lib/routes/session/AddSessionForm.dart | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index c26b69ac..4a6c3573 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -16,7 +16,6 @@ class AddSessionForm extends StatefulWidget { } const kinds = ["Talk", "Presentation", "Workshop"]; -const List ticketOptions = [Text('Yes'), Text('No')]; class _AddSessionFormState extends State { final _formKey = GlobalKey(); @@ -40,21 +39,22 @@ class _AddSessionFormState extends State { DateTime? dateTime; DateTime? _begin; DateTime? _end; + DateTime? _beginTicket; + DateTime? _endTicket; String _kind = ""; bool value = false; - final List _ticketSelection = [false, true]; - bool _yes = false; - double _currentSliderValue = 0; + double _currentTicketsValue = 0; bool _ticketsOn = false; void _submit() async { if (_formKey.currentState!.validate()) { var title = _titleController.text; + var description = _descriptionController.text; var place = _placeController.text; var speaker = _speakerController.text; - var description = _descriptionController.text; var company = _companyController.text; + var maxTickets = _currentTicketsValue; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Uploading')), @@ -108,7 +108,8 @@ class _AddSessionFormState extends State { } } - Future _selectDateTime(BuildContext context, bool isBegin) async { + Future _selectDateTime( + BuildContext context, bool isBegin, bool isTicket) async { final datePicker = await showDatePicker( context: context, initialDate: DateTime.now(), @@ -127,12 +128,18 @@ class _AddSessionFormState extends State { }); if (datePicker != null && timePicker != null) { - if (isBegin) { + if (isBegin && !isTicket) { _begin = DateTime(datePicker.year, datePicker.month, datePicker.day, timePicker.hour, timePicker.minute); - } else { + } else if (isBegin && isTicket) { + _beginTicket = DateTime(datePicker.year, datePicker.month, + datePicker.day, timePicker.hour, timePicker.minute); + } else if (!isBegin && !isTicket) { _end = DateTime(datePicker.year, datePicker.month, datePicker.day, timePicker.hour, timePicker.minute); + } else { + _endTicket = DateTime(datePicker.year, datePicker.month, datePicker.day, + timePicker.hour, timePicker.minute); } } } @@ -193,7 +200,7 @@ class _AddSessionFormState extends State { ), readOnly: true, //prevents editing the date in the form field onTap: () async { - await _selectDateTime(context, true); + await _selectDateTime(context, true, false); String formattedDate = getDateTime(_begin!); setState(() { @@ -217,7 +224,7 @@ class _AddSessionFormState extends State { ), readOnly: true, //prevents editing the date in the form field onTap: () async { - await _selectDateTime(context, false); + await _selectDateTime(context, false, false); String formattedDate = getDateTime(_end!); setState(() { @@ -302,16 +309,6 @@ class _AddSessionFormState extends State { color: Color.fromARGB(255, 124, 123, 123), size: 25.0, ), - // Container( - // child: Text( - // "Session Ticket", - // style: TextStyle(fontSize: 32), - // ), - // decoration: BoxDecoration( - // color: Color.fromARGB(255, 124, 123, 123), - // borderRadius: BorderRadius.circular(100), - // ), - // ), Text( "Add tickets ", style: TextStyle( @@ -346,13 +343,13 @@ class _AddSessionFormState extends State { textAlign: TextAlign.right, ), Slider( - value: _currentSliderValue, + value: _currentTicketsValue, max: 100, divisions: 100, - label: _currentSliderValue.round().toString(), + label: _currentTicketsValue.round().toString(), onChanged: (double value) { setState(() { - _currentSliderValue = value; + _currentTicketsValue = value; }); }, ), @@ -378,8 +375,8 @@ class _AddSessionFormState extends State { readOnly: true, //prevents editing the date in the form field onTap: () async { - await _selectDateTime(context, true); - String formattedDate = getDateTime(_begin!); + await _selectDateTime(context, true, true); + String formattedDate = getDateTime(_beginTicket!); setState(() { _ticketBeginDateController.text = formattedDate; }); @@ -404,8 +401,8 @@ class _AddSessionFormState extends State { readOnly: true, //prevents editing the date in the form field onTap: () async { - await _selectDateTime(context, true); - String formattedDate = getDateTime(_begin!); + await _selectDateTime(context, false, true); + String formattedDate = getDateTime(_endTicket!); setState(() { _ticketEndDateController.text = formattedDate; }); From c3229e63584fd7b562359155758ec7e5cff27169 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 26 Oct 2022 01:22:03 +0100 Subject: [PATCH 28/73] Added scrolling in addSessionForm --- .../lib/routes/session/AddSessionForm.dart | 507 +++++++++--------- 1 file changed, 256 insertions(+), 251 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 4a6c3573..6d7f1037 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:frontend/components/appbar.dart'; import 'package:frontend/models/session.dart'; import 'package:frontend/models/speaker.dart'; @@ -7,6 +8,7 @@ import 'package:frontend/services/sessionService.dart'; import 'package:frontend/services/speakerService.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; class AddSessionForm extends StatefulWidget { AddSessionForm({Key? key}) : super(key: key); @@ -71,6 +73,7 @@ class _AddSessionFormState extends State { description, speakersIds, company); + if (s != null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('HERE')), @@ -150,274 +153,277 @@ class _AddSessionFormState extends State { Widget _buildForm() { return Form( - key: _formKey, - child: Column(children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _titleController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a title'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.title), - labelText: "Title *", - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _descriptionController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a description'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.description), - labelText: "Description *", - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _beginDateController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a beggining date'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "Begin Date *", - ), - readOnly: true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, true, false); - String formattedDate = getDateTime(_begin!); - - setState(() { - _beginDateController.text = formattedDate; - }); - }, - )), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _endDateController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter an ending date'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "End Date *", + key: _formKey, + child: SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Column(children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _titleController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a title'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.title), + labelText: "Title *", + ), ), - readOnly: true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, false, false); - String formattedDate = getDateTime(_end!); - - setState(() { - _endDateController.text = formattedDate; - }); - }, - )), - Padding( - padding: const EdgeInsets.all(8.0), - child: DropdownButtonFormField( + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _descriptionController, validator: (value) { - if (value == null) { - return 'Please enter the kind of session'; + if (value == null || value.isEmpty) { + return 'Please enter a description'; } return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.category), - labelText: "Kind *", + icon: const Icon(Icons.description), + labelText: "Description *", ), - items: kinds.map((String kind) { - return new DropdownMenuItem(value: kind, child: Text(kind)); - }).toList(), - onChanged: (newValue) { - setState(() => _kind = newValue.toString()); - })), - Padding( - padding: const EdgeInsets.all(8.0), - child: (_kind == "Talk") - ? TextFormField( - controller: _speakerController, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _beginDateController, validator: (value) { - if (_kind == "Talk") { - if (value == null || value.isEmpty) { - return 'Please enter a speaker'; - } - return null; + if (value == null || value.isEmpty) { + return 'Please enter a beggining date'; } + return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.star), - labelText: "Speaker *", + icon: const Icon(Icons.calendar_today), + labelText: "Begin Date *", ), - onChanged: (newQuery) { - setState(() {}); - if (_speakerController.text.length > 1) { - this.speakers = speakerService.getSpeakers( - name: _speakerController.text); + readOnly: true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, true, false); + String formattedDate = getDateTime(_begin!); + + setState(() { + _beginDateController.text = formattedDate; + }); + }, + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _endDateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter an ending date'; } - }) - : null, - ), - ...getResults(MediaQuery.of(context).size.height / 3), - Padding( - padding: const EdgeInsets.all(8.0), - child: (_kind == "Workshop" || _kind == "Presentation") - ? TextFormField( - controller: _companyController, + return null; + }, decoration: const InputDecoration( - icon: const Icon(Icons.business), - labelText: "Company *", + icon: const Icon(Icons.calendar_today), + labelText: "End Date *", ), - ) - : null, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _placeController, - decoration: const InputDecoration( - icon: const Icon(Icons.place), - labelText: "Place ", - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Icon( - Icons.receipt, - color: Color.fromARGB(255, 124, 123, 123), - size: 25.0, - ), - Text( - "Add tickets ", - style: TextStyle( - fontSize: 17.0, - color: Color.fromARGB(255, 102, 101, 101)), - textAlign: TextAlign.right, - ), - Switch( - onChanged: (bool value) { + readOnly: true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, false, false); + String formattedDate = getDateTime(_end!); + setState(() { - _ticketsOn = value; + _endDateController.text = formattedDate; }); }, - value: _ticketsOn, - activeColor: Color.fromARGB(255, 19, 214, 77), - activeTrackColor: Color.fromARGB(255, 97, 233, 138), - inactiveThumbColor: Color.fromARGB(255, 216, 30, 30), - inactiveTrackColor: Color.fromARGB(255, 245, 139, 139), + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: DropdownButtonFormField( + validator: (value) { + if (value == null) { + return 'Please enter the kind of session'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.category), + labelText: "Kind *", + ), + items: kinds.map((String kind) { + return new DropdownMenuItem( + value: kind, child: Text(kind)); + }).toList(), + onChanged: (newValue) { + setState(() => _kind = newValue.toString()); + })), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_kind == "Talk") + ? TextFormField( + controller: _speakerController, + validator: (value) { + if (_kind == "Talk") { + if (value == null || value.isEmpty) { + return 'Please enter a speaker'; + } + return null; + } + }, + decoration: const InputDecoration( + icon: const Icon(Icons.star), + labelText: "Speaker *", + ), + onChanged: (newQuery) { + setState(() {}); + if (_speakerController.text.length > 1) { + this.speakers = speakerService.getSpeakers( + name: _speakerController.text); + } + }) + : null, + ), + ...getResults(MediaQuery.of(context).size.height / 3), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_kind == "Workshop" || _kind == "Presentation") + ? TextFormField( + controller: _companyController, + decoration: const InputDecoration( + icon: const Icon(Icons.business), + labelText: "Company *", + ), + ) + : null, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _placeController, + decoration: const InputDecoration( + icon: const Icon(Icons.place), + labelText: "Place ", ), - ], - )), - Padding( - padding: const EdgeInsets.all(8.0), - child: (_ticketsOn == true) - ? Row( + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( children: [ + Icon( + Icons.receipt, + color: Color.fromARGB(255, 124, 123, 123), + size: 25.0, + ), Text( - "Maximum number of tickets *", + "Add tickets ", style: TextStyle( fontSize: 17.0, color: Color.fromARGB(255, 102, 101, 101)), textAlign: TextAlign.right, ), - Slider( - value: _currentTicketsValue, - max: 100, - divisions: 100, - label: _currentTicketsValue.round().toString(), - onChanged: (double value) { + Switch( + onChanged: (bool value) { setState(() { - _currentTicketsValue = value; + _ticketsOn = value; }); }, + value: _ticketsOn, + activeColor: Color.fromARGB(255, 19, 214, 77), + activeTrackColor: Color.fromARGB(255, 97, 233, 138), + inactiveThumbColor: Color.fromARGB(255, 216, 30, 30), + inactiveTrackColor: Color.fromARGB(255, 245, 139, 139), ), ], - ) - : null, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: (_ticketsOn == true) - ? TextFormField( - controller: _ticketBeginDateController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a beggining date for ticket availability *'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "Ticket availability begin date *", - ), - readOnly: - true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, true, true); - String formattedDate = getDateTime(_beginTicket!); - setState(() { - _ticketBeginDateController.text = formattedDate; - }); - }, - ) - : null), - Padding( - padding: const EdgeInsets.all(8.0), - child: (_ticketsOn == true) - ? TextFormField( - controller: _ticketEndDateController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter an end date for ticket availability *'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "Ticket availability end date *", - ), - readOnly: - true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, false, true); - String formattedDate = getDateTime(_endTicket!); - setState(() { - _ticketEndDateController.text = formattedDate; - }); - }, - ) - : null), - Padding( - padding: const EdgeInsets.all(8.0), - child: ElevatedButton( - onPressed: () => _submit(), - child: const Text('Submit'), - ), - ), - ]), - ); + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_ticketsOn == true) + ? Row( + children: [ + Text( + "Maximum number of tickets *", + style: TextStyle( + fontSize: 17.0, + color: Color.fromARGB(255, 102, 101, 101)), + textAlign: TextAlign.right, + ), + Slider( + value: _currentTicketsValue, + max: 100, + divisions: 100, + label: _currentTicketsValue.round().toString(), + onChanged: (double value) { + setState(() { + _currentTicketsValue = value; + }); + }, + ), + ], + ) + : null, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_ticketsOn == true) + ? TextFormField( + controller: _ticketBeginDateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a beggining date for ticket availability *'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "Ticket availability begin date *", + ), + readOnly: + true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, true, true); + String formattedDate = getDateTime(_beginTicket!); + setState(() { + _ticketBeginDateController.text = formattedDate; + }); + }, + ) + : null), + Padding( + padding: const EdgeInsets.all(8.0), + child: (_ticketsOn == true) + ? TextFormField( + controller: _ticketEndDateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter an end date for ticket availability *'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "Ticket availability end date *", + ), + readOnly: + true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, false, true); + String formattedDate = getDateTime(_endTicket!); + setState(() { + _ticketEndDateController.text = formattedDate; + }); + }, + ) + : null), + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () => _submit(), + child: const Text('Submit'), + ), + ), + ]), + )); } List getResults(double height) { @@ -467,22 +473,21 @@ class _AddSessionFormState extends State { } SpeakerSearch({required Speaker speaker, required int index}) { - return InkWell( - onTap: () { - _speakerController.text = speaker.id; - setState(() {}); - }, - child: Center( - child: ListTile( - leading: CircleAvatar( - foregroundImage: NetworkImage(getImageURL(speaker)), - backgroundImage: AssetImage( - 'assets/noImage.png', - ), + var canShow = true; + return Card( + child: ListTile( + leading: CircleAvatar( + foregroundImage: NetworkImage(getImageURL(speaker)), + backgroundImage: AssetImage( + 'assets/noImage.png', ), - title: Text(getName(speaker)), ), - )); + title: Text(getName(speaker)), + onTap: () { + _speakerController.text = speaker.id; + canShow = false; + }), + ); } String getImageURL(Speaker speaker) { From fb17f954d3bddc2f272bf1fb368ef085cfb43bd1 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 26 Oct 2022 02:25:36 +0100 Subject: [PATCH 29/73] Added tickets to sessionService --- frontend/lib/routes/session/AddSessionForm.dart | 10 ++++++++-- frontend/lib/services/sessionService.dart | 12 +++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 6d7f1037..2d585fd8 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -57,6 +57,10 @@ class _AddSessionFormState extends State { var speaker = _speakerController.text; var company = _companyController.text; var maxTickets = _currentTicketsValue; + var videoURL = _videoURLController.text; + + var sessionTickets = new SessionTickets( + max: maxTickets as int, start: _beginTicket, end: _endTicket); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Uploading')), @@ -72,7 +76,9 @@ class _AddSessionFormState extends State { title, description, speakersIds, - company); + company, + videoURL, + sessionTickets); if (s != null) { ScaffoldMessenger.of(context).showSnackBar( @@ -82,7 +88,7 @@ class _AddSessionFormState extends State { Provider.of(context, listen: false); notifier.add(s); - ScaffoldMessenger.of(context).hideCurrentSnackBar(); + // ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 27de944d..59bb8be5 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -50,7 +50,9 @@ class SessionService extends Service { String title, String description, List? speakersIds, - String? company) async { + String? company, + String? videoURL, + SessionTickets sessionTickets) async { var body = kind == "Talk" ? { "begin": begin.toIso8601String(), @@ -59,7 +61,9 @@ class SessionService extends Service { "title": title, "kind": kind.toUpperCase(), "description": description, - "speaker": speakersIds + "speaker": speakersIds, + "videoURL": videoURL, + "tickets": sessionTickets.max == 0 ? null : sessionTickets } : { "begin": begin.toIso8601String(), @@ -68,7 +72,9 @@ class SessionService extends Service { "title": title, "kind": kind.toUpperCase(), "description": description, - "company": "62eb938a34a18caadf832709" + "company": company, + "videoURL": videoURL, + "tickets": sessionTickets.max == 0 ? null : sessionTickets }; Response response = await dio.post("/events/sessions", data: body); From d11d21e7295d24570909db0a255018231d70b6f6 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 26 Oct 2022 02:49:52 +0100 Subject: [PATCH 30/73] Cleaned code --- frontend/lib/routes/session/AddSessionForm.dart | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 2d585fd8..8dfa7d41 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -81,14 +81,11 @@ class _AddSessionFormState extends State { sessionTickets); if (s != null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('HERE')), - ); SessionsNotifier notifier = Provider.of(context, listen: false); notifier.add(s); - // ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -104,9 +101,6 @@ class _AddSessionFormState extends State { ), ); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('BL')), - ); ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( From 8ae1b07590aacb7881c63af9e03be196fc580967 Mon Sep 17 00:00:00 2001 From: JoaoGomes24 Date: Wed, 26 Oct 2022 17:10:04 +0100 Subject: [PATCH 31/73] Search members --- frontend/lib/routes/teams/TeamScreen.dart | 331 +++++++++++++++++----- frontend/lib/routes/teams/TeamsTable.dart | 27 +- 2 files changed, 276 insertions(+), 82 deletions(-) diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index 6b36115d..23f406f1 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -6,12 +6,17 @@ import 'package:frontend/main.dart'; import 'package:frontend/models/meeting.dart'; import 'package:frontend/models/member.dart'; import 'package:frontend/models/team.dart'; +import 'package:frontend/routes/UnknownScreen.dart'; import 'package:frontend/routes/meeting/MeetingCard.dart'; import 'package:frontend/routes/member/MemberScreen.dart'; import 'package:frontend/services/meetingService.dart'; import 'package:frontend/services/teamService.dart'; import 'package:frontend/services/memberService.dart'; +import 'package:frontend/services/authService.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; +import 'package:provider/provider.dart'; + +import '../../components/deckTheme.dart'; final Map roles = { "MEMBER": "Member", @@ -35,6 +40,10 @@ class _TeamScreen extends State with SingleTickerProviderStateMixin { late final TabController _tabController; TeamService _teamService = new TeamService(); + MemberService _memberService = new MemberService(); + final _searchMembersController = TextEditingController(); + + late Future> membs; _TeamScreen({Key? key}); @@ -56,52 +65,66 @@ class _TeamScreen extends State setState(() {}); } - SpeedDial buildSpeedDial() { - return SpeedDial( - animatedIcon: AnimatedIcons.menu_close, - animatedIconTheme: IconThemeData(size: 28.0), - backgroundColor: Color(0xff5C7FF2), - visible: true, - curve: Curves.bounceInOut, - children: [ - SpeedDialChild( - child: Icon(Icons.person_remove, color: Colors.white), - backgroundColor: Colors.indigo, - onTap: () => showRemoveMemberDialog(), - label: 'Remove Members', - labelStyle: - TextStyle(fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - SpeedDialChild( - child: Icon(Icons.person_add, color: Colors.white), - backgroundColor: Colors.indigo, - onTap: () => showAddMemberDialog(), - label: 'Add Member', - labelStyle: - TextStyle(fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - SpeedDialChild( - child: Icon(Icons.delete, color: Colors.white), - backgroundColor: Colors.indigo, - onTap: () => showDeleteTeamDialog(), - label: 'Delete Team', - labelStyle: - TextStyle(fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - SpeedDialChild( - child: Icon(Icons.edit, color: Colors.white), - backgroundColor: Colors.indigo, - onTap: () => showEditTeamDialog(), - label: 'Edit Team', - labelStyle: - TextStyle(fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - ], - ); + buildSpeedDial() { + return FutureBuilder( + future: Provider.of(context).role, + builder: (context, snapshot) { + if (snapshot.hasData) { + Role r = snapshot.data as Role; + + if (r == Role.ADMIN || r == Role.COORDINATOR) { + return SpeedDial( + animatedIcon: AnimatedIcons.menu_close, + animatedIconTheme: IconThemeData(size: 28.0), + backgroundColor: Color(0xff5C7FF2), + visible: true, + curve: Curves.bounceInOut, + children: [ + SpeedDialChild( + child: Icon(Icons.person_remove, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => showRemoveMemberDialog(), + label: 'Remove Members', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.person_add, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => showAddMemberDialog(), + label: 'Add Member', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.delete, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => showDeleteTeamDialog(), + label: 'Delete Team', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.edit, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => showEditTeamDialog(), + label: 'Edit Team', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + ], + ); + } else { + return Container(); //CONFIRMAR + } + } else { + return Container(); + } + }); } showEditTeamDialog() { @@ -158,18 +181,25 @@ class _TeamScreen extends State context: context, builder: (BuildContext context) => AlertDialog( title: const Text("Remove Team Member"), - content: TextFormField( - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a Id'; - } - return null; - }, - onChanged: (value) { - memberId = value; - }, - decoration: const InputDecoration(hintText: "Team Member Id"), - ), + content: DropdownButtonFormField( + validator: (value) { + if (value == null) { + return 'Please select one member'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.grid_3x3), + labelText: "MemberId *", + ), + // TODO: We need to fetch the event members somewhere and then see which of those are not part of the team already. + items: widget.members.map((Member? member) { + return new DropdownMenuItem( + value: member!.id, child: Text(member.name)); + }).toList(), + onChanged: (newValue) { + setState(() => memberId = newValue.toString()); + }), actions: [ TextButton( onPressed: () => Navigator.pop(context, "Cancel"), @@ -184,9 +214,67 @@ class _TeamScreen extends State ); } + List getResults(double height) { + if (_searchMembersController.text.length > 1) { + return [ + Container( + decoration: new BoxDecoration( + color: Theme.of(context).cardColor, + ), + child: FutureBuilder( + future: this.membs, + builder: (context, snapshot) { + if (snapshot.hasData) { + List membsMatched = snapshot.data as List; + return searchResults(membsMatched, height); + } else { + return Center(child: CircularProgressIndicator()); + } + })) + ]; + } else { + return []; + } + } + + Widget searchResults(List members, double listHeight) { + List results = getListCards(members); + return Container( + constraints: BoxConstraints(maxHeight: listHeight), + child: ListView.builder( + shrinkWrap: true, + itemCount: results.length, + itemBuilder: (BuildContext context, int index) { + return results[index]; + })); + } + + List getListCards(List members) { + List results = []; + if (members.length != 0) { + results.add(getDivider("Members")); + results.addAll(members.map((e) => SearchResultWidget(member: e))); + } + return results; + } + + Widget getDivider(String name) { + return Card( + margin: EdgeInsets.zero, + child: Column( + children: [ + Container( + child: Text(name, style: TextStyle(fontSize: 18)), + margin: EdgeInsets.fromLTRB(0, 8, 0, 4), + ), + ], + )); + } + showAddMemberDialog() { String memberId = ""; String memberRole = ""; + return showDialog( context: context, builder: (BuildContext context) { @@ -197,25 +285,36 @@ class _TeamScreen extends State child: Form( child: Column( children: [ - DropdownButtonFormField( - validator: (value) { - if (value == null) { - return 'Please select one member'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.category), - labelText: "MemberId *", + TextFormField( + controller: _searchMembersController, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + ), + filled: true, + fillColor: Provider.of(context).isDark + ? Colors.grey[800] + : Colors.white, + hintText: 'Search Member', + prefixIcon: Icon(Icons.search), + suffixIcon: _searchMembersController.text.length != 0 + ? IconButton( + onPressed: () { + _searchMembersController.clear(); + setState(() {}); + }, + icon: Icon(Icons.clear), + ) + : null, ), - // TODO: We need to fetch the event members somewhere and then see which of those are not part of the team already. - items: widget.members.map((Member? member) { - return new DropdownMenuItem( - value: member!.id, child: Text(member.name)); - }).toList(), - onChanged: (newValue) { - setState(() => memberId = newValue.toString()); + onChanged: (newQuery) { + setState(() {}); + if (_searchMembersController.text.length > 1) { + membs = _memberService.getMembers( + name: _searchMembersController.text); + } }), + ...getResults(MediaQuery.of(context).size.height / 2), DropdownButtonFormField( validator: (value) { if (value == null) { @@ -224,7 +323,7 @@ class _TeamScreen extends State return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.category), + icon: const Icon(Icons.person), labelText: "Role *", ), items: roles.keys.map((String role) { @@ -253,19 +352,53 @@ class _TeamScreen extends State }); } +/* + void _submit() async { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Uploading')), + ); + + Member? m = await _memberService.createMember( + istid: istid, name: name, sinfoid: sinfoid); + if (m != null) { + TODO: Redirect to members page + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + ), + ); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + + Navigator.pop(context); + } + } +*/ + void addMember(String? id, String memberId, String memberRole) async { if (id == null) { - // TODO do something + // TODO ? return; } MemberService _memberService = MemberService(); + await _teamService.addTeamMember(id, memberId, memberRole); + var members = await _memberService.getMembers( event: App.localStorage.getInt("event")); + setState(() { widget.members = members; }); + Navigator.pop(context, "Add"); } @@ -344,6 +477,50 @@ class _TeamScreen extends State } } +class SearchResultWidget extends StatelessWidget { + final Member? member; + const SearchResultWidget({Key? key, this.member}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) { + return UnknownScreen(); + })); + }, + child: Center( + child: ListTile( + leading: CircleAvatar( + foregroundImage: NetworkImage(getImageURL()), + backgroundImage: AssetImage( + 'assets/noImage.png', + ), + ), + title: Text(getName()), + ), + )); + } + + String getImageURL() { + if (this.member != null) { + return this.member!.image!; + } else { + //ERROR case + return ""; + } + } + + String getName() { + if (this.member != null) { + return this.member!.name; + } else { + //ERROR case + return ""; + } + } +} + class DisplayMeeting extends StatefulWidget { final List? meetingsIds; diff --git a/frontend/lib/routes/teams/TeamsTable.dart b/frontend/lib/routes/teams/TeamsTable.dart index ce9f98b0..9734a393 100644 --- a/frontend/lib/routes/teams/TeamsTable.dart +++ b/frontend/lib/routes/teams/TeamsTable.dart @@ -21,8 +21,11 @@ class TeamTable extends StatefulWidget { class _TeamTableState extends State with AutomaticKeepAliveClientMixin { final TeamService _teamService = TeamService(); + late Future> teams; + late Future> members; + bool get wantKeepAlive => true; @override @@ -68,9 +71,9 @@ class _TeamTableState extends State )); } - List> data = snapshot.data as List>; + List> dataTeam = snapshot.data as List>; - List tms = data[0] as List; + List tms = dataTeam[0] as List; tms.sort((a, b) => a.name!.compareTo(b.name!)); @@ -83,7 +86,7 @@ class _TeamTableState extends State child: ListView.builder( itemCount: tms.length, itemBuilder: (context, index) => - TeamMemberRow(team: tms[index]), + TeamMemberRow(team: tms[index], teams: tms), addAutomaticKeepAlives: true, physics: const AlwaysScrollableScrollPhysics(), ), @@ -151,8 +154,11 @@ class _TeamTableState extends State class TeamMemberRow extends StatelessWidget { final Team team; + final List teams; + MemberService _memberService = MemberService(); - TeamMemberRow({Key? key, required this.team}) : super(key: key); + TeamMemberRow({Key? key, required this.team, required this.teams}) + : super(key: key); static Widget fake() { return Container( @@ -210,8 +216,19 @@ class TeamMemberRow extends StatelessWidget { .map((m) => _memberService.getMember(m.memberID!)) .toList(); + List> _possiblefutureMembers; + + for (var i = 0; i < teams.length; i++) { + _possiblefutureMembers = teams[i] + .members! + .map((m) => _memberService.getMember(m.memberID!)) + .toList(); + } + return FutureBuilder( - future: Future.wait(_futureMembers), + future: Future.wait(_futureMembers + // _possiblefutureMembers + ), builder: (context, snapshot) { if (snapshot.hasData) { List membs = snapshot.data as List; From e23e72b0a73726211b267f0e4058f630399ebc60 Mon Sep 17 00:00:00 2001 From: bvlourenco Date: Wed, 26 Oct 2022 19:02:21 +0100 Subject: [PATCH 32/73] Add Team Member bug fix --- backend/src/mongodb/member.go | 98 ++++++----- frontend/lib/components/router.dart | 4 + .../lib/routes/teams/AddTeamMemberForm.dart | 122 +++++++++++++ frontend/lib/routes/teams/TeamScreen.dart | 166 ++---------------- 4 files changed, 201 insertions(+), 189 deletions(-) create mode 100644 frontend/lib/routes/teams/AddTeamMemberForm.dart diff --git a/backend/src/mongodb/member.go b/backend/src/mongodb/member.go index f0653b77..8cf293ac 100644 --- a/backend/src/mongodb/member.go +++ b/backend/src/mongodb/member.go @@ -183,48 +183,62 @@ func (m *MembersType) GetMembers(options GetMemberOptions) ([]*models.Member, er var members []*models.Member = make([]*models.Member, 0) - query := mongo.Pipeline{ - - // filter by name first - {{ - "$match", bson.M{ - "name": bson.M{ - "$regex": fmt.Sprintf(".*%s.*", nameFilter), - "$options": "i", + var query mongo.Pipeline + if len(*options.Name) > 0 { + query = mongo.Pipeline{ + {{ + "$match", bson.M{ + "name": bson.M{ + "$regex": fmt.Sprintf(".*%s.*", nameFilter), + "$options": "i", + }, }, - }, - }}, - - // get all the teams on which each member is participating, - // and add them to each member correspondingly - {{ - "$lookup", bson.D{ - {"from", Teams.Collection.Name()}, - {"localField", "_id"}, - {"foreignField", "members.member"}, - {"as", "team"}, - }, - }}, - - // get an instance of each member for every team he/she belonged to - {{ - "$unwind", "$team", - }}, - - // get the event associated with each team on each member - {{ - "$lookup", bson.D{ - {"from", Events.Collection.Name()}, - {"localField", "team._id"}, - {"foreignField", "teams"}, - {"as", "event"}, - }, - }}, + }}, + } + } else { + query = mongo.Pipeline{ + + // filter by name first + {{ + "$match", bson.M{ + "name": bson.M{ + "$regex": fmt.Sprintf(".*%s.*", nameFilter), + "$options": "i", + }, + }, + }}, + + // get all the teams on which each member is participating, + // and add them to each member correspondingly + {{ + "$lookup", bson.D{ + {"from", Teams.Collection.Name()}, + {"localField", "_id"}, + {"foreignField", "members.member"}, + {"as", "team"}, + }, + }}, + + // get an instance of each member for every team he/she belonged to + {{ + "$unwind", "$team", + }}, + + // get the event associated with each team on each member + {{ + "$lookup", bson.D{ + {"from", Events.Collection.Name()}, + {"localField", "team._id"}, + {"foreignField", "teams"}, + {"as", "event"}, + }, + }}, - // get an instance of each member for every event he/she belonged to - {{ - "$unwind", "$event", - }}, + // get an instance of each member for every event he/she belonged to + {{ + "$unwind", "$event", + }}, + } } if options.Event != nil { @@ -330,6 +344,7 @@ func (m *MembersType) GetMembersParticipations(id primitive.ObjectID) ([]*models options := GetEventsOptions{} events, err := Events.GetEvents(options) if err != nil { + print("ERROR #1", err) return nil, err } @@ -339,6 +354,9 @@ func (m *MembersType) GetMembersParticipations(id primitive.ObjectID) ([]*models for _, teamID := range event.Teams { team, err := Teams.GetTeam(teamID) if err != nil { + print("TEAM ID: ", teamID.Hex()) + print("EVENT: ", event) + print("ERROR #2", err) return nil, err } for _, teamMember := range team.Members { diff --git a/frontend/lib/components/router.dart b/frontend/lib/components/router.dart index a51b2b4b..43b5249a 100644 --- a/frontend/lib/components/router.dart +++ b/frontend/lib/components/router.dart @@ -9,6 +9,7 @@ import 'package:frontend/routes/member/AddMemberForm.dart'; import 'package:frontend/routes/member/MemberListWidget.dart'; import 'package:frontend/routes/speaker/SpeakerListWidget.dart'; import 'package:frontend/routes/speaker/AddSpeakerForm.dart'; +import 'package:frontend/routes/teams/AddTeamMemberForm.dart'; class Routes { static const String BaseRoute = '/'; @@ -20,6 +21,7 @@ class Routes { static const String AddSpeaker = '/add/speaker'; static const String ShowAllMembers = '/all/members'; static const String AddMember = '/add/member'; + static const String AddTeamMember = '/add/teamMember'; } Route generateRoute(RouteSettings settings) { @@ -40,6 +42,8 @@ Route generateRoute(RouteSettings settings) { return SlideRoute(page: AddSpeakerForm()); case Routes.ShowAllMembers: return MaterialPageRoute(builder: (context) => MemberListWidget()); + case Routes.AddTeamMember: + return MaterialPageRoute(builder: (context) => AddTeamMemberForm()); case Routes.AddMember: return SlideRoute(page: AddMemberForm()); default: diff --git a/frontend/lib/routes/teams/AddTeamMemberForm.dart b/frontend/lib/routes/teams/AddTeamMemberForm.dart new file mode 100644 index 00000000..d6ea47fa --- /dev/null +++ b/frontend/lib/routes/teams/AddTeamMemberForm.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/components/appbar.dart'; +import 'package:frontend/models/member.dart'; +import 'package:frontend/services/memberService.dart'; + +class AddTeamMemberForm extends StatefulWidget { + AddTeamMemberForm({Key? key}) : super(key: key); + + @override + _AddTeamMemberFormState createState() => _AddTeamMemberFormState(); +} + +class _AddTeamMemberFormState extends State { + final _formKey = GlobalKey(); + MemberService _memberService = new MemberService(); + final _searchMembersController = TextEditingController(); + late Future> membs; + String memberRole = ""; + + Widget _buildForm() { + return Form( + key: _formKey, + child: SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Column(children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _searchMembersController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a member'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.groups), + labelText: "Member *", + ), + onChanged: (newQuery) { + setState(() {}); + if (_searchMembersController.text.length > 1) { + this.membs = _memberService.getMembers( + name: _searchMembersController.text); + } + })), + ...getResults(MediaQuery.of(context).size.height * 0.7), + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: ElevatedButton( + // onPressed: () => _submit(), + // child: const Text('Submit'), + // ), + // ), + ]), + )); + } + + List getResults(double height) { + if (_searchMembersController.text.length > 1) { + return [ + Container( + decoration: new BoxDecoration( + color: Theme.of(context).cardColor, + ), + child: FutureBuilder( + future: this.membs, + builder: (context, snapshot) { + if (snapshot.hasData) { + List membsMatched = snapshot.data as List; + print("membs matched:" + membsMatched.toString()); + return searchResults(membsMatched, height); + } else { + return Center(child: CircularProgressIndicator()); + } + })) + ]; + } else { + return []; + } + } + + Widget searchResults(List members, double listHeight) { + List results = getListCards(members); + return Container( + constraints: BoxConstraints(maxHeight: listHeight), + child: ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + itemCount: results.length, + itemBuilder: (BuildContext context, int index) { + return results[index]; + })); + } + + List getListCards(List members) { + List results = []; + if (members.length != 0) { + results.add(getDivider("Members")); + results.addAll(members.map((e) => SearchResultWidget(member: e))); + } + return results; + } + + Widget getDivider(String name) { + return Card( + margin: EdgeInsets.zero, + child: Column( + children: [ + Container( + child: Text(name, style: TextStyle(fontSize: 18)), + margin: EdgeInsets.fromLTRB(0, 8, 0, 4), + ), + ], + )); + } + + @override + Widget build(BuildContext context) { + return _buildForm(); + } +} diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index 23f406f1..7cfb926b 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -1,7 +1,5 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:frontend/components/ListViewCard.dart'; +import 'package:frontend/components/router.dart'; import 'package:frontend/main.dart'; import 'package:frontend/models/meeting.dart'; import 'package:frontend/models/member.dart'; @@ -9,6 +7,7 @@ import 'package:frontend/models/team.dart'; import 'package:frontend/routes/UnknownScreen.dart'; import 'package:frontend/routes/meeting/MeetingCard.dart'; import 'package:frontend/routes/member/MemberScreen.dart'; +import 'package:frontend/routes/teams/AddTeamMemberForm.dart'; import 'package:frontend/services/meetingService.dart'; import 'package:frontend/services/teamService.dart'; import 'package:frontend/services/memberService.dart'; @@ -16,8 +15,6 @@ import 'package:frontend/services/authService.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:provider/provider.dart'; -import '../../components/deckTheme.dart'; - final Map roles = { "MEMBER": "Member", "TEAMLEADER": "Team Leader", @@ -40,10 +37,6 @@ class _TeamScreen extends State with SingleTickerProviderStateMixin { late final TabController _tabController; TeamService _teamService = new TeamService(); - MemberService _memberService = new MemberService(); - final _searchMembersController = TextEditingController(); - - late Future> membs; _TeamScreen({Key? key}); @@ -65,6 +58,20 @@ class _TeamScreen extends State setState(() {}); } + void _addTeamMember(context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) { + return FractionallySizedBox( + heightFactor: 0.7, + child: Container( + child: AddTeamMemberForm(), + )); + }, + ); + } + buildSpeedDial() { return FutureBuilder( future: Provider.of(context).role, @@ -92,7 +99,7 @@ class _TeamScreen extends State SpeedDialChild( child: Icon(Icons.person_add, color: Colors.white), backgroundColor: Colors.indigo, - onTap: () => showAddMemberDialog(), + onTap: () => _addTeamMember(context), label: 'Add Member', labelStyle: TextStyle( fontWeight: FontWeight.w500, color: Colors.white), @@ -213,145 +220,6 @@ class _TeamScreen extends State ), ); } - - List getResults(double height) { - if (_searchMembersController.text.length > 1) { - return [ - Container( - decoration: new BoxDecoration( - color: Theme.of(context).cardColor, - ), - child: FutureBuilder( - future: this.membs, - builder: (context, snapshot) { - if (snapshot.hasData) { - List membsMatched = snapshot.data as List; - return searchResults(membsMatched, height); - } else { - return Center(child: CircularProgressIndicator()); - } - })) - ]; - } else { - return []; - } - } - - Widget searchResults(List members, double listHeight) { - List results = getListCards(members); - return Container( - constraints: BoxConstraints(maxHeight: listHeight), - child: ListView.builder( - shrinkWrap: true, - itemCount: results.length, - itemBuilder: (BuildContext context, int index) { - return results[index]; - })); - } - - List getListCards(List members) { - List results = []; - if (members.length != 0) { - results.add(getDivider("Members")); - results.addAll(members.map((e) => SearchResultWidget(member: e))); - } - return results; - } - - Widget getDivider(String name) { - return Card( - margin: EdgeInsets.zero, - child: Column( - children: [ - Container( - child: Text(name, style: TextStyle(fontSize: 18)), - margin: EdgeInsets.fromLTRB(0, 8, 0, 4), - ), - ], - )); - } - - showAddMemberDialog() { - String memberId = ""; - String memberRole = ""; - - return showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text("Add Member"), - content: Padding( - padding: const EdgeInsets.all(4.0), - child: Form( - child: Column( - children: [ - TextFormField( - controller: _searchMembersController, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - ), - filled: true, - fillColor: Provider.of(context).isDark - ? Colors.grey[800] - : Colors.white, - hintText: 'Search Member', - prefixIcon: Icon(Icons.search), - suffixIcon: _searchMembersController.text.length != 0 - ? IconButton( - onPressed: () { - _searchMembersController.clear(); - setState(() {}); - }, - icon: Icon(Icons.clear), - ) - : null, - ), - onChanged: (newQuery) { - setState(() {}); - if (_searchMembersController.text.length > 1) { - membs = _memberService.getMembers( - name: _searchMembersController.text); - } - }), - ...getResults(MediaQuery.of(context).size.height / 2), - DropdownButtonFormField( - validator: (value) { - if (value == null) { - return 'Please select one role'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.person), - labelText: "Role *", - ), - items: roles.keys.map((String role) { - return new DropdownMenuItem( - value: role, child: Text(role)); - }).toList(), - onChanged: (newValue) { - setState(() => memberRole = newValue.toString()); - }), - ], - ), - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, "Cancel"), - child: const Text("Cancel"), - ), - TextButton( - onPressed: () => - addMember(widget.team.id, memberId, memberRole), - child: const Text("Add"), - ), - ], - ); - }); - } - /* void _submit() async { ScaffoldMessenger.of(context).showSnackBar( From e2f5f4a957926292e7a7df1c8b284236dcf32f1d Mon Sep 17 00:00:00 2001 From: GuiBaracho Date: Sun, 30 Oct 2022 18:36:55 +0000 Subject: [PATCH 33/73] Celaned up interaction with date picker fields, changed speaker dropdown field --- frontend/lib/models/speaker.dart | 9 +- .../lib/routes/session/AddSessionForm.dart | 181 +++++++++--------- frontend/pubspec.yaml | 4 + 3 files changed, 105 insertions(+), 89 deletions(-) diff --git a/frontend/lib/models/speaker.dart b/frontend/lib/models/speaker.dart index 6fea2aff..73e43f7a 100644 --- a/frontend/lib/models/speaker.dart +++ b/frontend/lib/models/speaker.dart @@ -95,6 +95,13 @@ class Speaker { (element) => element.event == this.lastParticipation)!; } + String speakerAsString() { + return '${this.name}'; + } + + @override + String toString() => name; + bool operator ==(o) => o is Speaker && id == o.id; int get hashCode => id.hashCode; } @@ -169,4 +176,4 @@ class SpeakerLight { participations[participations.length - 1]['status']) : ParticipationStatus.NO_STATUS); } -} \ No newline at end of file +} diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 8dfa7d41..bf6b10df 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -9,6 +9,9 @@ import 'package:frontend/services/speakerService.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:form_builder_extra_fields/form_builder_extra_fields.dart'; class AddSessionForm extends StatefulWidget { AddSessionForm({Key? key}) : super(key: key); @@ -190,74 +193,90 @@ class _AddSessionFormState extends State { ), ), Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _beginDateController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a beggining date'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "Begin Date *", - ), - readOnly: true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, true, false); - String formattedDate = getDateTime(_begin!); - - setState(() { - _beginDateController.text = formattedDate; - }); - }, - )), + padding: const EdgeInsets.all(8.0), + child: FormBuilderDateTimePicker( + name: 'beginDate', + controller: _beginDateController, + validator: (value) { + if (value == null) { + return 'Please enter a beggining date'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "Begin Date *", + ), + ), + ), Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _endDateController, + padding: const EdgeInsets.all(8.0), + child: FormBuilderDateTimePicker( + name: 'endDate', + controller: _endDateController, + validator: (value) { + if (value == null) { + return 'Please enter an ending date'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "End Date *", + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: FormBuilderDropdown( + name: 'kind', validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter an ending date'; + if (value == null) { + return 'Please enter the kind of session'; } return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "End Date *", + icon: const Icon(Icons.category), + labelText: "Kind *", ), - readOnly: true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, false, false); - String formattedDate = getDateTime(_end!); - - setState(() { - _endDateController.text = formattedDate; - }); - }, - )), - Padding( - padding: const EdgeInsets.all(8.0), - child: DropdownButtonFormField( - validator: (value) { - if (value == null) { - return 'Please enter the kind of session'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.category), - labelText: "Kind *", - ), - items: kinds.map((String kind) { - return new DropdownMenuItem( - value: kind, child: Text(kind)); - }).toList(), - onChanged: (newValue) { - setState(() => _kind = newValue.toString()); - })), + items: kinds.map((String kind) { + return new DropdownMenuItem(value: kind, child: Text(kind)); + }).toList(), + onChanged: (newValue) { + setState(() => _kind = newValue.toString()); + }), + ), Padding( + padding: (_kind == "Talk") + ? const EdgeInsets.all(8.0) + : EdgeInsets.all(0), + child: (_kind == "Talk") + ? DropdownSearch.multiSelection( + asyncItems: (String) => speakerService.getSpeakers(), + itemAsString: (Speaker u) => u.speakerAsString(), + popupProps: PopupPropsMultiSelection.menu( + showSearchBox: true, + ), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: const InputDecoration( + icon: const Icon(Icons.star), + labelText: "Speaker *", + ), + ), + validator: (value) { + if (_kind == "Talk") { + if (value == null || value.isEmpty) { + return 'Please enter a speaker'; + } + return null; + } + return null; + }, + ) + : null, + ), + /*Padding( //Dont know if my solution is good so I'll keep this padding: const EdgeInsets.all(8.0), child: (_kind == "Talk") ? TextFormField( @@ -283,9 +302,11 @@ class _AddSessionFormState extends State { }) : null, ), - ...getResults(MediaQuery.of(context).size.height / 3), + ...getResults(MediaQuery.of(context).size.height / 3),*/ Padding( - padding: const EdgeInsets.all(8.0), + padding: (_kind == "Workshop" || _kind == "Presentation") + ? const EdgeInsets.all(8.0) + : EdgeInsets.all(0), child: (_kind == "Workshop" || _kind == "Presentation") ? TextFormField( controller: _companyController, @@ -366,11 +387,12 @@ class _AddSessionFormState extends State { Padding( padding: const EdgeInsets.all(8.0), child: (_ticketsOn == true) - ? TextFormField( + ? FormBuilderDateTimePicker( + name: 'ticketBeginDate', controller: _ticketBeginDateController, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a beggining date for ticket availability *'; + if (value == null) { + return 'Please enter a beggining date for ticket availability'; } return null; }, @@ -378,25 +400,17 @@ class _AddSessionFormState extends State { icon: const Icon(Icons.calendar_today), labelText: "Ticket availability begin date *", ), - readOnly: - true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, true, true); - String formattedDate = getDateTime(_beginTicket!); - setState(() { - _ticketBeginDateController.text = formattedDate; - }); - }, ) : null), Padding( padding: const EdgeInsets.all(8.0), child: (_ticketsOn == true) - ? TextFormField( + ? FormBuilderDateTimePicker( + name: 'ticketEndDate', controller: _ticketEndDateController, validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter an end date for ticket availability *'; + if (value == null) { + return 'Please enter an end date for ticket availability '; } return null; }, @@ -404,15 +418,6 @@ class _AddSessionFormState extends State { icon: const Icon(Icons.calendar_today), labelText: "Ticket availability end date *", ), - readOnly: - true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, false, true); - String formattedDate = getDateTime(_endTicket!); - setState(() { - _ticketEndDateController.text = formattedDate; - }); - }, ) : null), Padding( @@ -492,7 +497,7 @@ class _AddSessionFormState extends State { String getImageURL(Speaker speaker) { if (speaker != null) { - return speaker!.imgs!.internal!; + return speaker.imgs!.internal!; } else { //ERROR case return ""; @@ -501,7 +506,7 @@ class _AddSessionFormState extends State { String getName(Speaker speaker) { if (speaker != null) { - return speaker!.name; + return speaker.name; } else { //ERROR case return ""; diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 5360db89..ff2759aa 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -46,6 +46,10 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.3 url_launcher: ^6.0.9 + # The following adds form fields and builders + flutter_form_builder: ^7.7.0 + dropdown_search: ^5.0.3 + form_builder_extra_fields: ^8.3.0 dev_dependencies: flutter_test: From 60c7fa61cbf9efc30ec6d480e01a3cfb65c5ddd4 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Mon, 31 Oct 2022 22:43:39 +0000 Subject: [PATCH 34/73] Fixed the connection between speakers' dropdown and speakersIds list and also the begin and end date --- .../lib/routes/session/AddSessionForm.dart | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index bf6b10df..d493a0b8 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -46,6 +46,7 @@ class _AddSessionFormState extends State { DateTime? _end; DateTime? _beginTicket; DateTime? _endTicket; + List speakersIds = []; String _kind = ""; bool value = false; @@ -69,7 +70,16 @@ class _AddSessionFormState extends State { const SnackBar(content: Text('Uploading')), ); - List speakersIds = [speaker]; + print(_begin?.toUtc()); + print(_end?.toUtc()); + print(place); + print(_kind); + print(title); + print(description); + print(speakersIds); + print(company); + print(videoURL); + print(sessionTickets); Session? s = await _sessionService.createSession( _begin!.toUtc(), @@ -207,6 +217,7 @@ class _AddSessionFormState extends State { icon: const Icon(Icons.calendar_today), labelText: "Begin Date *", ), + onChanged: (value) => {_begin = value}, ), ), Padding( @@ -224,6 +235,7 @@ class _AddSessionFormState extends State { icon: const Icon(Icons.calendar_today), labelText: "End Date *", ), + onChanged: (value) => {_end = value}, ), ), Padding( @@ -273,6 +285,14 @@ class _AddSessionFormState extends State { } return null; }, + onChanged: (List speakers) { + print(speakers); + speakersIds.clear(); + for (var speaker in speakers) { + speakersIds.add(speaker.id); + } + print(speakersIds); + }, ) : null, ), From 56b5d073a19e4fee25b4a6b56c34cf9b529c14fd Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Tue, 1 Nov 2022 00:39:54 +0000 Subject: [PATCH 35/73] Added button to delete all chosen speakers --- frontend/lib/routes/session/AddSessionForm.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index d493a0b8..1e01a7b3 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -293,6 +293,7 @@ class _AddSessionFormState extends State { } print(speakersIds); }, + clearButtonProps: ClearButtonProps(isVisible: true), ) : null, ), @@ -420,6 +421,7 @@ class _AddSessionFormState extends State { icon: const Icon(Icons.calendar_today), labelText: "Ticket availability begin date *", ), + onChanged: (value) => {_beginTicket = value}, ) : null), Padding( @@ -438,6 +440,7 @@ class _AddSessionFormState extends State { icon: const Icon(Icons.calendar_today), labelText: "Ticket availability end date *", ), + onChanged: (value) => {_endTicket = value}, ) : null), Padding( From 4bce4e8279fea1249318e46dd33b1fef5fcd0179 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Tue, 1 Nov 2022 03:19:38 +0000 Subject: [PATCH 36/73] made changes to session service receiving event as a response --- .../lib/routes/session/AddSessionForm.dart | 22 +++++++++---------- frontend/lib/services/sessionService.dart | 13 ++++++++--- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 1e01a7b3..798abf9f 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -60,7 +60,7 @@ class _AddSessionFormState extends State { var place = _placeController.text; var speaker = _speakerController.text; var company = _companyController.text; - var maxTickets = _currentTicketsValue; + var maxTickets = _currentTicketsValue as int; var videoURL = _videoURLController.text; var sessionTickets = new SessionTickets( @@ -70,16 +70,16 @@ class _AddSessionFormState extends State { const SnackBar(content: Text('Uploading')), ); - print(_begin?.toUtc()); - print(_end?.toUtc()); - print(place); - print(_kind); - print(title); - print(description); - print(speakersIds); - print(company); - print(videoURL); - print(sessionTickets); + // print(_begin?.toUtc()); + // print(_end?.toUtc()); + // print(place); + // print(_kind); + // print(title); + // print(description); + // print(speakersIds); + // print(company); + // print(videoURL); + // print(sessionTickets); Session? s = await _sessionService.createSession( _begin!.toUtc(), diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 59bb8be5..23d1cb1a 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -6,6 +6,8 @@ import 'package:frontend/models/session.dart'; import 'package:frontend/services/service.dart'; import 'package:frontend/components/deckException.dart'; +import '../models/event.dart'; + class SessionService extends Service { Future> getSessions( {int? event, String? company, String? kind}) async { @@ -63,7 +65,7 @@ class SessionService extends Service { "description": description, "speaker": speakersIds, "videoURL": videoURL, - "tickets": sessionTickets.max == 0 ? null : sessionTickets + "tickets": sessionTickets.toJson() } : { "begin": begin.toIso8601String(), @@ -74,13 +76,18 @@ class SessionService extends Service { "description": description, "company": company, "videoURL": videoURL, - "tickets": sessionTickets.max == 0 ? null : sessionTickets + "tickets": sessionTickets.max == 0 ? null : sessionTickets.toJson() }; Response response = await dio.post("/events/sessions", data: body); try { - return Session.fromJson(json.decode(response.data!)); + int eventId = Event.fromJson(json.decode(response.data!)).id; + Future> _futureSessions = getSessions(event: eventId); + List sessions = await _futureSessions; + Session s = sessions.elementAt(0); + + return s; } on SocketException { throw DeckException('No Internet connection'); } on HttpException { From 3fcef5e29eaca6cfcb88e058b4ee3bcf087d4196 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 2 Nov 2022 15:34:06 +0000 Subject: [PATCH 37/73] added utc in json ticket conversion --- frontend/lib/models/session.dart | 4 ++-- frontend/lib/routes/session/AddSessionForm.dart | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/lib/models/session.dart b/frontend/lib/models/session.dart index ed33bedc..31697f1c 100644 --- a/frontend/lib/models/session.dart +++ b/frontend/lib/models/session.dart @@ -146,8 +146,8 @@ class SessionTickets { } Map toJson() => { - 'start': start, - 'end': end, + 'start': start?.toUtc().toIso8601String(), + 'end': end?.toUtc().toIso8601String(), 'max': max, }; diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 798abf9f..75da499a 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -63,6 +63,8 @@ class _AddSessionFormState extends State { var maxTickets = _currentTicketsValue as int; var videoURL = _videoURLController.text; + print(maxTickets); + var sessionTickets = new SessionTickets( max: maxTickets as int, start: _beginTicket, end: _endTicket); @@ -79,7 +81,7 @@ class _AddSessionFormState extends State { // print(speakersIds); // print(company); // print(videoURL); - // print(sessionTickets); + //print(sessionTickets); Session? s = await _sessionService.createSession( _begin!.toUtc(), From fd46b664170f00aace7a28095d4608bb3b62ca8c Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Thu, 3 Nov 2022 23:33:32 +0000 Subject: [PATCH 38/73] Added dropdown for companies --- frontend/lib/models/company.dart | 4 ++ .../lib/routes/session/AddSessionForm.dart | 71 +++++++++++-------- frontend/lib/services/sessionService.dart | 8 ++- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/frontend/lib/models/company.dart b/frontend/lib/models/company.dart index 35ca5c85..be285c5c 100644 --- a/frontend/lib/models/company.dart +++ b/frontend/lib/models/company.dart @@ -131,6 +131,10 @@ class Company { (element) => element.event == this.lastParticipation)!; } + String companyAsString() { + return '${this.name}'; + } + bool operator ==(o) => o is Company && id == o.id; int get hashCode => id.hashCode; } diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 75da499a..2fa0cc35 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:frontend/components/appbar.dart'; +import 'package:frontend/models/company.dart'; import 'package:frontend/models/session.dart'; import 'package:frontend/models/speaker.dart'; import 'package:frontend/routes/session/SessionsNotifier.dart'; +import 'package:frontend/services/companyService.dart'; import 'package:frontend/services/sessionService.dart'; import 'package:frontend/services/speakerService.dart'; import 'package:intl/intl.dart'; @@ -40,6 +42,7 @@ class _AddSessionFormState extends State { late Future> speakers; SpeakerService speakerService = new SpeakerService(); + CompanyService companyService = new CompanyService(); DateTime? dateTime; DateTime? _begin; @@ -47,6 +50,7 @@ class _AddSessionFormState extends State { DateTime? _beginTicket; DateTime? _endTicket; List speakersIds = []; + List companiesIds = []; String _kind = ""; bool value = false; @@ -299,44 +303,39 @@ class _AddSessionFormState extends State { ) : null, ), - /*Padding( //Dont know if my solution is good so I'll keep this - padding: const EdgeInsets.all(8.0), - child: (_kind == "Talk") - ? TextFormField( - controller: _speakerController, + Padding( + padding: (_kind == "Workshop" || _kind == "Presentation") + ? const EdgeInsets.all(8.0) + : EdgeInsets.all(0), + child: (_kind == "Workshop" || _kind == "Presentation") + ? DropdownSearch.multiSelection( + asyncItems: (String) => companyService.getCompanies(), + itemAsString: (Company u) => u.companyAsString(), + popupProps: PopupPropsMultiSelection.menu( + showSearchBox: true, + ), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: const InputDecoration( + icon: const Icon(Icons.business), + labelText: "Company *", + ), + ), validator: (value) { - if (_kind == "Talk") { + if (_kind == "Workshop" || _kind == "Presentation") { if (value == null || value.isEmpty) { - return 'Please enter a speaker'; + return 'Please enter a company'; } return null; } + return null; }, - decoration: const InputDecoration( - icon: const Icon(Icons.star), - labelText: "Speaker *", - ), - onChanged: (newQuery) { - setState(() {}); - if (_speakerController.text.length > 1) { - this.speakers = speakerService.getSpeakers( - name: _speakerController.text); + onChanged: (List companies) { + companiesIds.clear(); + for (var company in companies) { + companiesIds.add(company.id); } - }) - : null, - ), - ...getResults(MediaQuery.of(context).size.height / 3),*/ - Padding( - padding: (_kind == "Workshop" || _kind == "Presentation") - ? const EdgeInsets.all(8.0) - : EdgeInsets.all(0), - child: (_kind == "Workshop" || _kind == "Presentation") - ? TextFormField( - controller: _companyController, - decoration: const InputDecoration( - icon: const Icon(Icons.business), - labelText: "Company *", - ), + }, + clearButtonProps: ClearButtonProps(isVisible: true), ) : null, ), @@ -350,6 +349,16 @@ class _AddSessionFormState extends State { ), ), ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _videoURLController, + decoration: const InputDecoration( + icon: const Icon(Icons.video_call), + labelText: "VideoURL ", + ), + ), + ), Padding( padding: const EdgeInsets.all(8.0), child: Row( diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 23d1cb1a..313c9d14 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -17,7 +17,11 @@ class SessionService extends Service { await dio.get("/sessions", queryParameters: queryParameters); try { + print("Response data"); + print(response.data); final responseJson = json.decode(response.data!) as List; + print("Response json"); + print(responseJson); List sessions = responseJson.map((e) => Session.fromJson(e)).toList(); return sessions; @@ -65,7 +69,8 @@ class SessionService extends Service { "description": description, "speaker": speakersIds, "videoURL": videoURL, - "tickets": sessionTickets.toJson() + "tickets": sessionTickets.toJson(), + "company": "" } : { "begin": begin.toIso8601String(), @@ -83,6 +88,7 @@ class SessionService extends Service { try { int eventId = Event.fromJson(json.decode(response.data!)).id; + print("Event id " + eventId.toString()); Future> _futureSessions = getSessions(event: eventId); List sessions = await _futureSessions; Session s = sessions.elementAt(0); From 97829162042e37fd7dd7ea4504cef66987b8d599 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Fri, 4 Nov 2022 00:37:18 +0000 Subject: [PATCH 39/73] Updated DateTime parsing from json --- frontend/lib/models/session.dart | 10 +++++----- frontend/lib/routes/session/AddSessionForm.dart | 2 +- frontend/lib/services/sessionService.dart | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/lib/models/session.dart b/frontend/lib/models/session.dart index 31697f1c..9fcb5142 100644 --- a/frontend/lib/models/session.dart +++ b/frontend/lib/models/session.dart @@ -30,14 +30,14 @@ class Session { factory Session.fromJson(Map json) { return Session( id: json['id'], - begin: DateTime(json['begin']), - end: DateTime(json['end']), + begin: DateTime.parse(json['begin']), + end: DateTime.parse(json['begin']), title: json['title'], description: json['description'], place: json['place'], kind: json['kind'], companyId: json['company'], - speakersIds: json['speaker'], + speakersIds: List.from(json['speaker']), videoURL: json['videoURL'], tickets: SessionTickets.fromJson(json['tickets']), ); @@ -139,8 +139,8 @@ class SessionTickets { factory SessionTickets.fromJson(Map json) { return SessionTickets( - start: DateTime(json['start']), - end: DateTime(json['end']), + start: DateTime.parse(json['start']), + end: DateTime.parse(json['end']), max: json['max'], ); } diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 2fa0cc35..78e72cc0 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -70,7 +70,7 @@ class _AddSessionFormState extends State { print(maxTickets); var sessionTickets = new SessionTickets( - max: maxTickets as int, start: _beginTicket, end: _endTicket); + max: maxTickets, start: _beginTicket, end: _endTicket); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Uploading')), diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 313c9d14..5497dc17 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -19,7 +19,7 @@ class SessionService extends Service { try { print("Response data"); print(response.data); - final responseJson = json.decode(response.data!) as List; + final responseJson = json.decode(response.data!); print("Response json"); print(responseJson); List sessions = From 3bfc4d1ecf797b1b18281c7e3be3f458580a0515 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sun, 6 Nov 2022 10:57:34 +0000 Subject: [PATCH 40/73] Session is createdgit add -A Issue with Session Page and Session Card --- frontend/lib/routes/session/AddSessionForm.dart | 6 ++++++ frontend/lib/services/sessionService.dart | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 78e72cc0..34c6b9f9 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -67,8 +67,14 @@ class _AddSessionFormState extends State { var maxTickets = _currentTicketsValue as int; var videoURL = _videoURLController.text; + print("Max tickets:"); print(maxTickets); + // var sessionTickets = maxTickets != 0 + // ? new SessionTickets( + // max: maxTickets, start: _beginTicket, end: _endTicket) + // : new SessionTickets(max: 0, start: _beginTicket, end: _endTicket); + var sessionTickets = new SessionTickets( max: maxTickets, start: _beginTicket, end: _endTicket); diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 5497dc17..313c9d14 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -19,7 +19,7 @@ class SessionService extends Service { try { print("Response data"); print(response.data); - final responseJson = json.decode(response.data!); + final responseJson = json.decode(response.data!) as List; print("Response json"); print(responseJson); List sessions = From 005174fd792a18d2f1d6511c4040ec413ef78d19 Mon Sep 17 00:00:00 2001 From: susmonteiro Date: Sun, 6 Nov 2022 11:09:53 +0000 Subject: [PATCH 41/73] Take care of errors when editing and removing teams #261 --- frontend/lib/routes/teams/TeamScreen.dart | 35 +++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index 0e2a9486..0882f0ff 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -145,25 +145,42 @@ class _TeamScreen extends State ); } + showError() { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('An error has occured. Please contact the admins'), + duration: Duration(seconds: 4), + ), + ); + return Center( + child: Icon( + Icons.error, + size: 200, + )); + } + void editTeam(String? id, String name) async { if (id == null) { - // TODO do something - return; + showError(); + } else { + final response = await _teamService.updateTeam(id, name); + setState(() { + widget.team.name = response?.name ?? "Empty name"; + }); } - final response = await _teamService.updateTeam(id, name); - setState(() { - widget.team.name = response?.name ?? "Empty name"; - }); Navigator.pop(context, "Update"); } void deleteTeam(String? id) async { if (id == null) { - // TODO do something + showError(); return; + } else { + final response = await _teamService.deleteTeam(id); + Navigator.pop(context, "Update"); } - final response = await _teamService.deleteTeam(id); - Navigator.pop(context, "Update"); // TODO when going back to TeamTable, it should be refreshed so that the deleted team is not shown. What is the best option to do this? Navigator.pop(context); } From 61491b5e4563631660c084c94d8e68fa3fa40778 Mon Sep 17 00:00:00 2001 From: Pedro Maximino Date: Sun, 6 Nov 2022 11:22:23 +0000 Subject: [PATCH 42/73] fix: Fix nil exception --- backend/src/mongodb/member.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/mongodb/member.go b/backend/src/mongodb/member.go index 8cf293ac..7686577e 100644 --- a/backend/src/mongodb/member.go +++ b/backend/src/mongodb/member.go @@ -184,7 +184,7 @@ func (m *MembersType) GetMembers(options GetMemberOptions) ([]*models.Member, er var members []*models.Member = make([]*models.Member, 0) var query mongo.Pipeline - if len(*options.Name) > 0 { + if len(nameFilter) > 0 { query = mongo.Pipeline{ {{ "$match", bson.M{ From 4e4c420cc25b74b796b342227e4e2630b2ab8ca3 Mon Sep 17 00:00:00 2001 From: JoaoGomes24 Date: Sun, 6 Nov 2022 11:39:58 +0000 Subject: [PATCH 43/73] Add Member done --- .../lib/components/SearchResultWidget.dart | 78 +++++++++++ frontend/lib/components/appbar.dart | 62 +-------- .../lib/routes/teams/AddTeamMemberForm.dart | 126 +++++++++++++++--- frontend/lib/routes/teams/TeamScreen.dart | 115 ++++++---------- frontend/lib/routes/teams/TeamsNotifier.dart | 26 ++++ frontend/lib/services/teamService.dart | 5 +- 6 files changed, 257 insertions(+), 155 deletions(-) create mode 100644 frontend/lib/components/SearchResultWidget.dart create mode 100644 frontend/lib/routes/teams/TeamsNotifier.dart diff --git a/frontend/lib/components/SearchResultWidget.dart b/frontend/lib/components/SearchResultWidget.dart new file mode 100644 index 00000000..404e11d6 --- /dev/null +++ b/frontend/lib/components/SearchResultWidget.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/models/company.dart'; +import 'package:frontend/models/member.dart'; +import 'package:frontend/models/speaker.dart'; +import 'package:frontend/routes/company/CompanyScreen.dart'; +import 'package:frontend/routes/member/MemberScreen.dart'; +import 'package:frontend/routes/speaker/SpeakerScreen.dart'; + +class SearchResultWidget extends StatelessWidget { + final Company? company; + final Speaker? speaker; + final Member? member; + final int? index; + final Function? getMemberData; + SearchResultWidget( + {Key? key, + this.company, + this.speaker, + this.member, + this.index, + this.getMemberData}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + if (getMemberData != null && member != null) { + getMemberData!(member!.id, member!.name); + } else { + Navigator.push(context, MaterialPageRoute(builder: (context) { + if (company != null) { + return CompanyScreen(company: company!); + } else if (speaker != null) { + return SpeakerScreen(speaker: speaker!); + } + return MemberScreen(member: member!); + })); + } + }, + child: Center( + child: ListTile( + leading: CircleAvatar( + foregroundImage: NetworkImage(getImageURL()), + backgroundImage: AssetImage( + 'assets/noImage.png', + ), + ), + title: Text(getName()), + ), + )); + } + + String getImageURL() { + if (this.company != null) { + return this.company!.companyImages.internal; + } else if (this.speaker != null) { + return this.speaker!.imgs!.internal!; + } else if (this.member != null) { + return this.member!.image!; + } else { + //ERROR case + return ""; + } + } + + String getName() { + if (this.company != null) { + return this.company!.name; + } else if (this.speaker != null) { + return this.speaker!.name; + } else if (this.member != null) { + return this.member!.name; + } else { + //ERROR case + return ""; + } + } +} diff --git a/frontend/lib/components/appbar.dart b/frontend/lib/components/appbar.dart index 12b2e41c..c53c944c 100644 --- a/frontend/lib/components/appbar.dart +++ b/frontend/lib/components/appbar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:frontend/components/deckTheme.dart'; import 'package:frontend/components/eventNotifier.dart'; +import 'package:frontend/components/SearchResultWidget.dart'; import 'package:frontend/main.dart'; import 'package:frontend/models/event.dart'; import 'package:frontend/routes/company/CompanyScreen.dart'; @@ -245,64 +246,3 @@ class _CustomAppBarState extends State { )); } } - -class SearchResultWidget extends StatelessWidget { - final Company? company; - final Speaker? speaker; - final Member? member; - final int? index; - const SearchResultWidget( - {Key? key, this.company, this.speaker, this.member, this.index}); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) { - if (company != null) { - return CompanyScreen(company: company!); - } else if (speaker != null) { - return SpeakerScreen(speaker: speaker!); - } - return MemberScreen(member: member!); - })); - }, - child: Center( - child: ListTile( - leading: CircleAvatar( - foregroundImage: NetworkImage(getImageURL()), - backgroundImage: AssetImage( - 'assets/noImage.png', - ), - ), - title: Text(getName()), - ), - )); - } - - String getImageURL() { - if (this.company != null) { - return this.company!.companyImages.internal; - } else if (this.speaker != null) { - return this.speaker!.imgs!.internal!; - } else if (this.member != null) { - return this.member!.image!; - } else { - //ERROR case - return ""; - } - } - - String getName() { - if (this.company != null) { - return this.company!.name; - } else if (this.speaker != null) { - return this.speaker!.name; - } else if (this.member != null) { - return this.member!.name; - } else { - //ERROR case - return ""; - } - } -} diff --git a/frontend/lib/routes/teams/AddTeamMemberForm.dart b/frontend/lib/routes/teams/AddTeamMemberForm.dart index d6ea47fa..67c9b6c0 100644 --- a/frontend/lib/routes/teams/AddTeamMemberForm.dart +++ b/frontend/lib/routes/teams/AddTeamMemberForm.dart @@ -1,10 +1,23 @@ import 'package:flutter/material.dart'; import 'package:frontend/components/appbar.dart'; +import 'package:frontend/components/SearchResultWidget.dart'; import 'package:frontend/models/member.dart'; +import 'package:frontend/models/team.dart'; +import 'package:frontend/services/teamService.dart'; import 'package:frontend/services/memberService.dart'; +final Map roles = { + "MEMBER": "Member", + "TEAMLEADER": "Team Leader", + "COORDINATOR": "Coordinator", + "ADMIN": "Administrator" +}; + class AddTeamMemberForm extends StatefulWidget { - AddTeamMemberForm({Key? key}) : super(key: key); + final Team? team; + final void Function(BuildContext, Team?)? onEditTeam; + + AddTeamMemberForm({Key? key, this.team, this.onEditTeam}) : super(key: key); @override _AddTeamMemberFormState createState() => _AddTeamMemberFormState(); @@ -13,16 +26,55 @@ class AddTeamMemberForm extends StatefulWidget { class _AddTeamMemberFormState extends State { final _formKey = GlobalKey(); MemberService _memberService = new MemberService(); + final _textController = TextEditingController(); final _searchMembersController = TextEditingController(); + final _teamroleController = TextEditingController(); + TeamService service = TeamService(); late Future> membs; String memberRole = ""; + String _memberID = ''; + String _memberName = ''; + bool disappearSearchResults = false; + String role = ""; + + void _submit(BuildContext context) async { + if (_formKey.currentState!.validate()) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Adding member...')), + ); + memberRole = _teamroleController.text; + Team? m = await service.addTeamMember( + id: widget.team!.id, memberId: _memberID, role: role); + if (m != null) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + ), + ); + widget.onEditTeam!(context, m); + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + + Navigator.pop(context); + } + } + } Widget _buildForm() { return Form( - key: _formKey, - child: SingleChildScrollView( - physics: BouncingScrollPhysics(), - child: Column(children: [ + key: _formKey, + child: SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Column( + children: [ Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( @@ -34,7 +86,7 @@ class _AddTeamMemberFormState extends State { return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.groups), + icon: const Icon(Icons.person_add), labelText: "Member *", ), onChanged: (newQuery) { @@ -45,19 +97,48 @@ class _AddTeamMemberFormState extends State { } })), ...getResults(MediaQuery.of(context).size.height * 0.7), - // Padding( - // padding: const EdgeInsets.all(8.0), - // child: ElevatedButton( - // onPressed: () => _submit(), - // child: const Text('Submit'), - // ), - // ), - ]), - )); + Padding( + padding: const EdgeInsets.all(8.0), + child: DropdownButtonFormField( + validator: (value) { + if (value == null) { + return 'Please select a role'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.grid_3x3), + labelText: "Role *", + ), + items: roles.values.map((e) { + return new DropdownMenuItem(value: e, child: Text(e)); + }).toList(), + onChanged: (newValue) { + setState(() => role = newValue.toString()); + }), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.secondary, + padding: EdgeInsets.symmetric(horizontal: 50), + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20)), + ), + onPressed: () => _submit(context), + child: const Text('SUBMIT'), + ), + ), + ], + ), + ), + ); } List getResults(double height) { - if (_searchMembersController.text.length > 1) { + if (_searchMembersController.text.length > 1 && !disappearSearchResults) { return [ Container( decoration: new BoxDecoration( @@ -93,11 +174,22 @@ class _AddTeamMemberFormState extends State { })); } + void _getMemberData(String id, String name) { + _memberID = id; + _memberName = name; + _searchMembersController.text = name; + disappearSearchResults = true; + setState(() {}); + } + List getListCards(List members) { List results = []; if (members.length != 0) { results.add(getDivider("Members")); - results.addAll(members.map((e) => SearchResultWidget(member: e))); + results.addAll(members.map((e) => SearchResultWidget( + member: e, + getMemberData: _getMemberData, + ))); } return results; } diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index 7cfb926b..5878179f 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -8,6 +8,7 @@ import 'package:frontend/routes/UnknownScreen.dart'; import 'package:frontend/routes/meeting/MeetingCard.dart'; import 'package:frontend/routes/member/MemberScreen.dart'; import 'package:frontend/routes/teams/AddTeamMemberForm.dart'; +import 'package:frontend/routes/teams/TeamsNotifier.dart'; import 'package:frontend/services/meetingService.dart'; import 'package:frontend/services/teamService.dart'; import 'package:frontend/services/memberService.dart'; @@ -23,7 +24,7 @@ final Map roles = { }; class TeamScreen extends StatefulWidget { - final Team team; + Team team; List members; TeamScreen({Key? key, required this.team, required this.members}) @@ -58,16 +59,37 @@ class _TeamScreen extends State setState(() {}); } + Future teamChangedCallback(BuildContext context, + {Future? fm, Team? team}) async { + Team? m; + if (fm != null) { + m = await fm; + } else if (team != null) { + m = team; + } + if (m != null) { + Provider.of(context, listen: false).edit(m); + setState(() { + widget.team = m!; + }); + } + } + void _addTeamMember(context) { showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) { return FractionallySizedBox( - heightFactor: 0.7, - child: Container( - child: AddTeamMemberForm(), - )); + heightFactor: 0.7, + child: Container( + child: AddTeamMemberForm( + team: widget.team, + onEditTeam: (context, _team) { + teamChangedCallback(context, team: _team); + }), + ), + ); }, ); } @@ -87,6 +109,15 @@ class _TeamScreen extends State visible: true, curve: Curves.bounceInOut, children: [ + SpeedDialChild( + child: Icon(Icons.groups, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => {}, + label: 'Add Meetings', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), SpeedDialChild( child: Icon(Icons.person_remove, color: Colors.white), backgroundColor: Colors.indigo, @@ -199,7 +230,6 @@ class _TeamScreen extends State icon: const Icon(Icons.grid_3x3), labelText: "MemberId *", ), - // TODO: We need to fetch the event members somewhere and then see which of those are not part of the team already. items: widget.members.map((Member? member) { return new DropdownMenuItem( value: member!.id, child: Text(member.name)); @@ -220,72 +250,15 @@ class _TeamScreen extends State ), ); } -/* - void _submit() async { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Uploading')), - ); - - Member? m = await _memberService.createMember( - istid: istid, name: name, sinfoid: sinfoid); - if (m != null) { - TODO: Redirect to members page - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Done'), - duration: Duration(seconds: 2), - ), - ); - } else { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('An error occured.')), - ); - - Navigator.pop(context); - } - } -*/ - - void addMember(String? id, String memberId, String memberRole) async { - if (id == null) { - // TODO ? - return; - } - - MemberService _memberService = MemberService(); - - await _teamService.addTeamMember(id, memberId, memberRole); - - var members = await _memberService.getMembers( - event: App.localStorage.getInt("event")); - - setState(() { - widget.members = members; - }); - - Navigator.pop(context, "Add"); - } void removeTeamMember(String? id, String memberId) async { - if (id == null) { - // TODO do something - return; - } - final response = await _teamService.deleteTeamMember(id, memberId); + final response = await _teamService.deleteTeamMember(id!, memberId); Navigator.pop(context, "Delete"); Navigator.pop(context); } void editTeam(String? id, String name) async { - if (id == null) { - // TODO do something - return; - } - final response = await _teamService.updateTeam(id, name); + final response = await _teamService.updateTeam(id!, name); setState(() { widget.team.name = response?.name ?? "Empty name"; }); @@ -293,11 +266,7 @@ class _TeamScreen extends State } void deleteTeam(String? id) async { - if (id == null) { - // TODO do something - return; - } - final response = await _teamService.deleteTeam(id); + final response = await _teamService.deleteTeam(id!); Navigator.pop(context, "Update"); // TODO when going back to TeamTable, it should be refreshed so that the deleted team is not shown. What is the best option to do this? Navigator.pop(context); @@ -443,12 +412,6 @@ class _DisplayMeetingState extends State { ); } }), - floatingActionButton: FloatingActionButton.extended( - onPressed: () {}, - label: const Text('Add Meetings'), - icon: const Icon(Icons.edit), - backgroundColor: Color(0xff5C7FF2), - ), ); } } diff --git a/frontend/lib/routes/teams/TeamsNotifier.dart b/frontend/lib/routes/teams/TeamsNotifier.dart new file mode 100644 index 00000000..4b0a87bc --- /dev/null +++ b/frontend/lib/routes/teams/TeamsNotifier.dart @@ -0,0 +1,26 @@ +import 'package:flutter/cupertino.dart'; +import 'package:frontend/models/team.dart'; + +class TeamsNotifier extends ChangeNotifier { + List team; + + TeamsNotifier({required this.team}); + + void add(Team s) { + team.add(s); + notifyListeners(); + } + + void remove(Team s) { + team.remove(s); + notifyListeners(); + } + + void edit(Team s) { + int index = team.indexOf(s); + if (index != -1) { + team[index] = s; + notifyListeners(); + } + } +} diff --git a/frontend/lib/services/teamService.dart b/frontend/lib/services/teamService.dart index 7d749747..e2dc29c0 100644 --- a/frontend/lib/services/teamService.dart +++ b/frontend/lib/services/teamService.dart @@ -97,7 +97,10 @@ class TeamService extends Service { } } - Future addTeamMember(String id, String memberId, String role) async { + Future addTeamMember( + {required String? id, + required String memberId, + required String role}) async { var body = { "member": memberId, "role": role, From d3481821cb0ef5fa99efbc0c6889fda9ef898faa Mon Sep 17 00:00:00 2001 From: susmonteiro Date: Sun, 6 Nov 2022 13:29:44 +0000 Subject: [PATCH 44/73] Implemented team filter. Need to use all teams in filter #261 --- frontend/lib/components/filterBarTeam.dart | 26 +++---- frontend/lib/routes/teams/TeamsTable.dart | 81 ++++++++++++++++------ 2 files changed, 69 insertions(+), 38 deletions(-) diff --git a/frontend/lib/components/filterBarTeam.dart b/frontend/lib/components/filterBarTeam.dart index a628c3f9..d6514e65 100644 --- a/frontend/lib/components/filterBarTeam.dart +++ b/frontend/lib/components/filterBarTeam.dart @@ -2,28 +2,24 @@ import 'package:flutter/material.dart'; class FilterBarTeam extends StatefulWidget { final Function onSelected; + final List teamFilters; - FilterBarTeam({Key? key, required this.onSelected}) : super(key: key); + FilterBarTeam({Key? key, required this.teamFilters, required this.onSelected}) + : super(key: key); @override - FilterBarTeamState createState() => FilterBarTeamState(onSelected: onSelected); + FilterBarTeamState createState() => + FilterBarTeamState(teamFilters: teamFilters, onSelected: onSelected); } class FilterBarTeamState extends State { final Function onSelected; + final List teamFilters; - FilterBarTeamState({Key? key, required this.onSelected}); + FilterBarTeamState( + {Key? key, required this.teamFilters, required this.onSelected}); int _currentIndex = 0; - List _filters = [ - "All", - "Coordination", - "DevTeam", - "Logistics", - "Multimedia", - "Partnerships", - "Social Network", - ]; @override Widget build(BuildContext context) { @@ -35,8 +31,8 @@ class FilterBarTeamState extends State { rowChips() { List filters = []; - for (int i = 0; i < _filters.length; i++) { - filters.add(createChip(_filters[i], i)); + for (int i = 0; i < teamFilters.length; i++) { + filters.add(createChip(teamFilters[i], i)); } return Row(children: filters); } @@ -58,7 +54,7 @@ class FilterBarTeamState extends State { onSelected: (bool selected) { setState(() { _currentIndex = selected ? index : _currentIndex; - onSelected(_filters[_currentIndex].toUpperCase()); + onSelected(teamFilters[_currentIndex].toUpperCase()); }); }, label: Text(label), diff --git a/frontend/lib/routes/teams/TeamsTable.dart b/frontend/lib/routes/teams/TeamsTable.dart index ce9f98b0..e24e9caa 100644 --- a/frontend/lib/routes/teams/TeamsTable.dart +++ b/frontend/lib/routes/teams/TeamsTable.dart @@ -8,9 +8,12 @@ import 'package:frontend/models/team.dart'; import 'package:frontend/routes/teams/TeamScreen.dart'; import 'package:frontend/services/memberService.dart'; import 'package:frontend/services/teamService.dart'; +import 'package:frontend/components/filterBarTeam.dart'; import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart'; +const ALL = "All"; + class TeamTable extends StatefulWidget { TeamTable({Key? key}) : super(key: key); @@ -21,7 +24,8 @@ class TeamTable extends StatefulWidget { class _TeamTableState extends State with AutomaticKeepAliveClientMixin { final TeamService _teamService = TeamService(); - late Future> teams; + String filter = ALL; + late List teams = []; bool get wantKeepAlive => true; @@ -40,8 +44,11 @@ class _TeamTableState extends State headerSliverBuilder: (context, innerBoxIsScrolled) => [ SliverToBoxAdapter( child: Padding( - padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), - ), + padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), + child: FilterBarTeam( + teamFilters: getTeamsFilter(), + onSelected: (value) => onSelected(value), + )), ), ], body: FutureBuilder( @@ -66,28 +73,23 @@ class _TeamTableState extends State Icons.error, size: 200, )); - } - - List> data = snapshot.data as List>; - - List tms = data[0] as List; + } else if (snapshot.hasData) { + List> data = snapshot.data as List>; - tms.sort((a, b) => a.name!.compareTo(b.name!)); + teams = data[0] as List; + teams.sort((a, b) => a.name!.compareTo(b.name!)); - return RefreshIndicator( - onRefresh: () { - return Future.delayed(Duration.zero, () { - setState(() {}); - }); - }, - child: ListView.builder( - itemCount: tms.length, - itemBuilder: (context, index) => - TeamMemberRow(team: tms[index]), - addAutomaticKeepAlives: true, - physics: const AlwaysScrollableScrollPhysics(), - ), - ); + return RefreshIndicator( + onRefresh: () { + return Future.delayed(Duration.zero, () { + setState(() {}); + }); + }, + child: buildTeamsList(), + ); + } else { + return Center(child: CircularProgressIndicator()); + } } else { return Shimmer.fromColors( baseColor: Colors.grey[400]!, @@ -115,6 +117,29 @@ class _TeamTableState extends State ); } + onSelected(String value) { + setState(() { + filter = value; + }); + print("The filter value is $value"); + } + + buildTeamsList() { + List filteredTeams; + if (filter.toLowerCase() == ALL.toLowerCase()) + filteredTeams = teams; + else + filteredTeams = teams + .where((team) => team.name?.toLowerCase() == filter.toLowerCase()) + .toList(); + return ListView.builder( + itemCount: filteredTeams.length, + itemBuilder: (context, index) => + TeamMemberRow(team: filteredTeams[index]), + addAutomaticKeepAlives: true, + physics: const AlwaysScrollableScrollPhysics()); + } + showCreateTeamDialog() { String name = ""; return showDialog( @@ -147,6 +172,16 @@ class _TeamTableState extends State setState(() {}); Navigator.pop(context, "Create"); } + + getTeamsFilter() { + print("List of teams: $teams"); + List filters = + teams.map((team) => team.name).whereType().toList(); + filters.add(ALL); + filters.add("SINFO 30 team"); + filters.add("SINFO 31 team"); + return filters; + } } class TeamMemberRow extends StatelessWidget { From 7b7cc1408e796f8af23920bcd78c6174aef7d10e Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sun, 6 Nov 2022 15:18:38 +0000 Subject: [PATCH 45/73] Fixed first edit session error --- frontend/lib/models/session.dart | 4 +- .../lib/routes/session/AddSessionForm.dart | 12 +-- .../lib/routes/session/EditSessionForm.dart | 90 +++++++++---------- frontend/lib/routes/session/SessionCard.dart | 5 +- frontend/lib/services/sessionService.dart | 2 - 5 files changed, 54 insertions(+), 59 deletions(-) diff --git a/frontend/lib/models/session.dart b/frontend/lib/models/session.dart index 9fcb5142..1c25d231 100644 --- a/frontend/lib/models/session.dart +++ b/frontend/lib/models/session.dart @@ -39,7 +39,9 @@ class Session { companyId: json['company'], speakersIds: List.from(json['speaker']), videoURL: json['videoURL'], - tickets: SessionTickets.fromJson(json['tickets']), + tickets: json['tickets'] == null + ? null + : SessionTickets.fromJson(json['tickets']), ); } diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 34c6b9f9..b5655d20 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -70,13 +70,13 @@ class _AddSessionFormState extends State { print("Max tickets:"); print(maxTickets); - // var sessionTickets = maxTickets != 0 - // ? new SessionTickets( - // max: maxTickets, start: _beginTicket, end: _endTicket) - // : new SessionTickets(max: 0, start: _beginTicket, end: _endTicket); + var sessionTickets = maxTickets != 0 + ? new SessionTickets( + max: maxTickets, start: _beginTicket, end: _endTicket) + : new SessionTickets(max: 0, start: null, end: null); - var sessionTickets = new SessionTickets( - max: maxTickets, start: _beginTicket, end: _endTicket); + // var sessionTickets = new SessionTickets( + // max: maxTickets, start: _beginTicket, end: _endTicket); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Uploading')), diff --git a/frontend/lib/routes/session/EditSessionForm.dart b/frontend/lib/routes/session/EditSessionForm.dart index eac960bc..329583d1 100644 --- a/frontend/lib/routes/session/EditSessionForm.dart +++ b/frontend/lib/routes/session/EditSessionForm.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:frontend/models/session.dart'; import 'package:frontend/routes/session/SessionsNotifier.dart'; import 'package:frontend/services/sessionService.dart'; @@ -13,7 +14,7 @@ class EditSessionForm extends StatefulWidget { _EditSessionFormState createState() => _EditSessionFormState(); } -const kinds = ["TALK", "PRESENTATION", "WORKSHOP"]; +const kinds = ["Talk", "Presentation", "Workshop"]; class _EditSessionFormState extends State { final _formKey = GlobalKey(); @@ -54,30 +55,29 @@ class _EditSessionFormState extends State { const SnackBar(content: Text('Uploading')), ); - // Session? s = await _sessionService.updateSession( - // widget.session.id, _begin.toUtc(), _end.toUtc(), place, _kind, title); - - // if (s != null) { - // SessionsNotifier notifier = - // Provider.of(context, listen: false); - // notifier.edit(s); - - // ScaffoldMessenger.of(context).hideCurrentSnackBar(); - - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // content: Text('Done'), - // duration: Duration(seconds: 2), - // ), - // ); - // Navigator.pop(context); - // } else { - // ScaffoldMessenger.of(context).hideCurrentSnackBar(); - - // ScaffoldMessenger.of(context).showSnackBar( - // const SnackBar(content: Text('An error occured.')), - // ); - // } + Session? s = await _sessionService.updateSession(widget.session); + + if (s != null) { + SessionsNotifier notifier = + Provider.of(context, listen: false); + notifier.edit(s); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + ), + ); + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + } } } @@ -148,29 +148,23 @@ class _EditSessionFormState extends State { ), ), Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _beginDateController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a beggining date'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "Begin Date *", - ), - readOnly: true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, true); - String formattedDate = getDateTime(_begin); - - setState(() { - _beginDateController.text = formattedDate; - }); - }, - )), + padding: const EdgeInsets.all(8.0), + child: FormBuilderDateTimePicker( + name: 'beginDate', + controller: _beginDateController, + validator: (value) { + if (value == null) { + return 'Please enter a beggining date'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "Begin Date *", + ), + onChanged: (value) => {_begin = value}, + ), + ), Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( diff --git a/frontend/lib/routes/session/SessionCard.dart b/frontend/lib/routes/session/SessionCard.dart index 398ca941..ccba64f0 100644 --- a/frontend/lib/routes/session/SessionCard.dart +++ b/frontend/lib/routes/session/SessionCard.dart @@ -201,8 +201,9 @@ class SessionCard extends StatelessWidget { if (r == Role.ADMIN || r == Role.COORDINATOR) { return IconButton( - onPressed: () => _deleteSessionDialog( - context, session.id), + onPressed: () => + _deleteSessionDialog( + context, session.id), icon: Icon(Icons.delete), color: Colors.red); } else { diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 313c9d14..082db538 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -17,8 +17,6 @@ class SessionService extends Service { await dio.get("/sessions", queryParameters: queryParameters); try { - print("Response data"); - print(response.data); final responseJson = json.decode(response.data!) as List; print("Response json"); print(responseJson); From d0e1676c79e99c2b1830e17f721f7dbc799fad8c Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sun, 6 Nov 2022 15:22:19 +0000 Subject: [PATCH 46/73] removed edit --- .../lib/routes/session/EditSessionForm.dart | 90 ++++++++++--------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/frontend/lib/routes/session/EditSessionForm.dart b/frontend/lib/routes/session/EditSessionForm.dart index 329583d1..eac960bc 100644 --- a/frontend/lib/routes/session/EditSessionForm.dart +++ b/frontend/lib/routes/session/EditSessionForm.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:frontend/models/session.dart'; import 'package:frontend/routes/session/SessionsNotifier.dart'; import 'package:frontend/services/sessionService.dart'; @@ -14,7 +13,7 @@ class EditSessionForm extends StatefulWidget { _EditSessionFormState createState() => _EditSessionFormState(); } -const kinds = ["Talk", "Presentation", "Workshop"]; +const kinds = ["TALK", "PRESENTATION", "WORKSHOP"]; class _EditSessionFormState extends State { final _formKey = GlobalKey(); @@ -55,29 +54,30 @@ class _EditSessionFormState extends State { const SnackBar(content: Text('Uploading')), ); - Session? s = await _sessionService.updateSession(widget.session); - - if (s != null) { - SessionsNotifier notifier = - Provider.of(context, listen: false); - notifier.edit(s); - - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Done'), - duration: Duration(seconds: 2), - ), - ); - Navigator.pop(context); - } else { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('An error occured.')), - ); - } + // Session? s = await _sessionService.updateSession( + // widget.session.id, _begin.toUtc(), _end.toUtc(), place, _kind, title); + + // if (s != null) { + // SessionsNotifier notifier = + // Provider.of(context, listen: false); + // notifier.edit(s); + + // ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text('Done'), + // duration: Duration(seconds: 2), + // ), + // ); + // Navigator.pop(context); + // } else { + // ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar(content: Text('An error occured.')), + // ); + // } } } @@ -148,23 +148,29 @@ class _EditSessionFormState extends State { ), ), Padding( - padding: const EdgeInsets.all(8.0), - child: FormBuilderDateTimePicker( - name: 'beginDate', - controller: _beginDateController, - validator: (value) { - if (value == null) { - return 'Please enter a beggining date'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "Begin Date *", - ), - onChanged: (value) => {_begin = value}, - ), - ), + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _beginDateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a beggining date'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "Begin Date *", + ), + readOnly: true, //prevents editing the date in the form field + onTap: () async { + await _selectDateTime(context, true); + String formattedDate = getDateTime(_begin); + + setState(() { + _beginDateController.text = formattedDate; + }); + }, + )), Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( From 429d904c41c0d2c19ff3b962cea16eace34e1c32 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sun, 6 Nov 2022 15:24:29 +0000 Subject: [PATCH 47/73] fix edit --- frontend/lib/routes/session/EditSessionForm.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/lib/routes/session/EditSessionForm.dart b/frontend/lib/routes/session/EditSessionForm.dart index eac960bc..e8846a28 100644 --- a/frontend/lib/routes/session/EditSessionForm.dart +++ b/frontend/lib/routes/session/EditSessionForm.dart @@ -13,7 +13,7 @@ class EditSessionForm extends StatefulWidget { _EditSessionFormState createState() => _EditSessionFormState(); } -const kinds = ["TALK", "PRESENTATION", "WORKSHOP"]; +const kinds = ["Talk", "Presentation", "Workshop"]; class _EditSessionFormState extends State { final _formKey = GlobalKey(); From 8b584b7b5a468b492e85b15f1c353a2d6bbcb623 Mon Sep 17 00:00:00 2001 From: susmonteiro Date: Sun, 6 Nov 2022 15:36:48 +0000 Subject: [PATCH 48/73] Finished working on filter teams, #261 --- frontend/lib/components/filterBarTeam.dart | 36 ++++++--- frontend/lib/components/filterbar.dart | 10 --- frontend/lib/routes/teams/TeamsTable.dart | 94 +++++++++++----------- 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/frontend/lib/components/filterBarTeam.dart b/frontend/lib/components/filterBarTeam.dart index d6514e65..43daca2e 100644 --- a/frontend/lib/components/filterBarTeam.dart +++ b/frontend/lib/components/filterBarTeam.dart @@ -1,25 +1,34 @@ import 'package:flutter/material.dart'; class FilterBarTeam extends StatefulWidget { + String currentFilter; final Function onSelected; final List teamFilters; - FilterBarTeam({Key? key, required this.teamFilters, required this.onSelected}) + FilterBarTeam( + {Key? key, + required this.currentFilter, + required this.teamFilters, + required this.onSelected}) : super(key: key); @override - FilterBarTeamState createState() => - FilterBarTeamState(teamFilters: teamFilters, onSelected: onSelected); + FilterBarTeamState createState() => FilterBarTeamState( + currentFilter: currentFilter, + teamFilters: teamFilters, + onSelected: onSelected); } class FilterBarTeamState extends State { - final Function onSelected; + String currentFilter; final List teamFilters; + final Function onSelected; FilterBarTeamState( - {Key? key, required this.teamFilters, required this.onSelected}); - - int _currentIndex = 0; + {Key? key, + required this.currentFilter, + required this.teamFilters, + required this.onSelected}); @override Widget build(BuildContext context) { @@ -41,25 +50,26 @@ class FilterBarTeamState extends State { return Container( margin: EdgeInsets.all(7.0), child: ChoiceChip( - selected: _currentIndex == index, + selected: label.toLowerCase() == currentFilter.toLowerCase(), backgroundColor: Colors.indigo[100], shape: RoundedRectangleBorder( side: BorderSide(color: Colors.black12, width: 1), borderRadius: BorderRadius.circular(15), ), - elevation: 2, - pressElevation: 1, + elevation: 1, + pressElevation: 3, shadowColor: Colors.teal, selectedColor: Colors.indigo[400], onSelected: (bool selected) { setState(() { - _currentIndex = selected ? index : _currentIndex; - onSelected(teamFilters[_currentIndex].toUpperCase()); + onSelected(label.toUpperCase()); }); }, label: Text(label), labelStyle: TextStyle( - color: _currentIndex != index ? Colors.indigo[400] : Colors.white, + color: label.toLowerCase() != currentFilter.toLowerCase() + ? Colors.indigo[400] + : Colors.white, ), padding: EdgeInsets.all(6.0), ), diff --git a/frontend/lib/components/filterbar.dart b/frontend/lib/components/filterbar.dart index 402388e3..1abfe232 100644 --- a/frontend/lib/components/filterbar.dart +++ b/frontend/lib/components/filterbar.dart @@ -17,16 +17,6 @@ class FilterBarState extends State { FilterBarState({Key? key, required this.onSelected}); int _currentIndex = 0; - List _filters = [ - "All", - "Suggested", - "Contacted", - "Rejected", - "Give Up", - "Announced", - "In Conversations", - "In Negotiations" - ]; @override Widget build(BuildContext context) { diff --git a/frontend/lib/routes/teams/TeamsTable.dart b/frontend/lib/routes/teams/TeamsTable.dart index e24e9caa..ed40645e 100644 --- a/frontend/lib/routes/teams/TeamsTable.dart +++ b/frontend/lib/routes/teams/TeamsTable.dart @@ -24,7 +24,7 @@ class TeamTable extends StatefulWidget { class _TeamTableState extends State with AutomaticKeepAliveClientMixin { final TeamService _teamService = TeamService(); - String filter = ALL; + String filter = ""; late List teams = []; bool get wantKeepAlive => true; @@ -32,6 +32,7 @@ class _TeamTableState extends State @override void initState() { super.initState(); + filter = ALL; } @override @@ -39,19 +40,7 @@ class _TeamTableState extends State super.build(context); return Scaffold( - body: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), - child: FilterBarTeam( - teamFilters: getTeamsFilter(), - onSelected: (value) => onSelected(value), - )), - ), - ], - body: FutureBuilder( + body: FutureBuilder( future: Future.wait([ _teamService.getTeams( event: Provider.of(context).event.id) @@ -59,34 +48,12 @@ class _TeamTableState extends State builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasError) { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('An error has occured. Please contact the admins'), - duration: Duration(seconds: 4), - ), - ); - return Center( - child: Icon( - Icons.error, - size: 200, - )); + return showError(); } else if (snapshot.hasData) { List> data = snapshot.data as List>; - teams = data[0] as List; teams.sort((a, b) => a.name!.compareTo(b.name!)); - - return RefreshIndicator( - onRefresh: () { - return Future.delayed(Duration.zero, () { - setState(() {}); - }); - }, - child: buildTeamsList(), - ); + return showTeams(); } else { return Center(child: CircularProgressIndicator()); } @@ -103,9 +70,7 @@ class _TeamTableState extends State ), ); } - }, - ), - ), + }), floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, floatingActionButton: FloatingActionButton.extended( onPressed: showCreateTeamDialog, @@ -121,7 +86,6 @@ class _TeamTableState extends State setState(() { filter = value; }); - print("The filter value is $value"); } buildTeamsList() { @@ -140,6 +104,47 @@ class _TeamTableState extends State physics: const AlwaysScrollableScrollPhysics()); } + showError() { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('An error has occured. Please contact the admins'), + duration: Duration(seconds: 4), + ), + ); + return Center( + child: Icon( + Icons.error, + size: 200, + )); + } + + showTeams() { + return NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), + child: FilterBarTeam( + currentFilter: filter, + teamFilters: getTeamsFilter(), + onSelected: (value) => onSelected(value), + )), + ), + ], + body: RefreshIndicator( + onRefresh: () { + return Future.delayed(Duration.zero, () { + setState(() {}); + }); + }, + child: buildTeamsList(), + ), + ); + } + showCreateTeamDialog() { String name = ""; return showDialog( @@ -174,12 +179,9 @@ class _TeamTableState extends State } getTeamsFilter() { - print("List of teams: $teams"); List filters = teams.map((team) => team.name).whereType().toList(); - filters.add(ALL); - filters.add("SINFO 30 team"); - filters.add("SINFO 31 team"); + filters.insert(0, ALL); return filters; } } From fedccfbed189b9f9615dc71d8ccb1977df41e6b5 Mon Sep 17 00:00:00 2001 From: GuiBaracho Date: Sun, 6 Nov 2022 15:42:49 +0000 Subject: [PATCH 49/73] Session with company fixed(?) --- .../lib/routes/session/AddSessionForm.dart | 33 +++++++++---------- frontend/lib/services/sessionService.dart | 1 + 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index b5655d20..4d44bbda 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -50,7 +50,7 @@ class _AddSessionFormState extends State { DateTime? _beginTicket; DateTime? _endTicket; List speakersIds = []; - List companiesIds = []; + String? companyId; String _kind = ""; bool value = false; @@ -62,21 +62,21 @@ class _AddSessionFormState extends State { var title = _titleController.text; var description = _descriptionController.text; var place = _placeController.text; - var speaker = _speakerController.text; - var company = _companyController.text; + //var speaker = _speakerController.text; + //var company = _companyController.text; var maxTickets = _currentTicketsValue as int; var videoURL = _videoURLController.text; print("Max tickets:"); print(maxTickets); - var sessionTickets = maxTickets != 0 - ? new SessionTickets( - max: maxTickets, start: _beginTicket, end: _endTicket) - : new SessionTickets(max: 0, start: null, end: null); + // var sessionTickets = maxTickets != 0 + // ? new SessionTickets( + // max: maxTickets, start: _beginTicket, end: _endTicket) + // : new SessionTickets(max: 0, start: null, end: null); - // var sessionTickets = new SessionTickets( - // max: maxTickets, start: _beginTicket, end: _endTicket); + var sessionTickets = new SessionTickets( + max: maxTickets, start: _beginTicket, end: _endTicket); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Uploading')), @@ -101,7 +101,7 @@ class _AddSessionFormState extends State { title, description, speakersIds, - company, + companyId, videoURL, sessionTickets); @@ -314,10 +314,10 @@ class _AddSessionFormState extends State { ? const EdgeInsets.all(8.0) : EdgeInsets.all(0), child: (_kind == "Workshop" || _kind == "Presentation") - ? DropdownSearch.multiSelection( + ? DropdownSearch( asyncItems: (String) => companyService.getCompanies(), itemAsString: (Company u) => u.companyAsString(), - popupProps: PopupPropsMultiSelection.menu( + popupProps: PopupProps.menu( showSearchBox: true, ), dropdownDecoratorProps: DropDownDecoratorProps( @@ -328,18 +328,15 @@ class _AddSessionFormState extends State { ), validator: (value) { if (_kind == "Workshop" || _kind == "Presentation") { - if (value == null || value.isEmpty) { + if (value == null) { return 'Please enter a company'; } return null; } return null; }, - onChanged: (List companies) { - companiesIds.clear(); - for (var company in companies) { - companiesIds.add(company.id); - } + onChanged: (Company? company) { + companyId = company!.id; }, clearButtonProps: ClearButtonProps(isVisible: true), ) diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 082db538..88380ab6 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -77,6 +77,7 @@ class SessionService extends Service { "title": title, "kind": kind.toUpperCase(), "description": description, + "speaker": [], "company": company, "videoURL": videoURL, "tickets": sessionTickets.max == 0 ? null : sessionTickets.toJson() From cb1d8ec5e5e56767053ee374f018022e14c8112e Mon Sep 17 00:00:00 2001 From: GuiBaracho Date: Sun, 6 Nov 2022 17:23:33 +0000 Subject: [PATCH 50/73] Session dart translation fixed, EditSession fields done --- frontend/lib/models/session.dart | 2 +- .../lib/routes/session/EditSessionForm.dart | 140 +++++++++--------- 2 files changed, 69 insertions(+), 73 deletions(-) diff --git a/frontend/lib/models/session.dart b/frontend/lib/models/session.dart index 1c25d231..fbf0ccd4 100644 --- a/frontend/lib/models/session.dart +++ b/frontend/lib/models/session.dart @@ -31,7 +31,7 @@ class Session { return Session( id: json['id'], begin: DateTime.parse(json['begin']), - end: DateTime.parse(json['begin']), + end: DateTime.parse(json['end']), title: json['title'], description: json['description'], place: json['place'], diff --git a/frontend/lib/routes/session/EditSessionForm.dart b/frontend/lib/routes/session/EditSessionForm.dart index e8846a28..d9298f0b 100644 --- a/frontend/lib/routes/session/EditSessionForm.dart +++ b/frontend/lib/routes/session/EditSessionForm.dart @@ -4,6 +4,9 @@ import 'package:frontend/routes/session/SessionsNotifier.dart'; import 'package:frontend/services/sessionService.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:form_builder_extra_fields/form_builder_extra_fields.dart'; class EditSessionForm extends StatefulWidget { final Session session; @@ -18,9 +21,11 @@ const kinds = ["Talk", "Presentation", "Workshop"]; class _EditSessionFormState extends State { final _formKey = GlobalKey(); late TextEditingController _titleController; + late TextEditingController _descriptionController; late TextEditingController _placeController; late TextEditingController _beginDateController; late TextEditingController _endDateController; + late TextEditingController _videoURLController; final _sessionService = SessionService(); late DateTime _begin; @@ -31,7 +36,10 @@ class _EditSessionFormState extends State { void initState() { super.initState(); _titleController = TextEditingController(text: widget.session.title); + _descriptionController = + TextEditingController(text: widget.session.description); _placeController = TextEditingController(text: widget.session.place); + _videoURLController = TextEditingController(text: widget.session.videoURL); _beginDateController = TextEditingController(text: getDateTime(widget.session.begin)); _endDateController = @@ -48,6 +56,7 @@ class _EditSessionFormState extends State { void _submit() async { if (_formKey.currentState!.validate()) { var title = _titleController.text; + var description = _descriptionController.text; var place = _placeController.text; ScaffoldMessenger.of(context).showSnackBar( @@ -134,90 +143,77 @@ class _EditSessionFormState extends State { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - controller: _placeController, + controller: _descriptionController, validator: (value) { if (value == null || value.isEmpty) { - return 'Please enter a place'; + return 'Please enter a description'; } return null; }, decoration: const InputDecoration( - icon: const Icon(Icons.place), - labelText: "Place *", + icon: const Icon(Icons.description), + labelText: "Description *", ), ), ), Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _beginDateController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a beggining date'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "Begin Date *", - ), - readOnly: true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, true); - String formattedDate = getDateTime(_begin); - - setState(() { - _beginDateController.text = formattedDate; - }); - }, - )), + padding: const EdgeInsets.all(8.0), + child: FormBuilderDateTimePicker( + name: 'beginDate', + controller: _beginDateController, + initialValue: widget.session.begin, + validator: (value) { + if (value == null) { + return 'Please enter a beggining date'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "Begin Date *", + ), + onChanged: (value) => {_begin = value!}, + ), + ), Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - controller: _endDateController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter an ending date'; - } - return null; - }, - decoration: const InputDecoration( - icon: const Icon(Icons.calendar_today), - labelText: "End Date *", - ), - readOnly: true, //prevents editing the date in the form field - onTap: () async { - await _selectDateTime(context, false); - String formattedDate = getDateTime(_end); - - setState(() { - _endDateController.text = formattedDate; - }); - }, - )), + padding: const EdgeInsets.all(8.0), + child: FormBuilderDateTimePicker( + name: 'endDate', + initialValue: widget.session.end, + controller: _endDateController, + validator: (value) { + if (value == null) { + return 'Please enter an ending date'; + } + return null; + }, + decoration: const InputDecoration( + icon: const Icon(Icons.calendar_today), + labelText: "End Date *", + ), + onChanged: (value) => {_end = value!}, + ), + ), Padding( - padding: const EdgeInsets.all(8.0), - child: DropdownButtonFormField( - validator: (value) { - if (value == null) { - return 'Please enter the kind of session'; - } - return null; - }, - // Transforming TEAM or COMPANY or EVENT into Team or Company or Event - value: - "${widget.session.kind[0].toUpperCase()}${widget.session.kind.substring(1).toLowerCase()}", - decoration: const InputDecoration( - icon: const Icon(Icons.category), - labelText: "Kind *", - ), - items: kinds.map((String kind) { - return new DropdownMenuItem(value: kind, child: Text(kind)); - }).toList(), - onChanged: (newValue) { - // do other stuff with _category - setState(() => _kind = newValue.toString()); - })), + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _placeController, + decoration: const InputDecoration( + icon: const Icon(Icons.place), + labelText: "Place ", + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _videoURLController, + decoration: const InputDecoration( + icon: const Icon(Icons.video_call), + labelText: "VideoURL ", + ), + ), + ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( From cd9fa28ab098e65c7b90209f753fac822baa4699 Mon Sep 17 00:00:00 2001 From: GuiBaracho Date: Sun, 6 Nov 2022 19:16:20 +0000 Subject: [PATCH 51/73] Edit sessions done, Duplicate session card until reload fixed --- frontend/lib/models/session.dart | 4 +- .../lib/routes/session/AddSessionForm.dart | 11 ---- .../lib/routes/session/EditSessionForm.dart | 59 +++++++++++-------- frontend/lib/services/sessionService.dart | 2 +- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/frontend/lib/models/session.dart b/frontend/lib/models/session.dart index fbf0ccd4..6e02e5ae 100644 --- a/frontend/lib/models/session.dart +++ b/frontend/lib/models/session.dart @@ -47,8 +47,8 @@ class Session { Map toJson() => { 'id': id, - 'begin': begin, - 'end': end, + 'begin': begin.toIso8601String(), + 'end': end.toIso8601String(), 'title': title, 'description': description, 'place': place, diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index 4d44bbda..f185165d 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -82,17 +82,6 @@ class _AddSessionFormState extends State { const SnackBar(content: Text('Uploading')), ); - // print(_begin?.toUtc()); - // print(_end?.toUtc()); - // print(place); - // print(_kind); - // print(title); - // print(description); - // print(speakersIds); - // print(company); - // print(videoURL); - //print(sessionTickets); - Session? s = await _sessionService.createSession( _begin!.toUtc(), _end!.toUtc(), diff --git a/frontend/lib/routes/session/EditSessionForm.dart b/frontend/lib/routes/session/EditSessionForm.dart index d9298f0b..e9e4f117 100644 --- a/frontend/lib/routes/session/EditSessionForm.dart +++ b/frontend/lib/routes/session/EditSessionForm.dart @@ -58,35 +58,46 @@ class _EditSessionFormState extends State { var title = _titleController.text; var description = _descriptionController.text; var place = _placeController.text; + var videoURL = _videoURLController.text; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Uploading')), ); - // Session? s = await _sessionService.updateSession( - // widget.session.id, _begin.toUtc(), _end.toUtc(), place, _kind, title); - - // if (s != null) { - // SessionsNotifier notifier = - // Provider.of(context, listen: false); - // notifier.edit(s); - - // ScaffoldMessenger.of(context).hideCurrentSnackBar(); - - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // content: Text('Done'), - // duration: Duration(seconds: 2), - // ), - // ); - // Navigator.pop(context); - // } else { - // ScaffoldMessenger.of(context).hideCurrentSnackBar(); - - // ScaffoldMessenger.of(context).showSnackBar( - // const SnackBar(content: Text('An error occured.')), - // ); - // } + Session? s = await _sessionService.updateSession(Session( + id: widget.session.id, + begin: _begin, + end: _end, + title: title, + description: description, + place: place, + kind: widget.session.kind, + companyId: widget.session.companyId, + speakersIds: widget.session.speakersIds, + videoURL: videoURL, + tickets: widget.session.tickets)); + + if (s != null) { + SessionsNotifier notifier = + Provider.of(context, listen: false); + notifier.edit(s); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + ), + ); + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + } } } diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 88380ab6..0aab62bf 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -90,7 +90,7 @@ class SessionService extends Service { print("Event id " + eventId.toString()); Future> _futureSessions = getSessions(event: eventId); List sessions = await _futureSessions; - Session s = sessions.elementAt(0); + Session s = sessions.last; return s; } on SocketException { From fd57257a7ce131cd3b9e2b3bbdda3441d9678b8a Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Sun, 6 Nov 2022 21:02:49 +0000 Subject: [PATCH 52/73] Fixed tickets issue --- frontend/lib/routes/session/AddSessionForm.dart | 4 ++-- frontend/lib/services/sessionService.dart | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index f185165d..d4ed2d1a 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -64,7 +64,7 @@ class _AddSessionFormState extends State { var place = _placeController.text; //var speaker = _speakerController.text; //var company = _companyController.text; - var maxTickets = _currentTicketsValue as int; + var maxTickets = _currentTicketsValue; var videoURL = _videoURLController.text; print("Max tickets:"); @@ -76,7 +76,7 @@ class _AddSessionFormState extends State { // : new SessionTickets(max: 0, start: null, end: null); var sessionTickets = new SessionTickets( - max: maxTickets, start: _beginTicket, end: _endTicket); + max: maxTickets as int, start: _beginTicket, end: _endTicket); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Uploading')), diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 0aab62bf..cbbe4291 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -67,7 +67,7 @@ class SessionService extends Service { "description": description, "speaker": speakersIds, "videoURL": videoURL, - "tickets": sessionTickets.toJson(), + // "tickets": sessionTickets.toJson(), "company": "" } : { @@ -80,9 +80,13 @@ class SessionService extends Service { "speaker": [], "company": company, "videoURL": videoURL, - "tickets": sessionTickets.max == 0 ? null : sessionTickets.toJson() + // "tickets": sessionTickets.max == 0 ? null : sessionTickets.toJson() }; + if (sessionTickets.max != 0) { + body["tickets"] = sessionTickets.toJson(); + } + Response response = await dio.post("/events/sessions", data: body); try { From 955f75e93793f64df6ab58943bf31bbcc9ea83cd Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Mon, 7 Nov 2022 01:33:35 +0000 Subject: [PATCH 53/73] Session cards with expansion tiles (not yet pretty) --- frontend/lib/routes/session/SessionCard.dart | 169 +++++++++++++------ frontend/pubspec.yaml | 1 + 2 files changed, 121 insertions(+), 49 deletions(-) diff --git a/frontend/lib/routes/session/SessionCard.dart b/frontend/lib/routes/session/SessionCard.dart index ccba64f0..2cfaebf8 100644 --- a/frontend/lib/routes/session/SessionCard.dart +++ b/frontend/lib/routes/session/SessionCard.dart @@ -15,6 +15,16 @@ class SessionCard extends StatelessWidget { final Session session; final _sessionService = SessionService(); + double _dateCardWidth = 120.0, + _dateFontSize = 30.0, + _titleFontSize = 23.0, + _placeDateFontSize = 20.0, + _cardMargin = 25.0, + _dateMargins = 25.0, + _iconsMargin = 8.0, + _titleUpBottomMargin = 20.0, + _titleLeftMargin = 15.0; + SessionCard({Key? key, required this.session}) : super(key: key); void _editSessionModal(context) { @@ -72,15 +82,6 @@ class SessionCard extends StatelessWidget { } Widget _buildSessionCard(BuildContext context) { - double _dateCardWidth = 120.0, - _dateFontSize = 30.0, - _titleFontSize = 23.0, - _placeDateFontSize = 20.0, - _cardMargin = 25.0, - _dateMargins = 25.0, - _iconsMargin = 8.0, - _titleUpBottomMargin = 20.0, - _titleLeftMargin = 15.0; return LayoutBuilder(builder: (context, constraints) { if (constraints.maxWidth < App.SIZE) { _dateCardWidth = 50.0; @@ -90,48 +91,71 @@ class SessionCard extends StatelessWidget { } return Card( shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5.0), - ), + borderRadius: BorderRadius.circular(5.0), + side: BorderSide(color: Colors.indigo, width: 2)), margin: EdgeInsets.all(_cardMargin), - child: Stack(children: [ - Positioned.fill( - child: Container( - alignment: Alignment.centerLeft, - // This child will fill full height, replace it with your leading widget - child: Container( - width: _dateCardWidth, - child: Column( - children: [ - Container( - margin: EdgeInsets.only(top: _dateMargins), - child: Text( - DateFormat.d().format(session.begin), - style: TextStyle( - color: Colors.white, fontSize: _dateFontSize), - ), + child: SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Column( + children: [ + Container( + alignment: Alignment.centerLeft, + child: Container( + width: _dateCardWidth, + child: Column( + children: [ + Container( + margin: EdgeInsets.only(top: _dateMargins), + child: Text( + DateFormat.d().format(session.begin), + style: TextStyle( + color: Colors.white, fontSize: _dateFontSize), + ), + ), + Container( + margin: EdgeInsets.only(bottom: _dateMargins), + child: Text( + DateFormat.MMM() + .format(session.begin) + .toUpperCase(), + style: TextStyle( + color: Colors.white, fontSize: _dateFontSize), + ), + ), + ], ), - Container( - margin: EdgeInsets.only(bottom: _dateMargins), - child: Text( - DateFormat.MMM().format(session.begin).toUpperCase(), - style: TextStyle( - color: Colors.white, fontSize: _dateFontSize), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(5.0), + topLeft: Radius.circular(5.0)), + image: DecorationImage( + image: AssetImage("assets/banner_background.png"), + fit: BoxFit.fill, ), ), - ], - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(5.0), - topLeft: Radius.circular(5.0)), - image: DecorationImage( - image: AssetImage("assets/banner_background.png"), - fit: BoxFit.fill, ), ), - ), - ), - ), + buildText(context) + ], + )), + ); + }); + } + + Widget buildText(BuildContext context) { + final DateTime? _beginTicket = session.tickets?.start; + final DateTime? _endTicket = session.tickets?.end; + final String? _maxTickets = session.tickets?.max.toString(); + + return ExpansionTile( + childrenPadding: EdgeInsets.all(16), + title: Text( + session.title, + style: TextStyle(fontSize: _titleFontSize), + textAlign: TextAlign.left, + ), + children: [ + Stack(children: [ Row( children: [ SizedBox(width: _dateCardWidth), @@ -146,14 +170,21 @@ class SessionCard extends StatelessWidget { children: [ Container( child: Text( - session.title, + session.kind, style: TextStyle(fontSize: _titleFontSize), textAlign: TextAlign.left, ), ), Container( child: Text( - session.place ?? 'another value', + session.description, + style: TextStyle(fontSize: _titleFontSize), + textAlign: TextAlign.left, + ), + ), + Container( + child: Text( + session.place ?? 'No place available yet', style: TextStyle( color: Colors.grey, fontSize: _placeDateFontSize), textAlign: TextAlign.left, @@ -170,6 +201,46 @@ class SessionCard extends StatelessWidget { textAlign: TextAlign.left, ), ), + Container( + child: Text( + session.videoURL ?? 'No video available yet', + style: TextStyle( + color: Colors.grey, fontSize: _placeDateFontSize), + textAlign: TextAlign.left, + ), + ), + Container( + child: (session.tickets != null) + ? Column( + children: [ + Container( + padding: EdgeInsets.only(top: 5.0), + child: Column( + children: [ + Text('Tickets'), + Text( + 'Available from ' + + DateFormat.yMd() + .add_jm() + .format(_beginTicket! + .toLocal()) + + ' to ' + + DateFormat.yMd() + .add_jm() + .format( + _endTicket!.toLocal()), + style: TextStyle( + color: Colors.grey, + fontSize: _placeDateFontSize), + textAlign: TextAlign.left, + ), + Text('Quantity: ' + _maxTickets!), + ], + )), + ], + ) + : null, + ), ], ), ), @@ -219,8 +290,8 @@ class SessionCard extends StatelessWidget { ], ), ]), - ); - }); + ], + ); } @override diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index ff2759aa..1040c540 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: flutter_form_builder: ^7.7.0 dropdown_search: ^5.0.3 form_builder_extra_fields: ^8.3.0 + syncfusion_flutter_calendar: ^20.3.52 dev_dependencies: flutter_test: From 07723a24a74b3f5f3b29cb5e7e16bc083a064a9a Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Mon, 7 Nov 2022 01:58:21 +0000 Subject: [PATCH 54/73] Session cards better, but not yet good --- frontend/lib/routes/session/SessionCard.dart | 85 +++++++++++--------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/frontend/lib/routes/session/SessionCard.dart b/frontend/lib/routes/session/SessionCard.dart index 2cfaebf8..eecaea4a 100644 --- a/frontend/lib/routes/session/SessionCard.dart +++ b/frontend/lib/routes/session/SessionCard.dart @@ -18,11 +18,12 @@ class SessionCard extends StatelessWidget { double _dateCardWidth = 120.0, _dateFontSize = 30.0, _titleFontSize = 23.0, + _descriptionFontSize = 15.0, _placeDateFontSize = 20.0, _cardMargin = 25.0, _dateMargins = 25.0, _iconsMargin = 8.0, - _titleUpBottomMargin = 20.0, + _titleUpBottomMargin = 5.0, _titleLeftMargin = 15.0; SessionCard({Key? key, required this.session}) : super(key: key); @@ -148,17 +149,16 @@ class SessionCard extends StatelessWidget { final String? _maxTickets = session.tickets?.max.toString(); return ExpansionTile( - childrenPadding: EdgeInsets.all(16), + // childrenPadding: EdgeInsets.all(16), title: Text( session.title, style: TextStyle(fontSize: _titleFontSize), textAlign: TextAlign.left, ), children: [ - Stack(children: [ + Stack(alignment: AlignmentDirectional.topStart, children: [ Row( children: [ - SizedBox(width: _dateCardWidth), Expanded( child: Container( margin: EdgeInsets.only( @@ -169,6 +169,7 @@ class SessionCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( + margin: EdgeInsets.only(bottom: _titleUpBottomMargin), child: Text( session.kind, style: TextStyle(fontSize: _titleFontSize), @@ -176,17 +177,22 @@ class SessionCard extends StatelessWidget { ), ), Container( + margin: EdgeInsets.only( + top: _titleUpBottomMargin, + bottom: _titleUpBottomMargin), child: Text( session.description, - style: TextStyle(fontSize: _titleFontSize), + style: TextStyle(fontSize: _descriptionFontSize), textAlign: TextAlign.left, ), ), Container( + margin: EdgeInsets.only( + top: _titleUpBottomMargin, + bottom: _titleUpBottomMargin), child: Text( session.place ?? 'No place available yet', - style: TextStyle( - color: Colors.grey, fontSize: _placeDateFontSize), + style: TextStyle(fontSize: _placeDateFontSize), textAlign: TextAlign.left, ), ), @@ -196,49 +202,50 @@ class SessionCard extends StatelessWidget { DateFormat.jm().format(session.begin.toLocal()) + ' - ' + DateFormat.jm().format(session.end.toLocal()), - style: TextStyle( - color: Colors.grey, fontSize: _placeDateFontSize), + style: TextStyle(fontSize: _placeDateFontSize), textAlign: TextAlign.left, ), ), Container( child: Text( session.videoURL ?? 'No video available yet', - style: TextStyle( - color: Colors.grey, fontSize: _placeDateFontSize), + style: TextStyle(fontSize: _placeDateFontSize), textAlign: TextAlign.left, ), ), Container( child: (session.tickets != null) - ? Column( - children: [ - Container( - padding: EdgeInsets.only(top: 5.0), - child: Column( - children: [ - Text('Tickets'), - Text( - 'Available from ' + - DateFormat.yMd() - .add_jm() - .format(_beginTicket! - .toLocal()) + - ' to ' + - DateFormat.yMd() - .add_jm() - .format( - _endTicket!.toLocal()), - style: TextStyle( - color: Colors.grey, - fontSize: _placeDateFontSize), - textAlign: TextAlign.left, - ), - Text('Quantity: ' + _maxTickets!), - ], - )), - ], - ) + ? Container( + padding: + EdgeInsets.only(top: 5.0, bottom: 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Tickets', + style: TextStyle( + fontSize: _placeDateFontSize), + textAlign: TextAlign.left, + ), + Text( + 'Available from ' + + DateFormat.yMd() + .add_jm() + .format(_beginTicket!.toLocal()) + + ' to ' + + DateFormat.yMd() + .add_jm() + .format(_endTicket!.toLocal()), + style: TextStyle(fontSize: 15.0), + textAlign: TextAlign.left, + ), + Text( + 'Quantity: ' + _maxTickets!, + style: TextStyle(fontSize: 15.0), + textAlign: TextAlign.left, + ), + ], + )) : null, ), ], From 6c3911aee22186453d0c53a0f15333b84dac9cc3 Mon Sep 17 00:00:00 2001 From: Pedro Maximino Date: Mon, 7 Nov 2022 15:08:47 +0000 Subject: [PATCH 55/73] fix: Map correct JSON key to type --- backend/src/mongodb/team.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/mongodb/team.go b/backend/src/mongodb/team.go index f3a4d226..faeff39d 100644 --- a/backend/src/mongodb/team.go +++ b/backend/src/mongodb/team.go @@ -42,7 +42,7 @@ type UpdateTeamMemberData struct { // CreateTeamMemberData contains data needed to create a team member type CreateTeamMemberData struct { - Member primitive.ObjectID `json:"id"` + Member primitive.ObjectID `json:"member"` Role models.TeamRole `json:"role"` } From 279b82d018530f33e493e71f0512eca35ffa3b55 Mon Sep 17 00:00:00 2001 From: JoaoGomes24 Date: Mon, 7 Nov 2022 16:40:04 +0000 Subject: [PATCH 56/73] Almost final --- frontend/lib/main.dart | 4 + .../lib/routes/teams/AddTeamMemberForm.dart | 14 +- frontend/lib/routes/teams/TeamScreen.dart | 245 ++++++++++++------ frontend/lib/routes/teams/TeamsNotifier.dart | 16 +- frontend/lib/routes/teams/TeamsTable.dart | 13 +- 5 files changed, 189 insertions(+), 103 deletions(-) diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index f2534f16..bf5e4ebd 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -5,6 +5,7 @@ import 'package:frontend/components/eventNotifier.dart'; import 'package:frontend/routes/company/CompanyTableNotifier.dart'; import 'package:frontend/routes/speaker/speakerNotifier.dart'; import 'package:frontend/models/event.dart'; +import 'package:frontend/routes/teams/TeamsNotifier.dart'; import 'package:frontend/services/authService.dart'; import 'package:frontend/services/eventService.dart'; import 'package:provider/provider.dart'; @@ -38,6 +39,9 @@ Future main() async { ChangeNotifierProvider( create: (_) => BottomNavigationBarProvider(), ), + ChangeNotifierProvider( + create: (_) => TeamsNotifier(teams: []), + ), ], child: App(), )); diff --git a/frontend/lib/routes/teams/AddTeamMemberForm.dart b/frontend/lib/routes/teams/AddTeamMemberForm.dart index 67c9b6c0..5f60c383 100644 --- a/frontend/lib/routes/teams/AddTeamMemberForm.dart +++ b/frontend/lib/routes/teams/AddTeamMemberForm.dart @@ -9,8 +9,6 @@ import 'package:frontend/services/memberService.dart'; final Map roles = { "MEMBER": "Member", "TEAMLEADER": "Team Leader", - "COORDINATOR": "Coordinator", - "ADMIN": "Administrator" }; class AddTeamMemberForm extends StatefulWidget { @@ -26,14 +24,11 @@ class AddTeamMemberForm extends StatefulWidget { class _AddTeamMemberFormState extends State { final _formKey = GlobalKey(); MemberService _memberService = new MemberService(); - final _textController = TextEditingController(); final _searchMembersController = TextEditingController(); - final _teamroleController = TextEditingController(); TeamService service = TeamService(); late Future> membs; String memberRole = ""; String _memberID = ''; - String _memberName = ''; bool disappearSearchResults = false; String role = ""; @@ -42,7 +37,8 @@ class _AddTeamMemberFormState extends State { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Adding member...')), ); - memberRole = _teamroleController.text; + // print(widget.team!.id); + // print(_memberID); Team? m = await service.addTeamMember( id: widget.team!.id, memberId: _memberID, role: role); if (m != null) { @@ -110,8 +106,9 @@ class _AddTeamMemberFormState extends State { icon: const Icon(Icons.grid_3x3), labelText: "Role *", ), - items: roles.values.map((e) { - return new DropdownMenuItem(value: e, child: Text(e)); + items: roles.keys.map((e) { + return new DropdownMenuItem( + value: e, child: Text(roles[e]!)); }).toList(), onChanged: (newValue) { setState(() => role = newValue.toString()); @@ -176,7 +173,6 @@ class _AddTeamMemberFormState extends State { void _getMemberData(String id, String name) { _memberID = id; - _memberName = name; _searchMembersController.text = name; disappearSearchResults = true; setState(() {}); diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index 5878179f..f212eeeb 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -16,6 +16,8 @@ import 'package:frontend/services/authService.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:provider/provider.dart'; +import '../../components/blurryDialog.dart'; + final Map roles = { "MEMBER": "Member", "TEAMLEADER": "Team Leader", @@ -23,6 +25,8 @@ final Map roles = { "ADMIN": "Administrator" }; +bool membersPage = true; + class TeamScreen extends StatefulWidget { Team team; List members; @@ -94,7 +98,7 @@ class _TeamScreen extends State ); } - buildSpeedDial() { + buildSpeedDial(BuildContext context) { return FutureBuilder( future: Provider.of(context).role, builder: (context, snapshot) { @@ -102,60 +106,92 @@ class _TeamScreen extends State Role r = snapshot.data as Role; if (r == Role.ADMIN || r == Role.COORDINATOR) { - return SpeedDial( - animatedIcon: AnimatedIcons.menu_close, - animatedIconTheme: IconThemeData(size: 28.0), - backgroundColor: Color(0xff5C7FF2), - visible: true, - curve: Curves.bounceInOut, - children: [ - SpeedDialChild( - child: Icon(Icons.groups, color: Colors.white), - backgroundColor: Colors.indigo, - onTap: () => {}, - label: 'Add Meetings', - labelStyle: TextStyle( - fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - SpeedDialChild( - child: Icon(Icons.person_remove, color: Colors.white), - backgroundColor: Colors.indigo, - onTap: () => showRemoveMemberDialog(), - label: 'Remove Members', - labelStyle: TextStyle( - fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - SpeedDialChild( - child: Icon(Icons.person_add, color: Colors.white), - backgroundColor: Colors.indigo, - onTap: () => _addTeamMember(context), - label: 'Add Member', - labelStyle: TextStyle( - fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - SpeedDialChild( - child: Icon(Icons.delete, color: Colors.white), - backgroundColor: Colors.indigo, - onTap: () => showDeleteTeamDialog(), - label: 'Delete Team', - labelStyle: TextStyle( - fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - SpeedDialChild( - child: Icon(Icons.edit, color: Colors.white), - backgroundColor: Colors.indigo, - onTap: () => showEditTeamDialog(), - label: 'Edit Team', - labelStyle: TextStyle( - fontWeight: FontWeight.w500, color: Colors.white), - labelBackgroundColor: Colors.black, - ), - ], - ); + if (membersPage) { + return SpeedDial( + animatedIcon: AnimatedIcons.menu_close, + animatedIconTheme: IconThemeData(size: 28.0), + backgroundColor: Color(0xff5C7FF2), + visible: true, + curve: Curves.bounceInOut, + children: [ + SpeedDialChild( + child: Icon(Icons.person_remove, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => showRemoveMemberDialog(context), + label: 'Remove Members', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.person_add, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => _addTeamMember(context), + label: 'Add Member', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.delete, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => + showDeleteTeamDialog(context, widget.team.id), + label: 'Delete Team', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.edit, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => showEditTeamDialog(), + label: 'Edit Team', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + ], + ); + } else { + return SpeedDial( + animatedIcon: AnimatedIcons.menu_close, + animatedIconTheme: IconThemeData(size: 28.0), + backgroundColor: Color(0xff5C7FF2), + visible: true, + curve: Curves.bounceInOut, + children: [ + SpeedDialChild( + child: Icon(Icons.groups, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => {}, + label: 'Add Meetings', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.delete, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => + showDeleteTeamDialog(context, widget.team.id), + label: 'Delete Team', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + SpeedDialChild( + child: Icon(Icons.edit, color: Colors.white), + backgroundColor: Colors.indigo, + onTap: () => showEditTeamDialog(), + label: 'Edit Team', + labelStyle: TextStyle( + fontWeight: FontWeight.w500, color: Colors.white), + labelBackgroundColor: Colors.black, + ), + ], + ); + } } else { return Container(); //CONFIRMAR } @@ -192,11 +228,18 @@ class _TeamScreen extends State ); } - showDeleteTeamDialog() { + showDeleteTeamDialog(context, id) { final String name = widget.team.name ?? "team"; return showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( + context: context, + builder: (BuildContext context) { + return BlurryDialog( + 'Warning', 'Are you sure you want to delete meeting $name?', () { + deleteTeam(context, id); + }); + } + /* => + AlertDialog( title: const Text("Remove Team"), content: Text("Are you sure you want to delete \"$name\"?"), actions: [ @@ -205,15 +248,15 @@ class _TeamScreen extends State child: const Text("No"), ), TextButton( - onPressed: () => deleteTeam(widget.team.id), + onPressed: () => deleteTeam(context, widget.team.id), child: const Text("Yes"), ), ], - ), - ); + ),*/ + ); } - showRemoveMemberDialog() { + showRemoveMemberDialog(context) { String memberId = ""; return showDialog( context: context, @@ -243,7 +286,8 @@ class _TeamScreen extends State child: const Text("Cancel"), ), TextButton( - onPressed: () => removeTeamMember(widget.team.id, memberId), + onPressed: () => + removeTeamMember(context, widget.team.id, memberId), child: const Text("Delete"), ), ], @@ -251,10 +295,39 @@ class _TeamScreen extends State ); } - void removeTeamMember(String? id, String memberId) async { - final response = await _teamService.deleteTeamMember(id!, memberId); - Navigator.pop(context, "Delete"); - Navigator.pop(context); + void removeTeamMember(context, String? id, String memberId) async { + showDialog( + context: context, + builder: (BuildContext context) { + return BlurryDialog( + 'Warning', 'Are you sure you want to delete this member?', + () async { + Team? team = await _teamService.deleteTeamMember(id!, memberId); + if (team != null) { + TeamsNotifier notifier = + Provider.of(context, listen: false); + notifier.edit(team); + + teamChangedCallback(context, team: team); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + ), + ); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + } + }); + }, + ); } void editTeam(String? id, String name) async { @@ -265,11 +338,32 @@ class _TeamScreen extends State Navigator.pop(context, "Update"); } - void deleteTeam(String? id) async { - final response = await _teamService.deleteTeam(id!); - Navigator.pop(context, "Update"); - // TODO when going back to TeamTable, it should be refreshed so that the deleted team is not shown. What is the best option to do this? - Navigator.pop(context); + void deleteTeam(context, String? id) async { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Deleting')), + ); + Team? team = await _teamService.deleteTeam(id!); + if (team != null) { + TeamsNotifier notifier = + Provider.of(context, listen: false); + notifier.remove(team); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + ), + ); + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + } } @override @@ -309,7 +403,7 @@ class _TeamScreen extends State ])), ), // TODO should only appear in Members tab? - floatingActionButton: buildSpeedDial(), + floatingActionButton: buildSpeedDial(context), ); } } @@ -383,6 +477,8 @@ class _DisplayMeetingState extends State { List> _futureMeetings = widget.meetingsIds!.map((m) => _meetingService.getMeeting(m)).toList(); + membersPage = false; + return Scaffold( backgroundColor: Color.fromRGBO(186, 196, 242, 0.1), body: (widget.meetingsIds == null) @@ -422,6 +518,7 @@ class DisplayMembers extends StatelessWidget { @override Widget build(BuildContext context) { + membersPage = true; return Scaffold( backgroundColor: Color.fromRGBO(186, 196, 242, 0.1), body: ListView( @@ -444,7 +541,7 @@ class ShowMember extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => MemberScreen(member: member))); //TODO + builder: (context) => MemberScreen(member: member))); }, child: Card( color: Colors.white, diff --git a/frontend/lib/routes/teams/TeamsNotifier.dart b/frontend/lib/routes/teams/TeamsNotifier.dart index 4b0a87bc..08154f73 100644 --- a/frontend/lib/routes/teams/TeamsNotifier.dart +++ b/frontend/lib/routes/teams/TeamsNotifier.dart @@ -2,24 +2,24 @@ import 'package:flutter/cupertino.dart'; import 'package:frontend/models/team.dart'; class TeamsNotifier extends ChangeNotifier { - List team; + List teams; - TeamsNotifier({required this.team}); + TeamsNotifier({required this.teams}); void add(Team s) { - team.add(s); + teams.add(s); notifyListeners(); } - void remove(Team s) { - team.remove(s); + void remove(Team t) { + teams.removeWhere((team) => t.id == team.id); notifyListeners(); } - void edit(Team s) { - int index = team.indexOf(s); + void edit(Team t) { + int index = teams.indexWhere((team) => t.id == team.id); if (index != -1) { - team[index] = s; + teams[index] = t; notifyListeners(); } } diff --git a/frontend/lib/routes/teams/TeamsTable.dart b/frontend/lib/routes/teams/TeamsTable.dart index 9734a393..d967f7a7 100644 --- a/frontend/lib/routes/teams/TeamsTable.dart +++ b/frontend/lib/routes/teams/TeamsTable.dart @@ -216,19 +216,8 @@ class TeamMemberRow extends StatelessWidget { .map((m) => _memberService.getMember(m.memberID!)) .toList(); - List> _possiblefutureMembers; - - for (var i = 0; i < teams.length; i++) { - _possiblefutureMembers = teams[i] - .members! - .map((m) => _memberService.getMember(m.memberID!)) - .toList(); - } - return FutureBuilder( - future: Future.wait(_futureMembers - // _possiblefutureMembers - ), + future: Future.wait(_futureMembers), builder: (context, snapshot) { if (snapshot.hasData) { List membs = snapshot.data as List; From 41c7d3a55fcdc6e537dbab5bada0b14bf840f78a Mon Sep 17 00:00:00 2001 From: Pedro Maximino Date: Tue, 8 Nov 2022 14:17:10 +0000 Subject: [PATCH 57/73] feat: Actually save videoURL on session create --- backend/src/mongodb/session.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/mongodb/session.go b/backend/src/mongodb/session.go index 6fc5f9f4..f1cad58d 100644 --- a/backend/src/mongodb/session.go +++ b/backend/src/mongodb/session.go @@ -79,6 +79,7 @@ type CreateSessionData struct { Description *string `json:"description"` Place *string `json:"place"` Kind *string `json:"kind"` + VideoURL *string `json:"videoURL"` Company *primitive.ObjectID `json:"company"` Speakers *[]primitive.ObjectID `json:"speaker"` Tickets *models.SessionTickets `json:"tickets"` @@ -199,6 +200,10 @@ func (s *SessionsType) CreateSession(data CreateSessionData) (*models.Session, e c["speaker"] = data.Speakers } + if data.VideoURL != nil { + c["videoURL"] = data.VideoURL + } + if data.Tickets != nil { c["tickets"] = bson.M{ "start": data.Tickets.Start.UTC(), From d53ee1c578b2288060789bae930f65dafee0efa8 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 9 Nov 2022 01:57:06 +0000 Subject: [PATCH 58/73] Session Page is now a calendar --- frontend/lib/routes/session/SessionPage.dart | 46 +-- frontend/lib/routes/session/calendar.dart | 302 +++++++++++++++++++ frontend/pubspec.yaml | 2 +- 3 files changed, 314 insertions(+), 36 deletions(-) create mode 100644 frontend/lib/routes/session/calendar.dart diff --git a/frontend/lib/routes/session/SessionPage.dart b/frontend/lib/routes/session/SessionPage.dart index 9ba2b56f..55577bea 100644 --- a/frontend/lib/routes/session/SessionPage.dart +++ b/frontend/lib/routes/session/SessionPage.dart @@ -3,8 +3,10 @@ import 'package:frontend/main.dart'; import 'package:frontend/models/session.dart'; import 'package:frontend/routes/session/SessionCard.dart'; import 'package:frontend/routes/session/SessionsNotifier.dart'; +import 'package:frontend/routes/session/calendar.dart'; import 'package:frontend/services/sessionService.dart'; import 'package:provider/provider.dart'; +import 'package:table_calendar/table_calendar.dart'; class SessionPage extends StatelessWidget { const SessionPage({Key? key}) : super(key: key); @@ -48,45 +50,19 @@ class _SessionListState extends State future: _sessions, builder: (context, snapshot) { if (snapshot.hasData) { + CalendarFormat format = CalendarFormat.month; + DateTime selectedDay = DateTime.now(); + DateTime focusedDay = DateTime.now(); SessionsNotifier notifier = Provider.of(context); notifier.sessions = snapshot.data as List; - return LayoutBuilder(builder: (context, constraints) { - bool small = constraints.maxWidth < App.SIZE; - return Column( - children: [ - TabBar( - isScrollable: small, - controller: _tabController, - tabs: [ - Tab(text: 'Upcoming'), - Tab(text: 'Past'), - ], - ), - Consumer( - builder: (context, cart, child) { - return Expanded( - child: TabBarView(controller: _tabController, children: [ - ListView( - children: notifier - .getUpcoming() - .map((e) => SessionCard(session: e)) - .toList(), - ), - ListView( - children: notifier - .getPast() - .map((e) => SessionCard(session: e)) - .toList(), - ), - ]), - ); - }, - ), - ], - ); - }); + var upcomingSessions = notifier.getUpcoming().toList(); + // print("Upciming Sessions size:"); + // print(upcomingSessions.length); + // print(upcomingSessions.toString()); + + return CustomTableCalendar(sessions: upcomingSessions); } else { return CircularProgressIndicator(); } diff --git a/frontend/lib/routes/session/calendar.dart b/frontend/lib/routes/session/calendar.dart new file mode 100644 index 00000000..8e6057aa --- /dev/null +++ b/frontend/lib/routes/session/calendar.dart @@ -0,0 +1,302 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/routes/session/event.dart'; +import 'package:table_calendar/table_calendar.dart'; + +import '../../models/session.dart'; + +class CustomTableCalendar extends StatefulWidget { + final List sessions; + const CustomTableCalendar({Key? key, required this.sessions}) + : super(key: key); + + @override + _CustomTableCalendarState createState() => + _CustomTableCalendarState(sessions: sessions); +} + +class _CustomTableCalendarState extends State { + final todaysDate = DateTime.now(); + var _focusedCalendarDate = DateTime.now(); + final _initialCalendarDate = DateTime(2000); + final _lastCalendarDate = DateTime(2050); + DateTime? selectedCalendarDate; + final titleController = TextEditingController(); + final descpController = TextEditingController(); + final List sessions; + + late Map> mySelectedEvents; + + _CustomTableCalendarState({required this.sessions}); + + @override + void initState() { + selectedCalendarDate = _focusedCalendarDate; + mySelectedEvents = {}; + fillMySelectedEvents(); + super.initState(); + // print("Sessions no calendar state"); + // print(sessions); + } + + void fillMySelectedEvents() { + for (var session in sessions) { + DateTime dateForCalendar = + DateTime(session.begin.year, session.begin.month, session.begin.day); + print("New date for calendar"); + print(dateForCalendar.toUtc()); + setState(() { + if (mySelectedEvents[dateForCalendar.toUtc()] != null) { + print( + "HEREEEEE****************************************************+"); + mySelectedEvents[session.begin]?.add(MyEvents( + eventTitle: session.title, eventDescp: session.description)); + } else { + mySelectedEvents[dateForCalendar!.toUtc()] = [ + MyEvents(eventTitle: session.title, eventDescp: session.description) + ]; + } + }); + } + } + + @override + void dispose() { + titleController.dispose(); + descpController.dispose(); + super.dispose(); + } + + List _listOfDayEvents(DateTime dateTime) { + // mySelectedEvents[selectedCalendarDate!] = [ + // MyEvents(eventTitle: 'titulo', eventDescp: 'descpController.text') + // ]; + //print(mySelectedEvents); + print(mySelectedEvents); + + return mySelectedEvents[dateTime] ?? []; + } + + _showAddEventDialog() async { + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('New Event'), + content: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + buildTextField( + controller: titleController, hint: 'Enter Title'), + const SizedBox( + height: 20.0, + ), + buildTextField( + controller: descpController, hint: 'Enter Description'), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + if (titleController.text.isEmpty && + descpController.text.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Please enter title & description'), + duration: Duration(seconds: 3), + ), + ); + //Navigator.pop(context); + return; + } else { + setState(() { + if (mySelectedEvents[selectedCalendarDate] != null) { + mySelectedEvents[selectedCalendarDate]?.add(MyEvents( + eventTitle: titleController.text, + eventDescp: descpController.text)); + } else { + mySelectedEvents[selectedCalendarDate!] = [ + MyEvents( + eventTitle: titleController.text, + eventDescp: descpController.text) + ]; + } + }); + + titleController.clear(); + descpController.clear(); + + Navigator.pop(context); + return; + } + }, + child: const Text('Add'), + ), + ], + )); + } + + Widget buildTextField( + {String? hint, required TextEditingController controller}) { + return TextField( + controller: controller, + textCapitalization: TextCapitalization.words, + decoration: InputDecoration( + labelText: hint ?? '', + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.blue, width: 1.5), + borderRadius: BorderRadius.circular( + 10.0, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.blue, width: 1.5), + borderRadius: BorderRadius.circular( + 10.0, + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Custom Calendar'), + ), + // floatingActionButton: FloatingActionButton.extended( + // onPressed: () => _showAddEventDialog(), + // label: const Text('Add Event'), + // ), + body: SingleChildScrollView( + child: Column( + children: [ + Card( + margin: const EdgeInsets.all(8.0), + elevation: 5.0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(10), + ), + side: BorderSide(color: Colors.blueAccent, width: 2.0), + ), + child: TableCalendar( + focusedDay: _focusedCalendarDate, + // today's date + firstDay: _initialCalendarDate, + // earliest possible date + lastDay: _lastCalendarDate, + // latest allowed date + calendarFormat: CalendarFormat.month, + // default view when displayed + // default is Saturday & Sunday but can be set to any day. + // instead of day number can be mentioned as well. + weekendDays: const [DateTime.sunday, 6], + // default is Sunday but can be changed according to locale + startingDayOfWeek: StartingDayOfWeek.monday, + // height between the day row and 1st date row, default is 16.0 + daysOfWeekHeight: 40.0, + // height between the date rows, default is 52.0 + rowHeight: 60.0, + // this property needs to be added if we want to show events + eventLoader: _listOfDayEvents, + // Calendar Header Styling + headerStyle: const HeaderStyle( + titleTextStyle: + TextStyle(color: Colors.lightBlue, fontSize: 20.0), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10))), + formatButtonTextStyle: + TextStyle(color: Colors.blueAccent, fontSize: 16.0), + formatButtonDecoration: BoxDecoration( + color: Colors.lightBlue, + borderRadius: BorderRadius.all( + Radius.circular(5.0), + ), + ), + leftChevronIcon: Icon( + Icons.chevron_left, + color: Colors.lightBlue, + size: 28, + ), + rightChevronIcon: Icon( + Icons.chevron_right, + color: Colors.lightBlue, + size: 28, + ), + ), + // Calendar Days Styling + daysOfWeekStyle: const DaysOfWeekStyle( + // Weekend days color (Sat,Sun) + weekendStyle: TextStyle(color: Colors.blueAccent), + ), + // Calendar Dates styling + calendarStyle: const CalendarStyle( + // Weekend dates color (Sat & Sun Column) + weekendTextStyle: TextStyle(color: Colors.blueAccent), + // highlighted color for today + todayDecoration: BoxDecoration( + color: Colors.blueAccent, + shape: BoxShape.circle, + ), + // highlighted color for selected day + selectedDecoration: BoxDecoration( + color: Colors.blueAccent, + shape: BoxShape.circle, + ), + markerDecoration: + BoxDecoration(color: Colors.red, shape: BoxShape.circle), + ), + selectedDayPredicate: (currentSelectedDate) { + // as per the documentation 'selectedDayPredicate' needs to determine + // current selected day + return (isSameDay( + selectedCalendarDate!, currentSelectedDate)); + }, + onDaySelected: (selectedDay, focusedDay) { + // as per the documentation + if (!isSameDay(selectedCalendarDate, selectedDay)) { + setState(() { + selectedCalendarDate = selectedDay; + _focusedCalendarDate = focusedDay; + }); + } + }, + ), + ), + ..._listOfDayEvents(selectedCalendarDate!).map( + (myEvents) => ListTile( + leading: const Icon( + Icons.done, + color: Colors.blue, + ), + title: Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text('Event Title: ${myEvents.eventTitle}'), + ), + subtitle: Text('Description: ${myEvents.eventDescp}'), + ), + ), + ], + ), + ), + ); + } +} + +class MyEvents { + final String eventTitle; + final String eventDescp; + + MyEvents({required this.eventTitle, required this.eventDescp}); + + @override + String toString() => eventTitle; +} diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 1040c540..bcb35ff6 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -50,7 +50,7 @@ dependencies: flutter_form_builder: ^7.7.0 dropdown_search: ^5.0.3 form_builder_extra_fields: ^8.3.0 - syncfusion_flutter_calendar: ^20.3.52 + table_calendar: ^3.0.8 dev_dependencies: flutter_test: From cd1981a5055f4a049414d7bf93b91eec9b3b7253 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 9 Nov 2022 02:14:23 +0000 Subject: [PATCH 59/73] updated calendar session view --- frontend/lib/routes/session/calendar.dart | 150 ++++++++++++---------- 1 file changed, 80 insertions(+), 70 deletions(-) diff --git a/frontend/lib/routes/session/calendar.dart b/frontend/lib/routes/session/calendar.dart index 8e6057aa..26254c22 100644 --- a/frontend/lib/routes/session/calendar.dart +++ b/frontend/lib/routes/session/calendar.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:frontend/routes/session/event.dart'; import 'package:table_calendar/table_calendar.dart'; import '../../models/session.dart'; @@ -24,7 +23,7 @@ class _CustomTableCalendarState extends State { final descpController = TextEditingController(); final List sessions; - late Map> mySelectedEvents; + late Map> mySelectedEvents; _CustomTableCalendarState({required this.sessions}); @@ -48,12 +47,9 @@ class _CustomTableCalendarState extends State { if (mySelectedEvents[dateForCalendar.toUtc()] != null) { print( "HEREEEEE****************************************************+"); - mySelectedEvents[session.begin]?.add(MyEvents( - eventTitle: session.title, eventDescp: session.description)); + mySelectedEvents[session.begin]?.add(session); } else { - mySelectedEvents[dateForCalendar!.toUtc()] = [ - MyEvents(eventTitle: session.title, eventDescp: session.description) - ]; + mySelectedEvents[dateForCalendar!.toUtc()] = [session]; } }); } @@ -66,7 +62,7 @@ class _CustomTableCalendarState extends State { super.dispose(); } - List _listOfDayEvents(DateTime dateTime) { + List _listOfDayEvents(DateTime dateTime) { // mySelectedEvents[selectedCalendarDate!] = [ // MyEvents(eventTitle: 'titulo', eventDescp: 'descpController.text') // ]; @@ -76,68 +72,68 @@ class _CustomTableCalendarState extends State { return mySelectedEvents[dateTime] ?? []; } - _showAddEventDialog() async { - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('New Event'), - content: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - buildTextField( - controller: titleController, hint: 'Enter Title'), - const SizedBox( - height: 20.0, - ), - buildTextField( - controller: descpController, hint: 'Enter Description'), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - if (titleController.text.isEmpty && - descpController.text.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Please enter title & description'), - duration: Duration(seconds: 3), - ), - ); - //Navigator.pop(context); - return; - } else { - setState(() { - if (mySelectedEvents[selectedCalendarDate] != null) { - mySelectedEvents[selectedCalendarDate]?.add(MyEvents( - eventTitle: titleController.text, - eventDescp: descpController.text)); - } else { - mySelectedEvents[selectedCalendarDate!] = [ - MyEvents( - eventTitle: titleController.text, - eventDescp: descpController.text) - ]; - } - }); + // _showAddEventDialog() async { + // await showDialog( + // context: context, + // builder: (context) => AlertDialog( + // title: const Text('New Event'), + // content: Column( + // crossAxisAlignment: CrossAxisAlignment.stretch, + // mainAxisSize: MainAxisSize.min, + // children: [ + // buildTextField( + // controller: titleController, hint: 'Enter Title'), + // const SizedBox( + // height: 20.0, + // ), + // buildTextField( + // controller: descpController, hint: 'Enter Description'), + // ], + // ), + // actions: [ + // TextButton( + // onPressed: () => Navigator.pop(context), + // child: const Text('Cancel'), + // ), + // TextButton( + // onPressed: () { + // if (titleController.text.isEmpty && + // descpController.text.isEmpty) { + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar( + // content: Text('Please enter title & description'), + // duration: Duration(seconds: 3), + // ), + // ); + // //Navigator.pop(context); + // return; + // } else { + // setState(() { + // if (mySelectedEvents[selectedCalendarDate] != null) { + // mySelectedEvents[selectedCalendarDate]?.add(MyEvents( + // eventTitle: titleController.text, + // eventDescp: descpController.text)); + // } else { + // mySelectedEvents[selectedCalendarDate!] = [ + // MyEvents( + // eventTitle: titleController.text, + // eventDescp: descpController.text) + // ]; + // } + // }); - titleController.clear(); - descpController.clear(); + // titleController.clear(); + // descpController.clear(); - Navigator.pop(context); - return; - } - }, - child: const Text('Add'), - ), - ], - )); - } + // Navigator.pop(context); + // return; + // } + // }, + // child: const Text('Add'), + // ), + // ], + // )); + // } Widget buildTextField( {String? hint, required TextEditingController controller}) { @@ -279,9 +275,23 @@ class _CustomTableCalendarState extends State { ), title: Padding( padding: const EdgeInsets.only(bottom: 8.0), - child: Text('Event Title: ${myEvents.eventTitle}'), + child: Text( + myEvents.kind.toUpperCase() + ' - ' + myEvents.title), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Description: ' + myEvents.description), + Text('From ' + + myEvents.begin.hour.toString() + + ':' + + myEvents.begin.minute.toString() + + ' to ' + + myEvents.end.hour.toString() + + ':' + + myEvents.end.minute.toString()), + ], ), - subtitle: Text('Description: ${myEvents.eventDescp}'), ), ), ], From 7144281cabef27481d8486b0bb037a7d8cec42cd Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 9 Nov 2022 02:31:59 +0000 Subject: [PATCH 60/73] Updated session view --- frontend/lib/routes/session/calendar.dart | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/frontend/lib/routes/session/calendar.dart b/frontend/lib/routes/session/calendar.dart index 26254c22..505ae8bb 100644 --- a/frontend/lib/routes/session/calendar.dart +++ b/frontend/lib/routes/session/calendar.dart @@ -290,6 +290,33 @@ class _CustomTableCalendarState extends State { myEvents.end.hour.toString() + ':' + myEvents.end.minute.toString()), + Text(myEvents.place ?? 'No place available yet'), + Text(myEvents.videoURL ?? 'No video available yet'), + (myEvents.tickets != null) + ? Text('Tickets\n' + + '*Quantity: ' + + myEvents.tickets!.max.toString() + + '\n*Available from ' + + myEvents.tickets!.start!.day.toString() + + '/' + + myEvents.tickets!.start!.month.toString() + + '/' + + myEvents.tickets!.start!.year.toString() + + ' at ' + + myEvents.tickets!.start!.hour.toString() + + ':' + + myEvents.tickets!.start!.minute.toString() + + ' to ' + + myEvents.tickets!.start!.day.toString() + + '/' + + myEvents.tickets!.start!.month.toString() + + '/' + + myEvents.tickets!.start!.year.toString() + + ' at ' + + myEvents.tickets!.end!.hour.toString() + + ':' + + myEvents.tickets!.end!.minute.toString()) + : Text('No tickets available for this session'), ], ), ), From c9154edbb839d1bea9e3e21dbeeaa9a1f43361fd Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 9 Nov 2022 02:43:27 +0000 Subject: [PATCH 61/73] Updated session time view --- frontend/lib/routes/session/calendar.dart | 32 ++++++++--------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/frontend/lib/routes/session/calendar.dart b/frontend/lib/routes/session/calendar.dart index 505ae8bb..78b1507f 100644 --- a/frontend/lib/routes/session/calendar.dart +++ b/frontend/lib/routes/session/calendar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:table_calendar/table_calendar.dart'; import '../../models/session.dart'; @@ -283,13 +284,9 @@ class _CustomTableCalendarState extends State { children: [ Text('Description: ' + myEvents.description), Text('From ' + - myEvents.begin.hour.toString() + - ':' + - myEvents.begin.minute.toString() + + DateFormat.jm().format(myEvents.begin.toLocal()) + ' to ' + - myEvents.end.hour.toString() + - ':' + - myEvents.end.minute.toString()), + DateFormat.jm().format(myEvents.end.toLocal())), Text(myEvents.place ?? 'No place available yet'), Text(myEvents.videoURL ?? 'No video available yet'), (myEvents.tickets != null) @@ -297,25 +294,18 @@ class _CustomTableCalendarState extends State { '*Quantity: ' + myEvents.tickets!.max.toString() + '\n*Available from ' + - myEvents.tickets!.start!.day.toString() + - '/' + - myEvents.tickets!.start!.month.toString() + - '/' + - myEvents.tickets!.start!.year.toString() + + DateFormat.yMd() + .format(myEvents.tickets!.start!.toLocal()) + ' at ' + - myEvents.tickets!.start!.hour.toString() + - ':' + - myEvents.tickets!.start!.minute.toString() + + DateFormat.jm() + .format(myEvents.tickets!.start!.toLocal()) + ' to ' + - myEvents.tickets!.start!.day.toString() + - '/' + + DateFormat.yMd() + .format(myEvents.tickets!.end!.toLocal()) + myEvents.tickets!.start!.month.toString() + - '/' + - myEvents.tickets!.start!.year.toString() + ' at ' + - myEvents.tickets!.end!.hour.toString() + - ':' + - myEvents.tickets!.end!.minute.toString()) + DateFormat.jm() + .format(myEvents.tickets!.start!.toLocal())) : Text('No tickets available for this session'), ], ), From b842b055353083ea528f9dbbf16ec0a6d191c6e5 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 9 Nov 2022 03:19:43 +0000 Subject: [PATCH 62/73] Modified calendar colors --- frontend/lib/routes/session/calendar.dart | 369 ++++++++++++++-------- 1 file changed, 232 insertions(+), 137 deletions(-) diff --git a/frontend/lib/routes/session/calendar.dart b/frontend/lib/routes/session/calendar.dart index 78b1507f..5eddeb6f 100644 --- a/frontend/lib/routes/session/calendar.dart +++ b/frontend/lib/routes/session/calendar.dart @@ -1,8 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:frontend/components/blurryDialog.dart'; +import 'package:frontend/routes/session/EditSessionForm.dart'; +import 'package:frontend/routes/session/SessionsNotifier.dart'; +import 'package:frontend/services/sessionService.dart'; import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; import 'package:table_calendar/table_calendar.dart'; import '../../models/session.dart'; +import '../../services/authService.dart'; class CustomTableCalendar extends StatefulWidget { final List sessions; @@ -23,6 +29,7 @@ class _CustomTableCalendarState extends State { final titleController = TextEditingController(); final descpController = TextEditingController(); final List sessions; + final _sessionService = SessionService(); late Map> mySelectedEvents; @@ -73,6 +80,62 @@ class _CustomTableCalendarState extends State { return mySelectedEvents[dateTime] ?? []; } + void _deleteSessionDialog(context, id) { + showDialog( + context: context, + builder: (BuildContext context) { + return BlurryDialog( + 'Warning', 'Are you sure you want to delete session?', () { + _deleteSession(context, id); + }); + }, + ); + } + + void _deleteSession(context, id) async { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Deleting')), + ); + + Session? s = await _sessionService.deleteSession(id); + if (s != null) { + SessionsNotifier notifier = + Provider.of(context, listen: false); + notifier.remove(s); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + ), + ); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + } + } + + Future _editSessionModal(context, id) async { + Future sessionFuture = _sessionService.getSession(id); + Session session = await sessionFuture; + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) { + return FractionallySizedBox( + heightFactor: 0.7, + child: Container( + child: EditSessionForm(session: session), + )); + }, + ); + } + // _showAddEventDialog() async { // await showDialog( // context: context, @@ -162,154 +225,186 @@ class _CustomTableCalendarState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Custom Calendar'), - ), - // floatingActionButton: FloatingActionButton.extended( - // onPressed: () => _showAddEventDialog(), - // label: const Text('Add Event'), - // ), body: SingleChildScrollView( child: Column( children: [ - Card( - margin: const EdgeInsets.all(8.0), - elevation: 5.0, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(10), - ), - side: BorderSide(color: Colors.blueAccent, width: 2.0), - ), - child: TableCalendar( - focusedDay: _focusedCalendarDate, - // today's date - firstDay: _initialCalendarDate, - // earliest possible date - lastDay: _lastCalendarDate, - // latest allowed date - calendarFormat: CalendarFormat.month, - // default view when displayed - // default is Saturday & Sunday but can be set to any day. - // instead of day number can be mentioned as well. - weekendDays: const [DateTime.sunday, 6], - // default is Sunday but can be changed according to locale - startingDayOfWeek: StartingDayOfWeek.monday, - // height between the day row and 1st date row, default is 16.0 - daysOfWeekHeight: 40.0, - // height between the date rows, default is 52.0 - rowHeight: 60.0, - // this property needs to be added if we want to show events - eventLoader: _listOfDayEvents, - // Calendar Header Styling - headerStyle: const HeaderStyle( - titleTextStyle: - TextStyle(color: Colors.lightBlue, fontSize: 20.0), - decoration: BoxDecoration( - color: Colors.blue, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10))), - formatButtonTextStyle: - TextStyle(color: Colors.blueAccent, fontSize: 16.0), - formatButtonDecoration: BoxDecoration( - color: Colors.lightBlue, - borderRadius: BorderRadius.all( - Radius.circular(5.0), - ), - ), - leftChevronIcon: Icon( - Icons.chevron_left, - color: Colors.lightBlue, - size: 28, - ), - rightChevronIcon: Icon( - Icons.chevron_right, - color: Colors.lightBlue, - size: 28, + TableCalendar( + focusedDay: _focusedCalendarDate, + firstDay: _initialCalendarDate, + lastDay: _lastCalendarDate, + calendarFormat: CalendarFormat.month, + weekendDays: const [DateTime.sunday, 6], + startingDayOfWeek: StartingDayOfWeek.monday, + daysOfWeekHeight: 40.0, + rowHeight: 60.0, + eventLoader: _listOfDayEvents, + headerStyle: const HeaderStyle( + titleTextStyle: TextStyle( + color: Color.fromARGB(255, 63, 81, 181), fontSize: 25.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10))), + formatButtonTextStyle: + TextStyle(color: Colors.white, fontSize: 16.0), + formatButtonDecoration: BoxDecoration( + color: Color.fromARGB(255, 63, 81, 181), + borderRadius: BorderRadius.all( + Radius.circular(5.0), ), ), - // Calendar Days Styling - daysOfWeekStyle: const DaysOfWeekStyle( - // Weekend days color (Sat,Sun) - weekendStyle: TextStyle(color: Colors.blueAccent), + leftChevronIcon: Icon( + Icons.chevron_left, + color: Color.fromARGB(255, 63, 81, 181), + size: 28, ), - // Calendar Dates styling - calendarStyle: const CalendarStyle( - // Weekend dates color (Sat & Sun Column) - weekendTextStyle: TextStyle(color: Colors.blueAccent), - // highlighted color for today - todayDecoration: BoxDecoration( - color: Colors.blueAccent, - shape: BoxShape.circle, - ), - // highlighted color for selected day - selectedDecoration: BoxDecoration( - color: Colors.blueAccent, - shape: BoxShape.circle, - ), - markerDecoration: - BoxDecoration(color: Colors.red, shape: BoxShape.circle), + rightChevronIcon: Icon( + Icons.chevron_right, + color: Color.fromARGB(255, 63, 81, 181), + size: 28, ), - selectedDayPredicate: (currentSelectedDate) { - // as per the documentation 'selectedDayPredicate' needs to determine - // current selected day - return (isSameDay( - selectedCalendarDate!, currentSelectedDate)); - }, - onDaySelected: (selectedDay, focusedDay) { - // as per the documentation - if (!isSameDay(selectedCalendarDate, selectedDay)) { - setState(() { - selectedCalendarDate = selectedDay; - _focusedCalendarDate = focusedDay; - }); - } - }, ), - ), - ..._listOfDayEvents(selectedCalendarDate!).map( - (myEvents) => ListTile( - leading: const Icon( - Icons.done, - color: Colors.blue, - ), - title: Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Text( - myEvents.kind.toUpperCase() + ' - ' + myEvents.title), + // Calendar Days Styling + daysOfWeekStyle: const DaysOfWeekStyle( + // Weekend days color (Sat,Sun) + weekendStyle: + TextStyle(color: Color.fromARGB(255, 63, 81, 181)), + ), + // Calendar Dates styling + calendarStyle: const CalendarStyle( + // Weekend dates color (Sat & Sun Column) + weekendTextStyle: + TextStyle(color: Color.fromARGB(255, 63, 81, 181)), + // highlighted color for today + todayDecoration: BoxDecoration( + color: Colors.blueAccent, + shape: BoxShape.circle, ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Description: ' + myEvents.description), - Text('From ' + - DateFormat.jm().format(myEvents.begin.toLocal()) + - ' to ' + - DateFormat.jm().format(myEvents.end.toLocal())), - Text(myEvents.place ?? 'No place available yet'), - Text(myEvents.videoURL ?? 'No video available yet'), - (myEvents.tickets != null) - ? Text('Tickets\n' + - '*Quantity: ' + - myEvents.tickets!.max.toString() + - '\n*Available from ' + - DateFormat.yMd() - .format(myEvents.tickets!.start!.toLocal()) + - ' at ' + - DateFormat.jm() - .format(myEvents.tickets!.start!.toLocal()) + - ' to ' + - DateFormat.yMd() - .format(myEvents.tickets!.end!.toLocal()) + - myEvents.tickets!.start!.month.toString() + - ' at ' + - DateFormat.jm() - .format(myEvents.tickets!.start!.toLocal())) - : Text('No tickets available for this session'), - ], + // highlighted color for selected day + selectedDecoration: BoxDecoration( + color: Color.fromARGB(255, 63, 81, 181), + shape: BoxShape.circle, ), + markerDecoration: BoxDecoration( + color: Color.fromARGB(255, 247, 172, 42), + shape: BoxShape.circle), ), + selectedDayPredicate: (currentSelectedDate) { + // as per the documentation 'selectedDayPredicate' needs to determine + // current selected day + return (isSameDay(selectedCalendarDate!, currentSelectedDate)); + }, + onDaySelected: (selectedDay, focusedDay) { + // as per the documentation + if (!isSameDay(selectedCalendarDate, selectedDay)) { + setState(() { + selectedCalendarDate = selectedDay; + _focusedCalendarDate = focusedDay; + }); + } + }, + ), + ..._listOfDayEvents(selectedCalendarDate!).map( + (myEvents) => ExpansionTile( + childrenPadding: const EdgeInsets.all(8.0), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + expandedAlignment: Alignment.topLeft, + leading: const Icon( + Icons.done, + color: Colors.blue, + ), + title: Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text( + myEvents.kind.toUpperCase() + ' - ' + myEvents.title), + ), + children: [ + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Description: ' + myEvents.description), + Text('From ' + + DateFormat.jm() + .format(myEvents.begin.toLocal()) + + ' to ' + + DateFormat.jm().format(myEvents.end.toLocal())), + Text(myEvents.place ?? 'No place available yet'), + Text(myEvents.videoURL ?? 'No video available yet'), + (myEvents.tickets != null) + ? Text('Tickets\n' + + '*Quantity: ' + + myEvents.tickets!.max.toString() + + '\n*Available from ' + + DateFormat.yMd().format( + myEvents.tickets!.start!.toLocal()) + + ' at ' + + DateFormat.jm().format( + myEvents.tickets!.start!.toLocal()) + + ' to ' + + DateFormat.yMd().format( + myEvents.tickets!.end!.toLocal()) + + myEvents.tickets!.start!.month.toString() + + ' at ' + + DateFormat.jm().format( + myEvents.tickets!.start!.toLocal())) + : Text('No tickets available for this session'), + ], + ), + Expanded( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.end, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + IconButton( + onPressed: () { + _editSessionModal( + context, myEvents.id); + }, + icon: Icon(Icons.edit), + color: const Color(0xff5c7ff2)), + FutureBuilder( + future: Provider.of( + context) + .role, + builder: (context, snapshot) { + if (snapshot.hasData) { + Role r = + snapshot.data as Role; + + if (r == Role.ADMIN || + r == Role.COORDINATOR) { + return IconButton( + onPressed: () => + _deleteSessionDialog( + context, + myEvents.id), + icon: + Icon(Icons.delete), + color: Colors.red); + } else { + return Container(); + } + } else { + return Container(); + } + }) + ]), + ])), + ), + ], + ) + ]), ), ], ), From f1335ec0204c8e8b236a7b844940c75595025f6d Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 9 Nov 2022 03:26:27 +0000 Subject: [PATCH 63/73] Changed calendr format --- frontend/lib/routes/session/calendar.dart | 72 +++-------------------- 1 file changed, 8 insertions(+), 64 deletions(-) diff --git a/frontend/lib/routes/session/calendar.dart b/frontend/lib/routes/session/calendar.dart index 5eddeb6f..29864e24 100644 --- a/frontend/lib/routes/session/calendar.dart +++ b/frontend/lib/routes/session/calendar.dart @@ -30,6 +30,7 @@ class _CustomTableCalendarState extends State { final descpController = TextEditingController(); final List sessions; final _sessionService = SessionService(); + CalendarFormat format = CalendarFormat.month; late Map> mySelectedEvents; @@ -136,69 +137,6 @@ class _CustomTableCalendarState extends State { ); } - // _showAddEventDialog() async { - // await showDialog( - // context: context, - // builder: (context) => AlertDialog( - // title: const Text('New Event'), - // content: Column( - // crossAxisAlignment: CrossAxisAlignment.stretch, - // mainAxisSize: MainAxisSize.min, - // children: [ - // buildTextField( - // controller: titleController, hint: 'Enter Title'), - // const SizedBox( - // height: 20.0, - // ), - // buildTextField( - // controller: descpController, hint: 'Enter Description'), - // ], - // ), - // actions: [ - // TextButton( - // onPressed: () => Navigator.pop(context), - // child: const Text('Cancel'), - // ), - // TextButton( - // onPressed: () { - // if (titleController.text.isEmpty && - // descpController.text.isEmpty) { - // ScaffoldMessenger.of(context).showSnackBar( - // const SnackBar( - // content: Text('Please enter title & description'), - // duration: Duration(seconds: 3), - // ), - // ); - // //Navigator.pop(context); - // return; - // } else { - // setState(() { - // if (mySelectedEvents[selectedCalendarDate] != null) { - // mySelectedEvents[selectedCalendarDate]?.add(MyEvents( - // eventTitle: titleController.text, - // eventDescp: descpController.text)); - // } else { - // mySelectedEvents[selectedCalendarDate!] = [ - // MyEvents( - // eventTitle: titleController.text, - // eventDescp: descpController.text) - // ]; - // } - // }); - - // titleController.clear(); - // descpController.clear(); - - // Navigator.pop(context); - // return; - // } - // }, - // child: const Text('Add'), - // ), - // ], - // )); - // } - Widget buildTextField( {String? hint, required TextEditingController controller}) { return TextField( @@ -232,7 +170,12 @@ class _CustomTableCalendarState extends State { focusedDay: _focusedCalendarDate, firstDay: _initialCalendarDate, lastDay: _lastCalendarDate, - calendarFormat: CalendarFormat.month, + calendarFormat: format, + onFormatChanged: (CalendarFormat _format) { + setState(() { + format = _format; + }); + }, weekendDays: const [DateTime.sunday, 6], startingDayOfWeek: StartingDayOfWeek.monday, daysOfWeekHeight: 40.0, @@ -246,6 +189,7 @@ class _CustomTableCalendarState extends State { borderRadius: BorderRadius.only( topLeft: Radius.circular(10), topRight: Radius.circular(10))), + formatButtonShowsNext: false, formatButtonTextStyle: TextStyle(color: Colors.white, fontSize: 16.0), formatButtonDecoration: BoxDecoration( From 6be367b1293621cade51cea51f2882851d785097 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 9 Nov 2022 03:45:10 +0000 Subject: [PATCH 64/73] Multiple sessions showing up in the calendargit add -A! --- frontend/lib/routes/session/calendar.dart | 20 ++++++++------------ frontend/lib/services/sessionService.dart | 3 +-- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/frontend/lib/routes/session/calendar.dart b/frontend/lib/routes/session/calendar.dart index 29864e24..699ef019 100644 --- a/frontend/lib/routes/session/calendar.dart +++ b/frontend/lib/routes/session/calendar.dart @@ -42,22 +42,19 @@ class _CustomTableCalendarState extends State { mySelectedEvents = {}; fillMySelectedEvents(); super.initState(); - // print("Sessions no calendar state"); - // print(sessions); } void fillMySelectedEvents() { for (var session in sessions) { DateTime dateForCalendar = DateTime(session.begin.year, session.begin.month, session.begin.day); - print("New date for calendar"); - print(dateForCalendar.toUtc()); + setState(() { if (mySelectedEvents[dateForCalendar.toUtc()] != null) { - print( - "HEREEEEE****************************************************+"); - mySelectedEvents[session.begin]?.add(session); + print("1"); + mySelectedEvents[dateForCalendar.toUtc()]!.add(session); } else { + print("2"); mySelectedEvents[dateForCalendar!.toUtc()] = [session]; } }); @@ -72,11 +69,10 @@ class _CustomTableCalendarState extends State { } List _listOfDayEvents(DateTime dateTime) { - // mySelectedEvents[selectedCalendarDate!] = [ - // MyEvents(eventTitle: 'titulo', eventDescp: 'descpController.text') - // ]; - //print(mySelectedEvents); - print(mySelectedEvents); + // print("Sessions"); + // print(sessions); + // print("mySelectedEvents"); + // print(mySelectedEvents); return mySelectedEvents[dateTime] ?? []; } diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index cbbe4291..8158b16c 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -18,8 +18,7 @@ class SessionService extends Service { try { final responseJson = json.decode(response.data!) as List; - print("Response json"); - print(responseJson); + List sessions = responseJson.map((e) => Session.fromJson(e)).toList(); return sessions; From d68d18ceb2125d3428e7dcf3e254ad7319b2b7a8 Mon Sep 17 00:00:00 2001 From: CatarinaBeirolas Date: Wed, 9 Nov 2022 04:41:52 +0000 Subject: [PATCH 65/73] Cleaned code --- .../lib/routes/session/AddSessionForm.dart | 140 ------------------ frontend/lib/routes/session/SessionPage.dart | 5 +- frontend/lib/routes/session/calendar.dart | 112 +++++++------- frontend/lib/services/sessionService.dart | 1 - 4 files changed, 51 insertions(+), 207 deletions(-) diff --git a/frontend/lib/routes/session/AddSessionForm.dart b/frontend/lib/routes/session/AddSessionForm.dart index d4ed2d1a..cd40c657 100644 --- a/frontend/lib/routes/session/AddSessionForm.dart +++ b/frontend/lib/routes/session/AddSessionForm.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:frontend/components/appbar.dart'; import 'package:frontend/models/company.dart'; import 'package:frontend/models/session.dart'; @@ -8,9 +7,7 @@ import 'package:frontend/routes/session/SessionsNotifier.dart'; import 'package:frontend/services/companyService.dart'; import 'package:frontend/services/sessionService.dart'; import 'package:frontend/services/speakerService.dart'; -import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:form_builder_extra_fields/form_builder_extra_fields.dart'; @@ -30,11 +27,8 @@ class _AddSessionFormState extends State { final _placeController = TextEditingController(); final _beginDateController = TextEditingController(); final _endDateController = TextEditingController(); - var _speakerController = TextEditingController(); final _descriptionController = TextEditingController(); - final _companyController = TextEditingController(); final _videoURLController = TextEditingController(); - final _maxTicketsController = TextEditingController(); final _ticketBeginDateController = TextEditingController(); final _ticketEndDateController = TextEditingController(); final _sessionService = SessionService(); @@ -62,19 +56,9 @@ class _AddSessionFormState extends State { var title = _titleController.text; var description = _descriptionController.text; var place = _placeController.text; - //var speaker = _speakerController.text; - //var company = _companyController.text; var maxTickets = _currentTicketsValue; var videoURL = _videoURLController.text; - print("Max tickets:"); - print(maxTickets); - - // var sessionTickets = maxTickets != 0 - // ? new SessionTickets( - // max: maxTickets, start: _beginTicket, end: _endTicket) - // : new SessionTickets(max: 0, start: null, end: null); - var sessionTickets = new SessionTickets( max: maxTickets as int, start: _beginTicket, end: _endTicket); @@ -125,46 +109,6 @@ class _AddSessionFormState extends State { } } - Future _selectDateTime( - BuildContext context, bool isBegin, bool isTicket) async { - final datePicker = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime(2000), - lastDate: DateTime(2025), - ); - - final timePicker = await showTimePicker( - context: context, - initialTime: TimeOfDay.now(), - builder: (BuildContext context, Widget? child) { - return MediaQuery( - data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true), - child: child!, - ); - }); - - if (datePicker != null && timePicker != null) { - if (isBegin && !isTicket) { - _begin = DateTime(datePicker.year, datePicker.month, datePicker.day, - timePicker.hour, timePicker.minute); - } else if (isBegin && isTicket) { - _beginTicket = DateTime(datePicker.year, datePicker.month, - datePicker.day, timePicker.hour, timePicker.minute); - } else if (!isBegin && !isTicket) { - _end = DateTime(datePicker.year, datePicker.month, datePicker.day, - timePicker.hour, timePicker.minute); - } else { - _endTicket = DateTime(datePicker.year, datePicker.month, datePicker.day, - timePicker.hour, timePicker.minute); - } - } - } - - String getDateTime(DateTime dateTime) { - return DateFormat('yyyy-MM-dd HH:mm').format(dateTime); - } - Widget _buildForm() { return Form( key: _formKey, @@ -287,12 +231,10 @@ class _AddSessionFormState extends State { return null; }, onChanged: (List speakers) { - print(speakers); speakersIds.clear(); for (var speaker in speakers) { speakersIds.add(speaker.id); } - print(speakersIds); }, clearButtonProps: ClearButtonProps(isVisible: true), ) @@ -457,88 +399,6 @@ class _AddSessionFormState extends State { )); } - List getResults(double height) { - if (_speakerController.text.length > 1) { - return [ - Container( - decoration: new BoxDecoration( - color: Theme.of(context).cardColor, - ), - child: FutureBuilder( - future: Future.wait([this.speakers]), - builder: (context, snapshot) { - if (snapshot.hasData) { - List data = snapshot.data as List; - - List speaksMatched = data[0] as List; - return searchResults(speaksMatched, height); - } else { - return Center(child: CircularProgressIndicator()); - } - })) - ]; - } else { - return []; - } - } - - Widget searchResults(List speakers, double listHeight) { - List results = getListCards(speakers); - return Container( - constraints: BoxConstraints(maxHeight: listHeight), - child: ListView.builder( - shrinkWrap: true, - itemCount: results.length, - itemBuilder: (BuildContext context, int index) { - return results[index]; - })); - } - - List getListCards(List speakers) { - List results = []; - if (speakers.length != 0) { - results.addAll(speakers - .map((e) => SpeakerSearch(speaker: e, index: speakers.indexOf(e)))); - } - return results; - } - - SpeakerSearch({required Speaker speaker, required int index}) { - var canShow = true; - return Card( - child: ListTile( - leading: CircleAvatar( - foregroundImage: NetworkImage(getImageURL(speaker)), - backgroundImage: AssetImage( - 'assets/noImage.png', - ), - ), - title: Text(getName(speaker)), - onTap: () { - _speakerController.text = speaker.id; - canShow = false; - }), - ); - } - - String getImageURL(Speaker speaker) { - if (speaker != null) { - return speaker.imgs!.internal!; - } else { - //ERROR case - return ""; - } - } - - String getName(Speaker speaker) { - if (speaker != null) { - return speaker.name; - } else { - //ERROR case - return ""; - } - } - @override Widget build(BuildContext context) { CustomAppBar appBar = CustomAppBar( diff --git a/frontend/lib/routes/session/SessionPage.dart b/frontend/lib/routes/session/SessionPage.dart index 55577bea..ec5a7c72 100644 --- a/frontend/lib/routes/session/SessionPage.dart +++ b/frontend/lib/routes/session/SessionPage.dart @@ -58,11 +58,8 @@ class _SessionListState extends State notifier.sessions = snapshot.data as List; var upcomingSessions = notifier.getUpcoming().toList(); - // print("Upciming Sessions size:"); - // print(upcomingSessions.length); - // print(upcomingSessions.toString()); - return CustomTableCalendar(sessions: upcomingSessions); + return Calendar(sessions: upcomingSessions); } else { return CircularProgressIndicator(); } diff --git a/frontend/lib/routes/session/calendar.dart b/frontend/lib/routes/session/calendar.dart index 699ef019..04676dd3 100644 --- a/frontend/lib/routes/session/calendar.dart +++ b/frontend/lib/routes/session/calendar.dart @@ -10,17 +10,15 @@ import 'package:table_calendar/table_calendar.dart'; import '../../models/session.dart'; import '../../services/authService.dart'; -class CustomTableCalendar extends StatefulWidget { +class Calendar extends StatefulWidget { final List sessions; - const CustomTableCalendar({Key? key, required this.sessions}) - : super(key: key); + const Calendar({Key? key, required this.sessions}) : super(key: key); @override - _CustomTableCalendarState createState() => - _CustomTableCalendarState(sessions: sessions); + _CalendarState createState() => _CalendarState(sessions: sessions); } -class _CustomTableCalendarState extends State { +class _CalendarState extends State { final todaysDate = DateTime.now(); var _focusedCalendarDate = DateTime.now(); final _initialCalendarDate = DateTime(2000); @@ -32,30 +30,28 @@ class _CustomTableCalendarState extends State { final _sessionService = SessionService(); CalendarFormat format = CalendarFormat.month; - late Map> mySelectedEvents; + late Map> calendarSessions; - _CustomTableCalendarState({required this.sessions}); + _CalendarState({required this.sessions}); @override void initState() { selectedCalendarDate = _focusedCalendarDate; - mySelectedEvents = {}; - fillMySelectedEvents(); + calendarSessions = {}; + fillCalendarSessions(); super.initState(); } - void fillMySelectedEvents() { + void fillCalendarSessions() { for (var session in sessions) { DateTime dateForCalendar = DateTime(session.begin.year, session.begin.month, session.begin.day); setState(() { - if (mySelectedEvents[dateForCalendar.toUtc()] != null) { - print("1"); - mySelectedEvents[dateForCalendar.toUtc()]!.add(session); + if (calendarSessions[dateForCalendar.toUtc()] != null) { + calendarSessions[dateForCalendar.toUtc()]!.add(session); } else { - print("2"); - mySelectedEvents[dateForCalendar!.toUtc()] = [session]; + calendarSessions[dateForCalendar!.toUtc()] = [session]; } }); } @@ -68,13 +64,8 @@ class _CustomTableCalendarState extends State { super.dispose(); } - List _listOfDayEvents(DateTime dateTime) { - // print("Sessions"); - // print(sessions); - // print("mySelectedEvents"); - // print(mySelectedEvents); - - return mySelectedEvents[dateTime] ?? []; + List _listOfDaySessions(DateTime dateTime) { + return calendarSessions[dateTime] ?? []; } void _deleteSessionDialog(context, id) { @@ -176,7 +167,7 @@ class _CustomTableCalendarState extends State { startingDayOfWeek: StartingDayOfWeek.monday, daysOfWeekHeight: 40.0, rowHeight: 60.0, - eventLoader: _listOfDayEvents, + eventLoader: _listOfDaySessions, headerStyle: const HeaderStyle( titleTextStyle: TextStyle( color: Color.fromARGB(255, 63, 81, 181), fontSize: 25.0), @@ -205,23 +196,17 @@ class _CustomTableCalendarState extends State { size: 28, ), ), - // Calendar Days Styling daysOfWeekStyle: const DaysOfWeekStyle( - // Weekend days color (Sat,Sun) weekendStyle: TextStyle(color: Color.fromARGB(255, 63, 81, 181)), ), - // Calendar Dates styling calendarStyle: const CalendarStyle( - // Weekend dates color (Sat & Sun Column) weekendTextStyle: TextStyle(color: Color.fromARGB(255, 63, 81, 181)), - // highlighted color for today todayDecoration: BoxDecoration( color: Colors.blueAccent, shape: BoxShape.circle, ), - // highlighted color for selected day selectedDecoration: BoxDecoration( color: Color.fromARGB(255, 63, 81, 181), shape: BoxShape.circle, @@ -231,12 +216,9 @@ class _CustomTableCalendarState extends State { shape: BoxShape.circle), ), selectedDayPredicate: (currentSelectedDate) { - // as per the documentation 'selectedDayPredicate' needs to determine - // current selected day return (isSameDay(selectedCalendarDate!, currentSelectedDate)); }, onDaySelected: (selectedDay, focusedDay) { - // as per the documentation if (!isSameDay(selectedCalendarDate, selectedDay)) { setState(() { selectedCalendarDate = selectedDay; @@ -245,8 +227,8 @@ class _CustomTableCalendarState extends State { } }, ), - ..._listOfDayEvents(selectedCalendarDate!).map( - (myEvents) => ExpansionTile( + ..._listOfDaySessions(selectedCalendarDate!).map( + (calSessions) => ExpansionTile( childrenPadding: const EdgeInsets.all(8.0), expandedCrossAxisAlignment: CrossAxisAlignment.start, expandedAlignment: Alignment.topLeft, @@ -255,9 +237,10 @@ class _CustomTableCalendarState extends State { color: Colors.blue, ), title: Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Text( - myEvents.kind.toUpperCase() + ' - ' + myEvents.title), + padding: const EdgeInsets.all(8.0), + child: Text(calSessions.kind.toUpperCase() + + ' - ' + + calSessions.title), ), children: [ Row( @@ -265,34 +248,49 @@ class _CustomTableCalendarState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Description: ' + myEvents.description), + Text('Description: ' + calSessions.description), Text('From ' + DateFormat.jm() - .format(myEvents.begin.toLocal()) + + .format(calSessions.begin.toLocal()) + ' to ' + - DateFormat.jm().format(myEvents.end.toLocal())), - Text(myEvents.place ?? 'No place available yet'), - Text(myEvents.videoURL ?? 'No video available yet'), - (myEvents.tickets != null) + DateFormat.jm() + .format(calSessions.end.toLocal())), + Text(calSessions.place ?? 'No place available yet'), + Text(calSessions.videoURL ?? + 'No video available yet'), + (calSessions.tickets != null) ? Text('Tickets\n' + '*Quantity: ' + - myEvents.tickets!.max.toString() + + calSessions.tickets!.max.toString() + '\n*Available from ' + DateFormat.yMd().format( - myEvents.tickets!.start!.toLocal()) + + calSessions.tickets!.start!.toLocal()) + ' at ' + DateFormat.jm().format( - myEvents.tickets!.start!.toLocal()) + + calSessions.tickets!.start!.toLocal()) + ' to ' + DateFormat.yMd().format( - myEvents.tickets!.end!.toLocal()) + - myEvents.tickets!.start!.month.toString() + + calSessions.tickets!.end!.toLocal()) + + calSessions.tickets!.start!.month + .toString() + ' at ' + DateFormat.jm().format( - myEvents.tickets!.start!.toLocal())) + calSessions.tickets!.start!.toLocal())) : Text('No tickets available for this session'), ], ), + Padding( + padding: const EdgeInsets.all(40.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + (calSessions.kind == 'TALK') + ? Text('Speakers: ' + + calSessions.speakersIds.toString()) + : Text('Company: ' + + calSessions.companyId.toString()) + ]), + ), Expanded( child: Padding( padding: EdgeInsets.all(8.0), @@ -309,7 +307,7 @@ class _CustomTableCalendarState extends State { IconButton( onPressed: () { _editSessionModal( - context, myEvents.id); + context, calSessions.id); }, icon: Icon(Icons.edit), color: const Color(0xff5c7ff2)), @@ -328,7 +326,7 @@ class _CustomTableCalendarState extends State { onPressed: () => _deleteSessionDialog( context, - myEvents.id), + calSessions.id), icon: Icon(Icons.delete), color: Colors.red); @@ -352,13 +350,3 @@ class _CustomTableCalendarState extends State { ); } } - -class MyEvents { - final String eventTitle; - final String eventDescp; - - MyEvents({required this.eventTitle, required this.eventDescp}); - - @override - String toString() => eventTitle; -} diff --git a/frontend/lib/services/sessionService.dart b/frontend/lib/services/sessionService.dart index 8158b16c..b192a98c 100644 --- a/frontend/lib/services/sessionService.dart +++ b/frontend/lib/services/sessionService.dart @@ -90,7 +90,6 @@ class SessionService extends Service { try { int eventId = Event.fromJson(json.decode(response.data!)).id; - print("Event id " + eventId.toString()); Future> _futureSessions = getSessions(event: eventId); List sessions = await _futureSessions; Session s = sessions.last; From f2ac0fab6431f5d979c8631f7a02ca0f87b6c101 Mon Sep 17 00:00:00 2001 From: Pedro Maximino Date: Wed, 9 Nov 2022 10:59:18 +0000 Subject: [PATCH 66/73] fix: Fix steps for companies --- .../lib/routes/company/CompanyScreen.dart | 133 +++++++++--------- frontend/lib/services/companyService.dart | 3 +- 2 files changed, 68 insertions(+), 68 deletions(-) diff --git a/frontend/lib/routes/company/CompanyScreen.dart b/frontend/lib/routes/company/CompanyScreen.dart index d331dcbc..a9f53863 100644 --- a/frontend/lib/routes/company/CompanyScreen.dart +++ b/frontend/lib/routes/company/CompanyScreen.dart @@ -121,74 +121,73 @@ class _CompanyScreenState extends State bool small = constraints.maxWidth < App.SIZE; return Consumer(builder: (context, notif, child) { return Scaffold( - appBar: CustomAppBar(disableEventChange: true), - body: DefaultTabController( - length: 4, - child: Column( - children: [ - CompanyBanner( - company: widget.company, - statusChangeCallback: (step, context) { - companyChangedCallback( - context, - fs: _companyService.stepParticipationStatus( - id: widget.company.id, step: step), - ); - }, - onEdit: (context, _comp) { - companyChangedCallback(context, company: _comp); - }, - ), - TabBar( - isScrollable: small, - controller: _tabController, - tabs: [ - Tab(text: 'Details'), - Tab(text: 'BillingInfo'), - Tab(text: 'Participations'), - Tab(text: 'Communications'), - ], - ), - Expanded( - child: TabBarView(controller: _tabController, children: [ - DetailsScreen( - company: widget.company, - ), - Container( - child: Center(child: Text('Work in progress :)')), - ), - ParticipationList( - company: widget.company, - onParticipationChanged: - (Map body) async { - await companyChangedCallback( - context, - fs: _companyService.updateParticipation( - id: widget.company.id, - notes: body['notes'], - member: body['member'], - partner: body['partner'], - confirmed: body['confirmed'], - ), - ); - }, - onParticipationAdded: () => - companyChangedCallback(context, - fs: _companyService.addParticipation( - id: widget.company.id, - partner: false, - )), - ), - CommunicationsList( - participations: widget.company.participations ?? [], - small: small), - ]), - ), - ], + appBar: CustomAppBar(disableEventChange: true), + body: DefaultTabController( + length: 4, + child: Column( + children: [ + CompanyBanner( + company: widget.company, + statusChangeCallback: (step, context) { + companyChangedCallback( + context, + fs: _companyService.stepParticipationStatus( + id: widget.company.id, step: step), + ); + }, + onEdit: (context, _comp) { + companyChangedCallback(context, company: _comp); + }, + ), + TabBar( + isScrollable: small, + controller: _tabController, + tabs: [ + Tab(text: 'Details'), + Tab(text: 'BillingInfo'), + Tab(text: 'Participations'), + Tab(text: 'Communications'), + ], + ), + Expanded( + child: TabBarView(controller: _tabController, children: [ + DetailsScreen( + company: widget.company, + ), + Container( + child: Center(child: Text('Work in progress :)')), + ), + ParticipationList( + company: widget.company, + onParticipationChanged: + (Map body) async { + await companyChangedCallback( + context, + fs: _companyService.updateParticipation( + id: widget.company.id, + notes: body['notes'], + member: body['member'], + partner: body['partner'], + confirmed: body['confirmed'], + ), + ); + }, + onParticipationAdded: () => + companyChangedCallback(context, + fs: _companyService.addParticipation( + id: widget.company.id, + partner: false, + )), + ), + CommunicationsList( + participations: widget.company.participations ?? [], + small: small), + ]), + ), + ], + ), ), - ), - floatingActionButton: _fabAtIndex(context) - ); + floatingActionButton: _fabAtIndex(context)); }); }); } diff --git a/frontend/lib/services/companyService.dart b/frontend/lib/services/companyService.dart index 9f809a5a..430adef4 100644 --- a/frontend/lib/services/companyService.dart +++ b/frontend/lib/services/companyService.dart @@ -328,9 +328,10 @@ class CompanyService extends Service { Response response = await dio.get("/companies/" + id + "/participation/status/next"); try { - final responseJson = json.decode(response.data!) as List; + final responseJson = json.decode(response.data!)['steps'] as List; List participationSteps = responseJson.map((e) => ParticipationStep.fromJson(e)).toList(); + return participationSteps; } on SocketException { throw DeckException('No Internet connection'); From ab912df0b330ad3f2aa804005abbaa6b83c16154 Mon Sep 17 00:00:00 2001 From: Pedro Maximino Date: Wed, 9 Nov 2022 14:56:19 +0000 Subject: [PATCH 67/73] feat: Change flutter channel to stable --- frontend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index bb1d0203..da565d02 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -11,7 +11,7 @@ RUN git clone https://github.com/flutter/flutter.git /usr/local/flutter ENV PATH="/usr/local/flutter/bin:/usr/local/flutter/bin/cache/dart-sdk/bin:${PATH}" # Enable flutter web -RUN flutter channel master +RUN flutter channel stable RUN flutter upgrade RUN flutter config --enable-web From 837828742156dc29ea8582f0a3303f8b24837708 Mon Sep 17 00:00:00 2001 From: JoaoGomes24 Date: Wed, 9 Nov 2022 17:05:25 +0000 Subject: [PATCH 68/73] Cant add member that exist --- .../lib/routes/teams/AddTeamMemberForm.dart | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/frontend/lib/routes/teams/AddTeamMemberForm.dart b/frontend/lib/routes/teams/AddTeamMemberForm.dart index 5f60c383..51f37418 100644 --- a/frontend/lib/routes/teams/AddTeamMemberForm.dart +++ b/frontend/lib/routes/teams/AddTeamMemberForm.dart @@ -37,11 +37,13 @@ class _AddTeamMemberFormState extends State { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Adding member...')), ); - // print(widget.team!.id); + //print(widget.team!.id); // print(_memberID); - Team? m = await service.addTeamMember( + //print(widget.team!.members!); + + Team? t = await service.addTeamMember( id: widget.team!.id, memberId: _memberID, role: role); - if (m != null) { + if (t != null) { ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( @@ -50,8 +52,8 @@ class _AddTeamMemberFormState extends State { duration: Duration(seconds: 2), ), ); - widget.onEditTeam!(context, m); Navigator.pop(context); + widget.onEditTeam!(context, t); } else { ScaffoldMessenger.of(context).hideCurrentSnackBar(); @@ -172,6 +174,29 @@ class _AddTeamMemberFormState extends State { } void _getMemberData(String id, String name) { + widget.team!.members!.map((memberteam) { + if (id == memberteam.memberID) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text("Member already exist"), + content: Text("Can't add again"), + actions: [ + TextButton( + child: Text("OK"), + onPressed: () { + Navigator.pop(context); + Navigator.pop(context); + }, + ), + ], + ); + }, + ); + return; + } + }).toList(); _memberID = id; _searchMembersController.text = name; disappearSearchResults = true; From 023710fc4de874f4b2af796c82f6d1d33ce22f85 Mon Sep 17 00:00:00 2001 From: bvlourenco Date: Wed, 9 Nov 2022 17:06:13 +0000 Subject: [PATCH 69/73] fix: team state management working --- .../lib/routes/teams/AddTeamMemberForm.dart | 2 +- frontend/lib/routes/teams/TeamScreen.dart | 155 +++++++++------ frontend/lib/routes/teams/TeamsTable.dart | 180 ++++++++++-------- 3 files changed, 200 insertions(+), 137 deletions(-) diff --git a/frontend/lib/routes/teams/AddTeamMemberForm.dart b/frontend/lib/routes/teams/AddTeamMemberForm.dart index 5f60c383..4609565a 100644 --- a/frontend/lib/routes/teams/AddTeamMemberForm.dart +++ b/frontend/lib/routes/teams/AddTeamMemberForm.dart @@ -118,7 +118,7 @@ class _AddTeamMemberFormState extends State { padding: const EdgeInsets.all(8.0), child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.secondary, + // backgroundColor: Theme.of(context).colorScheme.secondary, padding: EdgeInsets.symmetric(horizontal: 50), elevation: 2, shape: RoundedRectangleBorder( diff --git a/frontend/lib/routes/teams/TeamScreen.dart b/frontend/lib/routes/teams/TeamScreen.dart index f212eeeb..cde9af43 100644 --- a/frontend/lib/routes/teams/TeamScreen.dart +++ b/frontend/lib/routes/teams/TeamScreen.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:frontend/components/router.dart'; -import 'package:frontend/main.dart'; import 'package:frontend/models/meeting.dart'; import 'package:frontend/models/member.dart'; import 'package:frontend/models/team.dart'; @@ -10,8 +8,8 @@ import 'package:frontend/routes/member/MemberScreen.dart'; import 'package:frontend/routes/teams/AddTeamMemberForm.dart'; import 'package:frontend/routes/teams/TeamsNotifier.dart'; import 'package:frontend/services/meetingService.dart'; -import 'package:frontend/services/teamService.dart'; import 'package:frontend/services/memberService.dart'; +import 'package:frontend/services/teamService.dart'; import 'package:frontend/services/authService.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:provider/provider.dart'; @@ -29,10 +27,8 @@ bool membersPage = true; class TeamScreen extends StatefulWidget { Team team; - List members; - TeamScreen({Key? key, required this.team, required this.members}) - : super(key: key); + TeamScreen({Key? key, required this.team}) : super(key: key); @override _TeamScreen createState() => _TeamScreen(); @@ -42,14 +38,15 @@ class _TeamScreen extends State with SingleTickerProviderStateMixin { late final TabController _tabController; TeamService _teamService = new TeamService(); - - _TeamScreen({Key? key}); + MemberService _memberService = new MemberService(); + late List> _members; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); _tabController.addListener(_handleTabIndex); + _getTeamMembers(widget.team); } @override @@ -63,6 +60,12 @@ class _TeamScreen extends State setState(() {}); } + void _getTeamMembers(Team t) { + _members = t.members! + .map((teamMember) => _memberService.getMember(teamMember.memberID!)) + .toList(); + } + Future teamChangedCallback(BuildContext context, {Future? fm, Team? team}) async { Team? m; @@ -75,6 +78,7 @@ class _TeamScreen extends State Provider.of(context, listen: false).edit(m); setState(() { widget.team = m!; + _getTeamMembers(m); }); } } @@ -256,8 +260,9 @@ class _TeamScreen extends State ); } - showRemoveMemberDialog(context) { + showRemoveMemberDialog(context) async { String memberId = ""; + List _membs = await Future.wait(_members); return showDialog( context: context, builder: (BuildContext context) => AlertDialog( @@ -273,7 +278,7 @@ class _TeamScreen extends State icon: const Icon(Icons.grid_3x3), labelText: "MemberId *", ), - items: widget.members.map((Member? member) { + items: _membs.map((Member? member) { return new DropdownMenuItem( value: member!.id, child: Text(member.name)); }).toList(), @@ -318,6 +323,8 @@ class _TeamScreen extends State duration: Duration(seconds: 2), ), ); + + Navigator.pop(context); } else { ScaffoldMessenger.of(context).hideCurrentSnackBar(); @@ -331,10 +338,27 @@ class _TeamScreen extends State } void editTeam(String? id, String name) async { - final response = await _teamService.updateTeam(id!, name); - setState(() { - widget.team.name = response?.name ?? "Empty name"; - }); + Team? t = await _teamService.updateTeam(id!, name); + if (t != null) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + ), + ); + + teamChangedCallback(context, team: t); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + + Navigator.pop(context); + } Navigator.pop(context, "Update"); } @@ -368,43 +392,45 @@ class _TeamScreen extends State @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: GestureDetector( - child: Image.asset( - 'assets/logo-branco2.png', - height: 100, - width: 100, - )), - ), - body: Container( - child: DefaultTabController( - length: 2, - child: Column(children: [ - TeamBanner(team: widget.team), - TabBar( - controller: _tabController, - labelColor: Colors.black, - tabs: [ - Tab( - text: 'Members', - ), - Tab(text: 'Meetings'), - ], - ), - Expanded( - child: TabBarView( - controller: _tabController, - children: [ - DisplayMembers(members: widget.members), - DisplayMeeting(meetingsIds: widget.team.meetings), - ], - )) - ])), - ), - // TODO should only appear in Members tab? - floatingActionButton: buildSpeedDial(context), - ); + return Consumer(builder: (context, notif, child) { + return Scaffold( + appBar: AppBar( + title: GestureDetector( + child: Image.asset( + 'assets/logo-branco2.png', + height: 100, + width: 100, + )), + ), + body: Container( + child: DefaultTabController( + length: 2, + child: Column(children: [ + TeamBanner(team: widget.team), + TabBar( + controller: _tabController, + labelColor: Colors.black, + tabs: [ + Tab( + text: 'Members', + ), + Tab(text: 'Meetings'), + ], + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + DisplayMembers(members: _members), + DisplayMeeting(meetingsIds: widget.team.meetings), + ], + )) + ])), + ), + // TODO should only appear in Members tab? + floatingActionButton: buildSpeedDial(context), + ); + }); } } @@ -513,20 +539,29 @@ class _DisplayMeetingState extends State { } class DisplayMembers extends StatelessWidget { - final List members; + final List> members; const DisplayMembers({Key? key, required this.members}) : super(key: key); @override Widget build(BuildContext context) { - membersPage = true; return Scaffold( - backgroundColor: Color.fromRGBO(186, 196, 242, 0.1), - body: ListView( - padding: EdgeInsets.symmetric(horizontal: 32), - physics: BouncingScrollPhysics(), - scrollDirection: Axis.vertical, - children: members.map((e) => ShowMember(member: e!)).toList()), - ); + backgroundColor: Color.fromRGBO(186, 196, 242, 0.1), + body: FutureBuilder>( + future: Future.wait(members), + builder: (context, snapshot) { + if (snapshot.hasData) { + List membs = snapshot.data as List; + + return ListView( + padding: EdgeInsets.symmetric(horizontal: 32), + physics: BouncingScrollPhysics(), + scrollDirection: Axis.vertical, + children: + membs.map((e) => ShowMember(member: e!)).toList()); + } else { + return CircularProgressIndicator(); + } + })); } } diff --git a/frontend/lib/routes/teams/TeamsTable.dart b/frontend/lib/routes/teams/TeamsTable.dart index d967f7a7..830cb1fd 100644 --- a/frontend/lib/routes/teams/TeamsTable.dart +++ b/frontend/lib/routes/teams/TeamsTable.dart @@ -6,6 +6,7 @@ import 'package:frontend/models/member.dart'; import 'package:frontend/components/ListViewCard.dart'; import 'package:frontend/models/team.dart'; import 'package:frontend/routes/teams/TeamScreen.dart'; +import 'package:frontend/routes/teams/TeamsNotifier.dart'; import 'package:frontend/services/memberService.dart'; import 'package:frontend/services/teamService.dart'; import 'package:provider/provider.dart'; @@ -37,85 +38,92 @@ class _TeamTableState extends State Widget build(BuildContext context) { super.build(context); - return Scaffold( - body: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), + return Consumer(builder: (context, notif, child) { + return Scaffold( + body: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), + ), ), - ), - ], - body: FutureBuilder( - future: Future.wait([ - _teamService.getTeams( - event: Provider.of(context).event.id) - ]), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.hasError) { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ], + body: FutureBuilder( + future: Future.wait([ + _teamService.getTeams( + event: Provider.of(context).event.id) + ]), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('An error has occured. Please contact the admins'), - duration: Duration(seconds: 4), - ), - ); - return Center( - child: Icon( - Icons.error, - size: 200, - )); - } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'An error has occured. Please contact the admins'), + duration: Duration(seconds: 4), + ), + ); + return Center( + child: Icon( + Icons.error, + size: 200, + )); + } - List> dataTeam = snapshot.data as List>; + TeamsNotifier notifier = Provider.of(context); - List tms = dataTeam[0] as List; + List> dataTeam = + snapshot.data as List>; - tms.sort((a, b) => a.name!.compareTo(b.name!)); + List tms = dataTeam[0] as List; - return RefreshIndicator( - onRefresh: () { - return Future.delayed(Duration.zero, () { - setState(() {}); - }); - }, - child: ListView.builder( - itemCount: tms.length, - itemBuilder: (context, index) => - TeamMemberRow(team: tms[index], teams: tms), - addAutomaticKeepAlives: true, - physics: const AlwaysScrollableScrollPhysics(), - ), - ); - } else { - return Shimmer.fromColors( - baseColor: Colors.grey[400]!, - highlightColor: Colors.white, - child: ListView.builder( - itemCount: 5, - itemBuilder: (context, index) => TeamMemberRow.fake(), - addAutomaticKeepAlives: true, - physics: const BouncingScrollPhysics( - parent: AlwaysScrollableScrollPhysics()), - ), - ); - } - }, + notifier.teams = tms; + + tms.sort((a, b) => a.name!.compareTo(b.name!)); + + return RefreshIndicator( + onRefresh: () { + return Future.delayed(Duration.zero, () { + setState(() {}); + }); + }, + child: ListView.builder( + itemCount: tms.length, + itemBuilder: (context, index) => + TeamMemberRow(team: tms[index], teams: tms), + addAutomaticKeepAlives: true, + physics: const AlwaysScrollableScrollPhysics(), + ), + ); + } else { + return Shimmer.fromColors( + baseColor: Colors.grey[400]!, + highlightColor: Colors.white, + child: ListView.builder( + itemCount: 5, + itemBuilder: (context, index) => TeamMemberRow.fake(), + addAutomaticKeepAlives: true, + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics()), + ), + ); + } + }, + ), ), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, - floatingActionButton: FloatingActionButton.extended( - onPressed: showCreateTeamDialog, - label: const Text('Create New Team'), - icon: const Icon(Icons.person_add), - backgroundColor: Provider.of(context).isDark - ? Colors.grey[500] - : Colors.indigo), - ); + floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, + floatingActionButton: FloatingActionButton.extended( + onPressed: showCreateTeamDialog, + label: const Text('Create New Team'), + icon: const Icon(Icons.person_add), + backgroundColor: Provider.of(context).isDark + ? Colors.grey[500] + : Colors.indigo), + ); + }); } showCreateTeamDialog() { @@ -146,8 +154,29 @@ class _TeamTableState extends State } void createTeam(String name) async { - await _teamService.createTeam(name); - setState(() {}); + Team? t = await _teamService.createTeam(name); + if (t != null) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Done'), + duration: Duration(seconds: 2), + ), + ); + + TeamsNotifier notifier = + Provider.of(context, listen: false); + notifier.add(t); + } else { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An error occured.')), + ); + + Navigator.pop(context); + } Navigator.pop(context, "Create"); } } @@ -237,8 +266,7 @@ class TeamMemberRow extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => TeamScreen( - team: team, members: membs))); + builder: (context) => TeamScreen(team: team))); }, child: Column( children: [ From e297c23344914ddd48f5e1a91a1a81177f60f156 Mon Sep 17 00:00:00 2001 From: JoaoGomes24 Date: Wed, 9 Nov 2022 17:06:49 +0000 Subject: [PATCH 70/73] Cant add member that exist --- .../lib/routes/teams/AddTeamMemberForm.dart | 33 +++---------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/frontend/lib/routes/teams/AddTeamMemberForm.dart b/frontend/lib/routes/teams/AddTeamMemberForm.dart index 51f37418..5f60c383 100644 --- a/frontend/lib/routes/teams/AddTeamMemberForm.dart +++ b/frontend/lib/routes/teams/AddTeamMemberForm.dart @@ -37,13 +37,11 @@ class _AddTeamMemberFormState extends State { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Adding member...')), ); - //print(widget.team!.id); + // print(widget.team!.id); // print(_memberID); - //print(widget.team!.members!); - - Team? t = await service.addTeamMember( + Team? m = await service.addTeamMember( id: widget.team!.id, memberId: _memberID, role: role); - if (t != null) { + if (m != null) { ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( @@ -52,8 +50,8 @@ class _AddTeamMemberFormState extends State { duration: Duration(seconds: 2), ), ); + widget.onEditTeam!(context, m); Navigator.pop(context); - widget.onEditTeam!(context, t); } else { ScaffoldMessenger.of(context).hideCurrentSnackBar(); @@ -174,29 +172,6 @@ class _AddTeamMemberFormState extends State { } void _getMemberData(String id, String name) { - widget.team!.members!.map((memberteam) { - if (id == memberteam.memberID) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text("Member already exist"), - content: Text("Can't add again"), - actions: [ - TextButton( - child: Text("OK"), - onPressed: () { - Navigator.pop(context); - Navigator.pop(context); - }, - ), - ], - ); - }, - ); - return; - } - }).toList(); _memberID = id; _searchMembersController.text = name; disappearSearchResults = true; From 3520379409e9f52d74893e49a3335831c142612d Mon Sep 17 00:00:00 2001 From: JoaoGomes24 Date: Wed, 9 Nov 2022 18:12:38 +0000 Subject: [PATCH 71/73] =?UTF-8?q?vers=C3=A3o=20final?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/routes/teams/AddTeamMemberForm.dart | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/frontend/lib/routes/teams/AddTeamMemberForm.dart b/frontend/lib/routes/teams/AddTeamMemberForm.dart index 4609565a..62864c2e 100644 --- a/frontend/lib/routes/teams/AddTeamMemberForm.dart +++ b/frontend/lib/routes/teams/AddTeamMemberForm.dart @@ -37,11 +37,9 @@ class _AddTeamMemberFormState extends State { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Adding member...')), ); - // print(widget.team!.id); - // print(_memberID); - Team? m = await service.addTeamMember( + Team? t = await service.addTeamMember( id: widget.team!.id, memberId: _memberID, role: role); - if (m != null) { + if (t != null) { ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( @@ -50,8 +48,8 @@ class _AddTeamMemberFormState extends State { duration: Duration(seconds: 2), ), ); - widget.onEditTeam!(context, m); Navigator.pop(context); + widget.onEditTeam!(context, t); } else { ScaffoldMessenger.of(context).hideCurrentSnackBar(); @@ -118,7 +116,7 @@ class _AddTeamMemberFormState extends State { padding: const EdgeInsets.all(8.0), child: ElevatedButton( style: ElevatedButton.styleFrom( - // backgroundColor: Theme.of(context).colorScheme.secondary, + backgroundColor: Theme.of(context).colorScheme.secondary, padding: EdgeInsets.symmetric(horizontal: 50), elevation: 2, shape: RoundedRectangleBorder( @@ -172,6 +170,30 @@ class _AddTeamMemberFormState extends State { } void _getMemberData(String id, String name) { + widget.team!.members!.map((memberteam) { + if (id == memberteam.memberID) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text("Member already exist"), + content: Text("Can't add again"), + actions: [ + TextButton( + child: Text("OK"), + onPressed: () { + Navigator.pop(context); + Navigator.pop(context); + }, + ), + ], + ); + }, + ); + return; + } + }).toList(); + _memberID = id; _searchMembersController.text = name; disappearSearchResults = true; From fe35e10f27380a794b97bb1ca4dc9c7d2aef4163 Mon Sep 17 00:00:00 2001 From: bvlourenco Date: Wed, 9 Nov 2022 19:05:52 +0000 Subject: [PATCH 72/73] fix: Removing duplicate NestedScrollView --- frontend/lib/routes/teams/TeamsTable.dart | 90 ++++++++++------------- 1 file changed, 39 insertions(+), 51 deletions(-) diff --git a/frontend/lib/routes/teams/TeamsTable.dart b/frontend/lib/routes/teams/TeamsTable.dart index 86ddeca7..67f18bb7 100644 --- a/frontend/lib/routes/teams/TeamsTable.dart +++ b/frontend/lib/routes/teams/TeamsTable.dart @@ -26,9 +26,7 @@ class _TeamTableState extends State with AutomaticKeepAliveClientMixin { final TeamService _teamService = TeamService(); String filter = ""; - late List teams = []; - - late Future> futureTeams; + late List teams; late Future> members; @@ -43,58 +41,48 @@ class _TeamTableState extends State @override Widget build(BuildContext context) { super.build(context); - + return Consumer(builder: (context, notif, child) { return Scaffold( - body: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), - ), - ), - ], - body: FutureBuilder( - future: Future.wait([ - _teamService.getTeams( - event: Provider.of(context).event.id) - ]), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.hasError) { - return showError(); - } else if (snapshot.hasData) { - TeamsNotifier notifier = Provider.of(context); - - List> dataTeam = - snapshot.data as List>; - - teams = dataTeam[0] as List; - - notifier.teams = teams; - - teams.sort((a, b) => a.name!.compareTo(b.name!)); - - return showTeams(); - } else { - return Center(child: CircularProgressIndicator()); - } + body: FutureBuilder( + future: Future.wait([ + _teamService.getTeams( + event: Provider.of(context).event.id) + ]), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + return showError(); + } else if (snapshot.hasData) { + TeamsNotifier notifier = Provider.of(context); + + List> dataTeam = + snapshot.data as List>; + + teams = dataTeam[0] as List; + + notifier.teams = teams; + + teams.sort((a, b) => a.name!.compareTo(b.name!)); + + return showTeams(); } else { - return Shimmer.fromColors( - baseColor: Colors.grey[400]!, - highlightColor: Colors.white, - child: ListView.builder( - itemCount: 5, - itemBuilder: (context, index) => TeamMemberRow.fake(), - addAutomaticKeepAlives: true, - physics: const BouncingScrollPhysics( - parent: AlwaysScrollableScrollPhysics()), - ), - ); + return Center(child: CircularProgressIndicator()); } - }, - ), + } else { + return Shimmer.fromColors( + baseColor: Colors.grey[400]!, + highlightColor: Colors.white, + child: ListView.builder( + itemCount: 5, + itemBuilder: (context, index) => TeamMemberRow.fake(), + addAutomaticKeepAlives: true, + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics()), + ), + ); + } + }, ), floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, floatingActionButton: FloatingActionButton.extended( From 8ff7f77dbc7ed22d349419550099575fa44af01a Mon Sep 17 00:00:00 2001 From: Pedro Maximino Date: Wed, 9 Nov 2022 19:27:32 +0000 Subject: [PATCH 73/73] fix: Remove unecessary , --- frontend/lib/routes/teams/AddTeamMemberForm.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/lib/routes/teams/AddTeamMemberForm.dart b/frontend/lib/routes/teams/AddTeamMemberForm.dart index 62864c2e..35c5e0f3 100644 --- a/frontend/lib/routes/teams/AddTeamMemberForm.dart +++ b/frontend/lib/routes/teams/AddTeamMemberForm.dart @@ -8,7 +8,7 @@ import 'package:frontend/services/memberService.dart'; final Map roles = { "MEMBER": "Member", - "TEAMLEADER": "Team Leader", + "TEAMLEADER": "Team Leader" }; class AddTeamMemberForm extends StatefulWidget {