diff --git a/packages/uni_app/lib/generated/intl/messages_en.dart b/packages/uni_app/lib/generated/intl/messages_en.dart index 6fb90730a..2f135b9ef 100644 --- a/packages/uni_app/lib/generated/intl/messages_en.dart +++ b/packages/uni_app/lib/generated/intl/messages_en.dart @@ -257,6 +257,9 @@ class MessageLookup extends MessageLookupByLibrary { ), "nav_title": m3, "news": MessageLookupByLibrary.simpleMessage("News"), + "nextclasses": MessageLookupByLibrary.simpleMessage( + "Here are your classes for ", + ), "no": MessageLookupByLibrary.simpleMessage("No"), "noExamsScheduled": MessageLookupByLibrary.simpleMessage( "No exams scheduled", @@ -282,6 +285,9 @@ class MessageLookup extends MessageLookupByLibrary { "no_classes_this_week": MessageLookupByLibrary.simpleMessage( "You have no classes this week", ), + "no_classes_today": MessageLookupByLibrary.simpleMessage( + "You don\'t have any more classes today.", + ), "no_college": MessageLookupByLibrary.simpleMessage("no college"), "no_course_units": MessageLookupByLibrary.simpleMessage( "No course units in the selected period", @@ -434,6 +440,8 @@ class MessageLookup extends MessageLookupByLibrary { ), "theme": MessageLookupByLibrary.simpleMessage("Theme"), "title": MessageLookupByLibrary.simpleMessage("Title"), + "today": MessageLookupByLibrary.simpleMessage("today:"), + "tomorrow": MessageLookupByLibrary.simpleMessage("tomorrow:"), "try_again": MessageLookupByLibrary.simpleMessage("Try again"), "try_different_login": MessageLookupByLibrary.simpleMessage( "Having trouble signing in?", 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 0d32594cb..5d05bec5c 100644 --- a/packages/uni_app/lib/generated/intl/messages_pt_PT.dart +++ b/packages/uni_app/lib/generated/intl/messages_pt_PT.dart @@ -267,6 +267,9 @@ class MessageLookup extends MessageLookupByLibrary { ), "nav_title": m3, "news": MessageLookupByLibrary.simpleMessage("Notícias"), + "nextclasses": MessageLookupByLibrary.simpleMessage( + "Aqui estão as tuas aulas para ", + ), "no": MessageLookupByLibrary.simpleMessage("Não"), "noExamsScheduled": MessageLookupByLibrary.simpleMessage( "Não há exames agendados", @@ -296,6 +299,9 @@ class MessageLookup extends MessageLookupByLibrary { "no_classes_this_week": MessageLookupByLibrary.simpleMessage( "Não tens aulas esta semana", ), + "no_classes_today": MessageLookupByLibrary.simpleMessage( + "Não tens mais aulas hoje.", + ), "no_college": MessageLookupByLibrary.simpleMessage("sem faculdade"), "no_course_units": MessageLookupByLibrary.simpleMessage( "Sem cadeiras no período selecionado", @@ -458,6 +464,8 @@ class MessageLookup extends MessageLookupByLibrary { ), "theme": MessageLookupByLibrary.simpleMessage("Tema"), "title": MessageLookupByLibrary.simpleMessage("Título"), + "today": MessageLookupByLibrary.simpleMessage("hoje:"), + "tomorrow": MessageLookupByLibrary.simpleMessage("amanhã:"), "try_again": MessageLookupByLibrary.simpleMessage("Tentar de novo"), "try_different_login": MessageLookupByLibrary.simpleMessage( "Problemas ao iniciar sessão?", diff --git a/packages/uni_app/lib/generated/l10n.dart b/packages/uni_app/lib/generated/l10n.dart index d713a336e..2792aa598 100644 --- a/packages/uni_app/lib/generated/l10n.dart +++ b/packages/uni_app/lib/generated/l10n.dart @@ -883,6 +883,16 @@ class S { ); } + /// `You don't have any more classes today.` + String get no_classes_today { + return Intl.message( + 'You don\'t have any more classes today.', + name: 'no_classes_today', + desc: '', + args: [], + ); + } + /// `no college` String get no_college { return Intl.message('no college', name: 'no_college', desc: '', args: []); @@ -1885,6 +1895,26 @@ class S { args: [], ); } + + /// `tomorrow:` + String get tomorrow { + return Intl.message('tomorrow:', name: 'tomorrow', desc: '', args: []); + } + + /// `today:` + String get today { + return Intl.message('today:', name: 'today', desc: '', args: []); + } + + /// `Here are your classes for ` + String get nextclasses { + return Intl.message( + 'Here are your classes for ', + name: 'nextclasses', + 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 e7e9a27a7..abb779f35 100644 --- a/packages/uni_app/lib/l10n/intl_en.arb +++ b/packages/uni_app/lib/l10n/intl_en.arb @@ -208,6 +208,8 @@ "@no_classes_on": {}, "no_classes_on_weekend": "You don't have classes on", "@no_classes_on_weekend": {}, + "no_classes_today": "You don't have any more classes today.", + "@no_classes_today": {}, "no_college": "no college", "@no_college": {}, "no_course_units": "No course units in the selected period", @@ -451,5 +453,11 @@ "services": "Services", "@services": {}, "goi": "Orientation and Integration Office", - "@goi": {} -} \ No newline at end of file + "@goi": {}, + "tomorrow": "tomorrow:", + "@tomorrow": {}, + "today": "today:", + "@today": {}, + "nextclasses": "Here are your classes for ", + "@nextclasses": {} +} diff --git a/packages/uni_app/lib/l10n/intl_pt_PT.arb b/packages/uni_app/lib/l10n/intl_pt_PT.arb index 10587cafd..833cf403b 100644 --- a/packages/uni_app/lib/l10n/intl_pt_PT.arb +++ b/packages/uni_app/lib/l10n/intl_pt_PT.arb @@ -218,6 +218,8 @@ "@no_classes_on": {}, "no_classes_on_weekend": "Não possui aulas ao", "@no_classes_on_weekend": {}, + "no_classes_today": "Não tens mais aulas hoje.", + "@no_classes_today": {}, "no_college": "sem faculdade", "@no_college": {}, "no_course_units": "Sem cadeiras no período selecionado", @@ -451,5 +453,11 @@ "services": "Serviços", "@services": {}, "goi": "Gabinete de Orientação e Integração", - "@goi": {} -} \ No newline at end of file + "@goi": {}, + "tomorrow": "amanhã:", + "@tomorrow": {}, + "today": "hoje:", + "@today": {}, + "nextclasses": "Aqui estão as tuas aulas para ", + "@nextclasses": {} +} diff --git a/packages/uni_app/lib/view/home/widgets/schedule/schedule_home_card.dart b/packages/uni_app/lib/view/home/widgets/schedule/schedule_home_card.dart index 3f0f65d6b..d84eee6e0 100644 --- a/packages/uni_app/lib/view/home/widgets/schedule/schedule_home_card.dart +++ b/packages/uni_app/lib/view/home/widgets/schedule/schedule_home_card.dart @@ -10,7 +10,6 @@ import 'package:uni/model/providers/riverpod/default_consumer.dart'; import 'package:uni/model/providers/riverpod/lecture_provider.dart'; import 'package:uni/model/providers/riverpod/profile_provider.dart'; import 'package:uni/model/providers/riverpod/session_provider.dart'; -import 'package:uni/model/utils/time/week.dart'; import 'package:uni/utils/navigation_items.dart'; import 'package:uni/view/home/widgets/generic_home_card.dart'; import 'package:uni/view/home/widgets/schedule/timeline_shimmer.dart'; @@ -19,6 +18,73 @@ import 'package:uni_ui/cards/schedule_card.dart'; import 'package:uni_ui/cards/timeline_card.dart'; import 'package:uni_ui/icons.dart'; +class TimePeriod { + const TimePeriod({required this.start, required this.end}); + + final DateTime start; + final DateTime end; + + Duration get duration => end.difference(start); + + bool contains(DateTime date) => + (date.isAfter(start) || date.isAtSameMomentAs(start)) && + date.isBefore(end); +} + +/*List getMockLectures() { + final now = DateTime.now(); + + return [ + Lecture( + 'ESOF', + + 'ESOF', + + 'T', + + now.subtract(const Duration(hours: 2)), + + now.subtract(const Duration(hours: 1)), + + 'Room B123', + + 'ademaraguiar', + + 'ademaraguiar', + + 101, + + '1', + + 1001, + ), + + Lecture( + 'LTW', + + 'LTW', + + 'TP', + + now.add(const Duration(hours: 0)), + + now.add(const Duration(hours: 1)), + + 'Room B234', + + 'arestivo', + + 'arestivo', + + 102, + + '2', + + 1002, + ), + ]; +}*/ + class ScheduleHomeCard extends GenericHomecard { const ScheduleHomeCard({super.key}) : super( @@ -35,10 +101,135 @@ class ScheduleHomeCard extends GenericHomecard { Widget buildCardContent(BuildContext context) { return DefaultConsumer>( provider: lectureProvider, - builder: - (context, ref, lectures) => CardTimeline( - items: buildTimelineItems(lectures, ref).take(2).toList(), - ), + builder: (context, ref, lectures) { + final now = DateTime.now(); + //final mockLectures = getMockLectures(); + + // For the sake of testing uncomment what's below + /* + final upcomingLectures = mockLectures + .where((lecture) => lecture.endTime.isAfter(now)) + .toList() + ..sort((a, b) => a.startTime.compareTo(b.startTime)); + + */ + + // For the sake of testing comment what's below + // Get upcoming lectures (end time is after now) + final upcomingLectures = + lectures.where((lecture) => lecture.endTime.isAfter(now)).toList() + ..sort((a, b) => a.startTime.compareTo(b.startTime)); + + if (upcomingLectures.isEmpty) { + return Center( + child: IconLabel( + icon: const UniIcon(size: 45, UniIcons.beer), + label: S.of(context).no_classes, + labelTextStyle: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary, + ), + ), + ); + } + + // Check if any lecture is currently happening + final hasCurrentLecture = upcomingLectures.any( + (lecture) => _isLectureCurrent(lecture, now), + ); + + // If there's a current lecture, just show the timeline without any message + if (hasCurrentLecture) { + return CardTimeline(items: buildTimelineItems(upcomingLectures, ref)); + } + + // Otherwise, determine what message to show + final nextLecture = upcomingLectures.first; + final today = DateTime(now.year, now.month, now.day); + final tomorrow = today.add(const Duration(days: 1)); + final weekEnd = today.add(const Duration(days: 7)); + + // Check if next lecture is within this week + final isWithinThisWeek = nextLecture.startTime.isBefore(weekEnd); + + if (!isWithinThisWeek) { + // Next class is beyond this week - show "no classes this week" message + return Center( + child: IconLabel( + icon: const UniIcon(size: 45, UniIcons.beer), + label: S.of(context).no_classes_this_week, + labelTextStyle: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary, + ), + ), + ); + } + + if (nextLecture.startTime.isAfter(today) && + nextLecture.startTime.isBefore( + today.add(const Duration(days: 1)), + )) { + // Next lecture is today - just show the timeline without "Today" message + return CardTimeline(items: buildTimelineItems(upcomingLectures, ref)); + } else if (nextLecture.startTime.isAfter(tomorrow) && + nextLecture.startTime.isBefore( + tomorrow.add(const Duration(days: 1)), + )) { + // Next lecture is tomorrow + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + const UniIcon(size: 45, UniIcons.beer), + const SizedBox(height: 8), + Text( + '${S.of(context).no_classes_today}\n${S.of(context).nextclasses}${S.of(context).tomorrow}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + const SizedBox(height: 18), + CardTimeline(items: buildTimelineItems(upcomingLectures, ref)), + ], + ); + } else { + // Next lecture is later this week (but within this week) + final dateText = DateFormat( + 'EEEE', + Localizations.localeOf(context).toString(), + ).format(nextLecture.startTime); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + const UniIcon(size: 45, UniIcons.beer), + const SizedBox(height: 8), + Text( + '${S.of(context).no_classes_today}\n${S.of(context).nextclasses}$dateText:', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + const SizedBox(height: 18), + CardTimeline(items: buildTimelineItems(upcomingLectures, ref)), + ], + ); + } + }, hasContent: (lectures) => lectures.isNotEmpty, nullContentWidget: Center( child: IconLabel( @@ -50,15 +241,19 @@ class ScheduleHomeCard extends GenericHomecard { ), ), ), - mapper: - (lectures) => - lectures - .where((lecture) => lecture.endTime.isAfter(DateTime.now())) - .toList(), + mapper: (lectures) => lectures, loadingWidget: const ShimmerCardTimeline(), ); } + bool _isLectureCurrent(Lecture lecture, DateTime now) { + final isCurrent = + (now.isAfter(lecture.startTime) || + now.isAtSameMomentAs(lecture.startTime)) && + now.isBefore(lecture.endTime); + return isCurrent; + } + @override void onCardClick(BuildContext context) { Navigator.pushNamed( @@ -70,54 +265,60 @@ class ScheduleHomeCard extends GenericHomecard { List buildTimelineItems(List lectures, WidgetRef ref) { final now = DateTime.now(); - final week = Week(start: now); + final period = TimePeriod( + start: now, + end: now.add(const Duration(days: 7)), + ); + //final week = Week(start: now); final session = ref.read(sessionProvider); - final sortedLectures = lectures - .where((lecture) => week.contains(lecture.startTime)) - .toList() - .sortedBy((lecture) => week.getWeekday(lecture.startTime.weekday)); - - final items = - sortedLectures - .map( - (element) => TimelineItem( - isActive: - now.isAfter(element.startTime) && - now.isBefore(element.endTime), - title: DateFormat('HH:mm').format(element.startTime), - subtitle: DateFormat('HH:mm').format(element.endTime), - card: FutureBuilder( - future: - session.value != null - ? ProfileNotifier.fetchOrGetCachedProfilePicture( - session.value!, - studentNumber: element.teacherId, - ) - : Future.value(), - builder: (context, snapshot) { - return ScheduleCard( - isActive: - now.isAfter(element.startTime) && - now.isBefore(element.endTime), - name: element.subject, - acronym: element.acronym, - room: element.room, - type: element.typeClass, - teacherName: element.teacherName, - teacherPhoto: - snapshot.hasData && snapshot.data != null - ? Image(image: FileImage(snapshot.data!)) - : Image.asset( - 'assets/images/profile_placeholder.png', - ), - ); - }, - ), - ), - ) + // Filter lectures to only show those within the next 7 days + final lecturesThisWeek = + lectures + .where((lecture) => period.contains(lecture.startTime)) .toList(); - return items; + //Sort by actual date and time + final sortedLectures = lecturesThisWeek.sortedBy( + (lecture) => lecture.startTime, + ); + + // Take only the first 2 lectures for the home card + final timelineItems = + sortedLectures.take(2).map((element) { + final isActive = _isLectureCurrent(element, now); + return TimelineItem( + isActive: isActive, + title: DateFormat('HH:mm').format(element.startTime), + subtitle: DateFormat('HH:mm').format(element.endTime), + card: FutureBuilder( + future: + session.value != null + ? ProfileNotifier.fetchOrGetCachedProfilePicture( + session.value!, + studentNumber: element.teacherId, + ) + : Future.value(), + builder: (context, snapshot) { + final teacherPhoto = + (snapshot.hasData && snapshot.data != null) + ? Image.file(snapshot.data!) + : Image.asset('assets/images/profile_placeholder.png'); + + return ScheduleCard( + isActive: isActive, + name: element.subject, + acronym: element.acronym, + room: element.room, + type: element.typeClass, + teacherName: element.teacherName, + teacherPhoto: teacherPhoto, + ); + }, + ), + ); + }).toList(); + + return timelineItems; } }