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 6a741b4cb..9a6f4148e 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 c4ce2595f..aff6a0fc5 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 339abb28b..b2a759ba2 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 941eae1ad..5c679f5ef 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 6089670c7..0c4413155 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..2bce20ab8 --- /dev/null +++ b/packages/uni_app/lib/view/academic_path/schedule_page.dart @@ -0,0 +1,127 @@ +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/view/academic_path/widgets/schedule_page_view.dart'; +import 'package:uni/view/lazy_consumer.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) { + /*final mockLectures = + getMockLectures(); */ // since there are no classes, we can use this to test the schedule page populated + return SchedulePageView( + lectures, + now: widget.now, + ); + }, + hasContent: (lectures) => lectures.isNotEmpty, + onNullContent: SchedulePageView(const [], now: widget.now), + ), + ), + ); + } +} + +// Since there are no classes, we can use this to test the schedule page populated +List getMockLectures() { + return [ + Lecture( + 'Fundamentos de Segurança Informática', + 'T', + DateTime.now().subtract(const Duration(hours: 2)), + DateTime.now().subtract(const Duration(hours: 1)), + 'B101', + 'Dr. Smith', + 'Class 1', + 1, + ), + Lecture( + '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( + 'Interação Pessoa Computador', + 'T', + DateTime.now().add(const Duration(hours: 5)), + DateTime.now().add(const Duration(hours: 6)), + 'B201', + 'Dr. Brown', + 'Class 3', + 3, + ), + 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', + 'Dr. Taylor', + 'Class 4', + 4, + ), + 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)), + 'B104', + 'Dr. Martinez', + 'Class 5', + 5, + ), + 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)), + 'B105', + 'Dr. Lee', + 'Class 6', + 6, + ), + Lecture( + '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( + '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 new file mode 100644 index 000000000..891295747 --- /dev/null +++ b/packages/uni_app/lib/view/academic_path/widgets/academic_schedule_card.dart @@ -0,0 +1,73 @@ +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 const SizedBox.shrink(); + } + + return Padding( + padding: const EdgeInsets.fromLTRB(24, 16, 24, 8), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + DateFormat('EEEE, d MMMM').format(day), + style: Theme.of(context).textTheme.headlineLarge, + ), + const SizedBox(height: 14), + CardTimeline(items: _buildTimelineItems(lectures)), + ], + ), + ); + } + + List _buildTimelineItems(List lectures) { + return lectures + .map( + (lecture) => 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(' ') + .where((word) => word.length >= 3) + .map((word) => word[0]) + .join() + .toUpperCase(); + } + + bool _isLectureActive(Lecture lecture) { + final now = DateTime.now(); + return now.isAfter(lecture.startTime) && now.isBefore(lecture.endTime); + } +} 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(); + } +} 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/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, + ), ), ), ),