diff --git a/lib/blocs/welcome_cubit.dart b/lib/blocs/welcome_cubit.dart index b0c38276..5020576d 100644 --- a/lib/blocs/welcome_cubit.dart +++ b/lib/blocs/welcome_cubit.dart @@ -15,7 +15,7 @@ class WelcomeState extends Equatable { final List? articles; /// This can only be null when [isLoading] or [hasException] is true. - final List? upcomingEvents; + final List? events; /// This can only be null when [isLoading] or [hasException] is true. final List? announcements; @@ -30,14 +30,14 @@ class WelcomeState extends Equatable { bool get hasResults => slides != null && articles != null && - upcomingEvents != null && + events != null && announcements != null; @protected const WelcomeState({ required this.slides, required this.articles, - required this.upcomingEvents, + required this.events, required this.announcements, required this.isLoading, required this.message, @@ -47,7 +47,7 @@ class WelcomeState extends Equatable { List get props => [ slides, articles, - upcomingEvents, + events, announcements, message, isLoading, @@ -56,14 +56,14 @@ class WelcomeState extends Equatable { WelcomeState copyWith({ List? slides, List? articles, - List? upcomingEvents, + List? events, List? announcements, bool? isLoading, String? message, }) => WelcomeState( slides: slides ?? this.slides, articles: articles ?? this.articles, - upcomingEvents: upcomingEvents ?? this.upcomingEvents, + events: events ?? this.events, announcements: announcements ?? this.announcements, isLoading: isLoading ?? this.isLoading, message: message ?? this.message, @@ -72,7 +72,7 @@ class WelcomeState extends Equatable { const WelcomeState.result({ required List this.slides, required List this.articles, - required List this.upcomingEvents, + required List this.events, required List this.announcements, }) : message = null, isLoading = false; @@ -80,7 +80,7 @@ class WelcomeState extends Equatable { const WelcomeState.loading({ this.slides, this.articles, - this.upcomingEvents, + this.events, this.announcements, }) : message = null, isLoading = true; @@ -88,7 +88,7 @@ class WelcomeState extends Equatable { const WelcomeState.failure({required String this.message}) : slides = null, articles = null, - upcomingEvents = null, + events = null, announcements = null, isLoading = false; } @@ -137,7 +137,7 @@ class WelcomeCubit extends Cubit { WelcomeState.result( slides: slides, articles: articlesResponse.results, - upcomingEvents: events, + events: events, announcements: announcementsResponse, ), ); diff --git a/lib/ui/screens/welcome_screen.dart b/lib/ui/screens/welcome_screen.dart index ca714223..20658f9e 100644 --- a/lib/ui/screens/welcome_screen.dart +++ b/lib/ui/screens/welcome_screen.dart @@ -19,15 +19,6 @@ class WelcomeScreen extends StatefulWidget { } class _WelcomeScreenState extends State { - static final dateFormatter = DateFormat('EEEE d MMMM'); - - static Map> _groupByDay(List events) { - return groupBy( - events, - (event) => DateTime(event.start.year, event.start.month, event.start.day), - ); - } - Widget _makeAnnouncements(List announcements) { return AnimatedSize( curve: Curves.ease, @@ -109,64 +100,6 @@ class _WelcomeScreenState extends State { ); } - Widget _makeUpcomingEvents(List events) { - final dayGroupedEvents = _groupByDay(events); - return AnimatedSize( - curve: Curves.ease, - duration: const Duration(milliseconds: 300), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - 'UPCOMING EVENTS', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium, - ), - ...dayGroupedEvents.entries.map((entry) { - final now = DateTime.now(); - final today = DateTime(now.year, now.month, now.day); - final day = entry.key; - final dayEvents = entry.value; - String dayText; - switch (day.difference(today).inDays) { - case 0: - dayText = 'TODAY'; - break; - case 1: - dayText = 'TOMORROW'; - break; - default: - dayText = dateFormatter.format(day).toUpperCase(); - } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 8), - Text( - dayText, - textAlign: TextAlign.left, - style: Theme.of(context).textTheme.bodySmall, - ), - for (final event in dayEvents) EventDetailCard(event: event), - ], - ); - }), - if (events.isEmpty) - const Padding( - padding: EdgeInsets.all(8), - child: Text( - 'There are no upcoming events.', - textAlign: TextAlign.center, - ), - ), - ], - ), - ), - ); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -194,7 +127,8 @@ class _WelcomeScreenState extends State { _makeArticles(state.articles!), if (state.articles!.isNotEmpty) const Divider(indent: 16, endIndent: 16, height: 8), - _makeUpcomingEvents(state.upcomingEvents!), + // _makeEvents(state.events!), + Events(state.events!), TextButton( onPressed: () => context.goNamed('calendar'), child: const Text('SHOW THE ENTIRE AGENDA'), @@ -281,6 +215,102 @@ class _SlidesCarouselState extends State { } } +class Events extends StatelessWidget { + const Events(this.events); + final List? events; + + static final dateFormatter = DateFormat('EEEE d MMMM'); + static Map> _groupByDay(List events) { + return groupBy( + events, + (event) => DateTime(event.start.year, event.start.month, event.start.day), + ); + } + + @override + Widget build(BuildContext context) { + final now = DateTime.now(); + + bool isOngoing(BaseEvent event) => + event.start.isBefore(now) && event.end.isAfter(now); + + final ongoingEvents = events!.where((e) => isOngoing(e)).toList(); + final upcomingEvents = events!.where((e) => !isOngoing(e)).toList(); + return Column( + children: [ + if (ongoingEvents.isNotEmpty) + _makeEventsSection(context, ongoingEvents, 'ONGOING EVENTS', false), + _makeEventsSection(context, upcomingEvents, 'UPCOMING EVENTS', true), + ], + ); + } + + Widget _makeEventsSection( + BuildContext context, + List events, + String title, + bool showDayText, + ) { + final dayGroupedEvents = _groupByDay(events); + return AnimatedSize( + curve: Curves.ease, + duration: const Duration(milliseconds: 300), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + title, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium, + ), + ...dayGroupedEvents.entries.map((entry) { + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final day = entry.key; + final dayEvents = entry.value; + String dayText; + switch (day.difference(today).inDays) { + case 0: + dayText = 'TODAY'; + break; + case 1: + dayText = 'TOMORROW'; + break; + default: + dayText = dateFormatter.format(day).toUpperCase(); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (showDayText) SizedBox(height: 8), + if (showDayText) + Text( + dayText, + // dateFormatter.format(day).toUpperCase(), + textAlign: TextAlign.left, + style: Theme.of(context).textTheme.bodySmall, + ), + for (final event in dayEvents) EventDetailCard(event: event), + ], + ); + }), + if (events.isEmpty) + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'There are no events here.', + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ); + } +} + class Announcements extends StatefulWidget { final List announcements; diff --git a/lib/ui/widgets/event_detail_card.dart b/lib/ui/widgets/event_detail_card.dart index a04522a2..4443c36e 100644 --- a/lib/ui/widgets/event_detail_card.dart +++ b/lib/ui/widgets/event_detail_card.dart @@ -8,6 +8,7 @@ import 'package:reaxit/ui/theme.dart'; class EventDetailCard extends StatelessWidget { static final timeFormatter = DateFormat('HH:mm'); + static final dateFormatter = DateFormat('dd/MM HH:mm'); final BaseEvent event; final Color _indicatorColor; final bool _hasFoodEvent; @@ -53,8 +54,19 @@ class EventDetailCard extends StatelessWidget { PartnerEvent _ => Colors.black, _ => null, }; - final start = timeFormatter.format(event.start.toLocal()); - final end = timeFormatter.format(event.end.toLocal()); + String start = timeFormatter.format(event.start.toLocal()); + String end = timeFormatter.format(event.end.toLocal()); + + final startDate = event.start.toLocal(); + final endDate = event.end.toLocal(); + + if (startDate.day != endDate.day || + startDate.month != endDate.month || + startDate.year != endDate.year) { + // Event takes more than a day, show date as well + start = dateFormatter.format(startDate); + end = dateFormatter.format(endDate); + } // Remove HTML tags. final caption = event.caption.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ''); diff --git a/test/widget/welcome_test.dart b/test/widget/welcome_test.dart index 4be46af7..27fca7bc 100644 --- a/test/widget/welcome_test.dart +++ b/test/widget/welcome_test.dart @@ -58,7 +58,7 @@ void main() { final state = WelcomeState.result( slides: const [], articles: const [], - upcomingEvents: [normalEvent, partnerEvent], + events: [normalEvent, partnerEvent], announcements: const [], );