From 1ebbced93265fd583b1d98310808be5fa514910a Mon Sep 17 00:00:00 2001 From: thePeras Date: Sun, 3 Nov 2024 09:42:01 +0000 Subject: [PATCH 1/6] Academyc page tabs and exams timeline --- .../lib/view/academic_path/academic_path.dart | 45 ++++-- .../lib/view/academic_path/exam_page.dart | 144 ++++++++++++++++++ 2 files changed, 173 insertions(+), 16 deletions(-) create mode 100644 packages/uni_app/lib/view/academic_path/exam_page.dart 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 243b6aa08..e4e69c7be 100644 --- a/packages/uni_app/lib/view/academic_path/academic_path.dart +++ b/packages/uni_app/lib/view/academic_path/academic_path.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; import 'package:uni/generated/l10n.dart'; import 'package:uni/utils/navigation_items.dart'; -import 'package:uni/view/academic_path/widgets/course_units_card.dart'; -import 'package:uni/view/common_widgets/generic_card.dart'; +import 'package:uni/view/academic_path/exam_page.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/general.dart'; -import 'package:uni/view/home/widgets/exam_card.dart'; -import 'package:uni/view/home/widgets/schedule_card.dart'; class AcademicPathPageView extends StatefulWidget { const AcademicPathPageView({super.key}); @@ -15,28 +12,44 @@ class AcademicPathPageView extends StatefulWidget { } class AcademicPathPageViewState extends GeneralPageViewState { - List academicPathCards = [ - ScheduleCard(), - ExamCard(), - CourseUnitsCard(), - // Add more cards if needed - ]; - @override String? getTitle() => S.of(context).nav_title(NavigationItem.navAcademicPath.route); @override Widget getBody(BuildContext context) { - return ListView( - children: academicPathCards, + return DefaultTabController( + length: 3, + child: Scaffold( + appBar: AppBar( + title: Text( + S.of(context).nav_title(NavigationItem.navAcademicPath.route), + ), + bottom: const TabBar( + tabs: [ + Tab(text: 'Schedule'), + Tab(text: 'Exams'), + Tab(text: 'Courses'), + ], + ), + ), + body: const TabBarView( + children: [ + Center( + child: Text('To be implemented'), + ), + ExamsPage(), + Center( + child: Text('To be implemented'), + ), + ], + ), + ), ); } @override Future onRefresh(BuildContext context) async { - for (final card in academicPathCards) { - card.onRefresh(context); - } + // TODO: implement onRefresh } } diff --git a/packages/uni_app/lib/view/academic_path/exam_page.dart b/packages/uni_app/lib/view/academic_path/exam_page.dart new file mode 100644 index 000000000..7d541697a --- /dev/null +++ b/packages/uni_app/lib/view/academic_path/exam_page.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:uni/controller/local_storage/preferences_controller.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/model/entities/exam.dart'; +import 'package:uni/model/providers/lazy/exam_provider.dart'; +import 'package:uni/view/common_widgets/expanded_image_label.dart'; +import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni_ui/cards/exam_card.dart'; +import 'package:uni_ui/timeline/timeline.dart'; + +class ExamsPage extends StatefulWidget { + const ExamsPage({super.key}); + + @override + State createState() => _ExamsPageState(); +} + +class _ExamsPageState extends State { + List hiddenExams = PreferencesController.getHiddenExams(); + Map filteredExamTypes = + PreferencesController.getFilteredExams(); + + @override + Widget build(BuildContext context) { + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + /* + If we want to filters exams again + filteredExamTypes[Exam.getExamTypeLong(exam.examType)] ?? + */ + + return LazyConsumer>( + builder: (context, exams) => Timeline( + tabs: _examsByMonth(exams) + .keys + .map( + (key) => Column( + children: [ + Text(months[int.parse(key.split('-')[1]) - 1]), + Text(key.split('-')[1]), + ], + ), + ) + .toList(), + content: _examsByMonth(exams) + .entries + .map( + (entry) => ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: entry.value.length, + prototypeItem: const ExamCard( + //TODO(thePeras): Solve this at parser level + name: 'Computer Laboratory', + acronym: 'LCOM', + rooms: ['B315', 'B224', 'B207'], + type: 'MT', + startTime: '12:00', + ), + itemBuilder: (context, index) { + final exam = entry.value[index]; + return ExamCard( + name: 'Subject Name', + acronym: exam.subject, + //TODO(thePeras): Solve this at parser level + rooms: exam.rooms.where((room) => room.isNotEmpty).toList(), + type: exam.examType, + startTime: exam.formatTime(exam.start), + isInvisible: hiddenExams.contains(exam.id), + iconAction: () { + setState(() { + if (hiddenExams.contains(exam.id)) { + hiddenExams.remove(exam.id); + } else { + hiddenExams.add(exam.id); + } + + setState(() { + PreferencesController.saveHiddenExams( + hiddenExams, + ); + }); + }); + }, + ); + }, + ), + ) + .toList(), + ), + hasContent: (exams) => exams.isNotEmpty, + onNullContent: Center( + heightFactor: 1.2, + child: ImageLabel( + imagePath: 'assets/images/vacation.png', + label: S.of(context).no_exams_label, + labelTextStyle: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: Theme.of(context).colorScheme.primary, + ), + sublabel: S.of(context).no_exams, + sublabelTextStyle: const TextStyle(fontSize: 15), + ), + ), + ); + } + + Map> _examsByMonth(List exams) { + final months = >{}; + for (final exam in exams) { + final month = '${exam.start.year}-${exam.start.month}'; + months.putIfAbsent(month, () => []).add(exam); + } + return months; + } + + /* + @override + Widget? getTopRightButton(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: ExamFilterButton( + () => setState(() { + filteredExamTypes = PreferencesController.getFilteredExams(); + }), + ), + ); + } + */ +} From d7ae8a62d4bae26fd7dbcf27631f3812571f5574 Mon Sep 17 00:00:00 2001 From: thePeras Date: Mon, 4 Nov 2024 16:50:36 +0000 Subject: [PATCH 2/6] Academyc page tabs --- .../lib/generated/intl/messages_en.dart | 3 + .../lib/generated/intl/messages_pt_PT.dart | 3 + packages/uni_app/lib/generated/l10n.dart | 30 +++++++++ packages/uni_app/lib/l10n/intl_en.arb | 8 ++- packages/uni_app/lib/l10n/intl_pt_PT.arb | 8 ++- .../lib/view/academic_path/academic_path.dart | 66 +++++++++++-------- packages/uni_app/pubspec.lock | 8 +++ packages/uni_ui/lib/icons.dart | 7 ++ packages/uni_ui/lib/tabs/tab_icon.dart | 26 ++++++++ 9 files changed, 131 insertions(+), 28 deletions(-) create mode 100644 packages/uni_ui/lib/icons.dart create mode 100644 packages/uni_ui/lib/tabs/tab_icon.dart diff --git a/packages/uni_app/lib/generated/intl/messages_en.dart b/packages/uni_app/lib/generated/intl/messages_en.dart index 24826a3e3..fb6c834dd 100644 --- a/packages/uni_app/lib/generated/intl/messages_en.dart +++ b/packages/uni_app/lib/generated/intl/messages_en.dart @@ -102,6 +102,7 @@ class MessageLookup extends MessageLookupByLibrary { "Floor -1 of building B | AEFEUP building"), "course_class": MessageLookupByLibrary.simpleMessage("Classes"), "course_info": MessageLookupByLibrary.simpleMessage("Info"), + "courses": MessageLookupByLibrary.simpleMessage("Courses"), "current_state": MessageLookupByLibrary.simpleMessage("Current state: "), "current_year": @@ -122,6 +123,7 @@ class MessageLookup extends MessageLookupByLibrary { "empty_text": MessageLookupByLibrary.simpleMessage("Please fill in this field"), "evaluation": MessageLookupByLibrary.simpleMessage("Evaluation"), + "exams": MessageLookupByLibrary.simpleMessage("Exams"), "exams_filter": MessageLookupByLibrary.simpleMessage("Exams Filter Settings"), "exit_confirm": @@ -264,6 +266,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 502e9f9ec..7bf937edc 100644 --- a/packages/uni_app/lib/generated/intl/messages_pt_PT.dart +++ b/packages/uni_app/lib/generated/intl/messages_pt_PT.dart @@ -102,6 +102,7 @@ class MessageLookup extends MessageLookupByLibrary { "Piso -1 do edifício B | Edifício da AEFEUP"), "course_class": MessageLookupByLibrary.simpleMessage("Turmas"), "course_info": MessageLookupByLibrary.simpleMessage("Ficha"), + "courses": MessageLookupByLibrary.simpleMessage("Cursos"), "current_state": MessageLookupByLibrary.simpleMessage("Estado atual: "), "current_year": MessageLookupByLibrary.simpleMessage("Ano curricular atual: "), @@ -121,6 +122,7 @@ class MessageLookup extends MessageLookupByLibrary { "empty_text": MessageLookupByLibrary.simpleMessage( "Por favor preenche este campo"), "evaluation": MessageLookupByLibrary.simpleMessage("Avaliação"), + "exams": MessageLookupByLibrary.simpleMessage("Exames"), "exams_filter": MessageLookupByLibrary.simpleMessage("Definições Filtro de Exames"), "exit_confirm": MessageLookupByLibrary.simpleMessage( @@ -265,6 +267,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("Horário"), "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 4201d66c1..17931ff61 100644 --- a/packages/uni_app/lib/generated/l10n.dart +++ b/packages/uni_app/lib/generated/l10n.dart @@ -1697,6 +1697,36 @@ class S { args: [], ); } + + /// `Schedule` + String get schedule { + return Intl.message( + 'Schedule', + name: 'schedule', + desc: '', + args: [], + ); + } + + /// `Exams` + String get exams { + return Intl.message( + 'Exams', + name: 'exams', + desc: '', + args: [], + ); + } + + /// `Courses` + String get courses { + return Intl.message( + 'Courses', + name: 'courses', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/packages/uni_app/lib/l10n/intl_en.arb b/packages/uni_app/lib/l10n/intl_en.arb index 5e3eb22a6..1b17ec361 100644 --- a/packages/uni_app/lib/l10n/intl_en.arb +++ b/packages/uni_app/lib/l10n/intl_en.arb @@ -333,5 +333,11 @@ "wrong_credentials_exception": "Invalid credentials", "@wrong_credentials_exception": {}, "internet_status_exception": "Check your internet connection", - "@internet_status_exception": {} + "@internet_status_exception": {}, + "schedule": "Schedule", + "@schedule": {}, + "exams": "Exams", + "@exams": {}, + "courses": "Courses", + "@courses": {} } \ No newline at end of file diff --git a/packages/uni_app/lib/l10n/intl_pt_PT.arb b/packages/uni_app/lib/l10n/intl_pt_PT.arb index 4212fe69c..04fe68be7 100644 --- a/packages/uni_app/lib/l10n/intl_pt_PT.arb +++ b/packages/uni_app/lib/l10n/intl_pt_PT.arb @@ -333,5 +333,11 @@ "wrong_credentials_exception": "Credenciais inválidas", "@wrong_credentials_exception": {}, "internet_status_exception": "Verifique sua conexão com a internet", - "@internet_status_exception": {} + "@internet_status_exception": {}, + "schedule": "Horário", + "@schedule": {}, + "exams": "Exames", + "@exams": {}, + "courses": "Cursos", + "@courses": {} } \ No newline at end of file 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 e4e69c7be..a946ed8b6 100644 --- a/packages/uni_app/lib/view/academic_path/academic_path.dart +++ b/packages/uni_app/lib/view/academic_path/academic_path.dart @@ -3,6 +3,8 @@ 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/common_widgets/pages_layouts/general/general.dart'; +import 'package:uni_ui/icons.dart'; +import 'package:uni_ui/tabs/tab_icon.dart'; class AcademicPathPageView extends StatefulWidget { const AcademicPathPageView({super.key}); @@ -11,40 +13,52 @@ class AcademicPathPageView extends StatefulWidget { State createState() => AcademicPathPageViewState(); } -class AcademicPathPageViewState extends GeneralPageViewState { +class AcademicPathPageViewState extends GeneralPageViewState + with SingleTickerProviderStateMixin { @override String? getTitle() => S.of(context).nav_title(NavigationItem.navAcademicPath.route); + late TabController tabController; + + @override + void initState() { + super.initState(); + tabController = TabController(vsync: this, length: 3); + } + + @override + void dispose() { + tabController.dispose(); + super.dispose(); + } + + @override + Widget? getHeader(BuildContext context) { + return TabBar( + controller: tabController, + dividerHeight: 1, + tabs: [ + TabIcon(icon: UniIcons.calendar, text: S.of(context).schedule), + TabIcon(icon: UniIcons.exam, text: S.of(context).exams), + TabIcon(icon: UniIcons.courses, text: S.of(context).courses), + ], + ); + } + @override Widget getBody(BuildContext context) { - return DefaultTabController( - length: 3, - child: Scaffold( - appBar: AppBar( - title: Text( - S.of(context).nav_title(NavigationItem.navAcademicPath.route), - ), - bottom: const TabBar( - tabs: [ - Tab(text: 'Schedule'), - Tab(text: 'Exams'), - Tab(text: 'Courses'), - ], - ), + return TabBarView( + controller: tabController, + children: const [ + Center( + child: Text('To be implemented'), ), - body: const TabBarView( - children: [ - Center( - child: Text('To be implemented'), - ), - ExamsPage(), - Center( - child: Text('To be implemented'), - ), - ], + ExamsPage(), + Center( + child: Text('To be implemented'), ), - ), + ], ); } diff --git a/packages/uni_app/pubspec.lock b/packages/uni_app/pubspec.lock index 04778b73c..e9483f5c4 100644 --- a/packages/uni_app/pubspec.lock +++ b/packages/uni_app/pubspec.lock @@ -1147,6 +1147,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + scrollable_positioned_list: + dependency: transitive + description: + name: scrollable_positioned_list + sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287" + url: "https://pub.dev" + source: hosted + version: "0.3.8" sentry: dependency: transitive description: diff --git a/packages/uni_ui/lib/icons.dart b/packages/uni_ui/lib/icons.dart new file mode 100644 index 000000000..60cf7ca43 --- /dev/null +++ b/packages/uni_ui/lib/icons.dart @@ -0,0 +1,7 @@ +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +class UniIcons { + static const calendar = PhosphorIconsDuotone.calendarDots; + static const exam = PhosphorIconsDuotone.exam; + static const courses = PhosphorIconsDuotone.certificate; +} diff --git a/packages/uni_ui/lib/tabs/tab_icon.dart b/packages/uni_ui/lib/tabs/tab_icon.dart new file mode 100644 index 000000000..b8bbcb3a9 --- /dev/null +++ b/packages/uni_ui/lib/tabs/tab_icon.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +class TabIcon extends StatelessWidget { + const TabIcon({ + super.key, + required this.icon, + required this.text, + }); + + final IconData icon; + final String text; + + @override + Widget build(BuildContext context) { + return Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon), + const SizedBox(width: 4), + Text(text), + ], + ), + ); + } +} From d06f88e8082b76927cd56a50c1abe3cb1af72627 Mon Sep 17 00:00:00 2001 From: thePeras Date: Mon, 4 Nov 2024 17:17:52 +0000 Subject: [PATCH 3/6] update exams parser to get subject full name --- .../lib/controller/fetchers/exam_fetcher.dart | 2 +- .../database/app_exams_database.dart | 5 ++-- .../lib/controller/parsers/parser_exams.dart | 5 +++- .../models/response_lecture_unit.g.dart | 2 +- .../generated/model/entities/course.g.dart | 2 +- .../entities/course_units/course_unit.g.dart | 6 ++-- .../lib/generated/model/entities/exam.g.dart | 2 ++ .../generated/model/entities/lecture.g.dart | 2 +- .../model/entities/library_occupation.g.dart | 10 +++---- .../model/entities/location_group.g.dart | 2 +- .../generated/model/entities/reference.g.dart | 4 +-- .../model/entities/restaurant.g.dart | 2 +- .../lib/generated/model/entities/trip.g.dart | 2 +- packages/uni_app/lib/model/entities/exam.dart | 7 +++-- .../lib/view/academic_path/exam_page.dart | 7 ++--- .../lib/view/exams/widgets/exam_row.dart | 6 ++-- .../home/widgets/remaining_exams_card.dart | 2 +- .../unit/view/Pages/exams_page_view_test.dart | 15 ++++++++-- .../test/unit/view/Widgets/exam_row_test.dart | 29 +++++++++++++++---- 19 files changed, 75 insertions(+), 37 deletions(-) diff --git a/packages/uni_app/lib/controller/fetchers/exam_fetcher.dart b/packages/uni_app/lib/controller/fetchers/exam_fetcher.dart index 8d79b3ece..036393455 100644 --- a/packages/uni_app/lib/controller/fetchers/exam_fetcher.dart +++ b/packages/uni_app/lib/controller/fetchers/exam_fetcher.dart @@ -47,7 +47,7 @@ class ExamFetcher implements SessionDependantFetcher { ) && courseExam.examType != 'EE' && courseExam.examType != 'EAE' && - courseExam.subject == uc.abbreviation && + courseExam.subjectAcronym == uc.abbreviation && uc.enrollmentIsValid() && !courseExam.hasEnded()) { exams.add(courseExam); diff --git a/packages/uni_app/lib/controller/local_storage/database/app_exams_database.dart b/packages/uni_app/lib/controller/local_storage/database/app_exams_database.dart index b628ddcf0..c087a05e3 100644 --- a/packages/uni_app/lib/controller/local_storage/database/app_exams_database.dart +++ b/packages/uni_app/lib/controller/local_storage/database/app_exams_database.dart @@ -10,10 +10,10 @@ import 'package:uni/model/entities/exam.dart'; /// See the [Exam] class to see what data is stored in this database. class AppExamsDatabase extends AppDatabase> { AppExamsDatabase() - : super('exams.db', [_createScript], onUpgrade: migrate, version: 7); + : super('exams.db', [_createScript], onUpgrade: migrate, version: 8); static const _createScript = ''' -CREATE TABLE exams(id TEXT, subject TEXT, start TEXT, finish TEXT, +CREATE TABLE exams(id TEXT, subjectAcronym TEXT, subject TEXT, start TEXT, finish TEXT, rooms TEXT, examType TEXT, faculty TEXT, PRIMARY KEY (id,faculty)) '''; /// Returns a list containing all of the exams stored in this database. @@ -24,6 +24,7 @@ CREATE TABLE exams(id TEXT, subject TEXT, start TEXT, finish TEXT, return List.generate(maps.length, (i) { return Exam.secConstructor( maps[i]['id'] as String, + maps[i]['subjectAcronym'] as String, maps[i]['subject'] as String, DateTime.parse(maps[i]['start'] as String), DateTime.parse(maps[i]['finish'] as String), diff --git a/packages/uni_app/lib/controller/parsers/parser_exams.dart b/packages/uni_app/lib/controller/parsers/parser_exams.dart index 8069410a6..99a04cb6c 100644 --- a/packages/uni_app/lib/controller/parsers/parser_exams.dart +++ b/packages/uni_app/lib/controller/parsers/parser_exams.dart @@ -27,6 +27,7 @@ class ParserExams { final dates = []; final examTypes = []; var rooms = []; + String? subjectAcronym; String? subject; var id = '0'; var days = 0; @@ -46,7 +47,8 @@ class ParserExams { if (exams.querySelector('td.exame') != null) { exams.querySelectorAll('td.exame').forEach((examsDay) { if (examsDay.querySelector('a') != null) { - subject = examsDay.querySelector('a')!.text; + subjectAcronym = examsDay.querySelector('a')!.text; + subject = examsDay.querySelector('a')!.attributes['title']; id = Uri.parse(examsDay.querySelector('a')!.attributes['href']!) .queryParameters['p_exa_id']!; } @@ -73,6 +75,7 @@ class ParserExams { id, begin, end, + subjectAcronym ?? '', subject ?? '', rooms, examTypes[tableNum], diff --git a/packages/uni_app/lib/generated/controller/parsers/schedule/new_api/models/response_lecture_unit.g.dart b/packages/uni_app/lib/generated/controller/parsers/schedule/new_api/models/response_lecture_unit.g.dart index 1d1ba95b8..6844ccc8a 100644 --- a/packages/uni_app/lib/generated/controller/parsers/schedule/new_api/models/response_lecture_unit.g.dart +++ b/packages/uni_app/lib/generated/controller/parsers/schedule/new_api/models/response_lecture_unit.g.dart @@ -9,7 +9,7 @@ part of '../../../../../../controller/parsers/schedule/new_api/models/response_l ResponseLectureUnit _$ResponseLectureUnitFromJson(Map json) => ResponseLectureUnit( json['acronym'] as String, - json['sigarra_id'] as int, + (json['sigarra_id'] as num).toInt(), ); Map _$ResponseLectureUnitToJson( diff --git a/packages/uni_app/lib/generated/model/entities/course.g.dart b/packages/uni_app/lib/generated/model/entities/course.g.dart index d51524b13..a2b3fd763 100644 --- a/packages/uni_app/lib/generated/model/entities/course.g.dart +++ b/packages/uni_app/lib/generated/model/entities/course.g.dart @@ -8,7 +8,7 @@ part of '../../../model/entities/course.dart'; Map _$CourseToJson(Course instance) => { 'cur_id': instance.id, - 'fest_id ': instance.festId, + 'fest_id': instance.festId, 'cur_nome': instance.name, 'abbreviation': instance.abbreviation, 'ano_curricular': instance.currYear, diff --git a/packages/uni_app/lib/generated/model/entities/course_units/course_unit.g.dart b/packages/uni_app/lib/generated/model/entities/course_units/course_unit.g.dart index dba0cfd22..17c5b0503 100644 --- a/packages/uni_app/lib/generated/model/entities/course_units/course_unit.g.dart +++ b/packages/uni_app/lib/generated/model/entities/course_units/course_unit.g.dart @@ -9,10 +9,10 @@ part of '../../../../model/entities/course_units/course_unit.dart'; CourseUnit _$CourseUnitFromJson(Map json) => CourseUnit( abbreviation: json['ucurr_sigla'] as String, name: json['ucurr_nome'] as String, - occurrId: json['ocorr_id'] as int?, - id: json['ucurr_id'] as int? ?? 0, + occurrId: (json['ocorr_id'] as num?)?.toInt(), + id: (json['ucurr_id'] as num?)?.toInt() ?? 0, code: json['ucurr_codigo'] as String? ?? '', - curricularYear: json['ano'] as int?, + curricularYear: (json['ano'] as num?)?.toInt(), semesterCode: json['per_codigo'] as String?, semesterName: json['per_nome'] as String?, type: json['tipo'] as String?, diff --git a/packages/uni_app/lib/generated/model/entities/exam.g.dart b/packages/uni_app/lib/generated/model/entities/exam.g.dart index 837cd7646..096f779eb 100644 --- a/packages/uni_app/lib/generated/model/entities/exam.g.dart +++ b/packages/uni_app/lib/generated/model/entities/exam.g.dart @@ -10,6 +10,7 @@ Exam _$ExamFromJson(Map json) => Exam( json['id'] as String, const DateTimeConverter().fromJson(json['start'] as String), const DateTimeConverter().fromJson(json['finish'] as String), + json['subjectAcronym'] as String, json['subject'] as String, (json['rooms'] as List).map((e) => e as String).toList(), json['examType'] as String, @@ -20,6 +21,7 @@ Map _$ExamToJson(Exam instance) => { 'start': const DateTimeConverter().toJson(instance.start), 'finish': const DateTimeConverter().toJson(instance.finish), 'id': instance.id, + 'subjectAcronym': instance.subjectAcronym, 'subject': instance.subject, 'rooms': instance.rooms, 'examType': instance.examType, diff --git a/packages/uni_app/lib/generated/model/entities/lecture.g.dart b/packages/uni_app/lib/generated/model/entities/lecture.g.dart index 66a4c44aa..fcc77c8ad 100644 --- a/packages/uni_app/lib/generated/model/entities/lecture.g.dart +++ b/packages/uni_app/lib/generated/model/entities/lecture.g.dart @@ -14,7 +14,7 @@ Lecture _$LectureFromJson(Map json) => Lecture( json['room'] as String, json['teacher'] as String, json['classNumber'] as String, - json['occurrId'] as int, + (json['occurrId'] as num).toInt(), ); Map _$LectureToJson(Lecture instance) => { diff --git a/packages/uni_app/lib/generated/model/entities/library_occupation.g.dart b/packages/uni_app/lib/generated/model/entities/library_occupation.g.dart index fe8e7e4bb..4cd1c67ee 100644 --- a/packages/uni_app/lib/generated/model/entities/library_occupation.g.dart +++ b/packages/uni_app/lib/generated/model/entities/library_occupation.g.dart @@ -8,8 +8,8 @@ part of '../../../model/entities/library_occupation.dart'; LibraryOccupation _$LibraryOccupationFromJson(Map json) => LibraryOccupation( - json['occupation'] as int, - json['capacity'] as int, + (json['occupation'] as num).toInt(), + (json['capacity'] as num).toInt(), )..floors = (json['floors'] as List) .map((e) => FloorOccupation.fromJson(e as Map)) .toList(); @@ -23,9 +23,9 @@ Map _$LibraryOccupationToJson(LibraryOccupation instance) => FloorOccupation _$FloorOccupationFromJson(Map json) => FloorOccupation( - json['number'] as int, - json['occupation'] as int, - json['capacity'] as int, + (json['number'] as num).toInt(), + (json['occupation'] as num).toInt(), + (json['capacity'] as num).toInt(), ); Map _$FloorOccupationToJson(FloorOccupation instance) => diff --git a/packages/uni_app/lib/generated/model/entities/location_group.g.dart b/packages/uni_app/lib/generated/model/entities/location_group.g.dart index 4ca96e363..beb8245d1 100644 --- a/packages/uni_app/lib/generated/model/entities/location_group.g.dart +++ b/packages/uni_app/lib/generated/model/entities/location_group.g.dart @@ -10,7 +10,7 @@ LocationGroup _$LocationGroupFromJson(Map json) => LocationGroup( LatLng.fromJson(json['latlng'] as Map), isFloorless: json['isFloorless'] as bool? ?? false, - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), ); Map _$LocationGroupToJson(LocationGroup instance) => diff --git a/packages/uni_app/lib/generated/model/entities/reference.g.dart b/packages/uni_app/lib/generated/model/entities/reference.g.dart index 53b2a3150..6778cd83b 100644 --- a/packages/uni_app/lib/generated/model/entities/reference.g.dart +++ b/packages/uni_app/lib/generated/model/entities/reference.g.dart @@ -9,8 +9,8 @@ part of '../../../model/entities/reference.dart'; Reference _$ReferenceFromJson(Map json) => Reference( json['description'] as String, const DateTimeConverter().fromJson(json['limitDate'] as String), - json['entity'] as int, - json['reference'] as int, + (json['entity'] as num).toInt(), + (json['reference'] as num).toInt(), (json['amount'] as num).toDouble(), ); diff --git a/packages/uni_app/lib/generated/model/entities/restaurant.g.dart b/packages/uni_app/lib/generated/model/entities/restaurant.g.dart index 5471eb4f0..bd3f3be3b 100644 --- a/packages/uni_app/lib/generated/model/entities/restaurant.g.dart +++ b/packages/uni_app/lib/generated/model/entities/restaurant.g.dart @@ -7,7 +7,7 @@ part of '../../../model/entities/restaurant.dart'; // ************************************************************************** Restaurant _$RestaurantFromJson(Map json) => Restaurant( - json['id'] as int?, + (json['id'] as num?)?.toInt(), json['name'] as String, json['ref'] as String, meals: (json['meals'] as List) diff --git a/packages/uni_app/lib/generated/model/entities/trip.g.dart b/packages/uni_app/lib/generated/model/entities/trip.g.dart index 973f9d436..bdf83bd20 100644 --- a/packages/uni_app/lib/generated/model/entities/trip.g.dart +++ b/packages/uni_app/lib/generated/model/entities/trip.g.dart @@ -9,7 +9,7 @@ part of '../../../model/entities/trip.dart'; Trip _$TripFromJson(Map json) => Trip( line: json['line'] as String, destination: json['destination'] as String, - timeRemaining: json['timeRemaining'] as int, + timeRemaining: (json['timeRemaining'] as num).toInt(), ); Map _$TripToJson(Trip instance) => { diff --git a/packages/uni_app/lib/model/entities/exam.dart b/packages/uni_app/lib/model/entities/exam.dart index 280661e2d..5f7a5a24c 100644 --- a/packages/uni_app/lib/model/entities/exam.dart +++ b/packages/uni_app/lib/model/entities/exam.dart @@ -21,6 +21,7 @@ class Exam { this.id, this.start, this.finish, + this.subjectAcronym, this.subject, this.rooms, this.examType, @@ -31,6 +32,7 @@ class Exam { Exam.secConstructor( this.id, + this.subjectAcronym, this.subject, this.start, this.finish, @@ -42,6 +44,7 @@ class Exam { final DateTime start; final DateTime finish; final String id; + final String subjectAcronym; final String subject; final List rooms; final String examType; @@ -81,7 +84,7 @@ class Exam { @override String toString() { - return '''$id - $subject - ${start.year} - $month - ${start.day} - $startTime-$finishTime - $examType - $rooms - $weekDay'''; + return '''$id - $subjectAcronym - ${start.year} - $month - ${start.day} - $startTime-$finishTime - $examType - $rooms - $weekDay'''; } /// Prints the data in this exam to the [Logger] with an INFO level. @@ -92,7 +95,7 @@ class Exam { @override bool operator ==(Object other) => identical(this, other) || - other is Exam && id == other.id && subject == other.subject; + other is Exam && id == other.id && subjectAcronym == other.subjectAcronym; @override int get hashCode => id.hashCode; diff --git a/packages/uni_app/lib/view/academic_path/exam_page.dart b/packages/uni_app/lib/view/academic_path/exam_page.dart index 7d541697a..c08cf33e0 100644 --- a/packages/uni_app/lib/view/academic_path/exam_page.dart +++ b/packages/uni_app/lib/view/academic_path/exam_page.dart @@ -63,7 +63,6 @@ class _ExamsPageState extends State { physics: const NeverScrollableScrollPhysics(), itemCount: entry.value.length, prototypeItem: const ExamCard( - //TODO(thePeras): Solve this at parser level name: 'Computer Laboratory', acronym: 'LCOM', rooms: ['B315', 'B224', 'B207'], @@ -73,9 +72,9 @@ class _ExamsPageState extends State { itemBuilder: (context, index) { final exam = entry.value[index]; return ExamCard( - name: 'Subject Name', - acronym: exam.subject, - //TODO(thePeras): Solve this at parser level + name: exam.subject, + acronym: exam.subjectAcronym, + // TODO(thePeras): Solve this at parser level rooms: exam.rooms.where((room) => room.isNotEmpty).toList(), type: exam.examType, startTime: exam.formatTime(exam.start), diff --git a/packages/uni_app/lib/view/exams/widgets/exam_row.dart b/packages/uni_app/lib/view/exams/widgets/exam_row.dart index abacd5686..1a21e7e70 100644 --- a/packages/uni_app/lib/view/exams/widgets/exam_row.dart +++ b/packages/uni_app/lib/view/exams/widgets/exam_row.dart @@ -39,7 +39,7 @@ class _ExamRowState extends State { @override Widget build(BuildContext context) { final roomsKey = - '${widget.exam.subject}-${widget.exam.rooms}-${widget.exam.startTime}-' + '${widget.exam.subjectAcronym}-${widget.exam.rooms}-${widget.exam.startTime}-' '${widget.exam.finishTime}'; return Center( child: Container( @@ -63,7 +63,7 @@ class _ExamRowState extends State { : [], ), ExamTitle( - subject: widget.exam.subject, + subject: widget.exam.subjectAcronym, type: widget.exam.examType, ), Row( @@ -144,7 +144,7 @@ class _ExamRowState extends State { Event createExamEvent() { return Event( - title: '${widget.exam.examType} ${widget.exam.subject}', + title: '${widget.exam.examType} ${widget.exam.subjectAcronym}', location: widget.exam.rooms.toString(), startDate: widget.exam.start, endDate: widget.exam.finish, diff --git a/packages/uni_app/lib/view/home/widgets/remaining_exams_card.dart b/packages/uni_app/lib/view/home/widgets/remaining_exams_card.dart index 56f1f0180..37e4526e3 100644 --- a/packages/uni_app/lib/view/home/widgets/remaining_exams_card.dart +++ b/packages/uni_app/lib/view/home/widgets/remaining_exams_card.dart @@ -30,7 +30,7 @@ class RemainingExamsWidget extends StatelessWidget { style: Theme.of(context).textTheme.bodyLarge, ), ExamTitle( - subject: exam.subject, + subject: exam.subjectAcronym, type: exam.examType, reverseOrder: true, ), diff --git a/packages/uni_app/test/unit/view/Pages/exams_page_view_test.dart b/packages/uni_app/test/unit/view/Pages/exams_page_view_test.dart index 81555ec7c..892680059 100644 --- a/packages/uni_app/test/unit/view/Pages/exams_page_view_test.dart +++ b/packages/uni_app/test/unit/view/Pages/exams_page_view_test.dart @@ -14,9 +14,11 @@ void main() async { await initTestEnvironment(); group('ExamsPage', () { - const firstExamSubject = 'SOPE'; + const firstExamSubjectAcronym = 'SOPE'; + const firstExamSubject = 'Sistemas Operativos'; const firstExamDate = '2019-09-11'; - const secondExamSubject = 'SDIS'; + const secondExamSubjectAcronym = 'SDIS'; + const secondExamSubject = 'Sistemas Distribuídos'; const secondExamDate = '2019-09-12'; testWidgets('When given an empty list', (tester) async { @@ -37,6 +39,7 @@ void main() async { '1230', firstExamBegin, firstExamEnd, + firstExamSubjectAcronym, firstExamSubject, ['B119', 'B107', 'B205'], 'ER', @@ -63,6 +66,7 @@ void main() async { '1231', firstExamBegin, firstExamEnd, + firstExamSubjectAcronym, firstExamSubject, ['B119', 'B107', 'B205'], 'ER', @@ -74,6 +78,7 @@ void main() async { '1232', secondExamBegin, secondExamEnd, + secondExamSubjectAcronym, secondExamSubject, ['B119', 'B107', 'B205'], 'ER', @@ -109,6 +114,7 @@ void main() async { '1233', firstExamBegin, firstExamEnd, + firstExamSubjectAcronym, firstExamSubject, ['B119', 'B107', 'B205'], 'ER', @@ -120,6 +126,7 @@ void main() async { '1234', secondExamBegin, secondExamEnd, + secondExamSubjectAcronym, secondExamSubject, ['B119', 'B107', 'B205'], 'ER', @@ -154,6 +161,7 @@ void main() async { '1235', firstExamBegin, firstExamEnd, + firstExamSubjectAcronym, firstExamSubject, rooms, 'ER', @@ -165,6 +173,7 @@ void main() async { '1236', secondExamBegin, secondExamEnd, + firstExamSubjectAcronym, firstExamSubject, rooms, 'ER', @@ -176,6 +185,7 @@ void main() async { '1237', thirdExamBegin, thirdExamEnd, + secondExamSubjectAcronym, secondExamSubject, rooms, 'ER', @@ -187,6 +197,7 @@ void main() async { '1238', fourthExamBegin, fourthExamEnd, + secondExamSubjectAcronym, secondExamSubject, rooms, 'ER', diff --git a/packages/uni_app/test/unit/view/Widgets/exam_row_test.dart b/packages/uni_app/test/unit/view/Widgets/exam_row_test.dart index be4483997..6b9c1bac3 100644 --- a/packages/uni_app/test/unit/view/Widgets/exam_row_test.dart +++ b/packages/uni_app/test/unit/view/Widgets/exam_row_test.dart @@ -12,7 +12,8 @@ void main() async { await initTestEnvironment(); group('Exam Row', () { - const subject = 'SOPE'; + const subjectAcronym = 'SOPE'; + const subject = 'Sistemas Operativos'; final start = DateTime( DateTime.now().year, DateTime.now().month, @@ -30,7 +31,16 @@ void main() async { testWidgets('When given a single room', (tester) async { final rooms = ['B315']; - final exam = Exam('1230', start, finish, subject, rooms, '', 'feup'); + final exam = Exam( + '1230', + start, + finish, + subjectAcronym, + subject, + rooms, + '', + 'feup', + ); final widget = ExamRow( exam: exam, teacher: '', @@ -44,7 +54,7 @@ void main() async { await tester.pumpWidget(testableWidget(widget, providers: providers)); await tester.pump(); - final roomsKey = '$subject-$rooms-$startTime-$finishTime'; + final roomsKey = '$subjectAcronym-$rooms-$startTime-$finishTime'; expect( find.descendant( @@ -57,7 +67,16 @@ void main() async { testWidgets('When multiple rooms', (tester) async { final rooms = ['B315', 'B316', 'B330']; - final exam = Exam('1230', start, finish, subject, rooms, '', 'feup'); + final exam = Exam( + '1230', + start, + finish, + subjectAcronym, + subject, + rooms, + '', + 'feup', + ); final widget = ExamRow( exam: exam, teacher: '', @@ -72,7 +91,7 @@ void main() async { await tester.pumpWidget(testableWidget(widget, providers: providers)); await tester.pump(); - final roomsKey = '$subject-$rooms-$startTime-$finishTime'; + final roomsKey = '$subjectAcronym-$rooms-$startTime-$finishTime'; expect( find.descendant( From 71233eac1904bc98ac84fa7e4b2f119175ff5ecb Mon Sep 17 00:00:00 2001 From: thePeras Date: Mon, 11 Nov 2024 12:32:25 +0000 Subject: [PATCH 4/6] Adjust timeline card and implemented in exams page --- .../lib/view/academic_path/exam_page.dart | 47 ++++++++++--------- .../timeline_card.dart} | 12 ++--- 2 files changed, 32 insertions(+), 27 deletions(-) rename packages/uni_ui/lib/{card_timeline.dart => cards/timeline_card.dart} (92%) diff --git a/packages/uni_app/lib/view/academic_path/exam_page.dart b/packages/uni_app/lib/view/academic_path/exam_page.dart index c08cf33e0..b97fe074a 100644 --- a/packages/uni_app/lib/view/academic_path/exam_page.dart +++ b/packages/uni_app/lib/view/academic_path/exam_page.dart @@ -6,6 +6,7 @@ import 'package:uni/model/providers/lazy/exam_provider.dart'; import 'package:uni/view/common_widgets/expanded_image_label.dart'; import 'package:uni/view/lazy_consumer.dart'; import 'package:uni_ui/cards/exam_card.dart'; +import 'package:uni_ui/cards/timeline_card.dart'; import 'package:uni_ui/timeline/timeline.dart'; class ExamsPage extends StatefulWidget { @@ -71,29 +72,33 @@ class _ExamsPageState extends State { ), itemBuilder: (context, index) { final exam = entry.value[index]; - return ExamCard( - name: exam.subject, - acronym: exam.subjectAcronym, - // TODO(thePeras): Solve this at parser level - rooms: exam.rooms.where((room) => room.isNotEmpty).toList(), - type: exam.examType, - startTime: exam.formatTime(exam.start), - isInvisible: hiddenExams.contains(exam.id), - iconAction: () { - setState(() { - if (hiddenExams.contains(exam.id)) { - hiddenExams.remove(exam.id); - } else { - hiddenExams.add(exam.id); - } - + return TimelineItem( + title: exam.start.day.toString(), + subtitle: months[exam.start.month - 1], + card: ExamCard( + name: exam.subject, + acronym: exam.subjectAcronym, + rooms: + exam.rooms.where((room) => room.isNotEmpty).toList(), + type: exam.examType, + startTime: exam.formatTime(exam.start), + isInvisible: hiddenExams.contains(exam.id), + iconAction: () { setState(() { - PreferencesController.saveHiddenExams( - hiddenExams, - ); + if (hiddenExams.contains(exam.id)) { + hiddenExams.remove(exam.id); + } else { + hiddenExams.add(exam.id); + } + + setState(() { + PreferencesController.saveHiddenExams( + hiddenExams, + ); + }); }); - }); - }, + }, + ), ); }, ), diff --git a/packages/uni_ui/lib/card_timeline.dart b/packages/uni_ui/lib/cards/timeline_card.dart similarity index 92% rename from packages/uni_ui/lib/card_timeline.dart rename to packages/uni_ui/lib/cards/timeline_card.dart index 6bb44958e..88e8d705a 100644 --- a/packages/uni_ui/lib/card_timeline.dart +++ b/packages/uni_ui/lib/cards/timeline_card.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; class TimelineItem extends StatelessWidget { const TimelineItem( - {required this.startTime, - required this.endTime, + {required this.title, + required this.subtitle, required this.card, this.isActive = false, super.key}); - final String startTime; - final String endTime; + final String title; + final String subtitle; final Widget card; final bool isActive; @@ -20,9 +20,9 @@ class TimelineItem extends StatelessWidget { width: 50, child: Column( children: [ - Text(startTime, + Text(title, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), - Text(endTime, + Text(subtitle, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)) ], ), From 034ad96a56b07ddad400b71e0a77d8313df36750 Mon Sep 17 00:00:00 2001 From: thePeras Date: Mon, 11 Nov 2024 14:20:53 +0000 Subject: [PATCH 5/6] Exams page ui: month group, string extension, duotone icon --- .../lib/generated/intl/messages_en.dart | 2 +- .../lib/generated/intl/messages_pt_PT.dart | 2 +- packages/uni_app/lib/generated/l10n.dart | 8 +- packages/uni_app/lib/l10n/intl_en.arb | 4 +- packages/uni_app/lib/l10n/intl_pt_PT.arb | 4 +- packages/uni_app/lib/model/entities/exam.dart | 1 + .../lib/utils/date_time_formatter.dart | 11 +- .../uni_app/lib/utils/string_formatter.dart | 5 + .../lib/view/academic_path/academic_path.dart | 7 +- .../lib/view/academic_path/exam_page.dart | 151 ++++++++++-------- packages/uni_ui/lib/icons.dart | 24 ++- packages/uni_ui/lib/tabs/tab_icon.dart | 3 +- 12 files changed, 137 insertions(+), 85 deletions(-) create mode 100644 packages/uni_app/lib/utils/string_formatter.dart diff --git a/packages/uni_app/lib/generated/intl/messages_en.dart b/packages/uni_app/lib/generated/intl/messages_en.dart index fb6c834dd..5968962c5 100644 --- a/packages/uni_app/lib/generated/intl/messages_en.dart +++ b/packages/uni_app/lib/generated/intl/messages_en.dart @@ -159,6 +159,7 @@ 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( @@ -266,7 +267,6 @@ 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 7bf937edc..f36592457 100644 --- a/packages/uni_app/lib/generated/intl/messages_pt_PT.dart +++ b/packages/uni_app/lib/generated/intl/messages_pt_PT.dart @@ -158,6 +158,7 @@ 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( @@ -267,7 +268,6 @@ 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("Horário"), "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 17931ff61..7907355bf 100644 --- a/packages/uni_app/lib/generated/l10n.dart +++ b/packages/uni_app/lib/generated/l10n.dart @@ -1698,11 +1698,11 @@ class S { ); } - /// `Schedule` - String get schedule { + /// `Lectures` + String get lectures { return Intl.message( - 'Schedule', - name: 'schedule', + 'Lectures', + name: 'lectures', desc: '', args: [], ); diff --git a/packages/uni_app/lib/l10n/intl_en.arb b/packages/uni_app/lib/l10n/intl_en.arb index 1b17ec361..e98444d96 100644 --- a/packages/uni_app/lib/l10n/intl_en.arb +++ b/packages/uni_app/lib/l10n/intl_en.arb @@ -334,8 +334,8 @@ "@wrong_credentials_exception": {}, "internet_status_exception": "Check your internet connection", "@internet_status_exception": {}, - "schedule": "Schedule", - "@schedule": {}, + "lectures": "Lectures", + "@lectures": {}, "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 04fe68be7..9835f0071 100644 --- a/packages/uni_app/lib/l10n/intl_pt_PT.arb +++ b/packages/uni_app/lib/l10n/intl_pt_PT.arb @@ -334,8 +334,8 @@ "@wrong_credentials_exception": {}, "internet_status_exception": "Verifique sua conexão com a internet", "@internet_status_exception": {}, - "schedule": "Horário", - "@schedule": {}, + "lectures": "Aulas", + "@lectures": {}, "exams": "Exames", "@exams": {}, "courses": "Cursos", diff --git a/packages/uni_app/lib/model/entities/exam.dart b/packages/uni_app/lib/model/entities/exam.dart index 5f7a5a24c..9769ebc49 100644 --- a/packages/uni_app/lib/model/entities/exam.dart +++ b/packages/uni_app/lib/model/entities/exam.dart @@ -70,6 +70,7 @@ class Exam { .WEEKDAYS[start.weekday % 7]; } + // TODO(thePeras): Remove this method and use {start.month} in the toString. Tests will fail and need to be updated. String month(AppLocale locale) { return DateFormat.EEEE(locale.localeCode.languageCode) .dateSymbols diff --git a/packages/uni_app/lib/utils/date_time_formatter.dart b/packages/uni_app/lib/utils/date_time_formatter.dart index 840b4cc91..aaafbde06 100644 --- a/packages/uni_app/lib/utils/date_time_formatter.dart +++ b/packages/uni_app/lib/utils/date_time_formatter.dart @@ -8,10 +8,17 @@ extension DateTimeExtensions on DateTime { .WEEKDAYS[weekday % 7]; } - String month(AppLocale locale) { + String fullMonth(AppLocale locale) { return DateFormat.EEEE(locale.localeCode.languageCode) .dateSymbols - .MONTHS[this.month - 1]; + .MONTHS[month - 1]; + } + + String shortMonth(AppLocale locale) { + return DateFormat.EEEE(locale.localeCode.languageCode) + .dateSymbols + .SHORTMONTHS[month - 1] + .replaceAll('.', ''); } String formattedDate(AppLocale locale) { diff --git a/packages/uni_app/lib/utils/string_formatter.dart b/packages/uni_app/lib/utils/string_formatter.dart new file mode 100644 index 000000000..bfc20263f --- /dev/null +++ b/packages/uni_app/lib/utils/string_formatter.dart @@ -0,0 +1,5 @@ +extension StringExtension on String { + String capitalize() { + return '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; + } +} 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 a946ed8b6..bfac776d1 100644 --- a/packages/uni_app/lib/view/academic_path/academic_path.dart +++ b/packages/uni_app/lib/view/academic_path/academic_path.dart @@ -39,9 +39,12 @@ class AcademicPathPageViewState extends GeneralPageViewState controller: tabController, dividerHeight: 1, tabs: [ - TabIcon(icon: UniIcons.calendar, text: S.of(context).schedule), + TabIcon( + icon: UniIcons.lecture, + text: S.of(context).lectures, + ), TabIcon(icon: UniIcons.exam, text: S.of(context).exams), - TabIcon(icon: UniIcons.courses, text: S.of(context).courses), + TabIcon(icon: UniIcons.course, text: S.of(context).courses), ], ); } diff --git a/packages/uni_app/lib/view/academic_path/exam_page.dart b/packages/uni_app/lib/view/academic_path/exam_page.dart index b97fe074a..7874f40c7 100644 --- a/packages/uni_app/lib/view/academic_path/exam_page.dart +++ b/packages/uni_app/lib/view/academic_path/exam_page.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:uni/controller/local_storage/preferences_controller.dart'; import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/exam.dart'; import 'package:uni/model/providers/lazy/exam_provider.dart'; +import 'package:uni/utils/date_time_formatter.dart'; +import 'package:uni/utils/string_formatter.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/cards/exam_card.dart'; import 'package:uni_ui/cards/timeline_card.dart'; -import 'package:uni_ui/timeline/timeline.dart'; class ExamsPage extends StatefulWidget { const ExamsPage({super.key}); @@ -23,84 +26,89 @@ class _ExamsPageState extends State { @override Widget build(BuildContext context) { - const months = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec', - ]; - /* If we want to filters exams again filteredExamTypes[Exam.getExamTypeLong(exam.examType)] ?? */ return LazyConsumer>( - builder: (context, exams) => Timeline( - tabs: _examsByMonth(exams) - .keys - .map( - (key) => Column( - children: [ - Text(months[int.parse(key.split('-')[1]) - 1]), - Text(key.split('-')[1]), - ], - ), - ) - .toList(), - content: _examsByMonth(exams) + builder: (context, exams) => ListView( + children: _examsByMonth(exams) .entries .map( - (entry) => ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: entry.value.length, - prototypeItem: const ExamCard( - name: 'Computer Laboratory', - acronym: 'LCOM', - rooms: ['B315', 'B224', 'B207'], - type: 'MT', - startTime: '12:00', - ), - itemBuilder: (context, index) { - final exam = entry.value[index]; - return TimelineItem( - title: exam.start.day.toString(), - subtitle: months[exam.start.month - 1], - card: ExamCard( - name: exam.subject, - acronym: exam.subjectAcronym, - rooms: - exam.rooms.where((room) => room.isNotEmpty).toList(), - type: exam.examType, - startTime: exam.formatTime(exam.start), - isInvisible: hiddenExams.contains(exam.id), - iconAction: () { - setState(() { - if (hiddenExams.contains(exam.id)) { - hiddenExams.remove(exam.id); - } else { - hiddenExams.add(exam.id); - } + (entry) => Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + DateTime( + int.parse(entry.key.split('-')[0]), + int.parse(entry.key.split('-')[1]), + ) + .fullMonth( + Provider.of(context).getLocale(), + ) + .capitalize(), + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 8), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: entry.value.length, + prototypeItem: const TimelineItem( + title: '1', + subtitle: 'Jan', + card: ExamCard( + name: 'Computer Laboratory', + acronym: 'LCOM', + rooms: ['B315', 'B224', 'B207'], + type: 'MT', + startTime: '12:00', + ), + ), + itemBuilder: (context, index) { + final exam = entry.value[index]; + return TimelineItem( + title: exam.start.day.toString(), + subtitle: exam.start + .shortMonth( + Provider.of(context) + .getLocale(), + ) + .capitalize(), + isActive: _nextExam(exams) == exam, + card: ExamCard( + name: exam.subject, + acronym: exam.subjectAcronym, + rooms: exam.rooms + .where((room) => room.isNotEmpty) + .toList(), + type: exam.examType, + startTime: exam.formatTime(exam.start), + isInvisible: hiddenExams.contains(exam.id), + iconAction: () { + setState(() { + if (hiddenExams.contains(exam.id)) { + hiddenExams.remove(exam.id); + } else { + hiddenExams.add(exam.id); + } - setState(() { - PreferencesController.saveHiddenExams( - hiddenExams, - ); - }); - }); + setState(() { + PreferencesController.saveHiddenExams( + hiddenExams, + ); + }); + }); + }, + ), + ); }, ), - ); - }, + ], + ), ), ) .toList(), @@ -132,6 +140,13 @@ class _ExamsPageState extends State { return months; } + Exam? _nextExam(List exams) { + final now = DateTime.now(); + final nextExams = exams.where((exam) => exam.start.isAfter(now)).toList() + ..sort((a, b) => a.start.compareTo(b.start)); + return nextExams.isNotEmpty ? nextExams.first : null; + } + /* @override Widget? getTopRightButton(BuildContext context) { diff --git a/packages/uni_ui/lib/icons.dart b/packages/uni_ui/lib/icons.dart index 60cf7ca43..72ee466b8 100644 --- a/packages/uni_ui/lib/icons.dart +++ b/packages/uni_ui/lib/icons.dart @@ -1,7 +1,27 @@ +import 'package:flutter/material.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; +// A list of all available icons class UniIcons { - static const calendar = PhosphorIconsDuotone.calendarDots; + static const lecture = PhosphorIconsDuotone.lectern; static const exam = PhosphorIconsDuotone.exam; - static const courses = PhosphorIconsDuotone.certificate; + static const course = PhosphorIconsDuotone.certificate; +} + +// The same as default Icon class from material.dart but allowing to use PhosphorIcons duotone icons +class UniIcon extends PhosphorIcon { + const UniIcon( + IconData icon, { + super.key, + double size = 24, + Color? color, + String? semanticLabel, + TextDirection? textDirection, + }) : super( + icon, + size: size, + color: color, + semanticLabel: semanticLabel, + textDirection: textDirection, + ); } diff --git a/packages/uni_ui/lib/tabs/tab_icon.dart b/packages/uni_ui/lib/tabs/tab_icon.dart index b8bbcb3a9..9b45aa9e8 100644 --- a/packages/uni_ui/lib/tabs/tab_icon.dart +++ b/packages/uni_ui/lib/tabs/tab_icon.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:uni_ui/icons.dart'; class TabIcon extends StatelessWidget { const TabIcon({ @@ -16,7 +17,7 @@ class TabIcon extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon), + UniIcon(icon), const SizedBox(width: 4), Text(text), ], From e3b63df6a26252b22736ed93e518ffd5810139c2 Mon Sep 17 00:00:00 2001 From: thePeras Date: Mon, 11 Nov 2024 14:39:57 +0000 Subject: [PATCH 6/6] Fix bug on empty rooms --- packages/uni_app/lib/controller/parsers/parser_exams.dart | 1 + packages/uni_app/lib/view/academic_path/exam_page.dart | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/uni_app/lib/controller/parsers/parser_exams.dart b/packages/uni_app/lib/controller/parsers/parser_exams.dart index 99a04cb6c..1506e72f9 100644 --- a/packages/uni_app/lib/controller/parsers/parser_exams.dart +++ b/packages/uni_app/lib/controller/parsers/parser_exams.dart @@ -58,6 +58,7 @@ class ParserExams { .text .split(',') .map((e) => e.trim()) + .where((e) => e.isNotEmpty) .toList(); } final DateTime begin; diff --git a/packages/uni_app/lib/view/academic_path/exam_page.dart b/packages/uni_app/lib/view/academic_path/exam_page.dart index 7874f40c7..0e8332b4d 100644 --- a/packages/uni_app/lib/view/academic_path/exam_page.dart +++ b/packages/uni_app/lib/view/academic_path/exam_page.dart @@ -82,9 +82,7 @@ class _ExamsPageState extends State { card: ExamCard( name: exam.subject, acronym: exam.subjectAcronym, - rooms: exam.rooms - .where((room) => room.isNotEmpty) - .toList(), + rooms: exam.rooms, type: exam.examType, startTime: exam.formatTime(exam.start), isInvisible: hiddenExams.contains(exam.id),