From aaa16ba253dc4ca992cbe92308306444e21492f1 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Sat, 18 Jan 2025 01:33:39 +0000 Subject: [PATCH 01/18] Started working on integrating a schedule tab into the academic path page --- .../lib/generated/intl/messages_all.dart | 13 +- .../lib/generated/intl/messages_en.dart | 13 +- .../lib/generated/intl/messages_pt_PT.dart | 13 +- packages/uni_app/lib/generated/l10n.dart | 10 +- packages/uni_app/lib/l10n/intl_en.arb | 4 +- packages/uni_app/lib/l10n/intl_pt_PT.arb | 4 +- .../lib/view/academic_path/academic_path.dart | 13 +- .../lib/view/academic_path/schedule_page.dart | 207 ++++++++++++++++++ 8 files changed, 243 insertions(+), 34 deletions(-) create mode 100644 packages/uni_app/lib/view/academic_path/schedule_page.dart diff --git a/packages/uni_app/lib/generated/intl/messages_all.dart b/packages/uni_app/lib/generated/intl/messages_all.dart index 6b3ebeae5..b77f94db2 100644 --- a/packages/uni_app/lib/generated/intl/messages_all.dart +++ b/packages/uni_app/lib/generated/intl/messages_all.dart @@ -11,6 +11,7 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:intl/intl.dart'; import 'package:intl/message_lookup_by_library.dart'; import 'package:intl/src/intl_helpers.dart'; @@ -20,8 +21,8 @@ import 'messages_pt_PT.dart' as messages_pt_pt; typedef Future LibraryLoader(); Map _deferredLibraries = { - 'en': () => new Future.value(null), - 'pt_PT': () => new Future.value(null), + 'en': () => new SynchronousFuture(null), + 'pt_PT': () => new SynchronousFuture(null), }; MessageLookupByLibrary? _findExact(String localeName) { @@ -36,18 +37,18 @@ MessageLookupByLibrary? _findExact(String localeName) { } /// User programs should call this before using [localeName] for messages. -Future initializeMessages(String localeName) async { +Future initializeMessages(String localeName) { var availableLocale = Intl.verifiedLocale( localeName, (locale) => _deferredLibraries[locale] != null, onFailure: (_) => null); if (availableLocale == null) { - return new Future.value(false); + return new SynchronousFuture(false); } var lib = _deferredLibraries[availableLocale]; - await (lib == null ? new Future.value(false) : lib()); + lib == null ? new SynchronousFuture(false) : lib(); initializeInternalMessageLookup(() => new CompositeMessageLookup()); messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); - return new Future.value(true); + return new SynchronousFuture(true); } bool _messagesExistFor(String locale) { diff --git a/packages/uni_app/lib/generated/intl/messages_en.dart b/packages/uni_app/lib/generated/intl/messages_en.dart index 6202e906e..d19627698 100644 --- a/packages/uni_app/lib/generated/intl/messages_en.dart +++ b/packages/uni_app/lib/generated/intl/messages_en.dart @@ -7,7 +7,8 @@ // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases -// ignore_for_file:unused_import, file_names +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes import 'package:intl/intl.dart'; import 'package:intl/message_lookup_by_library.dart'; @@ -19,12 +20,12 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'en'; - static m0(time) => "last refresh at ${time}"; + static String m0(time) => "last refresh at ${time}"; - static m1(time) => + static String m1(time) => "${Intl.plural(time, zero: 'Refreshed ${time} minutes ago', one: 'Refreshed ${time} minute ago', other: 'Refreshed ${time} minutes ago')}"; - static m2(title) => "${Intl.select(title, { + static String m2(title) => "${Intl.select(title, { 'horario': 'Schedule', 'exames': 'Exams', 'area': 'Personal Area', @@ -41,7 +42,7 @@ class MessageLookup extends MessageLookupByLibrary { })}"; final messages = _notInlinedMessages(_notInlinedMessages); - static _notInlinedMessages(_) => { + static Map _notInlinedMessages(_) => { "about": MessageLookupByLibrary.simpleMessage("About us"), "academic_services": MessageLookupByLibrary.simpleMessage("Academic services"), @@ -161,7 +162,6 @@ class MessageLookup extends MessageLookupByLibrary { "language": MessageLookupByLibrary.simpleMessage("Language"), "last_refresh_time": m0, "last_timestamp": m1, - "lectures": MessageLookupByLibrary.simpleMessage("Lectures"), "library_occupation": MessageLookupByLibrary.simpleMessage("Library Occupation"), "load_error": MessageLookupByLibrary.simpleMessage( @@ -270,6 +270,7 @@ class MessageLookup extends MessageLookupByLibrary { "restaurant_main_page": MessageLookupByLibrary.simpleMessage( "Do you want to see your favorite restaurants in the main page?"), "room": MessageLookupByLibrary.simpleMessage("Room"), + "schedule": MessageLookupByLibrary.simpleMessage("Schedule"), "school_calendar": MessageLookupByLibrary.simpleMessage("School Calendar"), "search": MessageLookupByLibrary.simpleMessage("Search"), diff --git a/packages/uni_app/lib/generated/intl/messages_pt_PT.dart b/packages/uni_app/lib/generated/intl/messages_pt_PT.dart index 0aa3756ff..b83af16eb 100644 --- a/packages/uni_app/lib/generated/intl/messages_pt_PT.dart +++ b/packages/uni_app/lib/generated/intl/messages_pt_PT.dart @@ -7,7 +7,8 @@ // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases -// ignore_for_file:unused_import, file_names +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes import 'package:intl/intl.dart'; import 'package:intl/message_lookup_by_library.dart'; @@ -19,12 +20,12 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'pt_PT'; - static m0(time) => "última atualização às ${time}"; + static String m0(time) => "última atualização às ${time}"; - static m1(time) => + static String m1(time) => "${Intl.plural(time, zero: 'Atualizado há ${time} minutos', one: 'Atualizado há ${time} minuto', other: 'Atualizado há ${time} minutos')}"; - static m2(title) => "${Intl.select(title, { + static String m2(title) => "${Intl.select(title, { 'horario': 'Horário', 'exames': 'Exames', 'area': 'Área Pessoal', @@ -41,7 +42,7 @@ class MessageLookup extends MessageLookupByLibrary { })}"; final messages = _notInlinedMessages(_notInlinedMessages); - static _notInlinedMessages(_) => { + static Map _notInlinedMessages(_) => { "about": MessageLookupByLibrary.simpleMessage("Sobre nós"), "academic_services": MessageLookupByLibrary.simpleMessage("Serviços académicos"), @@ -160,7 +161,6 @@ class MessageLookup extends MessageLookupByLibrary { "language": MessageLookupByLibrary.simpleMessage("Idioma"), "last_refresh_time": m0, "last_timestamp": m1, - "lectures": MessageLookupByLibrary.simpleMessage("Aulas"), "library_occupation": MessageLookupByLibrary.simpleMessage("Ocupação da Biblioteca"), "load_error": MessageLookupByLibrary.simpleMessage( @@ -271,6 +271,7 @@ class MessageLookup extends MessageLookupByLibrary { "restaurant_main_page": MessageLookupByLibrary.simpleMessage( "Queres ver os teus restaurantes favoritos na página principal?"), "room": MessageLookupByLibrary.simpleMessage("Sala"), + "schedule": MessageLookupByLibrary.simpleMessage("Aulas"), "school_calendar": MessageLookupByLibrary.simpleMessage("Calendário Escolar"), "search": MessageLookupByLibrary.simpleMessage("Pesquisar"), diff --git a/packages/uni_app/lib/generated/l10n.dart b/packages/uni_app/lib/generated/l10n.dart index 83ba9835f..dce10b0ae 100644 --- a/packages/uni_app/lib/generated/l10n.dart +++ b/packages/uni_app/lib/generated/l10n.dart @@ -10,7 +10,7 @@ import 'intl/messages_all.dart'; // ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars // ignore_for_file: join_return_with_assignment, prefer_final_in_for_each -// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes class S { S(); @@ -1738,11 +1738,11 @@ class S { ); } - /// `Lectures` - String get lectures { + /// `Schedule` + String get schedule { return Intl.message( - 'Lectures', - name: 'lectures', + 'Schedule', + name: 'schedule', desc: '', args: [], ); diff --git a/packages/uni_app/lib/l10n/intl_en.arb b/packages/uni_app/lib/l10n/intl_en.arb index 84932240e..c15d9e982 100644 --- a/packages/uni_app/lib/l10n/intl_en.arb +++ b/packages/uni_app/lib/l10n/intl_en.arb @@ -342,8 +342,8 @@ "@wrong_credentials_exception": {}, "internet_status_exception": "Check your internet connection", "@internet_status_exception": {}, - "lectures": "Lectures", - "@lectures": {}, + "schedule": "Schedule", + "@schedule": {}, "exams": "Exams", "@exams": {}, "courses": "Courses", diff --git a/packages/uni_app/lib/l10n/intl_pt_PT.arb b/packages/uni_app/lib/l10n/intl_pt_PT.arb index 5a255d1f2..a31ee8016 100644 --- a/packages/uni_app/lib/l10n/intl_pt_PT.arb +++ b/packages/uni_app/lib/l10n/intl_pt_PT.arb @@ -342,8 +342,8 @@ "@wrong_credentials_exception": {}, "internet_status_exception": "Verifique sua conexão com a internet", "@internet_status_exception": {}, - "lectures": "Aulas", - "@lectures": {}, + "schedule": "Aulas", + "@schedule": {}, "exams": "Exames", "@exams": {}, "courses": "Cursos", diff --git a/packages/uni_app/lib/view/academic_path/academic_path.dart b/packages/uni_app/lib/view/academic_path/academic_path.dart index bfac776d1..dd27b8e5c 100644 --- a/packages/uni_app/lib/view/academic_path/academic_path.dart +++ b/packages/uni_app/lib/view/academic_path/academic_path.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:uni/generated/l10n.dart'; import 'package:uni/utils/navigation_items.dart'; import 'package:uni/view/academic_path/exam_page.dart'; +import 'package:uni/view/academic_path/schedule_page.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/general.dart'; import 'package:uni_ui/icons.dart'; import 'package:uni_ui/tabs/tab_icon.dart'; @@ -41,7 +42,7 @@ class AcademicPathPageViewState extends GeneralPageViewState tabs: [ TabIcon( icon: UniIcons.lecture, - text: S.of(context).lectures, + text: S.of(context).schedule, ), TabIcon(icon: UniIcons.exam, text: S.of(context).exams), TabIcon(icon: UniIcons.course, text: S.of(context).courses), @@ -53,12 +54,10 @@ class AcademicPathPageViewState extends GeneralPageViewState Widget getBody(BuildContext context) { return TabBarView( controller: tabController, - children: const [ - Center( - child: Text('To be implemented'), - ), - ExamsPage(), - Center( + children: [ + SchedulePage(), + const ExamsPage(), + const Center( child: Text('To be implemented'), ), ], diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart new file mode 100644 index 000000000..c6ccf78fa --- /dev/null +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/model/entities/lecture.dart'; +import 'package:uni/model/providers/lazy/lecture_provider.dart'; +import 'package:uni/model/utils/time/week.dart'; +import 'package:uni/model/utils/time/weekday_mapper.dart'; +import 'package:uni/view/common_widgets/expanded_image_label.dart'; +import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni/view/locale_notifier.dart'; +import 'package:uni/view/schedule/widgets/schedule_slot.dart'; + +class SchedulePage extends StatefulWidget { + SchedulePage({super.key, DateTime? now}) : now = now ?? DateTime.now(); + + final DateTime now; + + @override + SchedulePageState createState() => SchedulePageState(); +} + +class SchedulePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + body: RefreshIndicator( + onRefresh: () async { + await context.read().forceRefresh(context); + }, + child: LazyConsumer>( + builder: (context, lectures) => SchedulePageView( + lectures, + now: widget.now, + ), + hasContent: (lectures) => lectures.isNotEmpty, + onNullContent: SchedulePageView(const [], now: widget.now), + ), + ), + ); + } +} + +class SchedulePageView extends StatefulWidget { + SchedulePageView(this.lectures, {required DateTime now, super.key}) + : currentWeek = Week(start: now); + + final List lectures; + final Week currentWeek; + + @override + SchedulePageViewState createState() => SchedulePageViewState(); +} + +class SchedulePageViewState extends State + with TickerProviderStateMixin { + TabController? tabController; + late final List lecturesThisWeek; + + @override + void initState() { + super.initState(); + tabController = TabController( + vsync: this, + length: 6, + ); + + var weekDay = widget.currentWeek.start.weekday; + + lecturesThisWeek = []; + widget.currentWeek.weekdays.take(6).forEach((day) { + final lectures = lecturesOfDay(widget.lectures, day); + lecturesThisWeek.addAll(lectures); + }); + + if (lecturesThisWeek.isNotEmpty) { + final now = DateTime.now(); + + final nextLecture = lecturesThisWeek + .where((lecture) => lecture.endTime.isAfter(now)) + .fold(null, (closest, lecture) { + if (closest == null) { + return lecture; + } + return lecture.endTime.difference(now) < closest.endTime.difference(now) + ? lecture + : closest; + }); + + if (nextLecture != null) { + weekDay = nextLecture.endTime.weekday; + } + } + + final offset = (weekDay > 6) ? 0 : (weekDay - 1) % 6; + tabController?.animateTo(tabController!.index + offset); + } + + @override + void dispose() { + tabController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final queryData = MediaQuery.of(context); + return Column( + children: [ + TabBar( + controller: tabController, + isScrollable: true, + physics: const BouncingScrollPhysics(), + tabs: createTabs(queryData, context), + ), + Expanded( + child: TabBarView( + controller: tabController, + children: widget.currentWeek.weekdays.take(6).map((day) { + final lectures = lecturesOfDay(lecturesThisWeek, day); + final index = WeekdayMapper.fromDartToIndex.map(day.weekday); + if (lectures.isEmpty) { + return emptyDayColumn(context, index); + } else { + return dayColumnBuilder(index, lectures, context); + } + }).toList(), + ), + ), + ], + ); + } + + List createTabs(MediaQueryData queryData, BuildContext context) { + final tabs = []; + final workWeekDays = + context.read().getWeekdaysWithLocale().sublist(0, 6); + workWeekDays.asMap().forEach((index, day) { + tabs.add( + SizedBox( + width: (queryData.size.width * 1) / 4, + child: Tab( + key: Key('schedule-page-tab-$index'), + text: day, + ), + ), + ); + }); + return tabs; + } + + Widget dayColumnBuilder( + int day, + List lectures, + BuildContext context, + ) { + return Container( + key: Key('schedule-page-day-column-$day'), + child: ListView( + children: lectures + .map( + (lecture) => ScheduleSlot( + subject: lecture.subject, + typeClass: lecture.typeClass, + rooms: lecture.room, + begin: lecture.startTime, + end: lecture.endTime, + occurrId: lecture.occurrId, + teacher: lecture.teacher, + classNumber: lecture.classNumber, + ), + ) + .toList(), + ), + ); + } + + static List lecturesOfDay(List lectures, DateTime day) { + final filteredLectures = []; + for (var i = 0; i < lectures.length; i++) { + final lecture = lectures[i]; + if (lecture.startTime.day == day.day && + lecture.startTime.month == day.month && + lecture.startTime.year == day.year) { + filteredLectures.add(lecture); + } + } + return filteredLectures; + } + + Widget emptyDayColumn(BuildContext context, int day) { + final weekday = + Provider.of(context).getWeekdaysWithLocale()[day]; + + final noClassesText = day >= DateTime.saturday - 1 + ? S.of(context).no_classes_on_weekend + : S.of(context).no_classes_on; + + return Center( + child: ImageLabel( + imagePath: 'assets/images/schedule.png', + label: '$noClassesText $weekday.', + labelTextStyle: const TextStyle(fontSize: 15), + ), + ); + } +} From 91afb0d2cf92e645f689eb4d3987c310c3bf0eb4 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Sat, 18 Jan 2025 04:24:51 +0000 Subject: [PATCH 02/18] Small Code Refactor --- .../lib/view/academic_path/schedule_page.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index c6ccf78fa..be4991656 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/lecture.dart'; import 'package:uni/model/providers/lazy/lecture_provider.dart'; +import 'package:uni/model/utils/day_of_week.dart'; import 'package:uni/model/utils/time/week.dart'; import 'package:uni/model/utils/time/weekday_mapper.dart'; import 'package:uni/view/common_widgets/expanded_image_label.dart'; @@ -53,17 +54,15 @@ class SchedulePageView extends StatefulWidget { } class SchedulePageViewState extends State - with TickerProviderStateMixin { - TabController? tabController; - late final List lecturesThisWeek; + with SingleTickerProviderStateMixin { + late TabController tabController; + late ScrollController scrollViewController; + late List lecturesThisWeek; @override void initState() { super.initState(); - tabController = TabController( - vsync: this, - length: 6, - ); + tabController = TabController(vsync: this, length: DayOfWeek.values.length - 1); var weekDay = widget.currentWeek.start.weekday; @@ -92,13 +91,14 @@ class SchedulePageViewState extends State } } - final offset = (weekDay > 6) ? 0 : (weekDay - 1) % 6; - tabController?.animateTo(tabController!.index + offset); + tabController.animateTo(tabController.index + (weekDay - 1)); + scrollViewController = ScrollController(); } @override void dispose() { - tabController?.dispose(); + tabController.dispose(); + scrollViewController.dispose(); super.dispose(); } From a8539816c1a92b8d49eabbf6b6bf57e07cc43675 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Sat, 18 Jan 2025 06:09:57 +0000 Subject: [PATCH 03/18] Changed tabs to timeline (need to sync the current day to the default index) --- .../lib/view/academic_path/schedule_page.dart | 132 ++++++++---------- 1 file changed, 57 insertions(+), 75 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index be4991656..2479c8090 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -10,6 +10,7 @@ import 'package:uni/view/common_widgets/expanded_image_label.dart'; import 'package:uni/view/lazy_consumer.dart'; import 'package:uni/view/locale_notifier.dart'; import 'package:uni/view/schedule/widgets/schedule_slot.dart'; +import 'package:uni_ui/timeline/timeline.dart'; class SchedulePage extends StatefulWidget { SchedulePage({super.key, DateTime? now}) : now = now ?? DateTime.now(); @@ -56,13 +57,12 @@ class SchedulePageView extends StatefulWidget { class SchedulePageViewState extends State with SingleTickerProviderStateMixin { late TabController tabController; - late ScrollController scrollViewController; late List lecturesThisWeek; @override void initState() { super.initState(); - tabController = TabController(vsync: this, length: DayOfWeek.values.length - 1); + tabController = TabController(vsync: this, length: 6); var weekDay = widget.currentWeek.start.weekday; @@ -91,101 +91,70 @@ class SchedulePageViewState extends State } } - tabController.animateTo(tabController.index + (weekDay - 1)); - scrollViewController = ScrollController(); + final offset = (weekDay > 6) ? 0 : (weekDay - 1) % 6; + tabController.animateTo(tabController.index + offset); } @override void dispose() { tabController.dispose(); - scrollViewController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - final queryData = MediaQuery.of(context); - return Column( - children: [ - TabBar( - controller: tabController, - isScrollable: true, - physics: const BouncingScrollPhysics(), - tabs: createTabs(queryData, context), - ), - Expanded( - child: TabBarView( - controller: tabController, - children: widget.currentWeek.weekdays.take(6).map((day) { - final lectures = lecturesOfDay(lecturesThisWeek, day); - final index = WeekdayMapper.fromDartToIndex.map(day.weekday); - if (lectures.isEmpty) { - return emptyDayColumn(context, index); - } else { - return dayColumnBuilder(index, lectures, context); - } - }).toList(), - ), - ), - ], + return Timeline( + tabs: createTabs(context), + content: createTabViewBuilder(context), ); } - List createTabs(MediaQueryData queryData, BuildContext context) { + List createTabs(BuildContext context) { + final workWeekDays = Provider.of(context) + .getWeekdaysWithLocale() + .sublist(0, 6); final tabs = []; - final workWeekDays = - context.read().getWeekdaysWithLocale().sublist(0, 6); - workWeekDays.asMap().forEach((index, day) { + for (var i = 0; i < DayOfWeek.values.length - 1; i++) { tabs.add( - SizedBox( - width: (queryData.size.width * 1) / 4, - child: Tab( - key: Key('schedule-page-tab-$index'), - text: day, - ), + Tab( + key: Key('schedule-page-tab-$i'), + text: workWeekDays[i], ), ); - }); + } return tabs; } - Widget dayColumnBuilder( - int day, - List lectures, - BuildContext context, - ) { - return Container( - key: Key('schedule-page-day-column-$day'), - child: ListView( - children: lectures - .map( - (lecture) => ScheduleSlot( - subject: lecture.subject, - typeClass: lecture.typeClass, - rooms: lecture.room, - begin: lecture.startTime, - end: lecture.endTime, - occurrId: lecture.occurrId, - teacher: lecture.teacher, - classNumber: lecture.classNumber, - ), - ) - .toList(), - ), - ); + List createTabViewBuilder(BuildContext context) { + return widget.currentWeek.weekdays.take(6).map((day) { + final lectures = lecturesOfDay(lecturesThisWeek, day); + final index = WeekdayMapper.fromDartToIndex.map(day.weekday); + if (lectures.isEmpty) { + return emptyDayColumn(context, index); + } else { + return dayColumnBuilder(index, lectures); + } + }).toList(); } - static List lecturesOfDay(List lectures, DateTime day) { - final filteredLectures = []; - for (var i = 0; i < lectures.length; i++) { - final lecture = lectures[i]; - if (lecture.startTime.day == day.day && - lecture.startTime.month == day.month && - lecture.startTime.year == day.year) { - filteredLectures.add(lecture); - } - } - return filteredLectures; + Widget dayColumnBuilder(int day, List lectures) { + return ListView( + key: Key('schedule-page-day-column-$day'), + children: lectures + .map( + (lecture) => ScheduleSlot( + subject: lecture.subject, + typeClass: lecture.typeClass, + rooms: lecture.room, + begin: lecture.startTime, + end: lecture.endTime, + occurrId: lecture.occurrId, + teacher: lecture.teacher, + classNumber: lecture.classNumber, + ), + ) + .toList(), + ); } Widget emptyDayColumn(BuildContext context, int day) { @@ -204,4 +173,17 @@ class SchedulePageViewState extends State ), ); } + + static List lecturesOfDay(List lectures, DateTime day) { + final filteredLectures = []; + for (var i = 0; i < lectures.length; i++) { + final lecture = lectures[i]; + if (lecture.startTime.day == day.day && + lecture.startTime.month == day.month && + lecture.startTime.year == day.year) { + filteredLectures.add(lecture); + } + } + return filteredLectures; + } } From 8a4e91553a7163afa97cd330177e3b8293a941a5 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Sat, 18 Jan 2025 22:43:18 +0000 Subject: [PATCH 04/18] Working on the Day Tab Visuals --- .../lib/view/academic_path/schedule_page.dart | 79 ++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 2479c8090..9d9588514 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -118,13 +118,90 @@ class SchedulePageViewState extends State tabs.add( Tab( key: Key('schedule-page-tab-$i'), - text: workWeekDays[i], + height: 50, + child: AnimatedBuilder( + animation: tabController, + builder: (context, child) { + final isSelected = tabController.index == i; + + return Container( + width: 45, + height: 50, + decoration: BoxDecoration( + color: isSelected + ? const Color.fromRGBO(177, 77, 84, 0.25) + : Theme.of(context).scaffoldBackgroundColor, + borderRadius: BorderRadius.circular(10), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + toShortVersion(workWeekDays[i]), + style: isSelected + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 12, + color: Color.fromRGBO(102, 9, 16, 1), + ) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 12, + color: Color.fromRGBO(48, 48, 48, 1), + ), + ), + Text( + '$i', + style: isSelected + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 12, + color: Color.fromRGBO(102, 9, 16, 1), + ) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 12, + color: Color.fromRGBO(48, 48, 48, 1), + ), + ), + ], + ), + ); + }, + ), ), ); } return tabs; } + String toShortVersion(String dayOfTheWeek) { + String shortVersion; + switch (dayOfTheWeek) { + case 'Monday': + shortVersion = 'Mon'; + case 'Tuesday': + shortVersion = 'Tue'; + case 'Wednesday': + shortVersion = 'Wed'; + case 'Thursday': + shortVersion = 'Thu'; + case 'Friday': + shortVersion = 'Fri'; + case 'Saturday': + shortVersion = 'Sat'; + case 'Sunday': + shortVersion = 'Sun'; + default: + shortVersion = 'Blank'; + } + return shortVersion; + } + List createTabViewBuilder(BuildContext context) { return widget.currentWeek.weekdays.take(6).map((day) { final lectures = lecturesOfDay(lecturesThisWeek, day); From 381096f910455fbff0de46a4275d53c2a120fb39 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Sun, 19 Jan 2025 00:21:52 +0000 Subject: [PATCH 05/18] Tabs organized from Sunday to Saturday (still need to sync to the content and to color when selected) --- .../lib/view/academic_path/schedule_page.dart | 168 +++++++----------- 1 file changed, 63 insertions(+), 105 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 9d9588514..141b75510 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -3,9 +3,7 @@ import 'package:provider/provider.dart'; import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/lecture.dart'; import 'package:uni/model/providers/lazy/lecture_provider.dart'; -import 'package:uni/model/utils/day_of_week.dart'; import 'package:uni/model/utils/time/week.dart'; -import 'package:uni/model/utils/time/weekday_mapper.dart'; import 'package:uni/view/common_widgets/expanded_image_label.dart'; import 'package:uni/view/lazy_consumer.dart'; import 'package:uni/view/locale_notifier.dart'; @@ -57,42 +55,13 @@ class SchedulePageView extends StatefulWidget { class SchedulePageViewState extends State with SingleTickerProviderStateMixin { late TabController tabController; - late List lecturesThisWeek; + late List reorderedDates; @override void initState() { super.initState(); - tabController = TabController(vsync: this, length: 6); - - var weekDay = widget.currentWeek.start.weekday; - - lecturesThisWeek = []; - widget.currentWeek.weekdays.take(6).forEach((day) { - final lectures = lecturesOfDay(widget.lectures, day); - lecturesThisWeek.addAll(lectures); - }); - - if (lecturesThisWeek.isNotEmpty) { - final now = DateTime.now(); - - final nextLecture = lecturesThisWeek - .where((lecture) => lecture.endTime.isAfter(now)) - .fold(null, (closest, lecture) { - if (closest == null) { - return lecture; - } - return lecture.endTime.difference(now) < closest.endTime.difference(now) - ? lecture - : closest; - }); - - if (nextLecture != null) { - weekDay = nextLecture.endTime.weekday; - } - } - - final offset = (weekDay > 6) ? 0 : (weekDay - 1) % 6; - tabController.animateTo(tabController.index + offset); + tabController = TabController(vsync: this, length: 7); // Fixed to 7 days + reorderedDates = _getReorderedWeekDates(widget.currentWeek.start); } @override @@ -110,73 +79,59 @@ class SchedulePageViewState extends State } List createTabs(BuildContext context) { - final workWeekDays = Provider.of(context) - .getWeekdaysWithLocale() - .sublist(0, 6); - final tabs = []; - for (var i = 0; i < DayOfWeek.values.length - 1; i++) { - tabs.add( - Tab( - key: Key('schedule-page-tab-$i'), + final daysOfTheWeek = + Provider.of(context).getWeekdaysWithLocale(); + + // Reorder the days of the week to start with Sunday + final reorderedDaysOfTheWeek = [ + daysOfTheWeek[6], // Sunday (index 6 in default order) + ...daysOfTheWeek.sublist(0, 6), // Monday to Saturday + ]; + + return List.generate(7, (index) { + final isSelected = tabController.index == index; + return Tab( + key: Key('schedule-page-tab-$index'), + height: 50, + child: Container( + width: 45, height: 50, - child: AnimatedBuilder( - animation: tabController, - builder: (context, child) { - final isSelected = tabController.index == i; - - return Container( - width: 45, - height: 50, - decoration: BoxDecoration( + decoration: BoxDecoration( + color: isSelected + ? const Color.fromRGBO(177, 77, 84, 0.25) + : Theme.of(context).scaffoldBackgroundColor, + borderRadius: BorderRadius.circular(10), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + toShortVersion(reorderedDaysOfTheWeek[index]), + style: TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 12, color: isSelected - ? const Color.fromRGBO(177, 77, 84, 0.25) - : Theme.of(context).scaffoldBackgroundColor, - borderRadius: BorderRadius.circular(10), + ? const Color.fromRGBO(102, 9, 16, 1) + : const Color.fromRGBO(48, 48, 48, 1), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - toShortVersion(workWeekDays[i]), - style: isSelected - ? const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 12, - color: Color.fromRGBO(102, 9, 16, 1), - ) - : const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 12, - color: Color.fromRGBO(48, 48, 48, 1), - ), - ), - Text( - '$i', - style: isSelected - ? const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 12, - color: Color.fromRGBO(102, 9, 16, 1), - ) - : const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 12, - color: Color.fromRGBO(48, 48, 48, 1), - ), - ), - ], + ), + Text( + '${reorderedDates[index].day}', + style: TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 12, + color: isSelected + ? const Color.fromRGBO(102, 9, 16, 1) + : const Color.fromRGBO(48, 48, 48, 1), ), - ); - }, + ), + ], ), ), ); - } - return tabs; + }); } String toShortVersion(String dayOfTheWeek) { @@ -202,15 +157,20 @@ class SchedulePageViewState extends State return shortVersion; } + List _getReorderedWeekDates(DateTime startOfWeek) { + final sunday = + startOfWeek.subtract(Duration(days: startOfWeek.weekday % 7)); + return List.generate(7, (index) => sunday.add(Duration(days: index))); + } + List createTabViewBuilder(BuildContext context) { - return widget.currentWeek.weekdays.take(6).map((day) { - final lectures = lecturesOfDay(lecturesThisWeek, day); - final index = WeekdayMapper.fromDartToIndex.map(day.weekday); - if (lectures.isEmpty) { - return emptyDayColumn(context, index); - } else { - return dayColumnBuilder(index, lectures); - } + return reorderedDates.map((day) { + final lectures = lecturesOfDay(widget.lectures, day); + final index = day.weekday % 7; // Map Sunday (7) to 0, etc. + + return lectures.isEmpty + ? emptyDayColumn(context, index) + : dayColumnBuilder(index, lectures); }).toList(); } @@ -238,9 +198,7 @@ class SchedulePageViewState extends State final weekday = Provider.of(context).getWeekdaysWithLocale()[day]; - final noClassesText = day >= DateTime.saturday - 1 - ? S.of(context).no_classes_on_weekend - : S.of(context).no_classes_on; + final noClassesText = S.of(context).no_classes_on; return Center( child: ImageLabel( From 77e8c91cd8131f9669703ac680a5d662795a0d53 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Sun, 19 Jan 2025 00:27:15 +0000 Subject: [PATCH 06/18] Tab day correctly associated to the corresponding week day content --- .../lib/view/academic_path/schedule_page.dart | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 141b75510..999dcb2fb 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -164,19 +164,22 @@ class SchedulePageViewState extends State } List createTabViewBuilder(BuildContext context) { - return reorderedDates.map((day) { + return List.generate(7, (index) { + // Use reorderedDates[index] directly for alignment + final day = reorderedDates[index]; final lectures = lecturesOfDay(widget.lectures, day); - final index = day.weekday % 7; // Map Sunday (7) to 0, etc. return lectures.isEmpty - ? emptyDayColumn(context, index) - : dayColumnBuilder(index, lectures); - }).toList(); + ? emptyDayColumn(context, day) // Pass the exact reordered date + : dayColumnBuilder(day, lectures); // Pass the exact reordered date + }); } - Widget dayColumnBuilder(int day, List lectures) { + Widget dayColumnBuilder(DateTime day, List lectures) { return ListView( - key: Key('schedule-page-day-column-$day'), + key: Key( + 'schedule-page-day-column-${day.weekday}', + ), // Use day.weekday as identifier children: lectures .map( (lecture) => ScheduleSlot( @@ -194,16 +197,18 @@ class SchedulePageViewState extends State ); } - Widget emptyDayColumn(BuildContext context, int day) { - final weekday = - Provider.of(context).getWeekdaysWithLocale()[day]; + Widget emptyDayColumn(BuildContext context, DateTime day) { + final daysOfTheWeek = + Provider.of(context).getWeekdaysWithLocale(); + final weekdayName = + daysOfTheWeek[(day.weekday - 1) % 7]; // Adjust to zero-based index final noClassesText = S.of(context).no_classes_on; return Center( child: ImageLabel( imagePath: 'assets/images/schedule.png', - label: '$noClassesText $weekday.', + label: '$noClassesText $weekdayName.', labelTextStyle: const TextStyle(fontSize: 15), ), ); From db2085ded225e8fdb13ac0e67867736cf6ab79c7 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Sun, 19 Jan 2025 00:46:31 +0000 Subject: [PATCH 07/18] Week day tab bar complete --- .../lib/view/academic_path/schedule_page.dart | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 999dcb2fb..e238e452a 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -60,8 +60,18 @@ class SchedulePageViewState extends State @override void initState() { super.initState(); - tabController = TabController(vsync: this, length: 7); // Fixed to 7 days reorderedDates = _getReorderedWeekDates(widget.currentWeek.start); + final currentDayIndex = reorderedDates.indexWhere( + (date) => + date.day == widget.currentWeek.start.day && + date.month == widget.currentWeek.start.month && + date.year == widget.currentWeek.start.year, + ); + tabController = TabController( + vsync: this, + length: 7, + initialIndex: currentDayIndex >= 0 ? currentDayIndex : 0, + ); } @override @@ -92,16 +102,10 @@ class SchedulePageViewState extends State final isSelected = tabController.index == index; return Tab( key: Key('schedule-page-tab-$index'), - height: 50, - child: Container( - width: 45, - height: 50, - decoration: BoxDecoration( - color: isSelected - ? const Color.fromRGBO(177, 77, 84, 0.25) - : Theme.of(context).scaffoldBackgroundColor, - borderRadius: BorderRadius.circular(10), - ), + height: 45, + child: SizedBox( + width: 35, + height: 40, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -165,13 +169,12 @@ class SchedulePageViewState extends State List createTabViewBuilder(BuildContext context) { return List.generate(7, (index) { - // Use reorderedDates[index] directly for alignment final day = reorderedDates[index]; final lectures = lecturesOfDay(widget.lectures, day); return lectures.isEmpty - ? emptyDayColumn(context, day) // Pass the exact reordered date - : dayColumnBuilder(day, lectures); // Pass the exact reordered date + ? emptyDayColumn(context, day) + : dayColumnBuilder(day, lectures); }); } @@ -179,7 +182,7 @@ class SchedulePageViewState extends State return ListView( key: Key( 'schedule-page-day-column-${day.weekday}', - ), // Use day.weekday as identifier + ), children: lectures .map( (lecture) => ScheduleSlot( @@ -200,8 +203,7 @@ class SchedulePageViewState extends State Widget emptyDayColumn(BuildContext context, DateTime day) { final daysOfTheWeek = Provider.of(context).getWeekdaysWithLocale(); - final weekdayName = - daysOfTheWeek[(day.weekday - 1) % 7]; // Adjust to zero-based index + final weekdayName = daysOfTheWeek[(day.weekday - 1) % 7]; final noClassesText = S.of(context).no_classes_on; From 2f95f6c45d55b3ca34b652bc0ee74914946dca8b Mon Sep 17 00:00:00 2001 From: Granja5 Date: Mon, 20 Jan 2025 14:46:01 +0000 Subject: [PATCH 08/18] Small visual changes to the tab bar --- .../lib/view/academic_path/schedule_page.dart | 71 ++----------------- 1 file changed, 7 insertions(+), 64 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index e238e452a..b23eaae2c 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -52,32 +52,13 @@ class SchedulePageView extends StatefulWidget { SchedulePageViewState createState() => SchedulePageViewState(); } -class SchedulePageViewState extends State - with SingleTickerProviderStateMixin { - late TabController tabController; +class SchedulePageViewState extends State { late List reorderedDates; @override void initState() { super.initState(); reorderedDates = _getReorderedWeekDates(widget.currentWeek.start); - final currentDayIndex = reorderedDates.indexWhere( - (date) => - date.day == widget.currentWeek.start.day && - date.month == widget.currentWeek.start.month && - date.year == widget.currentWeek.start.year, - ); - tabController = TabController( - vsync: this, - length: 7, - initialIndex: currentDayIndex >= 0 ? currentDayIndex : 0, - ); - } - - @override - void dispose() { - tabController.dispose(); - super.dispose(); } @override @@ -99,37 +80,22 @@ class SchedulePageViewState extends State ]; return List.generate(7, (index) { - final isSelected = tabController.index == index; return Tab( key: Key('schedule-page-tab-$index'), - height: 45, + height: 35, child: SizedBox( - width: 35, - height: 40, + width: 26, + height: 35, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - toShortVersion(reorderedDaysOfTheWeek[index]), - style: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 12, - color: isSelected - ? const Color.fromRGBO(102, 9, 16, 1) - : const Color.fromRGBO(48, 48, 48, 1), - ), + reorderedDaysOfTheWeek[index].substring(0, 3), + style: Theme.of(context).textTheme.bodySmall, ), Text( '${reorderedDates[index].day}', - style: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 12, - color: isSelected - ? const Color.fromRGBO(102, 9, 16, 1) - : const Color.fromRGBO(48, 48, 48, 1), - ), + style: Theme.of(context).textTheme.bodySmall, ), ], ), @@ -138,29 +104,6 @@ class SchedulePageViewState extends State }); } - String toShortVersion(String dayOfTheWeek) { - String shortVersion; - switch (dayOfTheWeek) { - case 'Monday': - shortVersion = 'Mon'; - case 'Tuesday': - shortVersion = 'Tue'; - case 'Wednesday': - shortVersion = 'Wed'; - case 'Thursday': - shortVersion = 'Thu'; - case 'Friday': - shortVersion = 'Fri'; - case 'Saturday': - shortVersion = 'Sat'; - case 'Sunday': - shortVersion = 'Sun'; - default: - shortVersion = 'Blank'; - } - return shortVersion; - } - List _getReorderedWeekDates(DateTime startOfWeek) { final sunday = startOfWeek.subtract(Duration(days: startOfWeek.weekday % 7)); From 4891c3c7eada52393086a47412ed21f6f500972d Mon Sep 17 00:00:00 2001 From: Granja5 Date: Mon, 20 Jan 2025 22:44:00 +0000 Subject: [PATCH 09/18] Adding the new Schedule Cards --- .../lib/view/academic_path/schedule_page.dart | 77 ++++++++++++++++--- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index b23eaae2c..d25fc1769 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -7,7 +7,7 @@ import 'package:uni/model/utils/time/week.dart'; import 'package:uni/view/common_widgets/expanded_image_label.dart'; import 'package:uni/view/lazy_consumer.dart'; import 'package:uni/view/locale_notifier.dart'; -import 'package:uni/view/schedule/widgets/schedule_slot.dart'; +import 'package:uni_ui/cards/schedule_card.dart'; import 'package:uni_ui/timeline/timeline.dart'; class SchedulePage extends StatefulWidget { @@ -113,7 +113,8 @@ class SchedulePageViewState extends State { List createTabViewBuilder(BuildContext context) { return List.generate(7, (index) { final day = reorderedDates[index]; - final lectures = lecturesOfDay(widget.lectures, day); + final lectures = + getMockLectures(); // lecturesOfDay(widget.lectures, day); return lectures.isEmpty ? emptyDayColumn(context, day) @@ -122,27 +123,34 @@ class SchedulePageViewState extends State { } Widget dayColumnBuilder(DateTime day, List lectures) { - return ListView( + return Column( key: Key( 'schedule-page-day-column-${day.weekday}', ), children: lectures .map( - (lecture) => ScheduleSlot( - subject: lecture.subject, - typeClass: lecture.typeClass, - rooms: lecture.room, - begin: lecture.startTime, - end: lecture.endTime, - occurrId: lecture.occurrId, - teacher: lecture.teacher, - classNumber: lecture.classNumber, + (lecture) => ScheduleCard( + name: lecture.subject, + acronym: _getAcronym(lecture.subject), + room: lecture.room, + type: lecture.typeClass, + isActive: _isLectureActive(lecture), + teacherName: lecture.teacher, ), ) .toList(), ); } + String _getAcronym(String subject) { + return subject.split(' ').map((word) => word[0]).join().toUpperCase(); + } + + bool _isLectureActive(Lecture lecture) { + final now = DateTime.now(); + return now.isAfter(lecture.startTime) && now.isBefore(lecture.endTime); + } + Widget emptyDayColumn(BuildContext context, DateTime day) { final daysOfTheWeek = Provider.of(context).getWeekdaysWithLocale(); @@ -172,3 +180,48 @@ class SchedulePageViewState extends State { return filteredLectures; } } + +List getMockLectures() { + return [ + Lecture( + 'Mathematics', + 'Lecture', + DateTime.now().subtract(const Duration(hours: 1)), + DateTime.now().add(const Duration(hours: 1)), + '101', + 'Dr. Smith', + 'Class 1', + 1, + ), + Lecture( + 'Physics', + 'Lecture', + DateTime.now().add(const Duration(hours: 2)), + DateTime.now().add(const Duration(hours: 3)), + '102', + 'Dr. Johnson', + 'Class 2', + 2, + ), + Lecture( + 'Chemistry', + 'Lab', + DateTime.now().add(const Duration(days: 1, hours: 1)), + DateTime.now().add(const Duration(days: 1, hours: 2)), + 'Lab 201', + 'Dr. Brown', + 'Class 3', + 3, + ), + Lecture( + 'Biology', + 'Lecture', + DateTime.now().add(const Duration(days: 2, hours: 3)), + DateTime.now().add(const Duration(days: 2, hours: 4)), + '103', + 'Dr. Taylor', + 'Class 4', + 4, + ), + ]; +} From 1fd369d2898536dd4ea7847c73e2a268cb6ad47e Mon Sep 17 00:00:00 2001 From: Granja5 Date: Tue, 21 Jan 2025 00:03:01 +0000 Subject: [PATCH 10/18] Card Timeline Mock --- .../lib/view/academic_path/schedule_page.dart | 117 +++++++++--------- .../widgets/academic_schedule_card.dart | 76 ++++++++++++ 2 files changed, 133 insertions(+), 60 deletions(-) create mode 100644 packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index d25fc1769..8a37ee299 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/lecture.dart'; import 'package:uni/model/providers/lazy/lecture_provider.dart'; import 'package:uni/model/utils/time/week.dart'; -import 'package:uni/view/common_widgets/expanded_image_label.dart'; +import 'package:uni/view/academic_path/widgets/academic_schedule_card.dart'; import 'package:uni/view/lazy_consumer.dart'; import 'package:uni/view/locale_notifier.dart'; -import 'package:uni_ui/cards/schedule_card.dart'; import 'package:uni_ui/timeline/timeline.dart'; class SchedulePage extends StatefulWidget { @@ -29,10 +27,13 @@ class SchedulePageState extends State { await context.read().forceRefresh(context); }, child: LazyConsumer>( - builder: (context, lectures) => SchedulePageView( - lectures, - now: widget.now, - ), + builder: (context, lectures) { + final mockLectures = getMockLectures(); + return SchedulePageView( + mockLectures, + now: widget.now, + ); + }, hasContent: (lectures) => lectures.isNotEmpty, onNullContent: SchedulePageView(const [], now: widget.now), ), @@ -113,61 +114,17 @@ class SchedulePageViewState extends State { List createTabViewBuilder(BuildContext context) { return List.generate(7, (index) { final day = reorderedDates[index]; - final lectures = - getMockLectures(); // lecturesOfDay(widget.lectures, day); + final lectures = lecturesOfDay(widget.lectures, day); - return lectures.isEmpty - ? emptyDayColumn(context, day) - : dayColumnBuilder(day, lectures); + return ScheduleDayTimeline( + key: Key('schedule-page-day-view-${day.weekday}'), + day: day, + lectures: lectures, + ); }); } - Widget dayColumnBuilder(DateTime day, List lectures) { - return Column( - key: Key( - 'schedule-page-day-column-${day.weekday}', - ), - children: lectures - .map( - (lecture) => ScheduleCard( - name: lecture.subject, - acronym: _getAcronym(lecture.subject), - room: lecture.room, - type: lecture.typeClass, - isActive: _isLectureActive(lecture), - teacherName: lecture.teacher, - ), - ) - .toList(), - ); - } - - String _getAcronym(String subject) { - return subject.split(' ').map((word) => word[0]).join().toUpperCase(); - } - - bool _isLectureActive(Lecture lecture) { - final now = DateTime.now(); - return now.isAfter(lecture.startTime) && now.isBefore(lecture.endTime); - } - - Widget emptyDayColumn(BuildContext context, DateTime day) { - final daysOfTheWeek = - Provider.of(context).getWeekdaysWithLocale(); - final weekdayName = daysOfTheWeek[(day.weekday - 1) % 7]; - - final noClassesText = S.of(context).no_classes_on; - - return Center( - child: ImageLabel( - imagePath: 'assets/images/schedule.png', - label: '$noClassesText $weekdayName.', - labelTextStyle: const TextStyle(fontSize: 15), - ), - ); - } - - static List lecturesOfDay(List lectures, DateTime day) { + List lecturesOfDay(List lectures, DateTime day) { final filteredLectures = []; for (var i = 0; i < lectures.length; i++) { final lecture = lectures[i]; @@ -206,8 +163,8 @@ List getMockLectures() { Lecture( 'Chemistry', 'Lab', - DateTime.now().add(const Duration(days: 1, hours: 1)), - DateTime.now().add(const Duration(days: 1, hours: 2)), + DateTime.now().add(const Duration(hours: 5)), + DateTime.now().add(const Duration(hours: 6)), 'Lab 201', 'Dr. Brown', 'Class 3', @@ -223,5 +180,45 @@ List getMockLectures() { 'Class 4', 4, ), + Lecture( + 'Computer Science', + 'Lecture', + DateTime.now().add(const Duration(days: 3, hours: 4)), + DateTime.now().add(const Duration(days: 3, hours: 5)), + '104', + 'Dr. Martinez', + 'Class 5', + 5, + ), + Lecture( + 'Philosophy', + 'Lecture', + DateTime.now().add(const Duration(days: 4, hours: 5)), + DateTime.now().add(const Duration(days: 4, hours: 6)), + '105', + 'Dr. Lee', + 'Class 6', + 6, + ), + Lecture( + 'History', + 'Lecture', + DateTime.now().add(const Duration(days: 5, hours: 6)), + DateTime.now().add(const Duration(days: 5, hours: 7)), + '106', + 'Dr. Williams', + 'Class 7', + 7, + ), + Lecture( + 'Geography', + 'Lab', + DateTime.now().add(const Duration(days: 6, hours: 7)), + DateTime.now().add(const Duration(days: 6, hours: 8)), + '107', + 'Dr. Harris', + 'Class 8', + 8, + ), ]; } diff --git a/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart b/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart new file mode 100644 index 000000000..db539b250 --- /dev/null +++ b/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:uni/model/entities/lecture.dart'; +import 'package:uni_ui/cards/schedule_card.dart'; +import 'package:uni_ui/cards/timeline_card.dart'; + +class ScheduleDayTimeline extends StatelessWidget { + const ScheduleDayTimeline({ + super.key, + required this.day, + required this.lectures, + }); + + final List lectures; + final DateTime day; + + @override + Widget build(BuildContext context) { + if (lectures.isEmpty) { + return Center( + child: Text( + DateFormat('EEEE, d MMMM').format(day), + style: Theme.of(context).textTheme.titleLarge, + ), + ); + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + DateFormat('EEEE, d MMMM').format(day), + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 8), + ..._buildTimelineItems(lectures), + ], + ), + ); + } + + List _buildTimelineItems(List lectures) { + return lectures + .map( + (lecture) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: TimelineItem( + isActive: _isLectureActive(lecture), + title: DateFormat('HH:mm').format(lecture.startTime), + subtitle: DateFormat('HH:mm').format(lecture.endTime), + card: ScheduleCard( + isActive: _isLectureActive(lecture), + name: lecture.subject, + acronym: _getAcronym(lecture.subject), + room: lecture.room, + type: lecture.typeClass, + teacherName: lecture.teacher, + ), + ), + ), + ) + .toList(); + } + + String _getAcronym(String subject) { + return subject.split(' ').map((word) => word[0]).join().toUpperCase(); + } + + bool _isLectureActive(Lecture lecture) { + final now = DateTime.now(); + return now.isAfter(lecture.startTime) && now.isBefore(lecture.endTime); + } +} From 8f178d14d83cb7b0e3325fc9dde36f14df98a563 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Tue, 21 Jan 2025 02:13:38 +0000 Subject: [PATCH 11/18] Reformatted the Daily Cards Association and Created a Specific Timeline for the Schedule Page to allow for the needed requirements. --- .../lib/view/academic_path/schedule_page.dart | 14 +- .../widgets/academic_schedule_card.dart | 15 +- .../lib/timeline/schedule_timeline.dart | 144 ++++++++++++++++++ 3 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 packages/uni_ui/lib/timeline/schedule_timeline.dart diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 8a37ee299..576e10b7f 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -6,7 +6,7 @@ import 'package:uni/model/utils/time/week.dart'; import 'package:uni/view/academic_path/widgets/academic_schedule_card.dart'; import 'package:uni/view/lazy_consumer.dart'; import 'package:uni/view/locale_notifier.dart'; -import 'package:uni_ui/timeline/timeline.dart'; +import 'package:uni_ui/timeline/schedule_timeline.dart'; class SchedulePage extends StatefulWidget { SchedulePage({super.key, DateTime? now}) : now = now ?? DateTime.now(); @@ -55,18 +55,28 @@ class SchedulePageView extends StatefulWidget { class SchedulePageViewState extends State { late List reorderedDates; + late int initialTabIndex; @override void initState() { super.initState(); reorderedDates = _getReorderedWeekDates(widget.currentWeek.start); + final today = widget.currentWeek.start; + + initialTabIndex = reorderedDates.indexWhere( + (date) => + date.year == today.year && + date.month == today.month && + date.day == today.day, + ); } @override Widget build(BuildContext context) { - return Timeline( + return ScheduleTimeline( tabs: createTabs(context), content: createTabViewBuilder(context), + initialTabIndex: initialTabIndex, ); } diff --git a/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart b/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart index db539b250..952b83d65 100644 --- a/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart +++ b/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart @@ -17,25 +17,20 @@ class ScheduleDayTimeline extends StatelessWidget { @override Widget build(BuildContext context) { if (lectures.isEmpty) { - return Center( - child: Text( - DateFormat('EEEE, d MMMM').format(day), - style: Theme.of(context).textTheme.titleLarge, - ), - ); + return const SizedBox.shrink(); } return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( DateFormat('EEEE, d MMMM').format(day), - style: Theme.of(context).textTheme.headlineSmall, + style: Theme.of(context).textTheme.headlineLarge, ), - const SizedBox(height: 8), + const SizedBox(height: 12), ..._buildTimelineItems(lectures), ], ), @@ -46,7 +41,7 @@ class ScheduleDayTimeline extends StatelessWidget { return lectures .map( (lecture) => Padding( - padding: const EdgeInsets.symmetric(vertical: 4), + padding: EdgeInsets.zero, child: TimelineItem( isActive: _isLectureActive(lecture), title: DateFormat('HH:mm').format(lecture.startTime), diff --git a/packages/uni_ui/lib/timeline/schedule_timeline.dart b/packages/uni_ui/lib/timeline/schedule_timeline.dart new file mode 100644 index 000000000..2b7e98988 --- /dev/null +++ b/packages/uni_ui/lib/timeline/schedule_timeline.dart @@ -0,0 +1,144 @@ +import 'package:figma_squircle/figma_squircle.dart'; +import 'package:flutter/material.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; + +class ScheduleTimeline extends StatefulWidget { + const ScheduleTimeline({ + required this.tabs, + required this.content, + required this.initialTabIndex, + super.key, + }); + + final List tabs; + final List content; + final int initialTabIndex; + + @override + State createState() => _ScheduleTimelineState(); +} + +class _ScheduleTimelineState extends State { + late int _currentIndex; + final ItemScrollController _itemScrollController = ItemScrollController(); + final ItemPositionsListener _itemPositionsListener = + ItemPositionsListener.create(); + final ScrollController _tabScrollController = ScrollController(); + final List _tabKeys = []; + + @override + void initState() { + super.initState(); + _currentIndex = widget.initialTabIndex; + + _tabKeys.addAll(List.generate(widget.tabs.length, (index) => GlobalKey())); + + _itemPositionsListener.itemPositions.addListener(() { + final positions = _itemPositionsListener.itemPositions.value; + if (positions.isNotEmpty) { + final firstVisibleIndex = positions + .where((ItemPosition position) => position.itemLeadingEdge >= 0) + .reduce((ItemPosition current, ItemPosition next) => + current.itemLeadingEdge < next.itemLeadingEdge ? current : next) + .index; + + if (_currentIndex != firstVisibleIndex) { + setState(() { + _currentIndex = firstVisibleIndex; + }); + + _scrollToCenterTab(firstVisibleIndex); + } + } + }); + } + + @override + void dispose() { + _tabScrollController.dispose(); + super.dispose(); + } + + void _onTabTapped(int index) { + _itemScrollController.scrollTo( + index: index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + _scrollToCenterTab(index); + } + + void _scrollToCenterTab(int index) { + final screenWidth = MediaQuery.of(context).size.width; + final RenderBox tabBox = + _tabKeys[index].currentContext!.findRenderObject() as RenderBox; + + final tabWidth = tabBox.size.width; + final offset = (_tabScrollController.offset + + tabBox.localToGlobal(Offset.zero).dx + + (tabWidth / 2) - + (screenWidth / 2)) + .clamp( + 0.0, + _tabScrollController.position.maxScrollExtent, + ); + + _tabScrollController.animateTo( + offset, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: _tabScrollController, + child: Row( + children: widget.tabs.asMap().entries.map((entry) { + int index = entry.key; + Widget tab = entry.value; + return GestureDetector( + onTap: () => _onTabTapped(index), + child: Padding( + padding: const EdgeInsets.all(7.0), + child: ClipSmoothRect( + radius: SmoothBorderRadius( + cornerRadius: 10, + cornerSmoothing: 1, + ), + child: Container( + key: _tabKeys[index], + padding: const EdgeInsets.symmetric( + vertical: 10.0, horizontal: 15.0), + color: _currentIndex == index + ? Theme.of(context) + .colorScheme + .tertiary + .withOpacity(0.25) + : Colors.transparent, + child: tab, + ), + ), + ), + ); + }).toList(), + ), + ), + Expanded( + child: ScrollablePositionedList.builder( + itemCount: widget.content.length, + itemScrollController: _itemScrollController, + itemPositionsListener: _itemPositionsListener, + itemBuilder: (context, index) { + return widget.content[index]; + }, + ), + ), + ], + ); + } +} From 007b0e165fa382b09008e14ba0da41df42c99349 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Tue, 21 Jan 2025 03:12:20 +0000 Subject: [PATCH 12/18] Visually almost complete --- .../lib/view/academic_path/schedule_page.dart | 62 +++++++++---------- .../widgets/academic_schedule_card.dart | 9 ++- .../lib/timeline/schedule_timeline.dart | 16 ++++- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 576e10b7f..2ee3c224d 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -102,11 +102,9 @@ class SchedulePageViewState extends State { children: [ Text( reorderedDaysOfTheWeek[index].substring(0, 3), - style: Theme.of(context).textTheme.bodySmall, ), Text( '${reorderedDates[index].day}', - style: Theme.of(context).textTheme.bodySmall, ), ], ), @@ -151,38 +149,38 @@ class SchedulePageViewState extends State { List getMockLectures() { return [ Lecture( - 'Mathematics', - 'Lecture', + 'Fundamentos de Segurança Informática', + 'T', + DateTime.now().subtract(const Duration(hours: 2)), DateTime.now().subtract(const Duration(hours: 1)), - DateTime.now().add(const Duration(hours: 1)), - '101', + 'B101', 'Dr. Smith', 'Class 1', 1, ), Lecture( - 'Physics', - 'Lecture', - DateTime.now().add(const Duration(hours: 2)), - DateTime.now().add(const Duration(hours: 3)), - '102', + 'Fundamentos de Segurança Informática', + 'TP', + DateTime.now().add(Duration.zero), + DateTime.now().add(const Duration(hours: 1)), + 'B102', 'Dr. Johnson', 'Class 2', 2, ), Lecture( - 'Chemistry', - 'Lab', + 'Interação Pessoa Computador', + 'T', DateTime.now().add(const Duration(hours: 5)), DateTime.now().add(const Duration(hours: 6)), - 'Lab 201', + 'B201', 'Dr. Brown', 'Class 3', 3, ), Lecture( - 'Biology', - 'Lecture', + 'Interação Pessoa Computador', + 'TP', DateTime.now().add(const Duration(days: 2, hours: 3)), DateTime.now().add(const Duration(days: 2, hours: 4)), '103', @@ -191,41 +189,41 @@ List getMockLectures() { 4, ), Lecture( - 'Computer Science', - 'Lecture', + 'Laboratório de Bases de Dados e Aplicações Web', + 'T', DateTime.now().add(const Duration(days: 3, hours: 4)), DateTime.now().add(const Duration(days: 3, hours: 5)), - '104', + 'B104', 'Dr. Martinez', 'Class 5', 5, ), Lecture( - 'Philosophy', - 'Lecture', + 'Programação Funcional e em Lógica', + 'TP', DateTime.now().add(const Duration(days: 4, hours: 5)), DateTime.now().add(const Duration(days: 4, hours: 6)), - '105', + 'B105', 'Dr. Lee', 'Class 6', 6, ), Lecture( - 'History', - 'Lecture', - DateTime.now().add(const Duration(days: 5, hours: 6)), - DateTime.now().add(const Duration(days: 5, hours: 7)), - '106', + 'Redes de Computadores', + 'TP', + DateTime.now().subtract(const Duration(days: 1, hours: 1)), + DateTime.now().subtract(const Duration(days: 1)), + 'B106', 'Dr. Williams', 'Class 7', 7, ), Lecture( - 'Geography', - 'Lab', - DateTime.now().add(const Duration(days: 6, hours: 7)), - DateTime.now().add(const Duration(days: 6, hours: 8)), - '107', + 'Redes de Computadores', + 'T', + DateTime.now().add(const Duration(days: 4, hours: 7)), + DateTime.now().add(const Duration(days: 4, hours: 8)), + 'B107', 'Dr. Harris', 'Class 8', 8, diff --git a/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart b/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart index 952b83d65..cd05a63f5 100644 --- a/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart +++ b/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart @@ -21,7 +21,7 @@ class ScheduleDayTimeline extends StatelessWidget { } return Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), + padding: const EdgeInsets.fromLTRB(24, 8, 24, 16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -61,7 +61,12 @@ class ScheduleDayTimeline extends StatelessWidget { } String _getAcronym(String subject) { - return subject.split(' ').map((word) => word[0]).join().toUpperCase(); + return subject + .split(' ') + .where((word) => word.length >= 3) + .map((word) => word[0]) + .join() + .toUpperCase(); } bool _isLectureActive(Lecture lecture) { diff --git a/packages/uni_ui/lib/timeline/schedule_timeline.dart b/packages/uni_ui/lib/timeline/schedule_timeline.dart index 2b7e98988..87b7e1ca5 100644 --- a/packages/uni_ui/lib/timeline/schedule_timeline.dart +++ b/packages/uni_ui/lib/timeline/schedule_timeline.dart @@ -42,7 +42,8 @@ class _ScheduleTimelineState extends State { current.itemLeadingEdge < next.itemLeadingEdge ? current : next) .index; - if (_currentIndex != firstVisibleIndex) { + if (_currentIndex != firstVisibleIndex && + firstVisibleIndex >= widget.initialTabIndex) { setState(() { _currentIndex = firstVisibleIndex; }); @@ -101,6 +102,8 @@ class _ScheduleTimelineState extends State { children: widget.tabs.asMap().entries.map((entry) { int index = entry.key; Widget tab = entry.value; + bool isSelected = _currentIndex == index; + TextStyle textStyle = Theme.of(context).textTheme.bodySmall!; return GestureDetector( onTap: () => _onTabTapped(index), child: Padding( @@ -114,13 +117,20 @@ class _ScheduleTimelineState extends State { key: _tabKeys[index], padding: const EdgeInsets.symmetric( vertical: 10.0, horizontal: 15.0), - color: _currentIndex == index + color: isSelected ? Theme.of(context) .colorScheme .tertiary .withOpacity(0.25) : Colors.transparent, - child: tab, + child: DefaultTextStyle( + style: textStyle.copyWith( + color: isSelected + ? Theme.of(context).colorScheme.primary + : Colors.black, + ), + child: tab, + ), ), ), ), From 8652802618e91b1e64f458fbdece94bde173e15a Mon Sep 17 00:00:00 2001 From: Granja5 Date: Tue, 21 Jan 2025 04:31:31 +0000 Subject: [PATCH 13/18] "No classes for the week" image added and tab bar now fits the screen --- .../lib/view/academic_path/schedule_page.dart | 41 ++++++++++++++----- .../lib/timeline/schedule_timeline.dart | 5 +-- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 2ee3c224d..7a831f3c7 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -4,6 +4,7 @@ import 'package:uni/model/entities/lecture.dart'; import 'package:uni/model/providers/lazy/lecture_provider.dart'; import 'package:uni/model/utils/time/week.dart'; import 'package:uni/view/academic_path/widgets/academic_schedule_card.dart'; +import 'package:uni/view/common_widgets/expanded_image_label.dart'; import 'package:uni/view/lazy_consumer.dart'; import 'package:uni/view/locale_notifier.dart'; import 'package:uni_ui/timeline/schedule_timeline.dart'; @@ -73,9 +74,12 @@ class SchedulePageViewState extends State { @override Widget build(BuildContext context) { + final noLectures = + lecturesOfWeek(widget.lectures, widget.currentWeek).isEmpty; return ScheduleTimeline( tabs: createTabs(context), - content: createTabViewBuilder(context), + content: + noLectures ? [emptyWeek(context)] : createTabViewBuilder(context), initialTabIndex: initialTabIndex, ); } @@ -132,17 +136,32 @@ class SchedulePageViewState extends State { }); } + List lecturesOfWeek(List lectures, Week currentWeek) { + final startOfWeek = currentWeek.start; + final endOfWeek = startOfWeek.add(const Duration(days: 7)); + return lectures.where((lecture) { + final startTime = lecture.startTime; + return startTime.isAfter(startOfWeek) && startTime.isBefore(endOfWeek); + }).toList(); + } + List lecturesOfDay(List lectures, DateTime day) { - final filteredLectures = []; - for (var i = 0; i < lectures.length; i++) { - final lecture = lectures[i]; - if (lecture.startTime.day == day.day && - lecture.startTime.month == day.month && - lecture.startTime.year == day.year) { - filteredLectures.add(lecture); - } - } - return filteredLectures; + return lectures.where((lecture) { + final startTime = lecture.startTime; + return startTime.year == day.year && + startTime.month == day.month && + startTime.day == day.day; + }).toList(); + } + + Widget emptyWeek(BuildContext context) { + return const Center( + child: ImageLabel( + imagePath: 'assets/images/schedule.png', + label: 'You have no classes this week.', + labelTextStyle: TextStyle(fontSize: 15), + ), + ); } } diff --git a/packages/uni_ui/lib/timeline/schedule_timeline.dart b/packages/uni_ui/lib/timeline/schedule_timeline.dart index 87b7e1ca5..9ea5dc290 100644 --- a/packages/uni_ui/lib/timeline/schedule_timeline.dart +++ b/packages/uni_ui/lib/timeline/schedule_timeline.dart @@ -107,7 +107,7 @@ class _ScheduleTimelineState extends State { return GestureDetector( onTap: () => _onTabTapped(index), child: Padding( - padding: const EdgeInsets.all(7.0), + padding: const EdgeInsets.all(5.0), child: ClipSmoothRect( radius: SmoothBorderRadius( cornerRadius: 10, @@ -115,8 +115,7 @@ class _ScheduleTimelineState extends State { ), child: Container( key: _tabKeys[index], - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 15.0), + padding: const EdgeInsets.all(10.0), color: isSelected ? Theme.of(context) .colorScheme From 70096c7b9f92dace54e669272acf14abe1a49ea1 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Tue, 21 Jan 2025 05:52:47 +0000 Subject: [PATCH 14/18] Page using Lecture Provider instead of Mock Lecture List --- packages/uni_app/lib/view/academic_path/schedule_page.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 7a831f3c7..638d39652 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -29,9 +29,10 @@ class SchedulePageState extends State { }, child: LazyConsumer>( builder: (context, lectures) { - final mockLectures = getMockLectures(); + /*final mockLectures = + getMockLectures(); */ // since there are no classes, we can use this to test the schedule page populated return SchedulePageView( - mockLectures, + lectures, now: widget.now, ); }, @@ -165,6 +166,7 @@ class SchedulePageViewState extends State { } } +// since there are no classes, we can use this to test the schedule page populated List getMockLectures() { return [ Lecture( From 5f57aa5ceffc13f0cc9d69a7ba7f50aef019dc3c Mon Sep 17 00:00:00 2001 From: Granja5 Date: Tue, 21 Jan 2025 17:52:17 +0000 Subject: [PATCH 15/18] Refactored to use CardTimeline and added the schedule_timeline functionalities to the general timeline. Slight visual improvements. --- .../lib/view/academic_path/schedule_page.dart | 16 +- .../widgets/academic_schedule_card.dart | 31 ++-- packages/uni_ui/lib/cards/timeline_card.dart | 2 + .../lib/timeline/schedule_timeline.dart | 153 ------------------ packages/uni_ui/lib/timeline/timeline.dart | 24 ++- 5 files changed, 43 insertions(+), 183 deletions(-) delete mode 100644 packages/uni_ui/lib/timeline/schedule_timeline.dart diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 638d39652..649ea10eb 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -7,7 +7,7 @@ import 'package:uni/view/academic_path/widgets/academic_schedule_card.dart'; import 'package:uni/view/common_widgets/expanded_image_label.dart'; import 'package:uni/view/lazy_consumer.dart'; import 'package:uni/view/locale_notifier.dart'; -import 'package:uni_ui/timeline/schedule_timeline.dart'; +import 'package:uni_ui/timeline/timeline.dart'; class SchedulePage extends StatefulWidget { SchedulePage({super.key, DateTime? now}) : now = now ?? DateTime.now(); @@ -57,7 +57,7 @@ class SchedulePageView extends StatefulWidget { class SchedulePageViewState extends State { late List reorderedDates; - late int initialTabIndex; + late int initialTab; @override void initState() { @@ -65,7 +65,7 @@ class SchedulePageViewState extends State { reorderedDates = _getReorderedWeekDates(widget.currentWeek.start); final today = widget.currentWeek.start; - initialTabIndex = reorderedDates.indexWhere( + initialTab = reorderedDates.indexWhere( (date) => date.year == today.year && date.month == today.month && @@ -77,11 +77,11 @@ class SchedulePageViewState extends State { Widget build(BuildContext context) { final noLectures = lecturesOfWeek(widget.lectures, widget.currentWeek).isEmpty; - return ScheduleTimeline( + return Timeline( tabs: createTabs(context), content: noLectures ? [emptyWeek(context)] : createTabViewBuilder(context), - initialTabIndex: initialTabIndex, + initialTab: initialTab, ); } @@ -98,10 +98,10 @@ class SchedulePageViewState extends State { return List.generate(7, (index) { return Tab( key: Key('schedule-page-tab-$index'), - height: 35, + height: 32, child: SizedBox( width: 26, - height: 35, + height: 32, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -166,7 +166,7 @@ class SchedulePageViewState extends State { } } -// since there are no classes, we can use this to test the schedule page populated +// Since there are no classes, we can use this to test the schedule page populated List getMockLectures() { return [ Lecture( diff --git a/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart b/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart index cd05a63f5..891295747 100644 --- a/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart +++ b/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart @@ -21,7 +21,7 @@ class ScheduleDayTimeline extends StatelessWidget { } return Padding( - padding: const EdgeInsets.fromLTRB(24, 8, 24, 16), + padding: const EdgeInsets.fromLTRB(24, 16, 24, 8), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -30,30 +30,27 @@ class ScheduleDayTimeline extends StatelessWidget { DateFormat('EEEE, d MMMM').format(day), style: Theme.of(context).textTheme.headlineLarge, ), - const SizedBox(height: 12), - ..._buildTimelineItems(lectures), + const SizedBox(height: 14), + CardTimeline(items: _buildTimelineItems(lectures)), ], ), ); } - List _buildTimelineItems(List lectures) { + List _buildTimelineItems(List lectures) { return lectures .map( - (lecture) => Padding( - padding: EdgeInsets.zero, - child: TimelineItem( + (lecture) => TimelineItem( + isActive: _isLectureActive(lecture), + title: DateFormat('HH:mm').format(lecture.startTime), + subtitle: DateFormat('HH:mm').format(lecture.endTime), + card: ScheduleCard( isActive: _isLectureActive(lecture), - title: DateFormat('HH:mm').format(lecture.startTime), - subtitle: DateFormat('HH:mm').format(lecture.endTime), - card: ScheduleCard( - isActive: _isLectureActive(lecture), - name: lecture.subject, - acronym: _getAcronym(lecture.subject), - room: lecture.room, - type: lecture.typeClass, - teacherName: lecture.teacher, - ), + name: lecture.subject, + acronym: _getAcronym(lecture.subject), + room: lecture.room, + type: lecture.typeClass, + teacherName: lecture.teacher, ), ), ) diff --git a/packages/uni_ui/lib/cards/timeline_card.dart b/packages/uni_ui/lib/cards/timeline_card.dart index 88e8d705a..1d76d21aa 100644 --- a/packages/uni_ui/lib/cards/timeline_card.dart +++ b/packages/uni_ui/lib/cards/timeline_card.dart @@ -70,6 +70,8 @@ class CardTimeline extends StatelessWidget { @override Widget build(BuildContext context) { return ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), itemCount: items.length, itemBuilder: (context, index) => items[index], ); diff --git a/packages/uni_ui/lib/timeline/schedule_timeline.dart b/packages/uni_ui/lib/timeline/schedule_timeline.dart deleted file mode 100644 index 9ea5dc290..000000000 --- a/packages/uni_ui/lib/timeline/schedule_timeline.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'package:figma_squircle/figma_squircle.dart'; -import 'package:flutter/material.dart'; -import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; - -class ScheduleTimeline extends StatefulWidget { - const ScheduleTimeline({ - required this.tabs, - required this.content, - required this.initialTabIndex, - super.key, - }); - - final List tabs; - final List content; - final int initialTabIndex; - - @override - State createState() => _ScheduleTimelineState(); -} - -class _ScheduleTimelineState extends State { - late int _currentIndex; - final ItemScrollController _itemScrollController = ItemScrollController(); - final ItemPositionsListener _itemPositionsListener = - ItemPositionsListener.create(); - final ScrollController _tabScrollController = ScrollController(); - final List _tabKeys = []; - - @override - void initState() { - super.initState(); - _currentIndex = widget.initialTabIndex; - - _tabKeys.addAll(List.generate(widget.tabs.length, (index) => GlobalKey())); - - _itemPositionsListener.itemPositions.addListener(() { - final positions = _itemPositionsListener.itemPositions.value; - if (positions.isNotEmpty) { - final firstVisibleIndex = positions - .where((ItemPosition position) => position.itemLeadingEdge >= 0) - .reduce((ItemPosition current, ItemPosition next) => - current.itemLeadingEdge < next.itemLeadingEdge ? current : next) - .index; - - if (_currentIndex != firstVisibleIndex && - firstVisibleIndex >= widget.initialTabIndex) { - setState(() { - _currentIndex = firstVisibleIndex; - }); - - _scrollToCenterTab(firstVisibleIndex); - } - } - }); - } - - @override - void dispose() { - _tabScrollController.dispose(); - super.dispose(); - } - - void _onTabTapped(int index) { - _itemScrollController.scrollTo( - index: index, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - _scrollToCenterTab(index); - } - - void _scrollToCenterTab(int index) { - final screenWidth = MediaQuery.of(context).size.width; - final RenderBox tabBox = - _tabKeys[index].currentContext!.findRenderObject() as RenderBox; - - final tabWidth = tabBox.size.width; - final offset = (_tabScrollController.offset + - tabBox.localToGlobal(Offset.zero).dx + - (tabWidth / 2) - - (screenWidth / 2)) - .clamp( - 0.0, - _tabScrollController.position.maxScrollExtent, - ); - - _tabScrollController.animateTo( - offset, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - SingleChildScrollView( - scrollDirection: Axis.horizontal, - controller: _tabScrollController, - child: Row( - children: widget.tabs.asMap().entries.map((entry) { - int index = entry.key; - Widget tab = entry.value; - bool isSelected = _currentIndex == index; - TextStyle textStyle = Theme.of(context).textTheme.bodySmall!; - return GestureDetector( - onTap: () => _onTabTapped(index), - child: Padding( - padding: const EdgeInsets.all(5.0), - child: ClipSmoothRect( - radius: SmoothBorderRadius( - cornerRadius: 10, - cornerSmoothing: 1, - ), - child: Container( - key: _tabKeys[index], - padding: const EdgeInsets.all(10.0), - color: isSelected - ? Theme.of(context) - .colorScheme - .tertiary - .withOpacity(0.25) - : Colors.transparent, - child: DefaultTextStyle( - style: textStyle.copyWith( - color: isSelected - ? Theme.of(context).colorScheme.primary - : Colors.black, - ), - child: tab, - ), - ), - ), - ), - ); - }).toList(), - ), - ), - Expanded( - child: ScrollablePositionedList.builder( - itemCount: widget.content.length, - itemScrollController: _itemScrollController, - itemPositionsListener: _itemPositionsListener, - itemBuilder: (context, index) { - return widget.content[index]; - }, - ), - ), - ], - ); - } -} diff --git a/packages/uni_ui/lib/timeline/timeline.dart b/packages/uni_ui/lib/timeline/timeline.dart index 8ac5b68ed..7337e3eb3 100644 --- a/packages/uni_ui/lib/timeline/timeline.dart +++ b/packages/uni_ui/lib/timeline/timeline.dart @@ -6,18 +6,20 @@ class Timeline extends StatefulWidget { const Timeline({ required this.tabs, required this.content, + required this.initialTab, super.key, }); final List tabs; final List content; + final int initialTab; @override State createState() => _TimelineState(); } class _TimelineState extends State { - int _currentIndex = 0; + late int _currentIndex; final ItemScrollController _itemScrollController = ItemScrollController(); final ItemPositionsListener _itemPositionsListener = ItemPositionsListener.create(); @@ -27,6 +29,7 @@ class _TimelineState extends State { @override void initState() { super.initState(); + _currentIndex = widget.initialTab; _tabKeys.addAll(List.generate(widget.tabs.length, (index) => GlobalKey())); @@ -39,7 +42,8 @@ class _TimelineState extends State { current.itemLeadingEdge < next.itemLeadingEdge ? current : next) .index; - if (_currentIndex != firstVisibleIndex) { + if (_currentIndex != firstVisibleIndex && + firstVisibleIndex >= widget.initialTab) { setState(() { _currentIndex = firstVisibleIndex; }); @@ -98,10 +102,13 @@ class _TimelineState extends State { children: widget.tabs.asMap().entries.map((entry) { int index = entry.key; Widget tab = entry.value; + bool isSelected = _currentIndex == index; + TextStyle textStyle = Theme.of(context).textTheme.bodySmall!; return GestureDetector( onTap: () => _onTabTapped(index), child: Padding( - padding: const EdgeInsets.all(7.0), + padding: const EdgeInsets.symmetric( + vertical: 10.0, horizontal: 5.0), child: ClipSmoothRect( radius: SmoothBorderRadius( cornerRadius: 10, @@ -110,14 +117,21 @@ class _TimelineState extends State { child: Container( key: _tabKeys[index], padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 15.0), + vertical: 9.0, horizontal: 8.0), color: _currentIndex == index ? Theme.of(context) .colorScheme .tertiary .withOpacity(0.25) : Colors.transparent, - child: tab, + child: DefaultTextStyle( + style: textStyle.copyWith( + color: isSelected + ? Theme.of(context).colorScheme.primary + : Colors.black, + ), + child: tab, + ), ), ), ), From 60307bc9d75c2e3fe398366f4152d5e4b4d022e7 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Tue, 21 Jan 2025 22:52:48 +0000 Subject: [PATCH 16/18] refactor: separated the page state, view and helper widget logics into different files. --- .../lib/view/academic_path/schedule_page.dart | 128 +----------------- .../academic_path/widgets/empty_week.dart | 17 +++ .../widgets/schedule_page_view.dart | 119 ++++++++++++++++ 3 files changed, 137 insertions(+), 127 deletions(-) create mode 100644 packages/uni_app/lib/view/academic_path/widgets/empty_week.dart create mode 100644 packages/uni_app/lib/view/academic_path/widgets/schedule_page_view.dart diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 649ea10eb..2bce20ab8 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -2,12 +2,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uni/model/entities/lecture.dart'; import 'package:uni/model/providers/lazy/lecture_provider.dart'; -import 'package:uni/model/utils/time/week.dart'; -import 'package:uni/view/academic_path/widgets/academic_schedule_card.dart'; -import 'package:uni/view/common_widgets/expanded_image_label.dart'; +import 'package:uni/view/academic_path/widgets/schedule_page_view.dart'; import 'package:uni/view/lazy_consumer.dart'; -import 'package:uni/view/locale_notifier.dart'; -import 'package:uni_ui/timeline/timeline.dart'; class SchedulePage extends StatefulWidget { SchedulePage({super.key, DateTime? now}) : now = now ?? DateTime.now(); @@ -44,128 +40,6 @@ class SchedulePageState extends State { } } -class SchedulePageView extends StatefulWidget { - SchedulePageView(this.lectures, {required DateTime now, super.key}) - : currentWeek = Week(start: now); - - final List lectures; - final Week currentWeek; - - @override - SchedulePageViewState createState() => SchedulePageViewState(); -} - -class SchedulePageViewState extends State { - late List reorderedDates; - late int initialTab; - - @override - void initState() { - super.initState(); - reorderedDates = _getReorderedWeekDates(widget.currentWeek.start); - final today = widget.currentWeek.start; - - initialTab = reorderedDates.indexWhere( - (date) => - date.year == today.year && - date.month == today.month && - date.day == today.day, - ); - } - - @override - Widget build(BuildContext context) { - final noLectures = - lecturesOfWeek(widget.lectures, widget.currentWeek).isEmpty; - return Timeline( - tabs: createTabs(context), - content: - noLectures ? [emptyWeek(context)] : createTabViewBuilder(context), - initialTab: initialTab, - ); - } - - List createTabs(BuildContext context) { - final daysOfTheWeek = - Provider.of(context).getWeekdaysWithLocale(); - - // Reorder the days of the week to start with Sunday - final reorderedDaysOfTheWeek = [ - daysOfTheWeek[6], // Sunday (index 6 in default order) - ...daysOfTheWeek.sublist(0, 6), // Monday to Saturday - ]; - - return List.generate(7, (index) { - return Tab( - key: Key('schedule-page-tab-$index'), - height: 32, - child: SizedBox( - width: 26, - height: 32, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - reorderedDaysOfTheWeek[index].substring(0, 3), - ), - Text( - '${reorderedDates[index].day}', - ), - ], - ), - ), - ); - }); - } - - List _getReorderedWeekDates(DateTime startOfWeek) { - final sunday = - startOfWeek.subtract(Duration(days: startOfWeek.weekday % 7)); - return List.generate(7, (index) => sunday.add(Duration(days: index))); - } - - List createTabViewBuilder(BuildContext context) { - return List.generate(7, (index) { - final day = reorderedDates[index]; - final lectures = lecturesOfDay(widget.lectures, day); - - return ScheduleDayTimeline( - key: Key('schedule-page-day-view-${day.weekday}'), - day: day, - lectures: lectures, - ); - }); - } - - List lecturesOfWeek(List lectures, Week currentWeek) { - final startOfWeek = currentWeek.start; - final endOfWeek = startOfWeek.add(const Duration(days: 7)); - return lectures.where((lecture) { - final startTime = lecture.startTime; - return startTime.isAfter(startOfWeek) && startTime.isBefore(endOfWeek); - }).toList(); - } - - List lecturesOfDay(List lectures, DateTime day) { - return lectures.where((lecture) { - final startTime = lecture.startTime; - return startTime.year == day.year && - startTime.month == day.month && - startTime.day == day.day; - }).toList(); - } - - Widget emptyWeek(BuildContext context) { - return const Center( - child: ImageLabel( - imagePath: 'assets/images/schedule.png', - label: 'You have no classes this week.', - labelTextStyle: TextStyle(fontSize: 15), - ), - ); - } -} - // Since there are no classes, we can use this to test the schedule page populated List getMockLectures() { return [ diff --git a/packages/uni_app/lib/view/academic_path/widgets/empty_week.dart b/packages/uni_app/lib/view/academic_path/widgets/empty_week.dart new file mode 100644 index 000000000..40b86cd3c --- /dev/null +++ b/packages/uni_app/lib/view/academic_path/widgets/empty_week.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:uni/view/common_widgets/expanded_image_label.dart'; + +class EmptyWeek extends StatelessWidget { + const EmptyWeek({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: ImageLabel( + imagePath: 'assets/images/schedule.png', + label: 'You have no classes this week.', + labelTextStyle: TextStyle(fontSize: 15), + ), + ); + } +} diff --git a/packages/uni_app/lib/view/academic_path/widgets/schedule_page_view.dart b/packages/uni_app/lib/view/academic_path/widgets/schedule_page_view.dart new file mode 100644 index 000000000..4405b0b4b --- /dev/null +++ b/packages/uni_app/lib/view/academic_path/widgets/schedule_page_view.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:uni/model/entities/lecture.dart'; +import 'package:uni/model/utils/time/week.dart'; +import 'package:uni/view/academic_path/widgets/academic_schedule_card.dart'; +import 'package:uni/view/academic_path/widgets/empty_week.dart'; +import 'package:uni/view/locale_notifier.dart'; +import 'package:uni_ui/timeline/timeline.dart'; + +class SchedulePageView extends StatefulWidget { + SchedulePageView(this.lectures, {required DateTime now, super.key}) + : currentWeek = Week(start: now); + + final List lectures; + final Week currentWeek; + + @override + SchedulePageViewState createState() => SchedulePageViewState(); +} + +class SchedulePageViewState extends State { + late List reorderedDates; + late int initialTab; + + @override + void initState() { + super.initState(); + reorderedDates = _getReorderedWeekDates(widget.currentWeek.start); + final today = widget.currentWeek.start; + + initialTab = reorderedDates.indexWhere( + (date) => + date.year == today.year && + date.month == today.month && + date.day == today.day, + ); + } + + @override + Widget build(BuildContext context) { + final noLectures = + _lecturesOfWeek(widget.lectures, widget.currentWeek).isEmpty; + return Timeline( + tabs: createTabs(context), + content: noLectures ? [const EmptyWeek()] : createTabViewBuilder(context), + initialTab: initialTab, + ); + } + + List createTabs(BuildContext context) { + final daysOfTheWeek = + Provider.of(context).getWeekdaysWithLocale(); + + // Reorder the days of the week to start with Sunday + final reorderedDaysOfTheWeek = [ + daysOfTheWeek[6], // Sunday (index 6 in default order) + ...daysOfTheWeek.sublist(0, 6), // Monday to Saturday + ]; + + return List.generate(7, (index) { + return Tab( + key: Key('schedule-page-tab-$index'), + height: 32, + child: SizedBox( + width: 26, + height: 32, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + reorderedDaysOfTheWeek[index].substring(0, 3), + ), + Text( + '${reorderedDates[index].day}', + ), + ], + ), + ), + ); + }); + } + + List createTabViewBuilder(BuildContext context) { + return List.generate(7, (index) { + final day = reorderedDates[index]; + final lectures = _lecturesOfDay(widget.lectures, day); + + return ScheduleDayTimeline( + key: Key('schedule-page-day-view-${day.weekday}'), + day: day, + lectures: lectures, + ); + }); + } + + List _getReorderedWeekDates(DateTime startOfWeek) { + final sunday = + startOfWeek.subtract(Duration(days: startOfWeek.weekday % 7)); + return List.generate(7, (index) => sunday.add(Duration(days: index))); + } + + List _lecturesOfWeek(List lectures, Week currentWeek) { + final startOfWeek = currentWeek.start; + final endOfWeek = startOfWeek.add(const Duration(days: 7)); + return lectures.where((lecture) { + final startTime = lecture.startTime; + return startTime.isAfter(startOfWeek) && startTime.isBefore(endOfWeek); + }).toList(); + } + + List _lecturesOfDay(List lectures, DateTime day) { + return lectures.where((lecture) { + final startTime = lecture.startTime; + return startTime.year == day.year && + startTime.month == day.month && + startTime.day == day.day; + }).toList(); + } +} From 5ac6615fa36417f6cb2b06687642f0a6b1a999e7 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Thu, 30 Jan 2025 03:46:15 +0000 Subject: [PATCH 17/18] fix: Timeline initial tab and initial content correctly and instantly in sync; changed SchedulePage from StatefulWidget to Stateless. --- .../lib/view/academic_path/schedule_page.dart | 31 +++++++------------ packages/uni_ui/lib/timeline/timeline.dart | 4 +-- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 2bce20ab8..4dfe73833 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -5,16 +5,11 @@ import 'package:uni/model/providers/lazy/lecture_provider.dart'; import 'package:uni/view/academic_path/widgets/schedule_page_view.dart'; import 'package:uni/view/lazy_consumer.dart'; -class SchedulePage extends StatefulWidget { +class SchedulePage extends StatelessWidget { SchedulePage({super.key, DateTime? now}) : now = now ?? DateTime.now(); final DateTime now; - @override - SchedulePageState createState() => SchedulePageState(); -} - -class SchedulePageState extends State { @override Widget build(BuildContext context) { return Scaffold( @@ -25,15 +20,13 @@ class SchedulePageState extends State { }, child: LazyConsumer>( builder: (context, lectures) { - /*final mockLectures = - getMockLectures(); */ // since there are no classes, we can use this to test the schedule page populated return SchedulePageView( - lectures, - now: widget.now, + getMockLectures(), + now: now, ); }, hasContent: (lectures) => lectures.isNotEmpty, - onNullContent: SchedulePageView(const [], now: widget.now), + onNullContent: SchedulePageView(const [], now: now), ), ), ); @@ -66,8 +59,8 @@ List getMockLectures() { Lecture( 'Interação Pessoa Computador', 'T', - DateTime.now().add(const Duration(hours: 5)), - DateTime.now().add(const Duration(hours: 6)), + DateTime.now().subtract(const Duration(hours: 5)), + DateTime.now().subtract(const Duration(hours: 4)), 'B201', 'Dr. Brown', 'Class 3', @@ -76,8 +69,8 @@ List getMockLectures() { Lecture( 'Interação Pessoa Computador', 'TP', - DateTime.now().add(const Duration(days: 2, hours: 3)), - DateTime.now().add(const Duration(days: 2, hours: 4)), + DateTime.now().subtract(const Duration(days: 2, hours: 3)), + DateTime.now().subtract(const Duration(days: 2, hours: 4)), '103', 'Dr. Taylor', 'Class 4', @@ -96,8 +89,8 @@ List getMockLectures() { Lecture( 'Programação Funcional e em Lógica', 'TP', - DateTime.now().add(const Duration(days: 4, hours: 5)), - DateTime.now().add(const Duration(days: 4, hours: 6)), + DateTime.now().add(const Duration(days: 1, hours: 5)), + DateTime.now().add(const Duration(days: 1, hours: 6)), 'B105', 'Dr. Lee', 'Class 6', @@ -116,8 +109,8 @@ List getMockLectures() { Lecture( 'Redes de Computadores', 'T', - DateTime.now().add(const Duration(days: 4, hours: 7)), - DateTime.now().add(const Duration(days: 4, hours: 8)), + DateTime.now().add(const Duration(days: 1, hours: 7)), + DateTime.now().add(const Duration(days: 1, hours: 8)), 'B107', 'Dr. Harris', 'Class 8', diff --git a/packages/uni_ui/lib/timeline/timeline.dart b/packages/uni_ui/lib/timeline/timeline.dart index 7337e3eb3..1f69e68b8 100644 --- a/packages/uni_ui/lib/timeline/timeline.dart +++ b/packages/uni_ui/lib/timeline/timeline.dart @@ -42,8 +42,7 @@ class _TimelineState extends State { current.itemLeadingEdge < next.itemLeadingEdge ? current : next) .index; - if (_currentIndex != firstVisibleIndex && - firstVisibleIndex >= widget.initialTab) { + if (_currentIndex != firstVisibleIndex) { setState(() { _currentIndex = firstVisibleIndex; }); @@ -144,6 +143,7 @@ class _TimelineState extends State { itemCount: widget.content.length, itemScrollController: _itemScrollController, itemPositionsListener: _itemPositionsListener, + initialScrollIndex: _currentIndex, itemBuilder: (context, index) { return widget.content[index]; }, From 54ae46d392640728e5a0ad65d32d22687ac38b60 Mon Sep 17 00:00:00 2001 From: Granja5 Date: Thu, 30 Jan 2025 05:30:48 +0000 Subject: [PATCH 18/18] fix: timeline tab displays today's date correctly when there are no lectures in the week; refactor: renamed academic_schedule_card.dart to schedule_day_timeline.dart for consistency. --- packages/uni_app/lib/view/academic_path/schedule_page.dart | 2 +- .../{academic_schedule_card.dart => schedule_day_timeline.dart} | 2 +- .../lib/view/academic_path/widgets/schedule_page_view.dart | 2 +- packages/uni_ui/lib/timeline/timeline.dart | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename packages/uni_app/lib/view/academic_path/widgets/{academic_schedule_card.dart => schedule_day_timeline.dart} (97%) diff --git a/packages/uni_app/lib/view/academic_path/schedule_page.dart b/packages/uni_app/lib/view/academic_path/schedule_page.dart index 4dfe73833..ee8832a2b 100644 --- a/packages/uni_app/lib/view/academic_path/schedule_page.dart +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -21,7 +21,7 @@ class SchedulePage extends StatelessWidget { child: LazyConsumer>( builder: (context, lectures) { return SchedulePageView( - getMockLectures(), + getMockLectures(), // change to lectures now: now, ); }, diff --git a/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart b/packages/uni_app/lib/view/academic_path/widgets/schedule_day_timeline.dart similarity index 97% rename from packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart rename to packages/uni_app/lib/view/academic_path/widgets/schedule_day_timeline.dart index 891295747..9e2e16a25 100644 --- a/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart +++ b/packages/uni_app/lib/view/academic_path/widgets/schedule_day_timeline.dart @@ -21,7 +21,7 @@ class ScheduleDayTimeline extends StatelessWidget { } return Padding( - padding: const EdgeInsets.fromLTRB(24, 16, 24, 8), + padding: const EdgeInsets.fromLTRB(24, 8, 24, 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, diff --git a/packages/uni_app/lib/view/academic_path/widgets/schedule_page_view.dart b/packages/uni_app/lib/view/academic_path/widgets/schedule_page_view.dart index 4405b0b4b..fe3b0ad94 100644 --- a/packages/uni_app/lib/view/academic_path/widgets/schedule_page_view.dart +++ b/packages/uni_app/lib/view/academic_path/widgets/schedule_page_view.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uni/model/entities/lecture.dart'; import 'package:uni/model/utils/time/week.dart'; -import 'package:uni/view/academic_path/widgets/academic_schedule_card.dart'; import 'package:uni/view/academic_path/widgets/empty_week.dart'; +import 'package:uni/view/academic_path/widgets/schedule_day_timeline.dart'; import 'package:uni/view/locale_notifier.dart'; import 'package:uni_ui/timeline/timeline.dart'; diff --git a/packages/uni_ui/lib/timeline/timeline.dart b/packages/uni_ui/lib/timeline/timeline.dart index 1f69e68b8..745021071 100644 --- a/packages/uni_ui/lib/timeline/timeline.dart +++ b/packages/uni_ui/lib/timeline/timeline.dart @@ -35,7 +35,7 @@ class _TimelineState extends State { _itemPositionsListener.itemPositions.addListener(() { final positions = _itemPositionsListener.itemPositions.value; - if (positions.isNotEmpty) { + if (positions.isNotEmpty && widget.content.length > 1) { final firstVisibleIndex = positions .where((ItemPosition position) => position.itemLeadingEdge >= 0) .reduce((ItemPosition current, ItemPosition next) =>