From 39857fff47f34dff42d0d0529c77900e9a8d0463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20K=C3=B6rber?= <56073945+jakobkoerber@users.noreply.github.com> Date: Fri, 13 Oct 2023 15:43:35 +0200 Subject: [PATCH] Show Opening Hours for Cafeterias (#126) --- lib/base/helpers/info_row.dart | 18 ++++ lib/base/localization/l10n/app_de.arb | 45 +++++++--- lib/base/localization/l10n/app_en.arb | 45 +++++++--- .../contactComponent/views/tuition_view.dart | 22 ++--- .../recommender/time_strategy.dart | 4 +- .../model/cafeterias/cafeteria.dart | 24 +++++- .../model/cafeterias/cafeteria.g.dart | 4 + .../model/cafeterias/opening_hours.dart | 32 +++++++ .../model/cafeterias/opening_hours.g.dart | 45 ++++++++++ .../views/cafeterias/cafeteria_view.dart | 83 +++++++++++++++++++ .../homeWidget/cafeteria_widget_view.dart | 18 ++++ 11 files changed, 298 insertions(+), 42 deletions(-) create mode 100644 lib/base/helpers/info_row.dart create mode 100644 lib/placesComponent/model/cafeterias/opening_hours.dart create mode 100644 lib/placesComponent/model/cafeterias/opening_hours.g.dart diff --git a/lib/base/helpers/info_row.dart b/lib/base/helpers/info_row.dart new file mode 100644 index 00000000..42c08be0 --- /dev/null +++ b/lib/base/helpers/info_row.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class InfoRow extends StatelessWidget { + const InfoRow({super.key, required this.title, required this.info}); + + final String title; + final String info; + + @override + Widget build(BuildContext context) { + return Row(children: [ + Expanded( + child: + Text(title, style: const TextStyle(fontWeight: FontWeight.w500))), + Expanded(child: Text(info)) + ]); + } +} diff --git a/lib/base/localization/l10n/app_de.arb b/lib/base/localization/l10n/app_de.arb index ef76ed5a..d25ea4b5 100644 --- a/lib/base/localization/l10n/app_de.arb +++ b/lib/base/localization/l10n/app_de.arb @@ -62,7 +62,6 @@ "contactSupport":"Support kontaktieren", "cafeteria":"Mensa", "cafeterias":"Mensen", - "noMealPlanFound":"Kein Speiseplan gefunden", "latestNews":"Neueste Nachrichten", "scheduledLectureDates":"Termine", "@scheduledLectureDates":{ @@ -189,7 +188,6 @@ "rooms":"Räume", "roomDetails":"Raumdetails", "building":"Gebäude", - "departures":"Abfahrten", "nfreeRooms":"{count, plural, =0{Keine freien Räume} =1{1 freier Raum} other{{count} freie Räume}}", "@nfreeRooms":{ "description":"How many free rooms there are", @@ -203,15 +201,15 @@ "noRoomsFound":"Keine Räume gefunden", "noMealPlanFound":"Kein Essensplan gefunden", "noEntriesFoundSearch":"Keine {searchCategory} gefunden", - "@noEntriesFoundSearch":{ - "description":"Title of Search Category", - "placeholders":{ - "searchCategory":{ - "type":"String", - "example":"Grades" - } - } - }, + "@noEntriesFoundSearch":{ + "description":"Title of Search Category", + "placeholders":{ + "searchCategory":{ + "type":"String", + "example":"Grades" + } + } + }, "enterQueryStart":"Stelle eine Suchanfrage", "personalLectures":"Persönliche Vorlesungen", "persons":"Personen", @@ -228,5 +226,28 @@ "requestCancelled":"Anfrage abgebrochen", "pleaseReport":"Bitte melden Sie dies als Fehler \nper E-Mail oder auf GitHub", "connectionTimeout":"Zeitüberschreitung", - "unknownError":"Unbekannter Fehler" + "unknownError":"Unbekannter Fehler", + "monday":"Montag", + "tuesday":"Dienstag", + "wednesday":"Mittwoch", + "thursday":"Donnerstag", + "friday":"Freitag", + "weekend":"Wochenende", + "openingHours":"Öffnungszeiten", + "openToday":"Heute offen von {start} - {end}", + "@openToday":{ + "description":"Opening Times", + "placeholders":{ + "start":{ + "type":"String", + "example":"11:00" + }, + "end":{ + "type":"String", + "example":"14:00" + } + } + }, + "closed":"Geschlossen", + "closedToday":"Heute geschlossen" } \ No newline at end of file diff --git a/lib/base/localization/l10n/app_en.arb b/lib/base/localization/l10n/app_en.arb index 3d9c7d2d..65896e18 100644 --- a/lib/base/localization/l10n/app_en.arb +++ b/lib/base/localization/l10n/app_en.arb @@ -62,7 +62,6 @@ "contactSupport":"Contact Support", "cafeteria":"Cafeteria", "cafeterias":"Cafeterias", - "noMealPlanFound":"no meal plan found", "latestNews":"Latest News", "scheduledLectureDates":"Dates", "@scheduledLectureDates":{ @@ -189,7 +188,6 @@ "rooms":"Rooms", "roomDetails":"Room Details", "building":"Building", - "departures":"Departures", "nfreeRooms":"{count, plural, =0{no free rooms} =1{1 free room} other{{count} free rooms}}", "@nfreeRooms":{ "description":"How many free rooms there are", @@ -203,15 +201,15 @@ "noRoomsFound":"No Rooms Found", "noMealPlanFound":"No Meal Plan Found", "noEntriesFoundSearch":"No {searchCategory} Found", - "@noEntriesFoundSearch":{ - "description":"Title of Search Category", - "placeholders":{ - "searchCategory":{ - "type":"String", - "example":"Grades" - } - } - }, + "@noEntriesFoundSearch":{ + "description":"Title of Search Category", + "placeholders":{ + "searchCategory":{ + "type":"String", + "example":"Grades" + } + } + }, "enterQueryStart":"Enter a Query to Start", "personalLectures":"Personal Lectures", "persons":"Persons", @@ -228,5 +226,28 @@ "requestCancelled":"Request Cancelled", "pleaseReport":"Please report this is as a bug \nvia Email or on GitHub", "connectionTimeout":"Connection Timeout", - "unknownError":"Unknown Error" + "unknownError":"Unknown Error", + "monday":"Monday", + "tuesday":"Tuesday", + "wednesday":"Wednesday", + "thursday":"Thursday", + "friday":"Friday", + "weekend":"Weekend", + "openingHours":"Opening Hours", + "openToday":"Open today from {start} - {end}", + "@openToday":{ + "description":"Opening Times", + "placeholders":{ + "start":{ + "type":"String", + "example":"11:00" + }, + "end":{ + "type":"String", + "example":"14:00" + } + } + }, + "closed":"Closed", + "closedToday":"Closed Today" } \ No newline at end of file diff --git a/lib/homeComponent/contactComponent/views/tuition_view.dart b/lib/homeComponent/contactComponent/views/tuition_view.dart index 17ae54e5..b7443877 100644 --- a/lib/homeComponent/contactComponent/views/tuition_view.dart +++ b/lib/homeComponent/contactComponent/views/tuition_view.dart @@ -1,5 +1,6 @@ import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; import 'package:campus_flutter/base/helpers/icon_text.dart'; +import 'package:campus_flutter/base/helpers/info_row.dart'; import 'package:campus_flutter/profileComponent/model/tuition.dart'; import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; @@ -43,13 +44,13 @@ class TuitionView extends ConsumerWidget { ?.copyWith(fontWeight: FontWeight.w500), ), const Padding(padding: EdgeInsets.symmetric(vertical: 5.0)), - _infoRow( - context.localizations.tuitionDueDate, - DateFormat.yMd(context.localizations.localeName) + InfoRow( + title: context.localizations.tuitionDueDate, + info: DateFormat.yMd(context.localizations.localeName) .format(snapshot.data!.deadline)), - _infoRow( - context.localizations.tuitionOpenAmount, - NumberFormat.currency(locale: "de_DE", symbol: '€') + InfoRow( + title: context.localizations.tuitionOpenAmount, + info: NumberFormat.currency(locale: "de_DE", symbol: '€') .format(snapshot.data!.amount)) ]), actions: [ @@ -94,13 +95,4 @@ class TuitionView extends ConsumerWidget { ?.copyWith(color: Colors.red))); } } - - Widget _infoRow(String title, String info) { - return Row(children: [ - Expanded( - child: - Text(title, style: const TextStyle(fontWeight: FontWeight.w500))), - Expanded(child: Text(info)) - ]); - } } diff --git a/lib/homeComponent/widgetComponent/recommender/time_strategy.dart b/lib/homeComponent/widgetComponent/recommender/time_strategy.dart index 7b5e3299..87365295 100644 --- a/lib/homeComponent/widgetComponent/recommender/time_strategy.dart +++ b/lib/homeComponent/widgetComponent/recommender/time_strategy.dart @@ -22,10 +22,10 @@ class TimeStrategy implements WidgetRecommenderStrategy { } // The menu is not interesting anymore after the cafeteria has closed. - if (14 <= currentDate.hour) { + /*if (14 <= currentDate.hour) { priority = 0; break; - } + }*/ // The menu might be interesting before the opening hours. if (currentDate.hour < 14 && 6 < currentDate.hour) { diff --git a/lib/placesComponent/model/cafeterias/cafeteria.dart b/lib/placesComponent/model/cafeterias/cafeteria.dart index 254557e5..48754ee9 100644 --- a/lib/placesComponent/model/cafeterias/cafeteria.dart +++ b/lib/placesComponent/model/cafeterias/cafeteria.dart @@ -1,3 +1,4 @@ +import 'package:campus_flutter/placesComponent/model/cafeterias/opening_hours.dart'; import 'package:campus_flutter/searchComponent/model/comparison_token.dart'; import 'package:campus_flutter/searchComponent/protocols/searchable.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -40,11 +41,31 @@ class Cafeteria extends Searchable { @JsonKey(name: "queue_status") final String? queueStatusApi; Queue? queue; + @JsonKey(name: "open_hours") + final OpeningHours? openingHours; String? get title { return name; } + (bool, OpeningHour?) get openingHoursToday { + final today = DateTime.now(); + switch (today.weekday) { + case 1: + return (true, openingHours?.mon); + case 2: + return (true, openingHours?.tue); + case 3: + return (true, openingHours?.wed); + case 4: + return (true, openingHours?.thu); + case 5: + return (true, openingHours?.fri); + default: + return (false, null); + } + } + @override @JsonKey(includeFromJson: false, includeToJson: false) List get comparisonTokens => [ @@ -61,7 +82,8 @@ class Cafeteria extends Searchable { required this.name, required this.id, required this.queueStatusApi, - required this.queue}); + required this.queue, + this.openingHours}); factory Cafeteria.fromJson(Map json) => _$CafeteriaFromJson(json); diff --git a/lib/placesComponent/model/cafeterias/cafeteria.g.dart b/lib/placesComponent/model/cafeterias/cafeteria.g.dart index c241d052..b9272dd7 100644 --- a/lib/placesComponent/model/cafeterias/cafeteria.g.dart +++ b/lib/placesComponent/model/cafeterias/cafeteria.g.dart @@ -36,6 +36,9 @@ Cafeteria _$CafeteriaFromJson(Map json) => Cafeteria( queue: json['queue'] == null ? null : Queue.fromJson(json['queue'] as Map), + openingHours: json['open_hours'] == null + ? null + : OpeningHours.fromJson(json['open_hours'] as Map), ); Map _$CafeteriaToJson(Cafeteria instance) => { @@ -44,6 +47,7 @@ Map _$CafeteriaToJson(Cafeteria instance) => { 'canteen_id': instance.id, 'queue_status': instance.queueStatusApi, 'queue': instance.queue, + 'open_hours': instance.openingHours, }; Cafeterias _$CafeteriasFromJson(Map json) => Cafeterias( diff --git a/lib/placesComponent/model/cafeterias/opening_hours.dart b/lib/placesComponent/model/cafeterias/opening_hours.dart new file mode 100644 index 00000000..7f8d4681 --- /dev/null +++ b/lib/placesComponent/model/cafeterias/opening_hours.dart @@ -0,0 +1,32 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'opening_hours.g.dart'; + +@JsonSerializable() +class OpeningHours { + final OpeningHour? mon; + final OpeningHour? tue; + final OpeningHour? wed; + final OpeningHour? thu; + final OpeningHour? fri; + + OpeningHours({this.mon, this.tue, this.wed, this.thu, this.fri}); + + factory OpeningHours.fromJson(Map json) => + _$OpeningHoursFromJson(json); + + Map toJson() => _$OpeningHoursToJson(this); +} + +@JsonSerializable() +class OpeningHour { + final String start; + final String end; + + OpeningHour({required this.start, required this.end}); + + factory OpeningHour.fromJson(Map json) => + _$OpeningHourFromJson(json); + + Map toJson() => _$OpeningHourToJson(this); +} diff --git a/lib/placesComponent/model/cafeterias/opening_hours.g.dart b/lib/placesComponent/model/cafeterias/opening_hours.g.dart new file mode 100644 index 00000000..c49d7839 --- /dev/null +++ b/lib/placesComponent/model/cafeterias/opening_hours.g.dart @@ -0,0 +1,45 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'opening_hours.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +OpeningHours _$OpeningHoursFromJson(Map json) => OpeningHours( + mon: json['mon'] == null + ? null + : OpeningHour.fromJson(json['mon'] as Map), + tue: json['tue'] == null + ? null + : OpeningHour.fromJson(json['tue'] as Map), + wed: json['wed'] == null + ? null + : OpeningHour.fromJson(json['wed'] as Map), + thu: json['thu'] == null + ? null + : OpeningHour.fromJson(json['thu'] as Map), + fri: json['fri'] == null + ? null + : OpeningHour.fromJson(json['fri'] as Map), + ); + +Map _$OpeningHoursToJson(OpeningHours instance) => + { + 'mon': instance.mon, + 'tue': instance.tue, + 'wed': instance.wed, + 'thu': instance.thu, + 'fri': instance.fri, + }; + +OpeningHour _$OpeningHourFromJson(Map json) => OpeningHour( + start: json['start'] as String, + end: json['end'] as String, + ); + +Map _$OpeningHourToJson(OpeningHour instance) => + { + 'start': instance.start, + 'end': instance.end, + }; diff --git a/lib/placesComponent/views/cafeterias/cafeteria_view.dart b/lib/placesComponent/views/cafeterias/cafeteria_view.dart index 94c7aaba..66431450 100644 --- a/lib/placesComponent/views/cafeterias/cafeteria_view.dart +++ b/lib/placesComponent/views/cafeterias/cafeteria_view.dart @@ -1,7 +1,9 @@ import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/helpers/info_row.dart'; import 'package:campus_flutter/base/helpers/padded_divider.dart'; import 'package:campus_flutter/base/views/error_handling_view.dart'; import 'package:campus_flutter/placesComponent/model/cafeterias/cafeteria.dart'; +import 'package:campus_flutter/placesComponent/model/cafeterias/opening_hours.dart'; import 'package:campus_flutter/placesComponent/views/directions_button.dart'; import 'package:campus_flutter/placesComponent/views/homeWidget/cafeteria_widget_view.dart'; import 'package:campus_flutter/placesComponent/views/map_widget.dart'; @@ -24,12 +26,79 @@ class CafeteriaScaffold extends ConsumerWidget { appBar: AppBar( leading: const BackButton(), title: Text(cafeteria.name), + actions: [ + if (cafeteria.openingHours != null) + IconButton( + onPressed: () => _alertDialog(context), + icon: Icon( + Icons.access_time_filled, + color: context.theme.primaryColor, + )) + ], ), body: CafeteriaView( cafeteria: cafeteria, ), ); } + + void _alertDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) { + final openingHours = cafeteria.openingHours; + return AlertDialog( + title: Text( + context.localizations.openingHours, + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InfoRow( + title: context.localizations.monday, + info: + _openingHourStringBuilder(openingHours?.mon, context)), + InfoRow( + title: context.localizations.tuesday, + info: + _openingHourStringBuilder(openingHours?.tue, context)), + InfoRow( + title: context.localizations.wednesday, + info: + _openingHourStringBuilder(openingHours?.wed, context)), + InfoRow( + title: context.localizations.thursday, + info: + _openingHourStringBuilder(openingHours?.thu, context)), + InfoRow( + title: context.localizations.friday, + info: + _openingHourStringBuilder(openingHours?.fri, context)), + InfoRow( + title: context.localizations.weekend, + info: context.localizations.closed), + ], + ), + actions: [ + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text("Okay")) + ], + actionsAlignment: MainAxisAlignment.center, + ); + }); + } + + String _openingHourStringBuilder( + OpeningHour? openingHour, BuildContext context) { + if (openingHour == null) { + return context.localizations.unknown; + } else { + return "${openingHour.start} - ${openingHour.end}"; + } + } } class CafeteriaView extends ConsumerStatefulWidget { @@ -43,11 +112,13 @@ class CafeteriaView extends ConsumerStatefulWidget { class _CafeteriaViewState extends ConsumerState { late DateTime selectedDate; + late (bool, OpeningHour?) openingHours; @override void initState() { final today = DateTime.now(); selectedDate = DateTime(today.year, today.month, today.day); + openingHours = widget.cafeteria.openingHoursToday; super.initState(); } @@ -68,6 +139,8 @@ class _CafeteriaViewState extends ConsumerState { } else { return Column( children: [ + if (openingHours.$2 != null && openingHours.$1) + _openingTimes(openingHours, context), ..._mapAndDirections(), const PaddedDivider(), _pickerAndSlider() @@ -77,6 +150,16 @@ class _CafeteriaViewState extends ConsumerState { }); } + Widget _openingTimes( + (bool, OpeningHour?) openingHours, BuildContext context) { + if (!openingHours.$1) { + return Text(context.localizations.closedToday); + } else { + return Text(context.localizations + .openToday(openingHours.$2!.start, openingHours.$2!.end)); + } + } + List _mapAndDirections() { return [ MapWidget.fullPadding( diff --git a/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart b/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart index 3afe0336..e0819d91 100644 --- a/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart +++ b/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart @@ -59,10 +59,28 @@ class _CafeteriaWidgetViewState extends ConsumerState { ), ], ), + subtitle: _openingHours(), child: _dynamicContent(snapshot)); }); } + Widget? _openingHours() { + final openingHours = ref + .read(cafeteriasViewModel) + .closestCafeteria + .value + ?.$1 + .openingHoursToday; + if (openingHours?.$2 != null) { + return Padding( + padding: EdgeInsets.only(left: context.padding), + child: Text(context.localizations + .openToday(openingHours!.$2!.start, openingHours.$2!.end))); + } else { + return null; + } + } + Widget _dynamicContent(AsyncSnapshot<(Cafeteria, CafeteriaMenu)?> snapshot) { if (snapshot.hasData) { final dishes =