From 056ead7e7652b1ca8eab2b5e2aaf6313676f13c1 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sat, 9 Nov 2024 16:10:47 +0100 Subject: [PATCH 01/13] fix: no assignment title overflow by removing repeat icon --- .../widgets/assignments/assignments_widget.dart | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/frontend/lib/widgets/assignments/assignments_widget.dart b/frontend/lib/widgets/assignments/assignments_widget.dart index f2a245c..1703265 100644 --- a/frontend/lib/widgets/assignments/assignments_widget.dart +++ b/frontend/lib/widgets/assignments/assignments_widget.dart @@ -109,7 +109,6 @@ class AssignmentsWidgetState extends State { Map> groupedAssignments = {}; if (filterByTaskType == TaskType.recurring) { - // print(snapshot.data); final filteredAssignments = snapshot.data!.where( (assignment) => assignment.taskGroupTitle != null && @@ -194,18 +193,7 @@ class AssignmentsWidgetState extends State { onTap: () async { await updateAssignment(assignment); }, - title: Row( - children: [ - Text(assignment.title), - const SizedBox(width: 8), - if (!assignment.isOneOff) - Icon(Icons.repeat, - color: Theme.of(context) - .colorScheme - .onSurface, - size: 16) - ], - ), + title: Text(assignment.title), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, From 6a50e01e5207ddad65fd75b905bfef8d9059c71b Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sat, 9 Nov 2024 16:18:32 +0100 Subject: [PATCH 02/13] fix: use ListTile instead of weird Card architecture for more responsive design in TaskList --- frontend/lib/widgets/tasks/task_list.dart | 83 ++++++++++------------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/frontend/lib/widgets/tasks/task_list.dart b/frontend/lib/widgets/tasks/task_list.dart index 3315d7f..922ba7e 100644 --- a/frontend/lib/widgets/tasks/task_list.dart +++ b/frontend/lib/widgets/tasks/task_list.dart @@ -62,53 +62,42 @@ class TaskList extends StatelessWidget { ), )), child: Card( - elevation: generalElevation, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - task.title, - style: Theme.of(context).textTheme.titleMedium, - ), - Text(task.description ?? "") - ], - ), - ElevatedButton( - onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: SizedBox( - height: 350, - width: double.infinity, - child: Column( - mainAxisAlignment: - MainAxisAlignment.start, - children: [ - Text("Edit Task", - style: Theme.of(context) - .textTheme - .headlineMedium), - const SizedBox(height: 20), - EditTaskForm( - task: task, - ) - ], - )), - ); - }); - }, - child: const Icon(Icons.edit)) - ], - )), - ), + elevation: generalElevation, + child: ListTile( + title: Text(task.title), + subtitle: Text(task.description ?? ""), + trailing: ElevatedButton( + onPressed: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox( + height: 350, + width: double.infinity, + child: Column( + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Text("Edit Task", + style: Theme.of(context) + .textTheme + .headlineMedium), + const SizedBox(height: 20), + EditTaskForm( + task: task, + ) + ], + )), + ); + }); + }, + child: const Icon( + Icons.edit, + color: Colors.blueAccent, + )), + )), ), ); }, From 9a97d957ea777853012e1fded1364704176c1c90 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 13:37:30 +0100 Subject: [PATCH 03/13] feat: proper color schemes --- frontend/lib/authenticated_navigation.dart | 6 --- frontend/lib/main.dart | 36 +++++++++---- frontend/lib/unauthenticated_navigation.dart | 1 - frontend/lib/utils/date.dart | 1 + .../assignments/assignments_widget.dart | 6 +-- frontend/lib/widgets/expandable_fab.dart | 2 - frontend/lib/widgets/shopping_list.dart | 13 ++++- frontend/lib/widgets/tasks/create_task.dart | 15 ++---- .../lib/widgets/tasks/edit_task_form.dart | 4 +- frontend/lib/widgets/tasks/task_list.dart | 53 ++++++++++--------- frontend/lib/widgets/user/login_form.dart | 7 ++- frontend/lib/widgets/user/register_form.dart | 4 -- 12 files changed, 77 insertions(+), 71 deletions(-) diff --git a/frontend/lib/authenticated_navigation.dart b/frontend/lib/authenticated_navigation.dart index d76e96f..7093964 100644 --- a/frontend/lib/authenticated_navigation.dart +++ b/frontend/lib/authenticated_navigation.dart @@ -105,9 +105,6 @@ class _AuthenticatedNavigationState extends State { "Share this invite code with others so they can join your group: $inviteCode"), const SizedBox(height: 20), ElevatedButton( - style: const ButtonStyle( - foregroundColor: - WidgetStatePropertyAll(Colors.blueAccent)), onPressed: () => Share.share( "Join my Group on Flatshare by using this invite code: $inviteCodeUrl"), child: const Text("Share this invite code")) @@ -162,8 +159,6 @@ class _AuthenticatedNavigationState extends State { currentPageIndex != 2 && currentPageIndex != 0 ? FloatingActionButton( - backgroundColor: Colors.blueAccent, - foregroundColor: Colors.white, onPressed: () => Navigator.of(context).push(MaterialPageRoute( builder: (context) => const CreateTask())), child: const Icon(Icons.add), @@ -175,7 +170,6 @@ class _AuthenticatedNavigationState extends State { duration: const Duration(milliseconds: 150), curve: Curves.linear); }, - indicatorColor: Colors.blueAccent, selectedIndex: currentPageIndex, destinations: getNavigationDestinations(userGroupId), ), diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 5e4348f..62d6775 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -62,16 +62,32 @@ class _AppState extends State { theme: ThemeData( useMaterial3: true, brightness: Brightness.light, - checkboxTheme: CheckboxThemeData( - fillColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.selected)) { - return Colors.blueAccent; - } - return Colors.white; - }), - ), + colorScheme: const ColorScheme( + brightness: Brightness.light, + primary: Colors.blueAccent, + onPrimary: Colors.white, + secondary: Colors.blueAccent, + onSecondary: Colors.white, + error: Colors.red, + onError: Colors.white, + surface: Colors.white, + onSurface: Colors.black), ), - darkTheme: ThemeData(brightness: Brightness.dark), - themeMode: ThemeMode.system); + darkTheme: ThemeData( + useMaterial3: true, + brightness: Brightness.dark, + colorScheme: ColorScheme( + brightness: Brightness.dark, + primary: Colors.blueAccent, + onPrimary: Colors.white, + secondary: Colors.blueAccent, + onSecondary: Colors.white, + error: Colors.red, + onError: Colors.white, + surfaceContainer: Colors.grey[850], + surface: Colors.grey.shade900, + onSurface: Colors.white), + ), + themeMode: ThemeMode.light); } } diff --git a/frontend/lib/unauthenticated_navigation.dart b/frontend/lib/unauthenticated_navigation.dart index e08a4fb..1b073e0 100644 --- a/frontend/lib/unauthenticated_navigation.dart +++ b/frontend/lib/unauthenticated_navigation.dart @@ -24,7 +24,6 @@ class _UnauthenticatedNavigationState extends State { duration: const Duration(milliseconds: 150), curve: Curves.linear); }, - indicatorColor: Colors.blueAccent, selectedIndex: currentPageIndex, destinations: const [ NavigationDestination( diff --git a/frontend/lib/utils/date.dart b/frontend/lib/utils/date.dart index 590197c..b0786ce 100644 --- a/frontend/lib/utils/date.dart +++ b/frontend/lib/utils/date.dart @@ -1,4 +1,5 @@ String parseToDueDate(DateTime dueDate) { + print("Due date: $dueDate"); final diffInDays = dueDate.difference(DateTime.now()).inDays; if (diffInDays == 0) { return "Due today"; diff --git a/frontend/lib/widgets/assignments/assignments_widget.dart b/frontend/lib/widgets/assignments/assignments_widget.dart index 1703265..95d508e 100644 --- a/frontend/lib/widgets/assignments/assignments_widget.dart +++ b/frontend/lib/widgets/assignments/assignments_widget.dart @@ -84,7 +84,6 @@ class AssignmentsWidgetState extends State { height: 24, width: 24, child: Checkbox( - activeColor: Colors.blueAccent, value: showOnlyCurrentUserAssignments, onChanged: (newValue) { setState(() { @@ -165,9 +164,7 @@ class AssignmentsWidgetState extends State { : []), const SizedBox(width: 8), Text(sectionAssignments[0].assigneeName, - style: theme.textTheme.titleLarge! - .merge(const TextStyle( - color: Colors.blueAccent))) + style: theme.textTheme.titleLarge!) ], ), const SizedBox(height: generalSizedBoxHeight), @@ -206,7 +203,6 @@ class AssignmentsWidgetState extends State { ], ), trailing: Checkbox( - activeColor: Colors.blueAccent, onChanged: (bool? value) => updateAssignment(assignment), value: assignment.isCompleted, diff --git a/frontend/lib/widgets/expandable_fab.dart b/frontend/lib/widgets/expandable_fab.dart index 963343c..f674a13 100644 --- a/frontend/lib/widgets/expandable_fab.dart +++ b/frontend/lib/widgets/expandable_fab.dart @@ -97,7 +97,6 @@ class _ExpandableFabState extends State duration: const Duration(milliseconds: 250), child: FloatingActionButton( onPressed: _toggle, - backgroundColor: Colors.blueAccent, shape: const CircleBorder(), heroTag: "closeFAB", child: @@ -121,7 +120,6 @@ class _ExpandableFabState extends State duration: const Duration(milliseconds: 250), child: FloatingActionButton( onPressed: _toggle, - backgroundColor: Colors.blueAccent, shape: const CircleBorder(), heroTag: "openFAB", child: const Icon(Icons.add), diff --git a/frontend/lib/widgets/shopping_list.dart b/frontend/lib/widgets/shopping_list.dart index 4e6f6f2..c50111e 100644 --- a/frontend/lib/widgets/shopping_list.dart +++ b/frontend/lib/widgets/shopping_list.dart @@ -209,15 +209,24 @@ class ShoppingListWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [], ), + onTap: () { + final newState = + shoppingListItem.state == 'purchased' + ? 'pending' + : 'purchased'; + updateShoppingListItem( + shoppingListItem, newState); + }, trailing: Checkbox( onChanged: (bool? value) { final newState = - value! ? 'purchased' : 'pending'; + shoppingListItem.state == 'purchased' + ? 'pending' + : 'purchased'; updateShoppingListItem( shoppingListItem, newState); }, value: shoppingListItem.state == 'purchased', - activeColor: Colors.blueAccent, ), ))); }), diff --git a/frontend/lib/widgets/tasks/create_task.dart b/frontend/lib/widgets/tasks/create_task.dart index ddee0fa..7ef1a0e 100644 --- a/frontend/lib/widgets/tasks/create_task.dart +++ b/frontend/lib/widgets/tasks/create_task.dart @@ -137,7 +137,9 @@ class CreateTaskState extends State { } return null; }, - decoration: const InputDecoration(labelText: "Title"), + decoration: const InputDecoration( + labelText: "Title", + ), ), TextFormField( controller: descriptionController, @@ -191,21 +193,12 @@ class CreateTaskState extends State { items: const ['Daily', 'Weekly', 'Monthly'], onChanged: (value) { setState(() { - // im kinda scared that the value can be null here... why? selectedInterval = value!; }); }), - // Tooltip( - // message: 'Please note that', - // triggerMode: TooltipTriggerMode.tap, - // child: IconButton( - // onPressed: () {}, icon: const Icon(Icons.help)), - // ), const SizedBox(height: 20), ElevatedButton( - style: const ButtonStyle( - foregroundColor: - WidgetStatePropertyAll(Colors.blueAccent)), + style: const ButtonStyle(), onPressed: handleSubmit, child: const Text("Create")), ], diff --git a/frontend/lib/widgets/tasks/edit_task_form.dart b/frontend/lib/widgets/tasks/edit_task_form.dart index a214f76..284f5d4 100644 --- a/frontend/lib/widgets/tasks/edit_task_form.dart +++ b/frontend/lib/widgets/tasks/edit_task_form.dart @@ -113,7 +113,9 @@ class EditTaskFormState extends State { Navigator.pop(context); } }, - child: const Text("Update"), + child: const Text( + "Update", + ), ), ), ], diff --git a/frontend/lib/widgets/tasks/task_list.dart b/frontend/lib/widgets/tasks/task_list.dart index 922ba7e..2bc95d4 100644 --- a/frontend/lib/widgets/tasks/task_list.dart +++ b/frontend/lib/widgets/tasks/task_list.dart @@ -10,6 +10,30 @@ class TaskList extends StatelessWidget { final List tasks; + void editTask(BuildContext context, Task task) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox( + height: 350, + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text("Edit Task", + style: Theme.of(context).textTheme.headlineMedium), + const SizedBox(height: 20), + EditTaskForm( + task: task, + ) + ], + )), + ); + }); + } + @override Widget build(BuildContext context) { return ListView.builder( @@ -66,36 +90,15 @@ class TaskList extends StatelessWidget { child: ListTile( title: Text(task.title), subtitle: Text(task.description ?? ""), + onTap: () { + editTask(context, task); + }, trailing: ElevatedButton( onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: SizedBox( - height: 350, - width: double.infinity, - child: Column( - mainAxisAlignment: - MainAxisAlignment.start, - children: [ - Text("Edit Task", - style: Theme.of(context) - .textTheme - .headlineMedium), - const SizedBox(height: 20), - EditTaskForm( - task: task, - ) - ], - )), - ); - }); + editTask(context, task); }, child: const Icon( Icons.edit, - color: Colors.blueAccent, )), )), ), diff --git a/frontend/lib/widgets/user/login_form.dart b/frontend/lib/widgets/user/login_form.dart index ff3230a..24aba8e 100644 --- a/frontend/lib/widgets/user/login_form.dart +++ b/frontend/lib/widgets/user/login_form.dart @@ -83,6 +83,9 @@ class LoginFormState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ TextFormField( + textCapitalization: TextCapitalization.none, + autocorrect: false, + keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( icon: Icon(Icons.person), labelText: 'Email', @@ -117,10 +120,6 @@ class LoginFormState extends State { width: double.infinity, child: FilledButton( style: ButtonStyle( - backgroundColor: - const WidgetStatePropertyAll(Colors.blueAccent), - foregroundColor: - const WidgetStatePropertyAll(Colors.white), shape: WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)))), diff --git a/frontend/lib/widgets/user/register_form.dart b/frontend/lib/widgets/user/register_form.dart index dafe5ac..1410f03 100644 --- a/frontend/lib/widgets/user/register_form.dart +++ b/frontend/lib/widgets/user/register_form.dart @@ -100,10 +100,6 @@ class RegisterFormState extends State { width: double.infinity, child: FilledButton( style: ButtonStyle( - backgroundColor: - const WidgetStatePropertyAll(Colors.blueAccent), - foregroundColor: - const WidgetStatePropertyAll(Colors.white), shape: WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)))), From 12c8c9ee5109753432cc2f792c958cd77d1a2fb8 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 13:40:24 +0100 Subject: [PATCH 04/13] fix: assignee name color --- frontend/lib/utils/date.dart | 1 - frontend/lib/widgets/assignments/assignments_widget.dart | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/lib/utils/date.dart b/frontend/lib/utils/date.dart index b0786ce..590197c 100644 --- a/frontend/lib/utils/date.dart +++ b/frontend/lib/utils/date.dart @@ -1,5 +1,4 @@ String parseToDueDate(DateTime dueDate) { - print("Due date: $dueDate"); final diffInDays = dueDate.difference(DateTime.now()).inDays; if (diffInDays == 0) { return "Due today"; diff --git a/frontend/lib/widgets/assignments/assignments_widget.dart b/frontend/lib/widgets/assignments/assignments_widget.dart index 95d508e..888a03e 100644 --- a/frontend/lib/widgets/assignments/assignments_widget.dart +++ b/frontend/lib/widgets/assignments/assignments_widget.dart @@ -164,7 +164,9 @@ class AssignmentsWidgetState extends State { : []), const SizedBox(width: 8), Text(sectionAssignments[0].assigneeName, - style: theme.textTheme.titleLarge!) + style: theme.textTheme.titleLarge! + .merge(const TextStyle( + color: Colors.blueAccent))) ], ), const SizedBox(height: generalSizedBoxHeight), From c0f16f379a52975de008735d894efd8ec5f53497 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 13:56:47 +0100 Subject: [PATCH 05/13] feat: dont show due date for assignment in same task group --- .../assignments/assignments_widget.dart | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/lib/widgets/assignments/assignments_widget.dart b/frontend/lib/widgets/assignments/assignments_widget.dart index 888a03e..a63ef78 100644 --- a/frontend/lib/widgets/assignments/assignments_widget.dart +++ b/frontend/lib/widgets/assignments/assignments_widget.dart @@ -169,7 +169,17 @@ class AssignmentsWidgetState extends State { color: Colors.blueAccent))) ], ), - const SizedBox(height: generalSizedBoxHeight), + const SizedBox( + height: generalSizedBoxHeight / 4), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 0, horizontal: 4), + // for now, we can just pick the first assginment because they are gouped together and all have the same due date. + child: Text(parseToDueDate( + sectionAssignments[0].dueDate!)), + ), + const SizedBox( + height: generalSizedBoxHeight / 2), ListView.builder( shrinkWrap: true, physics: const ClampingScrollPhysics(), @@ -193,17 +203,7 @@ class AssignmentsWidgetState extends State { await updateAssignment(assignment); }, title: Text(assignment.title), - subtitle: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - if (description != null) - Text(description), - if (assignment.dueDate != null) - Text(parseToDueDate( - assignment.dueDate!)), - ], - ), + subtitle: Text(description ?? ""), trailing: Checkbox( onChanged: (bool? value) => updateAssignment(assignment), From 07cd18290089b70fee2de2c675dce7b8c2fece46 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 14:38:29 +0100 Subject: [PATCH 06/13] feat: colored container behind assignments --- .../assignments/assignments_widget.dart | 162 +++++++++--------- 1 file changed, 85 insertions(+), 77 deletions(-) diff --git a/frontend/lib/widgets/assignments/assignments_widget.dart b/frontend/lib/widgets/assignments/assignments_widget.dart index a63ef78..373f9ab 100644 --- a/frontend/lib/widgets/assignments/assignments_widget.dart +++ b/frontend/lib/widgets/assignments/assignments_widget.dart @@ -133,89 +133,97 @@ class AssignmentsWidgetState extends State { final sectionTitle = section.key; final sectionAssignments = section.value; - // TODO: i dont like this. final isRecurringAssignments = sectionAssignments.any( (assignment) => assignment.taskGroupTitle != null); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Padding( - padding: EdgeInsets.symmetric( - horizontal: - isRecurringAssignments ? 4 : 0, - vertical: 0), - child: Text( - isRecurringAssignments - ? sectionTitle - : "", - style: theme.textTheme.titleLarge)), - Row( - children: isRecurringAssignments - ? const [ - SizedBox(width: 8), - Icon( - Icons.arrow_right_alt, - ) - ] - : []), - const SizedBox(width: 8), - Text(sectionAssignments[0].assigneeName, - style: theme.textTheme.titleLarge! - .merge(const TextStyle( - color: Colors.blueAccent))) - ], - ), - const SizedBox( - height: generalSizedBoxHeight / 4), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 0, horizontal: 4), - // for now, we can just pick the first assginment because they are gouped together and all have the same due date. - child: Text(parseToDueDate( - sectionAssignments[0].dueDate!)), - ), - const SizedBox( - height: generalSizedBoxHeight / 2), - ListView.builder( - shrinkWrap: true, - physics: const ClampingScrollPhysics(), - itemCount: sectionAssignments.length, - itemBuilder: - (BuildContext context, int index) { - final assignment = - sectionAssignments[index]; - final description = - assignment.description; + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 0, vertical: 4), + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: + const BorderRadius.all(Radius.circular(8))), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Padding( + padding: EdgeInsets.symmetric( + horizontal: isRecurringAssignments + ? 4 + : 0, + vertical: 0), + child: Text( + isRecurringAssignments + ? sectionTitle + : "", + style: + theme.textTheme.titleLarge)), + Row( + children: isRecurringAssignments + ? const [ + SizedBox(width: 8), + Icon( + Icons.arrow_right_alt, + ) + ] + : []), + const SizedBox(width: 8), + Text(sectionAssignments[0].assigneeName, + style: theme.textTheme.titleLarge! + .merge(const TextStyle( + color: Colors.blueAccent))) + ], + ), + const SizedBox( + height: generalSizedBoxHeight / 4), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 0, horizontal: 4), + // for now, we can just pick the first assginment because they are gouped together and all have the same due date. + child: Text(parseToDueDate( + sectionAssignments[0].dueDate!)), + ), + const SizedBox( + height: generalSizedBoxHeight / 2), + ListView.builder( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + itemCount: sectionAssignments.length, + itemBuilder: + (BuildContext context, int index) { + final assignment = + sectionAssignments[index]; + final description = + assignment.description; - return Card( - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(13), - ), - elevation: generalElevation, - shadowColor: Colors.black, - child: ListTile( - onTap: () async { - await updateAssignment(assignment); - }, - title: Text(assignment.title), - subtitle: Text(description ?? ""), - trailing: Checkbox( - onChanged: (bool? value) => - updateAssignment(assignment), - value: assignment.isCompleted, + return Card( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(13), ), - ), - ); - }), - const SizedBox( - height: generalSizedBoxHeight, - ) - ]); + elevation: generalElevation, + shadowColor: Colors.black, + child: ListTile( + onTap: () async { + await updateAssignment( + assignment); + }, + title: Text(assignment.title), + subtitle: Text(description ?? ""), + trailing: Checkbox( + onChanged: (bool? value) => + updateAssignment(assignment), + value: assignment.isCompleted, + ), + ), + ); + }), + ]), + ); }, ); } else if (snapshot.hasError) { From f458618c568a6cdff0032babf11eb4720fbf88a5 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 17:53:05 +0100 Subject: [PATCH 07/13] fix: no non-null assertion --- .../lib/widgets/assignments/assignments_widget.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/lib/widgets/assignments/assignments_widget.dart b/frontend/lib/widgets/assignments/assignments_widget.dart index 373f9ab..0a9a572 100644 --- a/frontend/lib/widgets/assignments/assignments_widget.dart +++ b/frontend/lib/widgets/assignments/assignments_widget.dart @@ -133,6 +133,14 @@ class AssignmentsWidgetState extends State { final sectionTitle = section.key; final sectionAssignments = section.value; + // for now, we can just pick the first assginment because they are gouped together and all have the same due date. + final firstAssignmentDueDate = + sectionAssignments[0].dueDate; + final maybeParsedDueDate = + firstAssignmentDueDate != null + ? parseToDueDate(firstAssignmentDueDate) + : ""; + final isRecurringAssignments = sectionAssignments.any( (assignment) => assignment.taskGroupTitle != null); @@ -183,9 +191,7 @@ class AssignmentsWidgetState extends State { Padding( padding: const EdgeInsets.symmetric( vertical: 0, horizontal: 4), - // for now, we can just pick the first assginment because they are gouped together and all have the same due date. - child: Text(parseToDueDate( - sectionAssignments[0].dueDate!)), + child: Text(maybeParsedDueDate), ), const SizedBox( height: generalSizedBoxHeight / 2), From b23704664fda4e5ba0e41bf52fdf61bf075c98aa Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 18:00:44 +0100 Subject: [PATCH 08/13] fix: colored container behind assignments for dark mode --- frontend/lib/main.dart | 2 +- frontend/lib/widgets/assignments/assignments_widget.dart | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 62d6775..45afab7 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -88,6 +88,6 @@ class _AppState extends State { surface: Colors.grey.shade900, onSurface: Colors.white), ), - themeMode: ThemeMode.light); + themeMode: ThemeMode.system); } } diff --git a/frontend/lib/widgets/assignments/assignments_widget.dart b/frontend/lib/widgets/assignments/assignments_widget.dart index 0a9a572..c6bb398 100644 --- a/frontend/lib/widgets/assignments/assignments_widget.dart +++ b/frontend/lib/widgets/assignments/assignments_widget.dart @@ -150,7 +150,9 @@ class AssignmentsWidgetState extends State { horizontal: 0, vertical: 4), padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.grey.shade100, + color: theme.brightness == Brightness.light + ? Colors.grey.shade100 + : Colors.black26, borderRadius: const BorderRadius.all(Radius.circular(8))), child: Column( From 4c10c787c666cf21cb87500f731df26a9ac50872 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 21:04:47 +0100 Subject: [PATCH 09/13] feat: sort and group and filter assignments --- .../assignments/assignments_widget.dart | 47 ++++++------ frontend/lib/widgets/assignments/utils.dart | 73 +++++++++++++++++++ frontend/lib/widgets/shopping_list.dart | 19 +++-- 3 files changed, 113 insertions(+), 26 deletions(-) create mode 100644 frontend/lib/widgets/assignments/utils.dart diff --git a/frontend/lib/widgets/assignments/assignments_widget.dart b/frontend/lib/widgets/assignments/assignments_widget.dart index c6bb398..09b5176 100644 --- a/frontend/lib/widgets/assignments/assignments_widget.dart +++ b/frontend/lib/widgets/assignments/assignments_widget.dart @@ -5,10 +5,10 @@ import 'package:flatshare/models/task.dart'; import 'package:flatshare/models/user_group.dart'; import 'package:flatshare/providers/user.dart'; import 'package:flatshare/utils/date.dart'; +import 'package:flatshare/widgets/assignments/utils.dart'; import 'package:flatshare/widgets/task_type_switch.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import "package:collection/collection.dart"; class AssignmentsWidget extends StatefulWidget { const AssignmentsWidget({super.key}); @@ -105,33 +105,38 @@ class AssignmentsWidgetState extends State { "Currently, there are no assignments. To get started, use the + Action Button on the bottom right."), ); } - Map> groupedAssignments = {}; + List> sortedAssignmentGroups = []; if (filterByTaskType == TaskType.recurring) { - final filteredAssignments = snapshot.data!.where( - (assignment) => - assignment.taskGroupTitle != null && - (!showOnlyCurrentUserAssignments || - assignment.assigneeId == currentUserId)); - groupedAssignments = groupBy(filteredAssignments, - (assignment) => assignment.taskGroupTitle!); + final recurringAssignments = + snapshot.data!.where((assignment) { + return assignment.taskGroupTitle != null; + }).toList(); + sortedAssignmentGroups = sortRecurringAssignmentGroups( + groupRecurringAssignments(recurringAssignments)); } else if (filterByTaskType == TaskType.oneOff) { - final filteredAssignments = snapshot.data!.where( - (assignment) => - assignment.taskGroupTitle == null && - (!showOnlyCurrentUserAssignments || - assignment.assigneeId == currentUserId)); - groupedAssignments = groupBy(filteredAssignments, - (assignment) => assignment.assigneeName); + final oneOffAssignments = + snapshot.data!.where((assignment) { + return assignment.taskGroupTitle == null; + }).toList(); + // we dont have due dates yet for one off tasks, so only grouping here + sortedAssignmentGroups = + groupOneOffAssignments(oneOffAssignments); } + final filteredAssignments = filterGroupedAssignments( + sortedAssignmentGroups, + showOnlyCurrentUserAssignments, + currentUserId); + return ListView.builder( - itemCount: groupedAssignments.length, + itemCount: filteredAssignments.length, itemBuilder: (BuildContext context, int index) { - final section = - groupedAssignments.entries.elementAt(index); - final sectionTitle = section.key; - final sectionAssignments = section.value; + final sectionAssignments = filteredAssignments[index]; + final sectionTitle = + sectionAssignments[0].taskGroupTitle == null + ? sectionAssignments[0].assigneeName + : sectionAssignments[0].taskGroupTitle!; // for now, we can just pick the first assginment because they are gouped together and all have the same due date. final firstAssignmentDueDate = diff --git a/frontend/lib/widgets/assignments/utils.dart b/frontend/lib/widgets/assignments/utils.dart new file mode 100644 index 0000000..0ab44d5 --- /dev/null +++ b/frontend/lib/widgets/assignments/utils.dart @@ -0,0 +1,73 @@ +import 'package:flatshare/models/assignment.dart'; + +// we should make these generic once we reach a third duplication here + +List> groupRecurringAssignments( + List recurringAssignments) { + List> sortedAndGroupedAssignments = []; + for (final assignment in recurringAssignments) { + final maybeIndex = + sortedAndGroupedAssignments.indexWhere((assignmentGroup) { + final firstAssignmentInGroup = assignmentGroup[0]; + final taskGroupTitle = firstAssignmentInGroup.taskGroupTitle; + return taskGroupTitle == assignment.taskGroupTitle; + }); + if (maybeIndex == -1) { + sortedAndGroupedAssignments.add([assignment]); + } else { + sortedAndGroupedAssignments[maybeIndex].add(assignment); + } + } + + return sortedAndGroupedAssignments; +} + +List> sortRecurringAssignmentGroups( + List> assignmentGroups) { + assignmentGroups.sort((a, b) { + final firstAssignmentA = a[0]; + final firstAssignmentB = b[0]; + return firstAssignmentA.dueDate!.millisecondsSinceEpoch + .compareTo(firstAssignmentB.dueDate!.millisecondsSinceEpoch); + }); + return assignmentGroups; +} + +List> groupOneOffAssignments( + List oneOffAssignments) { + List> sortedAndGroupedAssignments = []; + for (final assignment in oneOffAssignments) { + final maybeIndex = + sortedAndGroupedAssignments.indexWhere((assignmentGroup) { + final firstAssignmentInGroup = assignmentGroup[0]; + final taskGroupTitle = firstAssignmentInGroup.assigneeName; + return taskGroupTitle == assignment.assigneeName; + }); + if (maybeIndex == -1) { + sortedAndGroupedAssignments.add([assignment]); + } else { + sortedAndGroupedAssignments[maybeIndex].add(assignment); + } + } + + return sortedAndGroupedAssignments; +} + +List> filterGroupedAssignments( + List> groupedAssignments, + bool showOnlyCurrentUserAssignments, + int? currentUserId) { + List> finalFilteredAssignments = []; + for (final assignmentGroup in groupedAssignments) { + final filteredAssignments = assignmentGroup + .where((assignment) => + (!showOnlyCurrentUserAssignments) || + assignment.assigneeId == currentUserId) + .toList(); + if (filteredAssignments.isEmpty) { + continue; + } + finalFilteredAssignments.add(filteredAssignments); + } + return finalFilteredAssignments; +} diff --git a/frontend/lib/widgets/shopping_list.dart b/frontend/lib/widgets/shopping_list.dart index c50111e..07b8475 100644 --- a/frontend/lib/widgets/shopping_list.dart +++ b/frontend/lib/widgets/shopping_list.dart @@ -22,6 +22,7 @@ class ShoppingListWidget extends StatefulWidget { class ShoppingListWidgetState extends State { final controller = TextEditingController(); + final formKey = GlobalKey(); late socket_io.Socket socket; var isConnected = false; @@ -165,11 +166,18 @@ class ShoppingListWidgetState extends State { children: getConnectionStatusRowWidgets(), ), Form( + key: formKey, child: TextFormField( - controller: controller, - decoration: const InputDecoration( - labelText: "New Shopping List Item"), - )), + controller: controller, + validator: (value) { + if (value == null || value.isEmpty) { + return "Please enter a title"; + } + return null; + }, + decoration: const InputDecoration( + labelText: "New Shopping List Item"), + )), const SizedBox(height: generalSizedBoxHeight), ElevatedButton( onPressed: sendNewShoppingListItem, @@ -236,9 +244,10 @@ class ShoppingListWidgetState extends State { } void sendNewShoppingListItem() { - if (controller.text.isEmpty) { + if (!formKey.currentState!.validate()) { return; } + var userProvider = Provider.of(context, listen: false); var userGroupId = userProvider.userGroup?.id; if (userGroupId == null) { From a434ec0c8fe8c96b62a0ad9fc9cbdb2794378d85 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 21:19:30 +0100 Subject: [PATCH 10/13] fix: dropdown header color edit task form --- frontend/lib/widgets/tasks/create_task.dart | 3 +- .../lib/widgets/tasks/edit_task_form.dart | 28 +++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/frontend/lib/widgets/tasks/create_task.dart b/frontend/lib/widgets/tasks/create_task.dart index 7ef1a0e..2485af8 100644 --- a/frontend/lib/widgets/tasks/create_task.dart +++ b/frontend/lib/widgets/tasks/create_task.dart @@ -53,7 +53,7 @@ class CreateTaskState extends State { foregroundColor: WidgetStatePropertyAll(taskTypeOfButton == selectedTaskType // TODO: Fix nested ternary, you baaaaad -> this can be fixed by finally fixing theme colors in the app - ? Colors.blue + ? Colors.blueAccent : MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.white : Colors.black), @@ -187,6 +187,7 @@ class CreateTaskState extends State { onListChanged: (value) {}) : CustomDropdown( decoration: const CustomDropdownDecoration( + headerStyle: TextStyle(color: Colors.black), listItemStyle: TextStyle(color: Colors.black), hintStyle: TextStyle(color: Colors.black)), hintText: "Select interval", diff --git a/frontend/lib/widgets/tasks/edit_task_form.dart b/frontend/lib/widgets/tasks/edit_task_form.dart index 284f5d4..4b04033 100644 --- a/frontend/lib/widgets/tasks/edit_task_form.dart +++ b/frontend/lib/widgets/tasks/edit_task_form.dart @@ -85,19 +85,20 @@ class EditTaskFormState extends State { const InputDecoration(labelText: "Description (optional)"), ), const SizedBox(height: 20), + // TODO: ensure this still actually works // I don't think you should have to do this, but without it the dropdown always displays the initial value as selected - if (selectTaskGroupController != null) - CustomDropdown.search( - controller: selectTaskGroupController, - decoration: const CustomDropdownDecoration( - listItemStyle: TextStyle(color: Colors.black), - hintStyle: TextStyle(color: Colors.black), - ), - hintText: "Select task group", - items: taskGroups, - onChanged: (value) {}, - ), - const SizedBox(height: 20), + // if (selectTaskGroupController != null) + // CustomDropdown.search( + // controller: selectTaskGroupController, + // decoration: const CustomDropdownDecoration( + // listItemStyle: TextStyle(color: Colors.black), + // hintStyle: TextStyle(color: Colors.black), + // ), + // hintText: "Select task group", + // items: taskGroups, + // onChanged: (value) {}, + // ), + // const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( @@ -107,8 +108,7 @@ class EditTaskFormState extends State { id: task.id, title: titleController.text, description: descriptionController.text, - recurringTaskGroupId: - selectTaskGroupController?.value?.id); + recurringTaskGroupId: task.recurringTaskGroupId); await onUpdateTask(updatedTask); Navigator.pop(context); } From df66ef3418c175e1fafa3357ae959dc0e767b235 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 21:31:05 +0100 Subject: [PATCH 11/13] fix: slightly increase padding for assignment container --- frontend/lib/widgets/assignments/assignments_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/lib/widgets/assignments/assignments_widget.dart b/frontend/lib/widgets/assignments/assignments_widget.dart index 09b5176..f98ae68 100644 --- a/frontend/lib/widgets/assignments/assignments_widget.dart +++ b/frontend/lib/widgets/assignments/assignments_widget.dart @@ -153,7 +153,7 @@ class AssignmentsWidgetState extends State { return Container( margin: const EdgeInsets.symmetric( horizontal: 0, vertical: 4), - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: theme.brightness == Brightness.light ? Colors.grey.shade100 From 1c611cc1eb35e6f51d868cd5450260a5b853a467 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 21:38:48 +0100 Subject: [PATCH 12/13] fix: keyboard overlaps when editing tasks --- frontend/lib/widgets/tasks/task_list.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/lib/widgets/tasks/task_list.dart b/frontend/lib/widgets/tasks/task_list.dart index 2bc95d4..fbd860b 100644 --- a/frontend/lib/widgets/tasks/task_list.dart +++ b/frontend/lib/widgets/tasks/task_list.dart @@ -13,9 +13,14 @@ class TaskList extends StatelessWidget { void editTask(BuildContext context, Task task) { showModalBottomSheet( context: context, + isScrollControlled: true, builder: (BuildContext context) { return Padding( - padding: const EdgeInsets.all(16.0), + padding: EdgeInsets.only( + top: 16, + left: 16, + right: 16, + bottom: MediaQuery.of(context).viewInsets.bottom), child: SizedBox( height: 350, width: double.infinity, From 39fcee2877e79deac3b7c086f193bd8ad7215d33 Mon Sep 17 00:00:00 2001 From: Jakob Stechow Date: Sun, 10 Nov 2024 21:41:21 +0100 Subject: [PATCH 13/13] fix: padding for share invite code modal sheet --- frontend/lib/authenticated_navigation.dart | 33 ++++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/frontend/lib/authenticated_navigation.dart b/frontend/lib/authenticated_navigation.dart index 7093964..7346c6c 100644 --- a/frontend/lib/authenticated_navigation.dart +++ b/frontend/lib/authenticated_navigation.dart @@ -95,21 +95,24 @@ class _AuthenticatedNavigationState extends State { showModalBottomSheet( context: context, builder: (BuildContext context) { - return SizedBox( - height: 200, - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Share this invite code with others so they can join your group: $inviteCode"), - const SizedBox(height: 20), - ElevatedButton( - onPressed: () => Share.share( - "Join my Group on Flatshare by using this invite code: $inviteCodeUrl"), - child: const Text("Share this invite code")) - ], - )); + return Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox( + height: 200, + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Share this invite code with others so they can join your group: $inviteCode"), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () => Share.share( + "Join my Group on Flatshare by using this invite code: $inviteCodeUrl"), + child: const Text("Share this invite code")) + ], + )), + ); }); }