From ff2a227436921a8f21ef1f5549adca1a18b0da88 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:44:01 +0000 Subject: [PATCH 01/12] Initial version of the uc classes page redesign. Implemented horizontal scrolling through all the classes. --- .../widgets/course_unit_classes.dart | 82 ++++++++++++------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index 5c5822c06..1510e697d 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -5,43 +5,67 @@ import 'package:uni/model/providers/startup/session_provider.dart'; import 'package:uni/view/course_unit_info/widgets/course_unit_info_card.dart'; import 'package:uni/view/course_unit_info/widgets/course_unit_student_row.dart'; -class CourseUnitClassesView extends StatelessWidget { +class CourseUnitClassesView extends StatefulWidget { const CourseUnitClassesView(this.classes, {super.key}); final List classes; + @override + _CourseUnitClassesViewState createState() => _CourseUnitClassesViewState(); +} + +class _CourseUnitClassesViewState extends State { + int selectedIndex = 0; + @override Widget build(BuildContext context) { final session = context.read().state!; - final cards = []; - for (final courseUnitClass in classes) { - final isMyClass = courseUnitClass.students - .where( - (student) => - student.number == - (int.tryParse( - session.username.replaceAll(RegExp(r'\D'), ''), - ) ?? - 0), - ) - .isNotEmpty; - cards.add( - CourseUnitInfoCard( - isMyClass - ? '${courseUnitClass.className} *' - : courseUnitClass.className, - Column( - children: courseUnitClass.students - .map((student) => CourseUnitStudentRow(student, session)) - .toList(), - ), - ), - ); - } + final studentNumber = + int.tryParse(session.username.replaceAll(RegExp(r'\D'), '')) ?? 0; - return Container( - padding: const EdgeInsets.only(left: 10, right: 10), - child: ListView(children: cards), + return SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: 50, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: widget.classes.length, + itemBuilder: (context, index) { + final courseUnitClass = widget.classes[index]; + final isMyClass = courseUnitClass.students + .any((student) => student.number == studentNumber); + final isSelected = index == selectedIndex; + return GestureDetector( + onTap: () { + setState(() { + selectedIndex = index; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10), + alignment: Alignment.center, + child: Text( + isMyClass + ? '${courseUnitClass.className} *' + : courseUnitClass.className, + style: TextStyle( + fontWeight: + isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + ), + ); + }, + ), + ), + Column( + children: widget.classes[selectedIndex].students + .map((student) => CourseUnitStudentRow(student, session)) + .toList(), + ), + ], + ), ); } } From 410ac057a62a0b1e81b84dff965c3e7eec6b331c Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Wed, 22 Jan 2025 19:38:36 +0000 Subject: [PATCH 02/12] Class selection done. --- .../widgets/course_unit_classes.dart | 113 +++++++++++------- 1 file changed, 71 insertions(+), 42 deletions(-) diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index 1510e697d..af2ea475a 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uni/model/entities/course_units/course_unit_class.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; -import 'package:uni/view/course_unit_info/widgets/course_unit_info_card.dart'; import 'package:uni/view/course_unit_info/widgets/course_unit_student_row.dart'; +import 'package:uni_ui/theme.dart'; class CourseUnitClassesView extends StatefulWidget { const CourseUnitClassesView(this.classes, {super.key}); @@ -19,53 +19,82 @@ class _CourseUnitClassesViewState extends State { @override Widget build(BuildContext context) { - final session = context.read().state!; - final studentNumber = - int.tryParse(session.username.replaceAll(RegExp(r'\D'), '')) ?? 0; + final sessionProvider = context.read(); + final studentNumber = int.tryParse( + sessionProvider.state!.username.replaceAll(RegExp(r'\D'), ''), + ) ?? + 0; return SingleChildScrollView( child: Column( children: [ - SizedBox( - height: 50, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: widget.classes.length, - itemBuilder: (context, index) { - final courseUnitClass = widget.classes[index]; - final isMyClass = courseUnitClass.students - .any((student) => student.number == studentNumber); - final isSelected = index == selectedIndex; - return GestureDetector( - onTap: () { - setState(() { - selectedIndex = index; - }); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10), - alignment: Alignment.center, - child: Text( - isMyClass - ? '${courseUnitClass.className} *' - : courseUnitClass.className, - style: TextStyle( - fontWeight: - isSelected ? FontWeight.bold : FontWeight.normal, - ), - ), - ), - ); - }, - ), - ), - Column( - children: widget.classes[selectedIndex].students - .map((student) => CourseUnitStudentRow(student, session)) - .toList(), - ), + _buildClassSelector(studentNumber), + _buildStudentList(sessionProvider), ], ), ); } + + Widget _buildClassSelector(int studentNumber) { + return Padding( + padding: const EdgeInsets.only(bottom: 20), + child: SizedBox( + height: 55, // Adjust height to fit your design. + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: widget.classes.length, + itemBuilder: (context, index) { + final courseUnitClass = widget.classes[index]; + final isMyClass = courseUnitClass.students + .any((student) => student.number == studentNumber); + final isSelected = index == selectedIndex; + + return GestureDetector( + onTap: () => setState(() { + selectedIndex = index; + }), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + decoration: BoxDecoration( + color: isSelected + ? lightTheme.colorScheme.primary + : lightTheme.colorScheme.secondary, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 2), + ), + ], + ), + padding: + const EdgeInsets.symmetric(horizontal: 40, vertical: 10), + alignment: Alignment.center, + child: Text( + isMyClass + ? '${courseUnitClass.className} *' + : courseUnitClass.className, + style: isSelected + ? lightTheme.textTheme.labelMedium?.copyWith( + color: lightTheme.colorScheme.onPrimary, + ) + : lightTheme.textTheme.labelMedium, + ), + ), + ); + }, + ), + ), + ); + } + + Widget _buildStudentList(SessionProvider session) { + return Column( + children: widget.classes[selectedIndex].students + .map((student) => CourseUnitStudentRow(student, session.state!)) + .toList(), + ); + } } From 5bfc1deab615166a29a5514111ecb47cf88d12c5 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Wed, 22 Jan 2025 22:50:50 +0000 Subject: [PATCH 03/12] First version of the students listing. Needs refining. --- .../widgets/course_unit_classes.dart | 22 ++++-- .../widgets/course_unit_student_row.dart | 67 +++++++++---------- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index af2ea475a..40ba32e29 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -91,10 +91,24 @@ class _CourseUnitClassesViewState extends State { } Widget _buildStudentList(SessionProvider session) { - return Column( - children: widget.classes[selectedIndex].students - .map((student) => CourseUnitStudentRow(student, session.state!)) - .toList(), + return Padding( + padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), // Adjust padding as needed + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 0.8, + ), + itemCount: widget.classes[selectedIndex].students.length, + itemBuilder: (context, index) { + final student = widget.classes[selectedIndex].students[index]; + return CourseUnitStudentRow(student, session.state!); + }, + ), ); } + } diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_row.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_row.dart index c50a2f297..93ac2ce03 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_row.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_row.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:uni/model/entities/course_units/course_unit_class.dart'; import 'package:uni/model/providers/startup/profile_provider.dart'; import 'package:uni/session/flows/base/session.dart'; +import 'package:uni_ui/theme.dart'; class CourseUnitStudentRow extends StatelessWidget { const CourseUnitStudentRow(this.student, this.session, {super.key}); @@ -17,48 +18,42 @@ class CourseUnitStudentRow extends StatelessWidget { ); return FutureBuilder( builder: (context, snapshot) { - return Container( - padding: const EdgeInsets.only(bottom: 10), - child: Row( - children: [ - Container( - width: 50, - height: 50, - decoration: BoxDecoration( - shape: BoxShape.circle, - image: DecorationImage( - fit: BoxFit.cover, - image: snapshot.hasData && snapshot.data!.lengthSync() > 0 - ? FileImage(snapshot.data!) as ImageProvider - : const AssetImage( - 'assets/images/profile_placeholder.png', - ), + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + image: DecorationImage( + fit: BoxFit.cover, + image: snapshot.hasData && snapshot.data!.lengthSync() > 0 + ? FileImage(snapshot.data!) as ImageProvider + : const AssetImage( + 'assets/images/profile_placeholder.png', ), ), ), - Expanded( - child: Container( - padding: const EdgeInsets.only(left: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - student.name, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyLarge, - ), - Opacity( - opacity: 0.8, - child: Text( - 'up${student.number}', - ), - ), - ], + child: AspectRatio( + aspectRatio: 1, // Ensures square shape + child: Container(), // Empty child to enforce aspect ratio + ), + ), + const SizedBox(height: 8), + Flexible( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), // Add padding for better alignment + child: Text( + '${student.name.split(RegExp(r'\s+')).first} ${student.name.split(RegExp(r'\s+')).last}', + overflow: TextOverflow.ellipsis, + maxLines: 2, // Allow up to two lines for the name + style: lightTheme.textTheme.headlineSmall?.copyWith( + color: grayText, ), + textAlign: TextAlign.center, ), ), - ], - ), + ), + ], ); }, future: userImage, From bc7d39b420d94a2237e150ecb8be1a0c81d556a8 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Thu, 23 Jan 2025 17:50:16 +0000 Subject: [PATCH 04/12] Refactor student list display and improve UI layout in course unit classes --- .../widgets/course_unit_classes.dart | 23 +++++--- .../widgets/course_unit_student_row.dart | 56 +++++++++++++------ 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index 40ba32e29..48e802d1d 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -39,7 +39,7 @@ class _CourseUnitClassesViewState extends State { return Padding( padding: const EdgeInsets.only(bottom: 20), child: SizedBox( - height: 55, // Adjust height to fit your design. + height: 55, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: widget.classes.length, @@ -91,24 +91,29 @@ class _CourseUnitClassesViewState extends State { } Widget _buildStudentList(SessionProvider session) { + final currentClass = widget.classes[selectedIndex]; return Padding( - padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), // Adjust padding as needed + padding: const EdgeInsets.only(left: 16, right: 16, bottom: 8), child: GridView.builder( + key: ValueKey(currentClass.className), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, - crossAxisSpacing: 10, - mainAxisSpacing: 10, - childAspectRatio: 0.8, + crossAxisSpacing: 20, + mainAxisSpacing: 5, + childAspectRatio: 0.60, ), - itemCount: widget.classes[selectedIndex].students.length, + itemCount: currentClass.students.length, itemBuilder: (context, index) { - final student = widget.classes[selectedIndex].students[index]; - return CourseUnitStudentRow(student, session.state!); + final student = currentClass.students[index]; + return CourseUnitStudentRow( + student, + session.state!, + key: ValueKey('${currentClass.className}_${student.number}'), + ); }, ), ); } - } diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_row.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_row.dart index 93ac2ce03..5ed753c5e 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_row.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_row.dart @@ -18,40 +18,60 @@ class CourseUnitStudentRow extends StatelessWidget { ); return FutureBuilder( builder: (context, snapshot) { + final names = student.name.split(RegExp(r'\s+')); + final firstName = names.first; + final lastName = names.last; + return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), + decoration: ShapeDecoration( + shape: const ContinuousRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(50)), + ), image: DecorationImage( fit: BoxFit.cover, image: snapshot.hasData && snapshot.data!.lengthSync() > 0 ? FileImage(snapshot.data!) as ImageProvider : const AssetImage( - 'assets/images/profile_placeholder.png', - ), + 'assets/images/profile_placeholder.png', + ), ), ), child: AspectRatio( - aspectRatio: 1, // Ensures square shape - child: Container(), // Empty child to enforce aspect ratio + aspectRatio: 1, + child: Container(), ), ), const SizedBox(height: 8), - Flexible( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), // Add padding for better alignment - child: Text( - '${student.name.split(RegExp(r'\s+')).first} ${student.name.split(RegExp(r'\s+')).last}', - overflow: TextOverflow.ellipsis, - maxLines: 2, // Allow up to two lines for the name - style: lightTheme.textTheme.headlineSmall?.copyWith( - color: grayText, + LayoutBuilder( + builder: (context, constraints) { + return Container( + width: constraints.maxWidth, + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Column( + children: [ + Text( + firstName, + overflow: TextOverflow.fade, + style: lightTheme.textTheme.titleLarge?.copyWith( + color: grayText, + ), + textAlign: TextAlign.center, + ), + Text( + lastName, + overflow: TextOverflow.ellipsis, + style: lightTheme.textTheme.titleLarge?.copyWith( + color: grayText, + ), + textAlign: TextAlign.center, + ), + ], ), - textAlign: TextAlign.center, - ), - ), + ); + }, ), ], ); From 30661ceea365f9a90957443f6ca5c950509f38a3 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Thu, 23 Jan 2025 17:52:16 +0000 Subject: [PATCH 05/12] Rename course unit student row widget to course unit student tile for consistency --- .../lib/view/course_unit_info/widgets/course_unit_classes.dart | 2 +- ...urse_unit_student_row.dart => course_unit_student_tile.dart} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/uni_app/lib/view/course_unit_info/widgets/{course_unit_student_row.dart => course_unit_student_tile.dart} (100%) diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index 48e802d1d..976637b56 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uni/model/entities/course_units/course_unit_class.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; -import 'package:uni/view/course_unit_info/widgets/course_unit_student_row.dart'; +import 'package:uni/view/course_unit_info/widgets/course_unit_student_tile.dart'; import 'package:uni_ui/theme.dart'; class CourseUnitClassesView extends StatefulWidget { diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_row.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart similarity index 100% rename from packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_row.dart rename to packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart From d33e36bab4b4bfbba73929e89643508d0544554f Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:23:16 +0000 Subject: [PATCH 06/12] Enhance course unit classes view with improved scrolling and selection logic --- .../widgets/course_unit_classes.dart | 158 +++++++++++++----- 1 file changed, 114 insertions(+), 44 deletions(-) diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index 976637b56..e9a3fd876 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -1,3 +1,5 @@ +import 'dart:math' as math; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uni/model/entities/course_units/course_unit_class.dart'; @@ -15,7 +17,49 @@ class CourseUnitClassesView extends StatefulWidget { } class _CourseUnitClassesViewState extends State { - int selectedIndex = 0; + late int selectedIndex; + final ScrollController _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + final sessionProvider = context.read(); + final studentNumber = int.tryParse( + sessionProvider.state!.username.replaceAll(RegExp(r'\D'), ''), + ) ?? + 0; + + selectedIndex = widget.classes.indexWhere( + (courseClass) => courseClass.students.any( + (student) => student.number == studentNumber, + ), + ); + + if (selectedIndex == -1) { + selectedIndex = 0; + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToSelectedClass(); + }); + } + + void _scrollToSelectedClass() { + final screenWidth = MediaQuery.of(context).size.width; + final offset = (140.0 * selectedIndex) - (screenWidth - 140) / 2; + + _scrollController.animateTo( + math.max(0, offset), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { @@ -40,51 +84,77 @@ class _CourseUnitClassesViewState extends State { padding: const EdgeInsets.only(bottom: 20), child: SizedBox( height: 55, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: widget.classes.length, - itemBuilder: (context, index) { - final courseUnitClass = widget.classes[index]; - final isMyClass = courseUnitClass.students - .any((student) => student.number == studentNumber); - final isSelected = index == selectedIndex; - - return GestureDetector( - onTap: () => setState(() { - selectedIndex = index; - }), - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - decoration: BoxDecoration( - color: isSelected - ? lightTheme.colorScheme.primary - : lightTheme.colorScheme.secondary, - borderRadius: BorderRadius.circular(25), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.2), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 2), - ), - ], + child: Center( + child: ListView.builder( + controller: _scrollController, + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: widget.classes.length, + itemBuilder: (context, index) { + final courseUnitClass = widget.classes[index]; + final isMyClass = courseUnitClass.students + .any((student) => student.number == studentNumber); + final isSelected = index == selectedIndex; + + return ConstrainedBox( + constraints: const BoxConstraints( + minWidth: 140, + maxWidth: 140, ), - padding: - const EdgeInsets.symmetric(horizontal: 40, vertical: 10), - alignment: Alignment.center, - child: Text( - isMyClass - ? '${courseUnitClass.className} *' - : courseUnitClass.className, - style: isSelected - ? lightTheme.textTheme.labelMedium?.copyWith( - color: lightTheme.colorScheme.onPrimary, - ) - : lightTheme.textTheme.labelMedium, + child: GestureDetector( + onTap: () { + setState(() { + selectedIndex = index; + }); + final screenWidth = MediaQuery.of(context).size.width; + final offset = (140.0 * index) - (screenWidth - 140) / 2; + + _scrollController.animateTo( + math.max(0, offset), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }, + child: Container( + margin: + const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + decoration: BoxDecoration( + color: isSelected + ? lightTheme.colorScheme.primary + : lightTheme.colorScheme.secondary, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 2), + ), + ], + ), + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + alignment: Alignment.center, + child: Text( + isMyClass + ? '${courseUnitClass.className} *' + : courseUnitClass.className, + style: isSelected + ? lightTheme.textTheme.labelMedium?.copyWith( + color: lightTheme.colorScheme.onPrimary, + ) + : lightTheme.textTheme.labelMedium, + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), ), - ), - ); - }, + ); + }, + ), ), ), ); From 24872a30e10c0b9f63309e1c240e2d6fa720e07f Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:33:29 +0000 Subject: [PATCH 07/12] Refactor class selection logic and improve scrolling behavior in course unit classes view --- .../widgets/course_unit_classes.dart | 140 +++++++++--------- 1 file changed, 66 insertions(+), 74 deletions(-) diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index e9a3fd876..5fe12c3dd 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -1,5 +1,3 @@ -import 'dart:math' as math; - import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uni/model/entities/course_units/course_unit_class.dart'; @@ -17,6 +15,9 @@ class CourseUnitClassesView extends StatefulWidget { } class _CourseUnitClassesViewState extends State { + static const double _itemWidth = 140; + static const Duration _scrollDuration = Duration(milliseconds: 300); + late int selectedIndex; final ScrollController _scrollController = ScrollController(); @@ -46,15 +47,21 @@ class _CourseUnitClassesViewState extends State { void _scrollToSelectedClass() { final screenWidth = MediaQuery.of(context).size.width; - final offset = (140.0 * selectedIndex) - (screenWidth - 140) / 2; + final offset = + (_itemWidth * selectedIndex) - (screenWidth - _itemWidth) / 2; _scrollController.animateTo( - math.max(0, offset), - duration: const Duration(milliseconds: 300), + offset < 0 ? 0 : offset, + duration: _scrollDuration, curve: Curves.easeInOut, ); } + void _handleClassTap(int index) { + setState(() => selectedIndex = index); + _scrollToSelectedClass(); + } + @override void dispose() { _scrollController.dispose(); @@ -84,77 +91,62 @@ class _CourseUnitClassesViewState extends State { padding: const EdgeInsets.only(bottom: 20), child: SizedBox( height: 55, - child: Center( - child: ListView.builder( - controller: _scrollController, - scrollDirection: Axis.horizontal, - shrinkWrap: true, - itemCount: widget.classes.length, - itemBuilder: (context, index) { - final courseUnitClass = widget.classes[index]; - final isMyClass = courseUnitClass.students - .any((student) => student.number == studentNumber); - final isSelected = index == selectedIndex; - - return ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 140, - maxWidth: 140, - ), - child: GestureDetector( - onTap: () { - setState(() { - selectedIndex = index; - }); - final screenWidth = MediaQuery.of(context).size.width; - final offset = (140.0 * index) - (screenWidth - 140) / 2; - - _scrollController.animateTo( - math.max(0, offset), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - }, - child: Container( - margin: - const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - decoration: BoxDecoration( - color: isSelected - ? lightTheme.colorScheme.primary - : lightTheme.colorScheme.secondary, - borderRadius: BorderRadius.circular(25), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.2), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 2), - ), - ], - ), - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 10, - ), - alignment: Alignment.center, - child: Text( - isMyClass - ? '${courseUnitClass.className} *' - : courseUnitClass.className, - style: isSelected - ? lightTheme.textTheme.labelMedium?.copyWith( - color: lightTheme.colorScheme.onPrimary, - ) - : lightTheme.textTheme.labelMedium, - textAlign: TextAlign.center, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + child: ListView.builder( + controller: _scrollController, + scrollDirection: Axis.horizontal, + itemCount: widget.classes.length, + itemBuilder: (context, index) { + final courseUnitClass = widget.classes[index]; + final isMyClass = courseUnitClass.students + .any((student) => student.number == studentNumber); + final isSelected = index == selectedIndex; + + return ConstrainedBox( + constraints: const BoxConstraints( + minWidth: _itemWidth, + maxWidth: _itemWidth, + ), + child: GestureDetector( + onTap: () => _handleClassTap(index), + child: Container( + margin: + const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + decoration: BoxDecoration( + color: isSelected + ? lightTheme.colorScheme.primary + : lightTheme.colorScheme.secondary, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 2), + ), + ], + ), + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + alignment: Alignment.center, + child: Text( + isMyClass + ? '${courseUnitClass.className} *' + : courseUnitClass.className, + style: isSelected + ? lightTheme.textTheme.labelMedium?.copyWith( + color: lightTheme.colorScheme.onPrimary, + ) + : lightTheme.textTheme.labelMedium, + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ), - ); - }, - ), + ), + ); + }, ), ), ); From 87f2126a225fc6183c0263a0973f1c2309ca3dd2 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:57:29 +0000 Subject: [PATCH 08/12] Refactored --- .../view/course_unit_info/widgets/course_unit_classes.dart | 2 +- .../course_unit_info/widgets/course_unit_student_tile.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index 5fe12c3dd..87faa1e41 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -169,7 +169,7 @@ class _CourseUnitClassesViewState extends State { itemCount: currentClass.students.length, itemBuilder: (context, index) { final student = currentClass.students[index]; - return CourseUnitStudentRow( + return CourseUnitStudentTile( student, session.state!, key: ValueKey('${currentClass.className}_${student.number}'), diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart index 5ed753c5e..ddfd6ca1a 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart @@ -4,8 +4,8 @@ import 'package:uni/model/providers/startup/profile_provider.dart'; import 'package:uni/session/flows/base/session.dart'; import 'package:uni_ui/theme.dart'; -class CourseUnitStudentRow extends StatelessWidget { - const CourseUnitStudentRow(this.student, this.session, {super.key}); +class CourseUnitStudentTile extends StatelessWidget { + const CourseUnitStudentTile(this.student, this.session, {super.key}); final CourseUnitStudent student; final Session session; From cf9ac93453db92f92ffbe97e1be2590954dc2458 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:58:53 +0000 Subject: [PATCH 09/12] Add figma_squircle package and updated CourseUnitStudentTile --- .../widgets/course_unit_student_tile.dart | 8 ++++++-- packages/uni_app/pubspec.lock | 2 +- packages/uni_app/pubspec.yaml | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart index ddfd6ca1a..1554b18e0 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart @@ -1,3 +1,4 @@ +import 'package:figma_squircle/figma_squircle.dart'; import 'package:flutter/material.dart'; import 'package:uni/model/entities/course_units/course_unit_class.dart'; import 'package:uni/model/providers/startup/profile_provider.dart'; @@ -27,8 +28,11 @@ class CourseUnitStudentTile extends StatelessWidget { children: [ Container( decoration: ShapeDecoration( - shape: const ContinuousRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(50)), + shape: SmoothRectangleBorder( + borderRadius: SmoothBorderRadius( + cornerRadius: 25, + cornerSmoothing: 1, + ), ), image: DecorationImage( fit: BoxFit.cover, diff --git a/packages/uni_app/pubspec.lock b/packages/uni_app/pubspec.lock index 0bd7483ed..26889d843 100644 --- a/packages/uni_app/pubspec.lock +++ b/packages/uni_app/pubspec.lock @@ -439,7 +439,7 @@ packages: source: hosted version: "2.1.3" figma_squircle: - dependency: transitive + dependency: "direct main" description: name: figma_squircle sha256: "790b91a9505e90d246f6efe2fa065ff7fffe658c7b44fe9b5b20c7b0ad3818c0" diff --git a/packages/uni_app/pubspec.yaml b/packages/uni_app/pubspec.yaml index e2e794f70..e2c6e7285 100644 --- a/packages/uni_app/pubspec.yaml +++ b/packages/uni_app/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: email_validator: ^2.0.1 expandable: ^5.0.1 expansion_tile_card: ^3.0.0 + figma_squircle: ^0.5.3 flutter: sdk: flutter flutter_cache_manager: ^3.3.1 From 16b73d165b663ff756a4faf3ff751d14ab1bbbbf Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:06:10 +0000 Subject: [PATCH 10/12] Add student number getter utility --- packages/uni_app/lib/utils/student_number_getter.dart | 7 +++++++ .../course_unit_info/widgets/course_unit_classes.dart | 11 +++-------- 2 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 packages/uni_app/lib/utils/student_number_getter.dart diff --git a/packages/uni_app/lib/utils/student_number_getter.dart b/packages/uni_app/lib/utils/student_number_getter.dart new file mode 100644 index 000000000..d84b2d6e0 --- /dev/null +++ b/packages/uni_app/lib/utils/student_number_getter.dart @@ -0,0 +1,7 @@ +import 'package:uni/model/providers/startup/session_provider.dart'; + +int getStudentNumber(SessionProvider sessionProvider) { + return int.tryParse( + sessionProvider.state!.username.replaceAll(RegExp(r'\D'), ''), + ) ?? 0; +} diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index 87faa1e41..533466243 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uni/model/entities/course_units/course_unit_class.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; +import 'package:uni/utils/student_number_getter.dart'; import 'package:uni/view/course_unit_info/widgets/course_unit_student_tile.dart'; import 'package:uni_ui/theme.dart'; @@ -25,10 +26,7 @@ class _CourseUnitClassesViewState extends State { void initState() { super.initState(); final sessionProvider = context.read(); - final studentNumber = int.tryParse( - sessionProvider.state!.username.replaceAll(RegExp(r'\D'), ''), - ) ?? - 0; + final studentNumber = getStudentNumber(sessionProvider); selectedIndex = widget.classes.indexWhere( (courseClass) => courseClass.students.any( @@ -71,10 +69,7 @@ class _CourseUnitClassesViewState extends State { @override Widget build(BuildContext context) { final sessionProvider = context.read(); - final studentNumber = int.tryParse( - sessionProvider.state!.username.replaceAll(RegExp(r'\D'), ''), - ) ?? - 0; + final studentNumber = getStudentNumber(sessionProvider); return SingleChildScrollView( child: Column( From e60b577be21357d6778a85c922df85da38fab13c Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:12:50 +0000 Subject: [PATCH 11/12] Fix student number initialization in CourseUnitClassesView --- .../view/course_unit_info/widgets/course_unit_classes.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index 533466243..7ccb46481 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -21,12 +21,13 @@ class _CourseUnitClassesViewState extends State { late int selectedIndex; final ScrollController _scrollController = ScrollController(); + late int studentNumber; @override void initState() { super.initState(); final sessionProvider = context.read(); - final studentNumber = getStudentNumber(sessionProvider); + studentNumber = getStudentNumber(sessionProvider); selectedIndex = widget.classes.indexWhere( (courseClass) => courseClass.students.any( @@ -69,7 +70,6 @@ class _CourseUnitClassesViewState extends State { @override Widget build(BuildContext context) { final sessionProvider = context.read(); - final studentNumber = getStudentNumber(sessionProvider); return SingleChildScrollView( child: Column( From 43a7368c23ee25b1436342dff166ad0773f15833 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:18:02 +0000 Subject: [PATCH 12/12] Formatted and improved session provider usage in CourseUnitClassesView --- packages/uni_app/lib/utils/student_number_getter.dart | 5 +++-- .../course_unit_info/widgets/course_unit_classes.dart | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/uni_app/lib/utils/student_number_getter.dart b/packages/uni_app/lib/utils/student_number_getter.dart index d84b2d6e0..4188446a0 100644 --- a/packages/uni_app/lib/utils/student_number_getter.dart +++ b/packages/uni_app/lib/utils/student_number_getter.dart @@ -2,6 +2,7 @@ import 'package:uni/model/providers/startup/session_provider.dart'; int getStudentNumber(SessionProvider sessionProvider) { return int.tryParse( - sessionProvider.state!.username.replaceAll(RegExp(r'\D'), ''), - ) ?? 0; + sessionProvider.state!.username.replaceAll(RegExp(r'\D'), ''), + ) ?? + 0; } diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index 7ccb46481..67c32ddf9 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -19,14 +19,16 @@ class _CourseUnitClassesViewState extends State { static const double _itemWidth = 140; static const Duration _scrollDuration = Duration(milliseconds: 300); - late int selectedIndex; final ScrollController _scrollController = ScrollController(); + + late int selectedIndex; late int studentNumber; + late SessionProvider sessionProvider; @override void initState() { super.initState(); - final sessionProvider = context.read(); + sessionProvider = context.read(); studentNumber = getStudentNumber(sessionProvider); selectedIndex = widget.classes.indexWhere( @@ -69,8 +71,6 @@ class _CourseUnitClassesViewState extends State { @override Widget build(BuildContext context) { - final sessionProvider = context.read(); - return SingleChildScrollView( child: Column( children: [