diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6bc3cb56..8532f1df 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index e86e443b..a8309cd5 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,5 +45,19 @@ CADisableMinimumFrameDurationOnPhone + + NSCalendarsUsageDescription + Access your calendar to add bridge closure events + + NSCalendarsFullAccessUsageDescription + Full access to view and edit your calendar events + NSCalendarsWriteOnlyAccessUsageDescription + Add bridge closure events without reading existing events + CFBundleLocalizations + + en + fr + es + diff --git a/lib/extensions/boats_extension.dart b/lib/extensions/boats_extension.dart index 3f78cae6..c54084cd 100644 --- a/lib/extensions/boats_extension.dart +++ b/lib/extensions/boats_extension.dart @@ -4,20 +4,6 @@ import 'package:chabo_app/models/boat.dart'; import 'package:flutter/material.dart'; extension BoatsExtension on List { - TextSpan toLocalizedTextSpan(BuildContext context) { - final finalTextSpan = []; - for (var index = 0; index < length; index++) { - finalTextSpan.add(this[index].toLocalizedStatusTextSpan(context, true)); - if (index + 1 != length) { - finalTextSpan.add( - TextSpan(text: ' ${AppLocalizations.of(context)!.and} '), - ); - } - } - - return TextSpan(children: finalTextSpan); - } - String getNames(BuildContext context) { var finalString = ''; for (var index = 0; index < length; index++) { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7f1f8504..97437e51 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -231,5 +231,30 @@ "timeFormatTitle": "Time format", "timeFormatSubTitle": "Display hours in 12 or 24 format", "noMoreForecastsTitle": "No upcoming events", - "noMoreForecastsMessage": "Bordeaux Métropole has not communicated any closure of the Chaban-Delmas Bridge for the coming weeks.\nStay informed of upcoming closures by returning here regularly" + "noMoreForecastsMessage": "Bordeaux Métropole has not communicated any closure of the Chaban-Delmas Bridge for the coming weeks.\nStay informed of upcoming closures by returning here regularly", + "shareEventTitle": "Share", + "calendarEventAddToCalendar": "Add to calendar", + "calendarEventTitle": "Closure of the Chaban Delmas bridge", + "calendarEventLocation": "Bordeaux, Chaban Delmas bridge", + "calendarEventMaintenanceDescription": "The Chaban bridge will close for maintenance \uD83D\uDEE0. It will remains closed for {duration} \uD83C\uDF09", + "@calendarEventMaintenanceDescription": { + "placeholders": { + "duration": {} + } + }, + "calendarEventBoatDescription": "The Chaban bridge will close for {boat} \uD83D\uDEA2. It will remains closed for {duration} \uD83C\uDF09", + "@calendarEventBoatDescription": { + "placeholders": { + "boat": {}, + "duration": {} + } + }, + "bottomSheetTitle_maintenance": "Maintenance", + "bottomSheetTitle_boat": "Passage of vessel(s)", + "bottomSheetAdditionalInfo_maintenance": "The Chaban Bridge is closed for scheduled maintenance.", + "bottomSheetAdditionalInfo_boatLeaving": "leaving", + "bottomSheetAdditionalInfo_boatArriving": "arriving", + "bottomSheetAdditionalInfo_boatCrossingTimeDisclaimer": "Arrival and departure times are for information purposes only and are not guaranteed.", + "bottomSheetAdditionalInfo_shareMessage": "Hi, I'm going to be late, the Chaban Bridge is closed until" } + diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index abeb5b75..e9c11b96 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -46,25 +46,25 @@ } }, "dialogInformationContentBridge_closed_maintenance": "mantenimiento", - "dialogInformationContentTime_of_crossing": "hora estimada de cruce", + "dialogInformationContentTime_of_crossing": "hora de paso estimada", "duration": "Duración", "errorScreenContentError": "Error", - "errorScreenContentMessage": "Se produjo un error al abrir esta página. Encuentre la información técnica a continuación", + "errorScreenContentMessage": "Se ha producido un error al abrir esta pàgina. Encuentre información técnica a continuación", "errorScreenContentTechnical_Info": "Información técnica", - "unableAppInfo": "No se puede obtener información de la aplicación", - "appDescription": "La aplicación móvil para obtener los horarios de cierre y apertura del puente Chaban Delmas ubicado en Bordeaux, Francia", + "unableAppInfo": "No se puede recuperar la información de la aplicación", + "appDescription": "Aplicación móvil para obtener los horarios de cierre y apertura del puente Chaban-Delmas ubicado en Burdeos, Francia", "informationAboutTheApp": "Información sobre la aplicación", "about": "Acerca de", - "disclaimer": "Descargo de responsabilidad: cierres provisionales. Sujeto a confirmación de la Capitanía de Puerto.", - "openSetting": "Ajustes", + "disclaimer": "Aviso: cierres provisionales. Sujeto a confirmación de la Capitania de Puerto.", + "openSetting": "Configuración", "themeSettingTitle": "Tema", "themeSettingSubTitle": "Cambiar la apariencia de la aplicación", "lightTheme": "Tema claro", "darkTheme": "Tema oscuro", - "schedules": "Programar", + "schedules": "Horarios", "systemTheme": "Tema del sistema", "notifications": "Notificaciones", - "notificationsSubtitle": "Administre las notificaciones de la aplicación", + "notificationsSubtitle": "Administrar las notificaciones de la aplicación", "durationNotificationTitle": "{duration}antes", "@durationNotificationTitle": { "placeholders": { @@ -95,35 +95,35 @@ "day": {} } }, - "dayNotificationExplanation": "Reciba una notificación el {day} a las {time} que enumere todos los cierres planeados para la próxima semana", + "dayNotificationExplanation": "Reciba una notificación el {day} a las {time} que enumere todos los cierres planificados para la próxima semana", "@dayNotificationExplanation": { "placeholders": { "day": {}, "time": {} } }, - "closingNotificationTitle": "En el cierre", - "closingNotificationExplanation": "Reciba una notificación cuando el puente cierre", + "closingNotificationTitle": "Al cierre", + "closingNotificationExplanation": "Reciba una notificación cuando el puente se cierra", "notificationClosingChannelName": "Cierre", - "notificationClosingTitle": "El puente Chaban ha cerrado ⛔", - "notificationClosingBoatMessage": "El puente Chaban acaba de cerrarse para {boat} \uD83D\uDEA2. Permanecerá cerrado durante {duration} \uD83C\uDF09", + "notificationClosingTitle": "El puente Chaban está cerrado ⛔", + "notificationClosingBoatMessage": "El puente Chaban acaba de cerrarse para {boat} \uD83D\uDEA2. Permanecerà cerrado durante {duration} \uD83C\uDF09", "@notificationClosingBoatMessage": { "placeholders": { "boat": {}, "duration": {} } }, - "notificationClosingMaintenanceMessage": "El puente Chaban acaba de cerrarse para mantenimiento \uD83D\uDEE0. Permanecerá cerrado durante {duration} \uD83C\uDF09", + "notificationClosingMaintenanceMessage": "El puente Chaban acaba de cerrarse para mantenimiento \uD83D\uDEE0. Permanecerà cerrado durante {duration} \uD83C\uDF09", "@notificationClosingMaintenanceMessage": { "placeholders": { "duration": {} } }, "openingNotificationTitle": "En la apertura", - "openingNotificationExplanation": "Reciba una notificación cuando el puente abra", + "openingNotificationExplanation": "Reciba una notificación cuando el puente se abre", "notificationOpeningChannelName": "Apertura", "notificationOpeningTitle": "El puente Chaban está abierto ✅", - "notificationOpeningMessage": "El puente Chaban acaba de abrirse al tráfico \uD83D\uDEB2", + "notificationOpeningMessage": "El puente Chaban acaba de abrirse al tràfico \uD83D\uDEB2", "monday": "Lunes", "tuesday": "Martes", "wednesday": "Miércoles", @@ -132,7 +132,7 @@ "saturday": "Sábado", "sunday": "Domingo", "notificationDurationTitle": "Cierre inminente ⚠️", - "notificationDurationBoatMessage": "El puente Chaban cerrará en {timeLeft} para {boat} \uD83D\uDEA2. Permanecerá cerrado durante {duration} \uD83C\uDF09", + "notificationDurationBoatMessage": "El puente Chaban se cerrarà en {timeLeft} para {boat} \uD83D\uDEA2. Permanecerà cerrado durante {duration} \uD83C\uDF09", "@notificationDurationBoatMessage": { "placeholders": { "boat": {}, @@ -140,7 +140,7 @@ "duration": {} } }, - "notificationDurationMaintenanceMessage": "El puente Chaban cerrará en {timeLeft} para mantenimiento \uD83D\uDEE0. Permanecerá cerrado durante {duration} \uD83C\uDF09", + "notificationDurationMaintenanceMessage": "El puente Chaban se cerrarà en {timeLeft} para mantenimiento \uD83D\uDEE0. Permanecerà cerrado durante {duration} \uD83C\uDF09", "@notificationDurationMaintenanceMessage": { "placeholders": { "timeLeft": {}, @@ -149,7 +149,7 @@ }, "notificationDurationChannelName": "Cierres inminentes", "notificationTimeTitle": "\uD83D\uDDD3 Cierre programado para mañana", - "notificationTimeBoatMessage": "El puente Chaban cerrará mañana a las {time} para {boat} \uD83D\uDEA2. Permanecerá cerrado durante {duration} \uD83C\uDF09", + "notificationTimeBoatMessage": "El puente Chaban se cerrarà mañana a las {time} para {boat} \uD83D\uDEA2. Permanecerà cerrado durante {duration} \uD83C\uDF09", "@notificationTimeBoatMessage": { "placeholders": { "boat": {}, @@ -169,34 +169,34 @@ "boat": {} } }, - "notificationTimeMaintenanceMessage": "El puente Chaban cerrará mañana a las {time} por mantenimiento \uD83D\uDEE0. Permanecerá cerrado durante {duration} \uD83C\uDF09", + "notificationTimeMaintenanceMessage": "El puente Chaban se cerrarà mañana a las {time} para mantenimiento \uD83D\uDEE0. Permanecerà cerrado durante {duration} \uD83C\uDF09", "@notificationTimeMaintenanceMessage": { "placeholders": { "time": {}, "duration": {} } }, - "notificationTimeChannelName": "Cierres del próximo día", + "notificationTimeChannelName": "Cierres del dìa siguiente", "passedClosure": "Cierre pasado", - "selectAboutDialog": "{choice, select, source_code {Código fuente} privacy_policy {Política de privacidad} yuhliet_instagram {Instagram de Yuhliet's} city_of_bordeaux {Ciudad de Bordeaux} bordeaux_open_data {Bordeaux Open Data} licenses {Licencias} changelog {Registro de cambios} other {Undefined}}", + "selectAboutDialog": "{choice, select, source_code {Código fuente} privacy_policy {Polìtica de privacidad} yuhliet_instagram {Instagram de Yuhliet} city_of_bordeaux {Ciudad de Burdeos} bordeaux_open_data {Bordeaux Open Data} licenses {Licencias} changelog {Registro de cambios} other {Indefinido}}", "@selectAboutDialog": { "placeholders": { "choice": {} } }, - "day": "Día", + "day": "Dìa", "notificationDayTitle": "\uD83D\uDD2E Cierre programado", - "notificationDayMessage": "{count, plural, =0{No hay cierres programados para la próxima semana} =1{La próxima semana, el puente Chaban Delmas solo cerrará una vez} other{La próxima semana, el puente Chaban Delmas cerrará {count} veces}}", + "notificationDayMessage": "{count, plural, =0{Ningún cierre programado para la próxima semana} =1{La próxima semana, el puente Chaban-Delmas solo se cerrarà una vez} other{La próxima semana, el puente Chaban-Delmas se cerrarà {count} veces}}", "@notificationDayMessage": { "placeholders": { "count": {} } }, - "notificationDayChannelName": "Cierres planificados", - "leftHanded": "Zurdo.a", - "rightHanded": "Diestro.a", + "notificationDayChannelName": "Cierres programados", + "leftHanded": "Zurdo", + "rightHanded": "Diestro", "status": "Estado", - "statusLoadMessage": "Carga del estado actual del puente", + "statusLoadMessage": "Cargando el estado actual del puente", "loading": "Cargando...", "dayNotificationAt": "en las", "favoriteSlotsFrom": "De", @@ -209,16 +209,16 @@ "index": {} } }, - "favoriteSlotsInterferenceWarning": "Este horario interfiere con uno o más intervalos de tiempo", - "favoriteTimeSlotEnabledWarning": "Atención, al activar este parámetro solo recibirás notificaciones cuando ocurra un evento durante una de tus franjas horarias", - "favoriteSlotsChooseDay": "Elegir los días de la semana", - "moonHarborStatus": "{count, plural, =1 {está actualmente amarrado} other {están actualmente amarrados}} en el 'Puerto de la Luna'", + "favoriteSlotsInterferenceWarning": "Este horario interfiere con uno o màs intervalos de tiempo", + "favoriteTimeSlotEnabledWarning": "Atención, al activar este paràmetro solo recibiràs notificaciones cuando ocurra un evento durante una de tus franjas horarias", + "favoriteSlotsChooseDay": "Elegir los dìas de la semana", + "moonHarborStatus": "{count, plural, =1 {está actualmente amarrado} other {estàn actualmente amarrados}} en el 'Puerto de la Luna'", "@moonHarborStatus": { "placeholders": { "count": {} } }, - "moonHarborShortStatus": "{count} {count, plural, =1 {barco está actualmente} other {buques están actualmente}} en Bordeaux", + "moonHarborShortStatus": "{count} {count, plural, =1 {barco está actualmente} other {buques estàn actualmente}} en Bordeaux", "@moonHarborShortStatus": { "placeholders": { "count": {} @@ -231,5 +231,29 @@ "timeFormatTitle": "Formato de hora", "timeFormatSubTitle": "Visualización de las horas en formato de 12 o 24 horas.", "noMoreForecastsTitle": "Ningún acontecimiento por venir", - "noMoreForecastsMessage": "Bordeaux Métropole no ha comunicado ningún cierre del Puente Chaban-Delmas para las próximas semanas.\nManténgase informado de los próximos cierres volviendo aquí regularmente" + "noMoreForecastsMessage": "Bordeaux Métropole no ha comunicado ningún cierre del Puente Chaban-Delmas para las próximas semanas.\nManténgase informado de los próximos cierres volviendo aquì regularmente", + "shareEventTitle": "Compartir", + "calendarEventAddToCalendar": "Añadir al calendario", + "calendarEventTitle": "Cierre del puente Chaban Delmas", + "calendarEventLocation": "Bordeaux, puente Chaban Delmas", + "calendarEventMaintenanceDescription": "\"El puente Chaban permanecerá cerrado para mantenimiento \uD83D\uDEE0. Permanecerà cerrado durante {duration} \uD83C\uDF09", + "@calendarEventMaintenanceDescription": { + "placeholders": { + "duration": {} + } + }, + "calendarEventBoatDescription": "El puente Chaban permanecerá cerrado para {boat} \uD83D\uDEA2. Permanecerà cerrado durante {duration} \uD83C\uDF09", + "@calendarEventBoatDescription": { + "placeholders": { + "boat": {}, + "duration": {} + } + }, + "bottomSheetTitle_maintenance": "Mantenimiento", + "bottomSheetTitle_boat": "Paso de la(s) embarcación(es)", + "bottomSheetAdditionalInfo_maintenance": "El puente Chaban está cerrado por mantenimiento programado.", + "bottomSheetAdditionalInfo_boatLeaving": "llegada", + "bottomSheetAdditionalInfo_boatArriving": "partida", + "bottomSheetAdditionalInfo_boatCrossingTimeDisclaimer": "Los horarios de llegada y salida son únicamente a título informativo y no están garantizados.", + "bottomSheetAdditionalInfo_shareMessage": "Hola, voy a llegar tarde; el puente Chaban está cerrado hasta" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e28ecb95..8cb5f0b9 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -49,20 +49,19 @@ "dialogInformationContentTime_of_crossing": "heure de passage estimée", "duration": "Durée", "errorScreenContentError": "Erreur", - "errorScreenContentMessage": "Une erreur est survenue lors de l'ouverture de cette page. Veuillez trouver les informations techniques ci-dessous", + "errorScreenContentMessage": "Une erreur est survenue lors de l'ouverture de cette page. Vous trouverez des informations techniques ci-dessous", "errorScreenContentTechnical_Info": "Informations techniques", - "unableAppInfo": "Impossible d'obtenir les informations de l'application", - "appDescription": "L'application Mobile pour connaître les horaires de fermeture et d'ouverture du pont Chaban Delmas situé à Bordeaux, France", - "informationAboutTheApp": "Informations concernant l'application", + "unableAppInfo": "Impossible de récupérer les informations de l'application", + "appDescription": "Application mobile pour obtenir les horaires de fermeture et d'ouverture du pont Chaban-Delmas situé à Bordeaux, France", + "informationAboutTheApp": "Informations sur l'application", "about": "À propos", - "disclaimer": "Avis de non-responsabilité : fermetures provisoires. Sous réserve de confirmation de la Capitainerie.", + "disclaimer": "Avertissement : fermetures provisoires. Sous réserve de confirmation de la Capitainerie.", "openSetting": "Paramètres", "themeSettingTitle": "Thème", - "themeSettingSubTitle": "Changer l'apparence de l'application", + "themeSettingSubTitle": "Modifier l'apparence de l'application", "lightTheme": "Thème clair", "darkTheme": "Thème sombre", - "schedules": "Calendrier", - "status": "État", + "schedules": "Plannings", "systemTheme": "Thème système", "notifications": "Notifications", "notificationsSubtitle": "Gérer les notifications de l'application", @@ -72,7 +71,7 @@ "duration": {} } }, - "durationNotificationExplanation": "Recevoir une notification {duration}avant la prochaine fermeture. Cette valeur gère aussi le changement de couleur du statut actuel", + "durationNotificationExplanation": "Recevez une notification {duration}avant la prochaine fermeture. Cette valeur gère également le changement de couleur de l'état actuel", "@durationNotificationExplanation": { "placeholders": { "duration": {} @@ -84,19 +83,19 @@ "time": {} } }, - "timeNotificationExplanation": "Recevoir une notification la veille à {time} si une fermeture est prévue le lendemain", + "timeNotificationExplanation": "Recevez une notification la veille à {time} si une fermeture est prévue le lendemain", "@timeNotificationExplanation": { "placeholders": { "time": {} } }, - "dayNotificationTitle": "Récap hebdo le {day}", + "dayNotificationTitle": "Récapitulatif hebdomadaire le {day}", "@dayNotificationTitle": { "placeholders": { "day": {} } }, - "dayNotificationExplanation": "Recevoir une notification le {day} à {time} listant toutes les fermetures prévues pour la semaine qui arrive", + "dayNotificationExplanation": "Recevez une notification le {day} à {time} listant toutes les fermetures prévues pour la semaine à venir", "@dayNotificationExplanation": { "placeholders": { "day": {}, @@ -104,27 +103,27 @@ } }, "closingNotificationTitle": "À la fermeture", - "closingNotificationExplanation": "Recevoir une notification quand le pont ferme à la circulation", + "closingNotificationExplanation": "Recevez une notification lorsque le pont se ferme", "notificationClosingChannelName": "Fermeture", "notificationClosingTitle": "Le pont Chaban est fermé ⛔", - "notificationClosingBoatMessage": "Le pont Chaban vient de fermer pour {boat} \uD83D\uDEA2. Il restera fermé pendant {duration} \uD83C\uDF09", + "notificationClosingBoatMessage": "Le pont Chaban vient de se fermer pour {boat} \uD83D\uDEA2. Il restera fermé pendant {duration} \uD83C\uDF09", "@notificationClosingBoatMessage": { "placeholders": { "boat": {}, "duration": {} } }, - "notificationClosingMaintenanceMessage": "Le pont Chaban vient de fermer pour maintenance \uD83D\uDEE0. Il restera fermé pendant {duration} \uD83C\uDF09", + "notificationClosingMaintenanceMessage": "Le pont Chaban vient de se fermer pour maintenance \uD83D\uDEE0. Il restera fermé pendant {duration} \uD83C\uDF09", "@notificationClosingMaintenanceMessage": { "placeholders": { "duration": {} } }, "openingNotificationTitle": "À l'ouverture", - "openingNotificationExplanation": "Recevoir une notification quand le pont ouvre à la circulation", + "openingNotificationExplanation": "Recevez une notification lorsque le pont s'ouvre", "notificationOpeningChannelName": "Ouverture", "notificationOpeningTitle": "Le pont Chaban est ouvert ✅", - "notificationOpeningMessage": "Le pont Chaban vient de rouvir à la circulation \uD83D\uDEB2", + "notificationOpeningMessage": "Le pont Chaban vient de s'ouvrir à la circulation \uD83D\uDEB2", "monday": "Lundi", "tuesday": "Mardi", "wednesday": "Mercredi", @@ -132,8 +131,8 @@ "friday": "Vendredi", "saturday": "Samedi", "sunday": "Dimanche", - "notificationDurationTitle": "Fermeture imminante ⚠️", - "notificationDurationBoatMessage": "Le pont Chaban fermera dans {timeLeft} pour {boat} \uD83D\uDEA2. Il restera fermé pendant {duration} \uD83C\uDF09", + "notificationDurationTitle": "Fermeture imminente ⚠️", + "notificationDurationBoatMessage": "Le pont Chaban va se fermer dans {timeLeft} pour {boat} \uD83D\uDEA2. Il restera fermé pendant {duration} \uD83C\uDF09", "@notificationDurationBoatMessage": { "placeholders": { "boat": {}, @@ -179,7 +178,7 @@ }, "notificationTimeChannelName": "Fermetures du lendemain", "passedClosure": "Fermeture passée", - "selectAboutDialog": "{choice, select, source_code {Code source} privacy_policy {Politique de confidentialité} yuhliet_instagram {Instagram de Yuhliet's} city_of_bordeaux {Ville de Bordeaux} bordeaux_open_data {Bordeaux Open Data} changelog {Journal des modifications} licenses {Licences} other {Undefined}}", + "selectAboutDialog": "{choice, select, source_code {Code source} privacy_policy {Politique de confidentialité} yuhliet_instagram {Instagram de Yuhliet} city_of_bordeaux {Ville de Bordeaux} bordeaux_open_data {Bordeaux Open Data} licenses {Licences} changelog {Journal des modifications} other {Undefined}}", "@selectAboutDialog": { "placeholders": { "choice": {} @@ -187,7 +186,7 @@ }, "day": "Jour", "notificationDayTitle": "\uD83D\uDD2E Fermetures prévues", - "notificationDayMessage": "{count, plural, =0{Aucune fermetures de prévue pour la semaine prochaine} =1{La semaine prochaine, le pont Chaban Delmas ne fermera qu'une seul fois} other{La semaine prochaine, le pont Chaban Delmas fermera à {count} reprises}}", + "notificationDayMessage": "{count, plural, =0{Aucune fermeture prévue pour la semaine prochaine} =1{La semaine prochaine, le pont Chaban-Delmas ne se fermera qu'une seule fois} other{La semaine prochaine, le pont Chaban-Delmas se fermera {count} fois}}", "@notificationDayMessage": { "placeholders": { "count": {} @@ -196,6 +195,7 @@ "notificationDayChannelName": "Fermetures prévues", "leftHanded": "Gaucher.ère", "rightHanded": "Droitier.ère", + "status": "État", "statusLoadMessage": "Chargement de l'état actuel du pont", "loading": "Chargement...", "dayNotificationAt": "à", @@ -231,5 +231,29 @@ "timeFormatTitle": "Format des heures", "timeFormatSubTitle": "Afficher les heures au format 12 ou 24 heures", "noMoreForecastsTitle": "Aucun évènement à venir", - "noMoreForecastsMessage": "Bordeaux Métropole n'a communiqué aucune fermeture du Pont Chaban-Delmas pour les prochaines semaines.\nTenez-vous informer des prochaines fermetures en revenant ici régulièrement" + "noMoreForecastsMessage": "Bordeaux Métropole n'a communiqué aucune fermeture du Pont Chaban-Delmas pour les prochaines semaines.\nTenez-vous informer des prochaines fermetures en revenant ici régulièrement", + "shareEventTitle": "Partager", + "calendarEventAddToCalendar": "Ajouter au calendrier", + "calendarEventTitle": "Fermeture du pont Chaban Delmas", + "calendarEventLocation": "Bordeaux, pont Chaban Delmas", + "calendarEventMaintenanceDescription": "Le pont Chaban sera fermé pour maintenance \uD83D\uDEE0. Il restera fermé pendant {duration} \uD83C\uDF09", + "@calendarEventMaintenanceDescription": { + "placeholders": { + "duration": {} + } + }, + "calendarEventBoatDescription": "Le pont Chaban sera pour {boat} \uD83D\uDEA2. Il restera fermé pendant {duration} \uD83C\uDF09", + "@calendarEventBoatDescription": { + "placeholders": { + "boat": {}, + "duration": {} + } + }, + "bottomSheetTitle_maintenance": "Maintenance", + "bottomSheetTitle_boat": "Passage de navire(s)", + "bottomSheetAdditionalInfo_maintenance": "Le pont Chaban est fermé pour maintenance planifiée.", + "bottomSheetAdditionalInfo_boatLeaving": "arrivée", + "bottomSheetAdditionalInfo_boatArriving": "départ", + "bottomSheetAdditionalInfo_boatCrossingTimeDisclaimer": "Les heures d'arrivées et de départs sont purement à des fins indicatives et ne sont pas garanties.", + "bottomSheetAdditionalInfo_shareMessage": "Salut, je vais avoir du retard, le Pont Chaban est fermé jusqu'à" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index df18b9aa..8ea19d7a 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -784,6 +784,84 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Bordeaux Métropole has not communicated any closure of the Chaban-Delmas Bridge for the coming weeks.\nStay informed of upcoming closures by returning here regularly'** String get noMoreForecastsMessage; + + /// No description provided for @shareEventTitle. + /// + /// In en, this message translates to: + /// **'Share'** + String get shareEventTitle; + + /// No description provided for @calendarEventAddToCalendar. + /// + /// In en, this message translates to: + /// **'Add to calendar'** + String get calendarEventAddToCalendar; + + /// No description provided for @calendarEventTitle. + /// + /// In en, this message translates to: + /// **'Closure of the Chaban Delmas bridge'** + String get calendarEventTitle; + + /// No description provided for @calendarEventLocation. + /// + /// In en, this message translates to: + /// **'Bordeaux, Chaban Delmas bridge'** + String get calendarEventLocation; + + /// No description provided for @calendarEventMaintenanceDescription. + /// + /// In en, this message translates to: + /// **'The Chaban bridge will close for maintenance 🛠. It will remains closed for {duration} 🌉'** + String calendarEventMaintenanceDescription(Object duration); + + /// No description provided for @calendarEventBoatDescription. + /// + /// In en, this message translates to: + /// **'The Chaban bridge will close for {boat} 🚢. It will remains closed for {duration} 🌉'** + String calendarEventBoatDescription(Object boat, Object duration); + + /// No description provided for @bottomSheetTitle_maintenance. + /// + /// In en, this message translates to: + /// **'Maintenance'** + String get bottomSheetTitle_maintenance; + + /// No description provided for @bottomSheetTitle_boat. + /// + /// In en, this message translates to: + /// **'Passage of vessel(s)'** + String get bottomSheetTitle_boat; + + /// No description provided for @bottomSheetAdditionalInfo_maintenance. + /// + /// In en, this message translates to: + /// **'The Chaban Bridge is closed for scheduled maintenance.'** + String get bottomSheetAdditionalInfo_maintenance; + + /// No description provided for @bottomSheetAdditionalInfo_boatLeaving. + /// + /// In en, this message translates to: + /// **'leaving'** + String get bottomSheetAdditionalInfo_boatLeaving; + + /// No description provided for @bottomSheetAdditionalInfo_boatArriving. + /// + /// In en, this message translates to: + /// **'arriving'** + String get bottomSheetAdditionalInfo_boatArriving; + + /// No description provided for @bottomSheetAdditionalInfo_boatCrossingTimeDisclaimer. + /// + /// In en, this message translates to: + /// **'Arrival and departure times are for information purposes only and are not guaranteed.'** + String get bottomSheetAdditionalInfo_boatCrossingTimeDisclaimer; + + /// No description provided for @bottomSheetAdditionalInfo_shareMessage. + /// + /// In en, this message translates to: + /// **'Hi, I\'m going to be late, the Chaban Bridge is closed until'** + String get bottomSheetAdditionalInfo_shareMessage; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index b13a599b..9f2fa657 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -460,4 +460,50 @@ class AppLocalizationsEn extends AppLocalizations { @override String get noMoreForecastsMessage => 'Bordeaux Métropole has not communicated any closure of the Chaban-Delmas Bridge for the coming weeks.\nStay informed of upcoming closures by returning here regularly'; + + @override + String get shareEventTitle => 'Share'; + + @override + String get calendarEventAddToCalendar => 'Add to calendar'; + + @override + String get calendarEventTitle => 'Closure of the Chaban Delmas bridge'; + + @override + String get calendarEventLocation => 'Bordeaux, Chaban Delmas bridge'; + + @override + String calendarEventMaintenanceDescription(Object duration) { + return 'The Chaban bridge will close for maintenance 🛠. It will remains closed for $duration 🌉'; + } + + @override + String calendarEventBoatDescription(Object boat, Object duration) { + return 'The Chaban bridge will close for $boat 🚢. It will remains closed for $duration 🌉'; + } + + @override + String get bottomSheetTitle_maintenance => 'Maintenance'; + + @override + String get bottomSheetTitle_boat => 'Passage of vessel(s)'; + + @override + String get bottomSheetAdditionalInfo_maintenance => + 'The Chaban Bridge is closed for scheduled maintenance.'; + + @override + String get bottomSheetAdditionalInfo_boatLeaving => 'leaving'; + + @override + String get bottomSheetAdditionalInfo_boatArriving => 'arriving'; + + @override + String get bottomSheetAdditionalInfo_boatCrossingTimeDisclaimer => + 'Arrival and departure times are for information purposes only and are not guaranteed.'; + + @override + String get bottomSheetAdditionalInfo_shareMessage => + 'Hi, I\'m going to be late, the Chaban Bridge is closed until'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 1721d53e..0f3d7d2c 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -127,7 +127,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get dialogInformationContentTime_of_crossing => - 'hora estimada de cruce'; + 'hora de paso estimada'; @override String get duration => 'Duración'; @@ -137,18 +137,18 @@ class AppLocalizationsEs extends AppLocalizations { @override String get errorScreenContentMessage => - 'Se produjo un error al abrir esta página. Encuentre la información técnica a continuación'; + 'Se ha producido un error al abrir esta pàgina. Encuentre información técnica a continuación'; @override String get errorScreenContentTechnical_Info => 'Información técnica'; @override String get unableAppInfo => - 'No se puede obtener información de la aplicación'; + 'No se puede recuperar la información de la aplicación'; @override String get appDescription => - 'La aplicación móvil para obtener los horarios de cierre y apertura del puente Chaban Delmas ubicado en Bordeaux, Francia'; + 'Aplicación móvil para obtener los horarios de cierre y apertura del puente Chaban-Delmas ubicado en Burdeos, Francia'; @override String get informationAboutTheApp => 'Información sobre la aplicación'; @@ -158,10 +158,10 @@ class AppLocalizationsEs extends AppLocalizations { @override String get disclaimer => - 'Descargo de responsabilidad: cierres provisionales. Sujeto a confirmación de la Capitanía de Puerto.'; + 'Aviso: cierres provisionales. Sujeto a confirmación de la Capitania de Puerto.'; @override - String get openSetting => 'Ajustes'; + String get openSetting => 'Configuración'; @override String get themeSettingTitle => 'Tema'; @@ -176,7 +176,7 @@ class AppLocalizationsEs extends AppLocalizations { String get darkTheme => 'Tema oscuro'; @override - String get schedules => 'Programar'; + String get schedules => 'Horarios'; @override String get systemTheme => 'Tema del sistema'; @@ -186,7 +186,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get notificationsSubtitle => - 'Administre las notificaciones de la aplicación'; + 'Administrar las notificaciones de la aplicación'; @override String durationNotificationTitle(Object duration) { @@ -215,30 +215,30 @@ class AppLocalizationsEs extends AppLocalizations { @override String dayNotificationExplanation(Object day, Object time) { - return 'Reciba una notificación el $day a las $time que enumere todos los cierres planeados para la próxima semana'; + return 'Reciba una notificación el $day a las $time que enumere todos los cierres planificados para la próxima semana'; } @override - String get closingNotificationTitle => 'En el cierre'; + String get closingNotificationTitle => 'Al cierre'; @override String get closingNotificationExplanation => - 'Reciba una notificación cuando el puente cierre'; + 'Reciba una notificación cuando el puente se cierra'; @override String get notificationClosingChannelName => 'Cierre'; @override - String get notificationClosingTitle => 'El puente Chaban ha cerrado ⛔'; + String get notificationClosingTitle => 'El puente Chaban está cerrado ⛔'; @override String notificationClosingBoatMessage(Object boat, Object duration) { - return 'El puente Chaban acaba de cerrarse para $boat 🚢. Permanecerá cerrado durante $duration 🌉'; + return 'El puente Chaban acaba de cerrarse para $boat 🚢. Permanecerà cerrado durante $duration 🌉'; } @override String notificationClosingMaintenanceMessage(Object duration) { - return 'El puente Chaban acaba de cerrarse para mantenimiento 🛠. Permanecerá cerrado durante $duration 🌉'; + return 'El puente Chaban acaba de cerrarse para mantenimiento 🛠. Permanecerà cerrado durante $duration 🌉'; } @override @@ -246,7 +246,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get openingNotificationExplanation => - 'Reciba una notificación cuando el puente abra'; + 'Reciba una notificación cuando el puente se abre'; @override String get notificationOpeningChannelName => 'Apertura'; @@ -256,7 +256,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get notificationOpeningMessage => - 'El puente Chaban acaba de abrirse al tráfico 🚲'; + 'El puente Chaban acaba de abrirse al tràfico 🚲'; @override String get monday => 'Lunes'; @@ -288,7 +288,7 @@ class AppLocalizationsEs extends AppLocalizations { Object timeLeft, Object duration, ) { - return 'El puente Chaban cerrará en $timeLeft para $boat 🚢. Permanecerá cerrado durante $duration 🌉'; + return 'El puente Chaban se cerrarà en $timeLeft para $boat 🚢. Permanecerà cerrado durante $duration 🌉'; } @override @@ -296,7 +296,7 @@ class AppLocalizationsEs extends AppLocalizations { Object timeLeft, Object duration, ) { - return 'El puente Chaban cerrará en $timeLeft para mantenimiento 🛠. Permanecerá cerrado durante $duration 🌉'; + return 'El puente Chaban se cerrarà en $timeLeft para mantenimiento 🛠. Permanecerà cerrado durante $duration 🌉'; } @override @@ -311,7 +311,7 @@ class AppLocalizationsEs extends AppLocalizations { Object time, Object duration, ) { - return 'El puente Chaban cerrará mañana a las $time para $boat 🚢. Permanecerá cerrado durante $duration 🌉'; + return 'El puente Chaban se cerrarà mañana a las $time para $boat 🚢. Permanecerà cerrado durante $duration 🌉'; } @override @@ -326,11 +326,11 @@ class AppLocalizationsEs extends AppLocalizations { @override String notificationTimeMaintenanceMessage(Object time, Object duration) { - return 'El puente Chaban cerrará mañana a las $time por mantenimiento 🛠. Permanecerá cerrado durante $duration 🌉'; + return 'El puente Chaban se cerrarà mañana a las $time para mantenimiento 🛠. Permanecerà cerrado durante $duration 🌉'; } @override - String get notificationTimeChannelName => 'Cierres del próximo día'; + String get notificationTimeChannelName => 'Cierres del dìa siguiente'; @override String get passedClosure => 'Cierre pasado'; @@ -339,19 +339,19 @@ class AppLocalizationsEs extends AppLocalizations { String selectAboutDialog(String choice) { String _temp0 = intl.Intl.selectLogic(choice, { 'source_code': 'Código fuente', - 'privacy_policy': 'Política de privacidad', - 'yuhliet_instagram': 'Instagram de Yuhliet\'s', - 'city_of_bordeaux': 'Ciudad de Bordeaux', + 'privacy_policy': 'Polìtica de privacidad', + 'yuhliet_instagram': 'Instagram de Yuhliet', + 'city_of_bordeaux': 'Ciudad de Burdeos', 'bordeaux_open_data': 'Bordeaux Open Data', 'licenses': 'Licencias', 'changelog': 'Registro de cambios', - 'other': 'Undefined', + 'other': 'Indefinido', }); return '$_temp0'; } @override - String get day => 'Día'; + String get day => 'Dìa'; @override String get notificationDayTitle => '🔮 Cierre programado'; @@ -361,27 +361,28 @@ class AppLocalizationsEs extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'La próxima semana, el puente Chaban Delmas cerrará $count veces', - one: 'La próxima semana, el puente Chaban Delmas solo cerrará una vez', - zero: 'No hay cierres programados para la próxima semana', + other: + 'La próxima semana, el puente Chaban-Delmas se cerrarà $count veces', + one: 'La próxima semana, el puente Chaban-Delmas solo se cerrarà una vez', + zero: 'Ningún cierre programado para la próxima semana', ); return '$_temp0'; } @override - String get notificationDayChannelName => 'Cierres planificados'; + String get notificationDayChannelName => 'Cierres programados'; @override - String get leftHanded => 'Zurdo.a'; + String get leftHanded => 'Zurdo'; @override - String get rightHanded => 'Diestro.a'; + String get rightHanded => 'Diestro'; @override String get status => 'Estado'; @override - String get statusLoadMessage => 'Carga del estado actual del puente'; + String get statusLoadMessage => 'Cargando el estado actual del puente'; @override String get loading => 'Cargando...'; @@ -409,21 +410,21 @@ class AppLocalizationsEs extends AppLocalizations { @override String get favoriteSlotsInterferenceWarning => - 'Este horario interfiere con uno o más intervalos de tiempo'; + 'Este horario interfiere con uno o màs intervalos de tiempo'; @override String get favoriteTimeSlotEnabledWarning => - 'Atención, al activar este parámetro solo recibirás notificaciones cuando ocurra un evento durante una de tus franjas horarias'; + 'Atención, al activar este paràmetro solo recibiràs notificaciones cuando ocurra un evento durante una de tus franjas horarias'; @override - String get favoriteSlotsChooseDay => 'Elegir los días de la semana'; + String get favoriteSlotsChooseDay => 'Elegir los dìas de la semana'; @override String moonHarborStatus(num count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'están actualmente amarrados', + other: 'estàn actualmente amarrados', one: 'está actualmente amarrado', ); return '$_temp0 en el \'Puerto de la Luna\''; @@ -434,7 +435,7 @@ class AppLocalizationsEs extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'buques están actualmente', + other: 'buques estàn actualmente', one: 'barco está actualmente', ); return '$count $_temp0 en Bordeaux'; @@ -464,5 +465,51 @@ class AppLocalizationsEs extends AppLocalizations { @override String get noMoreForecastsMessage => - 'Bordeaux Métropole no ha comunicado ningún cierre del Puente Chaban-Delmas para las próximas semanas.\nManténgase informado de los próximos cierres volviendo aquí regularmente'; + 'Bordeaux Métropole no ha comunicado ningún cierre del Puente Chaban-Delmas para las próximas semanas.\nManténgase informado de los próximos cierres volviendo aquì regularmente'; + + @override + String get shareEventTitle => 'Compartir'; + + @override + String get calendarEventAddToCalendar => 'Añadir al calendario'; + + @override + String get calendarEventTitle => 'Cierre del puente Chaban Delmas'; + + @override + String get calendarEventLocation => 'Bordeaux, puente Chaban Delmas'; + + @override + String calendarEventMaintenanceDescription(Object duration) { + return '\"El puente Chaban permanecerá cerrado para mantenimiento 🛠. Permanecerà cerrado durante $duration 🌉'; + } + + @override + String calendarEventBoatDescription(Object boat, Object duration) { + return 'El puente Chaban permanecerá cerrado para $boat 🚢. Permanecerà cerrado durante $duration 🌉'; + } + + @override + String get bottomSheetTitle_maintenance => 'Mantenimiento'; + + @override + String get bottomSheetTitle_boat => 'Paso de la(s) embarcación(es)'; + + @override + String get bottomSheetAdditionalInfo_maintenance => + 'El puente Chaban está cerrado por mantenimiento programado.'; + + @override + String get bottomSheetAdditionalInfo_boatLeaving => 'llegada'; + + @override + String get bottomSheetAdditionalInfo_boatArriving => 'partida'; + + @override + String get bottomSheetAdditionalInfo_boatCrossingTimeDisclaimer => + 'Los horarios de llegada y salida son únicamente a título informativo y no están garantizados.'; + + @override + String get bottomSheetAdditionalInfo_shareMessage => + 'Hola, voy a llegar tarde; el puente Chaban está cerrado hasta'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 8974df52..fe99156c 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -136,28 +136,28 @@ class AppLocalizationsFr extends AppLocalizations { @override String get errorScreenContentMessage => - 'Une erreur est survenue lors de l\'ouverture de cette page. Veuillez trouver les informations techniques ci-dessous'; + 'Une erreur est survenue lors de l\'ouverture de cette page. Vous trouverez des informations techniques ci-dessous'; @override String get errorScreenContentTechnical_Info => 'Informations techniques'; @override String get unableAppInfo => - 'Impossible d\'obtenir les informations de l\'application'; + 'Impossible de récupérer les informations de l\'application'; @override String get appDescription => - 'L\'application Mobile pour connaître les horaires de fermeture et d\'ouverture du pont Chaban Delmas situé à Bordeaux, France'; + 'Application mobile pour obtenir les horaires de fermeture et d\'ouverture du pont Chaban-Delmas situé à Bordeaux, France'; @override - String get informationAboutTheApp => 'Informations concernant l\'application'; + String get informationAboutTheApp => 'Informations sur l\'application'; @override String get about => 'À propos'; @override String get disclaimer => - 'Avis de non-responsabilité : fermetures provisoires. Sous réserve de confirmation de la Capitainerie.'; + 'Avertissement : fermetures provisoires. Sous réserve de confirmation de la Capitainerie.'; @override String get openSetting => 'Paramètres'; @@ -166,7 +166,7 @@ class AppLocalizationsFr extends AppLocalizations { String get themeSettingTitle => 'Thème'; @override - String get themeSettingSubTitle => 'Changer l\'apparence de l\'application'; + String get themeSettingSubTitle => 'Modifier l\'apparence de l\'application'; @override String get lightTheme => 'Thème clair'; @@ -175,7 +175,7 @@ class AppLocalizationsFr extends AppLocalizations { String get darkTheme => 'Thème sombre'; @override - String get schedules => 'Calendrier'; + String get schedules => 'Plannings'; @override String get systemTheme => 'Thème système'; @@ -194,7 +194,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String durationNotificationExplanation(Object duration) { - return 'Recevoir une notification ${duration}avant la prochaine fermeture. Cette valeur gère aussi le changement de couleur du statut actuel'; + return 'Recevez une notification ${duration}avant la prochaine fermeture. Cette valeur gère également le changement de couleur de l\'état actuel'; } @override @@ -204,17 +204,17 @@ class AppLocalizationsFr extends AppLocalizations { @override String timeNotificationExplanation(Object time) { - return 'Recevoir une notification la veille à $time si une fermeture est prévue le lendemain'; + return 'Recevez une notification la veille à $time si une fermeture est prévue le lendemain'; } @override String dayNotificationTitle(Object day) { - return 'Récap hebdo le $day'; + return 'Récapitulatif hebdomadaire le $day'; } @override String dayNotificationExplanation(Object day, Object time) { - return 'Recevoir une notification le $day à $time listant toutes les fermetures prévues pour la semaine qui arrive'; + return 'Recevez une notification le $day à $time listant toutes les fermetures prévues pour la semaine à venir'; } @override @@ -222,7 +222,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get closingNotificationExplanation => - 'Recevoir une notification quand le pont ferme à la circulation'; + 'Recevez une notification lorsque le pont se ferme'; @override String get notificationClosingChannelName => 'Fermeture'; @@ -232,12 +232,12 @@ class AppLocalizationsFr extends AppLocalizations { @override String notificationClosingBoatMessage(Object boat, Object duration) { - return 'Le pont Chaban vient de fermer pour $boat 🚢. Il restera fermé pendant $duration 🌉'; + return 'Le pont Chaban vient de se fermer pour $boat 🚢. Il restera fermé pendant $duration 🌉'; } @override String notificationClosingMaintenanceMessage(Object duration) { - return 'Le pont Chaban vient de fermer pour maintenance 🛠. Il restera fermé pendant $duration 🌉'; + return 'Le pont Chaban vient de se fermer pour maintenance 🛠. Il restera fermé pendant $duration 🌉'; } @override @@ -245,7 +245,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get openingNotificationExplanation => - 'Recevoir une notification quand le pont ouvre à la circulation'; + 'Recevez une notification lorsque le pont s\'ouvre'; @override String get notificationOpeningChannelName => 'Ouverture'; @@ -255,7 +255,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get notificationOpeningMessage => - 'Le pont Chaban vient de rouvir à la circulation 🚲'; + 'Le pont Chaban vient de s\'ouvrir à la circulation 🚲'; @override String get monday => 'Lundi'; @@ -279,7 +279,7 @@ class AppLocalizationsFr extends AppLocalizations { String get sunday => 'Dimanche'; @override - String get notificationDurationTitle => 'Fermeture imminante ⚠️'; + String get notificationDurationTitle => 'Fermeture imminente ⚠️'; @override String notificationDurationBoatMessage( @@ -287,7 +287,7 @@ class AppLocalizationsFr extends AppLocalizations { Object timeLeft, Object duration, ) { - return 'Le pont Chaban fermera dans $timeLeft pour $boat 🚢. Il restera fermé pendant $duration 🌉'; + return 'Le pont Chaban va se fermer dans $timeLeft pour $boat 🚢. Il restera fermé pendant $duration 🌉'; } @override @@ -339,11 +339,11 @@ class AppLocalizationsFr extends AppLocalizations { String _temp0 = intl.Intl.selectLogic(choice, { 'source_code': 'Code source', 'privacy_policy': 'Politique de confidentialité', - 'yuhliet_instagram': 'Instagram de Yuhliet\'s', + 'yuhliet_instagram': 'Instagram de Yuhliet', 'city_of_bordeaux': 'Ville de Bordeaux', 'bordeaux_open_data': 'Bordeaux Open Data', - 'changelog': 'Journal des modifications', 'licenses': 'Licences', + 'changelog': 'Journal des modifications', 'other': 'Undefined', }); return '$_temp0'; @@ -361,10 +361,10 @@ class AppLocalizationsFr extends AppLocalizations { count, locale: localeName, other: - 'La semaine prochaine, le pont Chaban Delmas fermera à $count reprises', + 'La semaine prochaine, le pont Chaban-Delmas se fermera $count fois', one: - 'La semaine prochaine, le pont Chaban Delmas ne fermera qu\'une seul fois', - zero: 'Aucune fermetures de prévue pour la semaine prochaine', + 'La semaine prochaine, le pont Chaban-Delmas ne se fermera qu\'une seule fois', + zero: 'Aucune fermeture prévue pour la semaine prochaine', ); return '$_temp0'; } @@ -466,4 +466,50 @@ class AppLocalizationsFr extends AppLocalizations { @override String get noMoreForecastsMessage => 'Bordeaux Métropole n\'a communiqué aucune fermeture du Pont Chaban-Delmas pour les prochaines semaines.\nTenez-vous informer des prochaines fermetures en revenant ici régulièrement'; + + @override + String get shareEventTitle => 'Partager'; + + @override + String get calendarEventAddToCalendar => 'Ajouter au calendrier'; + + @override + String get calendarEventTitle => 'Fermeture du pont Chaban Delmas'; + + @override + String get calendarEventLocation => 'Bordeaux, pont Chaban Delmas'; + + @override + String calendarEventMaintenanceDescription(Object duration) { + return 'Le pont Chaban sera fermé pour maintenance 🛠. Il restera fermé pendant $duration 🌉'; + } + + @override + String calendarEventBoatDescription(Object boat, Object duration) { + return 'Le pont Chaban sera pour $boat 🚢. Il restera fermé pendant $duration 🌉'; + } + + @override + String get bottomSheetTitle_maintenance => 'Maintenance'; + + @override + String get bottomSheetTitle_boat => 'Passage de navire(s)'; + + @override + String get bottomSheetAdditionalInfo_maintenance => + 'Le pont Chaban est fermé pour maintenance planifiée.'; + + @override + String get bottomSheetAdditionalInfo_boatLeaving => 'arrivée'; + + @override + String get bottomSheetAdditionalInfo_boatArriving => 'départ'; + + @override + String get bottomSheetAdditionalInfo_boatCrossingTimeDisclaimer => + 'Les heures d\'arrivées et de départs sont purement à des fins indicatives et ne sont pas garanties.'; + + @override + String get bottomSheetAdditionalInfo_shareMessage => + 'Salut, je vais avoir du retard, le Pont Chaban est fermé jusqu\'à'; } diff --git a/lib/models/abstract_forecast.dart b/lib/models/abstract_forecast.dart index 1e4edc06..0d6097c7 100644 --- a/lib/models/abstract_forecast.dart +++ b/lib/models/abstract_forecast.dart @@ -65,8 +65,6 @@ abstract class AbstractForecast extends Equatable { _circulationClosingDate = value; } - RichText getInformationWidget(BuildContext context); - List getCoreInformationWidget(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final timeFormat = context.read().state.timeFormat; @@ -164,6 +162,12 @@ abstract class AbstractForecast extends Equatable { String getNotificationClosingMessage(BuildContext context); + String getCalendarEventClosingDescription(BuildContext context); + + Widget getDetailedInfo(BuildContext context); + + String getBottomSheetTitle(BuildContext context); + String getClosingReason(BuildContext context); Color getColor(BuildContext context, bool reversed); diff --git a/lib/models/boat.dart b/lib/models/boat.dart index 9d83210f..a73f0d42 100644 --- a/lib/models/boat.dart +++ b/lib/models/boat.dart @@ -37,14 +37,13 @@ class Boat extends Equatable { baseURL.replaceAll(Const.vesselFinderLinkPlaceholder, name), ), text: text, - style: TextStyle( + style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.bold, color: colored ? isWineFestivalSailBoats ? Theme.of(context).colorScheme.bordeauxColor : Theme.of(context).colorScheme.boatColor : Theme.of(context).colorScheme.surface, - decoration: TextDecoration.underline, ), ); } diff --git a/lib/models/boat_forecast.dart b/lib/models/boat_forecast.dart index e5ba5f8f..f4299a58 100644 --- a/lib/models/boat_forecast.dart +++ b/lib/models/boat_forecast.dart @@ -3,7 +3,6 @@ import 'package:chabo_app/cubits/time_format_cubit.dart'; import 'package:chabo_app/extensions/boats_extension.dart'; import 'package:chabo_app/extensions/color_scheme_extension.dart'; import 'package:chabo_app/extensions/duration_extension.dart'; -import 'package:chabo_app/extensions/string_extension.dart'; import 'package:chabo_app/l10n/app_localizations.dart'; import 'package:chabo_app/models/abstract_forecast.dart'; import 'package:chabo_app/models/boat.dart'; @@ -96,45 +95,6 @@ class BoatForecast extends AbstractForecast { closingType, ]; - @override - RichText getInformationWidget(BuildContext context) { - final timeFormat = context.read().state.timeFormat; - final colorScheme = Theme.of(context).colorScheme; - - var schedule = circulationClosingDate.add( - Duration(microseconds: closedDuration.inMicroseconds ~/ 2), - ); - var scheduleString = intl.DateFormat( - timeFormat.icuName, - Localizations.localeOf(context).languageCode, - ).format(schedule); - if (isDuringTwoDays) { - scheduleString = - '${MaterialLocalizations.of(context).formatMediumDate(schedule)} ${AppLocalizations.of(context)!.at} ${intl.DateFormat(timeFormat.icuName, Localizations.localeOf(context).languageCode).format(schedule)}'; - } - - return RichText( - text: TextSpan( - style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.6), - children: [ - ...getCoreInformationWidget(context), - boats.toLocalizedTextSpan(context), - TextSpan( - text: - '\n\n${AppLocalizations.of(context)!.dialogInformationContentTime_of_crossing.capitalize()} : ', - ), - TextSpan( - text: scheduleString, - style: TextStyle( - fontWeight: FontWeight.bold, - color: colorScheme.timeColor, - ), - ), - ], - ), - ); - } - @override String getNotificationDurationMessage( BuildContext context, @@ -165,6 +125,102 @@ class BoatForecast extends AbstractForecast { ); } + @override + String getCalendarEventClosingDescription(BuildContext context) { + return AppLocalizations.of(context)!.calendarEventBoatDescription( + boats.toLocalizedString(context), + closedDuration.durationToString(context), + ); + } + + List _generateCrossingTimes(DateTime t, int n, int p) { + // Calculate the step in seconds + int stepInSeconds = p; + + // Generate the list of times + List result = []; + for (int i = 0; i < n; i++) { + // Calculate the offset for the current time in seconds + double offset = (i - (n - 1) / 2.0) * stepInSeconds; + int offsetInSeconds = offset.round(); + + // Create a new DateTime by adding the offset + DateTime newTime = t.add(Duration(seconds: offsetInSeconds)); + + // Format the time as HH:MM:SS (or HH:MM if seconds are zero) + String formattedTime = + '${newTime.hour.toString().padLeft(2, '0')}h' + '${newTime.minute.toString().padLeft(2, '0')}'; + + result.add(formattedTime); + } + + return result; + } + + @override + Widget getDetailedInfo(BuildContext context) { + var halfDuration = Duration(seconds: closedDuration.inSeconds ~/ 2); + var medianTime = circulationClosingDate.add(halfDuration); + var crossingTimes = _generateCrossingTimes(medianTime, boats.length, 1800); + return Column( + spacing: 13, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 3, + children: boats.asMap().entries.map((elem) { + final boat = elem.value; + final index = elem.key; + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RichText(text: boat.toLocalizedTextSpan(context, true)), + Row( + spacing: 5, + children: [ + Text( + boat.isLeaving + ? AppLocalizations.of( + context, + )!.bottomSheetAdditionalInfo_boatArriving + : AppLocalizations.of( + context, + )!.bottomSheetAdditionalInfo_boatLeaving, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + fontStyle: FontStyle.italic, + fontWeight: FontWeight.bold, + ), + ), + Text('- ${crossingTimes[index]}'), + ], + ), + ], + ); + }).toList(), + ), + Text( + AppLocalizations.of( + context, + )!.bottomSheetAdditionalInfo_boatCrossingTimeDisclaimer, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + fontStyle: FontStyle.italic, + color: Theme.of( + context, + ).colorScheme.inverseSurface.withValues(alpha: .4), + ), + ), + ], + ); + } + + @override + String getBottomSheetTitle(BuildContext context) { + return AppLocalizations.of(context)!.bottomSheetTitle_boat; + } + @override Widget getIconWidget( BuildContext context, diff --git a/lib/models/maintenance_forecast.dart b/lib/models/maintenance_forecast.dart index 5e1e176a..937df129 100644 --- a/lib/models/maintenance_forecast.dart +++ b/lib/models/maintenance_forecast.dart @@ -94,28 +94,24 @@ class MaintenanceForecast extends AbstractForecast { } @override - RichText getInformationWidget(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; + String getCalendarEventClosingDescription(BuildContext context) { + return AppLocalizations.of(context)!.calendarEventMaintenanceDescription( + closedDuration.durationToString(context), + ); + } - return RichText( - text: TextSpan( - style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.6), - children: [ - ...getCoreInformationWidget(context), - TextSpan( - text: AppLocalizations.of( - context, - )!.dialogInformationContentBridge_closed_maintenance, - style: TextStyle( - color: colorScheme.maintenanceColor, - fontWeight: FontWeight.bold, - ), - ), - ], - ), + @override + Widget getDetailedInfo(BuildContext context) { + return Text( + AppLocalizations.of(context)!.bottomSheetAdditionalInfo_maintenance, ); } + @override + String getBottomSheetTitle(BuildContext context) { + return AppLocalizations.of(context)!.bottomSheetTitle_maintenance; + } + @override Widget getIconWidget( BuildContext context, diff --git a/lib/screens/about_screen/about_screen.dart b/lib/screens/about_screen/about_screen.dart index a10bb080..a8afcfed 100644 --- a/lib/screens/about_screen/about_screen.dart +++ b/lib/screens/about_screen/about_screen.dart @@ -88,10 +88,11 @@ class AboutScreen extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Flexible( + Expanded( child: IconTheme( data: Theme.of(context).iconTheme, child: Row( + spacing: 20, children: [ Container( decoration: BoxDecoration( @@ -106,36 +107,32 @@ class AboutScreen extends StatelessWidget { ), child: iconWidget, ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - ), + Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Row( - children: [ - Text( - snapshot.data!.appName, - style: Theme.of(context) - .textTheme - .headlineSmall! - .copyWith( - fontWeight: FontWeight.bold, - fontSize: 30, - ), - ), - Text( - ' | ${_formatBetaVersion(snapshot.data!.version)} (${snapshot.data!.buildNumber})', - style: textTheme.bodyMedium, - ), - ], + Text( + snapshot.data!.appName, + style: Theme.of(context) + .textTheme + .headlineSmall! + .copyWith( + fontWeight: FontWeight.bold, + fontSize: 35, + ), + ), + Text( + '${_formatBetaVersion(snapshot.data!.version)} (${snapshot.data!.buildNumber})', + style: textTheme.bodyMedium?.copyWith( + overflow: TextOverflow.fade, + ), ), + Divider(), Text( Const.legalLease, - style: textTheme.bodySmall!.copyWith(), + style: textTheme.labelSmall!.copyWith(), ), ], ), diff --git a/lib/screens/notification_screen/custom_list_tile_widget.dart b/lib/screens/notification_screen/custom_list_tile_widget.dart index 876f9b81..9ddbf11c 100644 --- a/lib/screens/notification_screen/custom_list_tile_widget.dart +++ b/lib/screens/notification_screen/custom_list_tile_widget.dart @@ -33,83 +33,88 @@ class _CustomListTileWidget extends StatelessWidget { @override Widget build(BuildContext context) { return SimpleContainer( - child: ListTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Flexible( - flex: 10, - child: Text( - title, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontSize: 19), - overflow: TextOverflow.fade, + child: Material( + // Fix "The ListTile is wrapped in a DecoratedBox that has a background colo..." + type: MaterialType.transparency, + child: ListTile( + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Flexible( + flex: 10, + child: Text( + title, + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontSize: 19), + overflow: TextOverflow.fade, + ), ), - ), - const SizedBox(width: 10), - Flexible( - child: AnimatedSwitcher( - duration: const Duration( - milliseconds: CustomProperties.animationDurationMs, + const SizedBox(width: 10), + Flexible( + child: AnimatedSwitcher( + duration: const Duration( + milliseconds: CustomProperties.animationDurationMs, + ), + transitionBuilder: + (Widget child, Animation animation) { + return FadeTransition( + opacity: CurvedAnimation( + parent: animation, + curve: Curves.easeIn, + ), + child: SlideTransition( + position: Tween( + begin: const Offset(-1.0, 0.0), + end: const Offset(0.0, 0.0), + ).animate(animation), + child: child, + ), + ); + }, + child: constrainedBySlots && enabled + ? CircleAvatar( + radius: 5, + backgroundColor: Theme.of( + context, + ).colorScheme.warningColor, + child: Container(), + ) + : const SizedBox(), ), - transitionBuilder: (Widget child, Animation animation) { - return FadeTransition( - opacity: CurvedAnimation( - parent: animation, - curve: Curves.easeIn, - ), - child: SlideTransition( - position: Tween( - begin: const Offset(-1.0, 0.0), - end: const Offset(0.0, 0.0), - ).animate(animation), - child: child, + ), + ], + ), + subtitle: Text(subtitle), + leading: Icon(leadingIcon), + onTap: onTap, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + onTap != null + ? const VerticalDivider(width: 20) + : const SizedBox.shrink(), + Switch.adaptive( + thumbIcon: thumbIcon, + value: enabled, + onChanged: (value) { + Sentry.addBreadcrumb( + Breadcrumb( + message: 'Change "$title" state', + level: SentryLevel.info, + category: 'notification.change-state', + type: 'Notification', + data: {'old-state': !value, 'new-state': value}, ), ); + onChanged(value); }, - child: constrainedBySlots && enabled - ? CircleAvatar( - radius: 5, - backgroundColor: Theme.of( - context, - ).colorScheme.warningColor, - child: Container(), - ) - : const SizedBox(), ), - ), - ], - ), - subtitle: Text(subtitle), - leading: Icon(leadingIcon), - onTap: onTap, - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - onTap != null - ? const VerticalDivider(width: 20) - : const SizedBox.shrink(), - Switch.adaptive( - thumbIcon: thumbIcon, - value: enabled, - onChanged: (value) { - Sentry.addBreadcrumb( - Breadcrumb( - message: 'Change "$title" state', - level: SentryLevel.info, - category: 'notification.change-state', - type: 'Notification', - data: {'old-state': !value, 'new-state': value}, - ), - ); - onChanged(value); - }, - ), - ], + ], + ), + iconColor: iconColor ?? Theme.of(context).colorScheme.primary, + enabled: enabled, ), - iconColor: iconColor ?? Theme.of(context).colorScheme.primary, - enabled: enabled, ), ); } diff --git a/lib/service/event_share_service.dart b/lib/service/event_share_service.dart new file mode 100644 index 00000000..fc20fcbf --- /dev/null +++ b/lib/service/event_share_service.dart @@ -0,0 +1,51 @@ +import 'package:chabo_app/const.dart'; +import 'package:chabo_app/cubits/time_format_cubit.dart'; +import 'package:chabo_app/l10n/app_localizations.dart'; +import 'package:chabo_app/models/abstract_forecast.dart'; +import 'package:chabo_app/models/enums/time_format.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:add_2_calendar/add_2_calendar.dart'; + +class EventShareService { + /// Add the forecast event to the user's calendar + static Future addToCalendar( + BuildContext context, + AbstractForecast forecast, + ) async { + final localizations = AppLocalizations.of(context)!; + // Create the event + final event = Event( + title: localizations.calendarEventTitle, + description: forecast.getCalendarEventClosingDescription(context), + location: localizations.calendarEventLocation, + startDate: forecast.circulationClosingDate, + endDate: forecast.circulationReOpeningDate, + allDay: false, + ); + + return Add2Calendar.addEvent2Cal(event); + } + + /// Share the forecast event via messaging apps + static Future shareEvent( + BuildContext context, + AbstractForecast forecast, + ) async { + final timeFormat = context.read().state.timeFormat; + final languageCode = Localizations.localeOf(context).languageCode; + var stringDate = DateFormat( + timeFormat.icuName, + languageCode, + ).format(forecast.circulationReOpeningDate); + final ShareParams shareParams = ShareParams( + title: Const.appName, + text: + '${AppLocalizations.of(context)!.bottomSheetAdditionalInfo_shareMessage} $stringDate', + ); + + await SharePlus.instance.share(shareParams); + } +} diff --git a/lib/widgets/bottom_sheets/forecast_information_bottom_sheet.dart b/lib/widgets/bottom_sheets/forecast_information_bottom_sheet.dart index abcda09d..355777b3 100644 --- a/lib/widgets/bottom_sheets/forecast_information_bottom_sheet.dart +++ b/lib/widgets/bottom_sheets/forecast_information_bottom_sheet.dart @@ -1,9 +1,13 @@ +import 'package:chabo_app/custom_properties.dart'; import 'package:chabo_app/extensions/color_scheme_extension.dart'; +import 'package:chabo_app/extensions/date_time_extension.dart'; import 'package:chabo_app/helpers/custom_page_routes.dart'; import 'package:chabo_app/l10n/app_localizations.dart'; import 'package:chabo_app/models/abstract_forecast.dart'; import 'package:chabo_app/screens/notification_screen/notification_screen.dart'; +import 'package:chabo_app/service/event_share_service.dart'; import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; class ForecastInformationBottomSheet extends StatefulWidget { @@ -27,9 +31,7 @@ class _ForecastInformationBottomSheetState @override void initState() { super.initState(); - _controller = AnimationController(duration: _duration, vsync: this); - _controller.forward(); } @@ -39,13 +41,12 @@ class _ForecastInformationBottomSheetState super.dispose(); } - /// convert 0-1 to 0-1-0 - double shake(double animation) => - 2 * (0.5 - (0.5 - Curves.bounceOut.transform(animation)).abs()); - @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + final appLocalization = AppLocalizations.of(context); + final warning = widget.forecast.interferingTimeSlots.isNotEmpty; Sentry.addBreadcrumb( Breadcrumb( @@ -58,100 +59,266 @@ class _ForecastInformationBottomSheetState ); return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), + padding: const EdgeInsets.symmetric(horizontal: 25.0, vertical: 25), child: Column( mainAxisSize: MainAxisSize.min, + spacing: 20, children: [ - if (widget.forecast.interferingTimeSlots.isNotEmpty) - Flexible( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon( - Icons.warning_rounded, - size: 25, - color: colorScheme.warningColor, + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ScaleTransition( + scale: _tween.animate( + CurvedAnimation( + parent: _controller, + curve: Curves.elasticOut, + ), + ), + child: widget.forecast.getIconWidget(context, false, 20, true), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + widget.forecast.getBottomSheetTitle(context), + style: textTheme.titleLarge, + ), + ), + ], + ), + if (warning) _TimeSlotWarning(tween: _tween, duration: _duration), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _TimeWidget( + isWarning: warning, + date: widget.forecast.circulationClosingDate + .toLocalizedTextSpan( + context, + Theme.of(context).textTheme.headlineLarge?.copyWith( + color: colorScheme.error, + ), ), - const SizedBox(width: 10), - Flexible( - child: Text( - AppLocalizations.of( - context, - )!.favoriteSlotsInterferenceWarning, - overflow: TextOverflow.clip, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.warningColor, - ), + text: appLocalization!.circulationClosing.toUpperCase(), + ), + FaIcon(FontAwesomeIcons.arrowRight, size: 20), + _TimeWidget( + isWarning: warning, + date: widget.forecast.circulationReOpeningDate + .toLocalizedTextSpan( + context, + Theme.of(context).textTheme.headlineLarge?.copyWith( + color: colorScheme.primary, ), ), - const SizedBox(width: 10), - ElevatedButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - colorScheme.warningColor, - ), - foregroundColor: WidgetStateProperty.all( - colorScheme.surface, - ), + text: appLocalization.circulationReOpening.toUpperCase(), + ), + ], + ), + Column( + spacing: 15, + children: [ + Container( + padding: EdgeInsets.symmetric(vertical: 10, horizontal: 14), + width: double.infinity, + decoration: BoxDecoration( + border: Border.all( + color: colorScheme.surfaceContainerHigh, + width: 3, + ), + borderRadius: BorderRadius.circular( + CustomProperties.borderRadius, + ), + ), + child: widget.forecast.getDetailedInfo(context), + ), + SizedBox( + width: double.infinity, + height: 60, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: widget.forecast.getColor(context, false), + foregroundColor: widget.forecast.getColor(context, true), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(CustomProperties.borderRadius), ), - onPressed: () => { - Navigator.of(context).pop(), - Navigator.of(context).push( - LeftToRightPageRoute( - builder: (context) => const NotificationScreen( - highlightTimeSlots: true, - ), - settings: const RouteSettings( - name: NotificationScreen.routeName, - ), - ), + ), + ), + onPressed: () => + EventShareService.addToCalendar(context, widget.forecast), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 20, + children: [ + const FaIcon(FontAwesomeIcons.calendarPlus, size: 20), + Text( + appLocalization.calendarEventAddToCalendar, + style: textTheme.titleMedium?.copyWith( + color: widget.forecast.getColor(context, true), ), - }, - child: TweenAnimationBuilder( - tween: _tween, - duration: _duration, - builder: (context, animation, child) => - Transform.translate( - offset: Offset(20 * shake(animation), 0), - child: child, - ), - child: const Icon(Icons.notifications_active), ), - ), - ], + ], + ), ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 30), - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ScaleTransition( - scale: _tween.animate( - CurvedAnimation( - parent: _controller, - curve: Curves.elasticOut, + SizedBox( + width: double.infinity, + height: 60, + child: OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: widget.forecast.getColor(context, false), + side: BorderSide( + width: 3, + color: widget.forecast.getColor(context, false), ), - ), - child: CircleAvatar( - radius: 35, - backgroundColor: widget.forecast.getColor(context, false), - child: widget.forecast.getIconWidget( - context, - true, - 33, - true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(CustomProperties.borderRadius), + ), ), ), + onPressed: () => + EventShareService.shareEvent(context, widget.forecast), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 20, + children: [ + const FaIcon(FontAwesomeIcons.shareNodes, size: 20), + Text( + appLocalization.shareEventTitle, + style: textTheme.titleMedium?.copyWith( + color: widget.forecast.getColor(context, false), + ), + ), + ], + ), + ), + ), + ], + ), + ], + ), + ); + } +} + +class _TimeWidget extends StatelessWidget { + final TextSpan date; + final String text; + final bool isWarning; + + const _TimeWidget({ + required this.date, + required this.text, + required this.isWarning, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + return Container( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(CustomProperties.borderRadius), + border: Border.all( + color: isWarning + ? colorScheme.warningColor.withValues(alpha: 0.5) + : Colors.transparent, + width: 3, + ), + color: colorScheme.surfaceContainerHigh, + ), + child: SizedBox( + height: 70, + width: 150, + child: Stack( + children: [ + Positioned( + top: 2, + left: 15, + child: Text( + text, + style: textTheme.labelLarge?.copyWith( + fontWeight: FontWeight.bold, + color: colorScheme.onPrimaryContainer, + ), + ), + ), + Positioned(top: 30, left: 15, child: RichText(text: date)), + ], + ), + ), + ); + } +} + +class _TimeSlotWarning extends StatelessWidget { + final Tween tween; + final Duration duration; + + const _TimeSlotWarning({required this.tween, required this.duration}); + + /// convert 0-1 to 0-1-0 + double shake(double animation) => + 2 * (0.5 - (0.5 - Curves.bounceOut.transform(animation)).abs()); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return Flexible( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Icon( + Icons.warning_rounded, + size: 25, + color: colorScheme.warningColor, + ), + const SizedBox(width: 10), + Flexible( + child: Text( + AppLocalizations.of(context)!.favoriteSlotsInterferenceWarning, + overflow: TextOverflow.clip, + style: textTheme.labelLarge?.copyWith( + color: colorScheme.warningColor, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(width: 10), + ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + colorScheme.warningColor, + ), + foregroundColor: WidgetStateProperty.all( + colorScheme.surface, + ), + ), + onPressed: () => { + Navigator.of(context).pop(), + Navigator.of(context).push( + LeftToRightPageRoute( + builder: (context) => + const NotificationScreen(highlightTimeSlots: true), + settings: const RouteSettings( + name: NotificationScreen.routeName, + ), ), - const SizedBox(width: 10), - Expanded(child: widget.forecast.getInformationWidget(context)), - ], + ), + }, + child: TweenAnimationBuilder( + tween: tween, + duration: duration, + builder: (context, animation, child) => Transform.translate( + offset: Offset(20 * shake(animation), 0), + child: child, + ), + child: const Icon(Icons.notifications_active), ), ), ], diff --git a/lib/widgets/forecast/forecast_widget.dart b/lib/widgets/forecast/forecast_widget.dart index 60df33f1..a9e71072 100644 --- a/lib/widgets/forecast/forecast_widget.dart +++ b/lib/widgets/forecast/forecast_widget.dart @@ -3,7 +3,6 @@ import 'package:chabo_app/custom_properties.dart'; import 'package:chabo_app/extensions/color_scheme_extension.dart'; import 'package:chabo_app/extensions/date_time_extension.dart'; import 'package:chabo_app/extensions/duration_extension.dart'; -import 'package:chabo_app/helpers/device_helper.dart'; import 'package:chabo_app/l10n/app_localizations.dart'; import 'package:chabo_app/models/abstract_forecast.dart'; import 'package:chabo_app/models/time_slot.dart'; @@ -71,20 +70,9 @@ class ForecastWidget extends StatelessWidget { onTap ?? () async => { await showModalBottomSheet( - useSafeArea: false, + isScrollControlled: true, barrierColor: Colors.black.withValues(alpha: 0.65), - constraints: BoxConstraints( - maxWidth: DeviceHelper.isPortrait(context) - ? double.infinity - : MediaQuery.of(context).size.width / 1.8, - ), - enableDrag: true, context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(CustomProperties.borderRadius * 2), - ), - ), builder: (context) { return ForecastInformationBottomSheet(forecast: forecast); }, diff --git a/lib/widgets/simple_container.dart b/lib/widgets/simple_container.dart index 319aebc0..62274b76 100644 --- a/lib/widgets/simple_container.dart +++ b/lib/widgets/simple_container.dart @@ -12,7 +12,7 @@ class SimpleContainer extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(CustomProperties.borderRadius), - color: Theme.of(context).colorScheme.surfaceContainer, + color: Theme.of(context).colorScheme.surfaceContainerHigh, ), child: child, ); diff --git a/pubspec.lock b/pubspec.lock index e43269b2..3e558367 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "99.0.0" + add_2_calendar: + dependency: "direct main" + description: + name: add_2_calendar + sha256: "8274d1c7a776fdb46b7b7b9a4ed6b1b80ec8f2a9325fe46e2336687bcbc18ce5" + url: "https://pub.dev" + source: hosted + version: "3.1.1" analyzer: dependency: transitive description: @@ -217,6 +225,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" + url: "https://pub.dev" + source: hosted + version: "0.3.5+4" crypto: dependency: transitive description: @@ -821,6 +837,22 @@ packages: url: "https://pub.dev" source: hosted version: "9.23.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: "9eee8283462d91a7a1c8bdb67d08874abd75a2f8fae3bc0ca033035e375fb3d8" + url: "https://pub.dev" + source: hosted + version: "13.2.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "7f7ae28cf400d13f811e297ff37742dba83b79e0a6f5dce14eec0248274e6ce9" + url: "https://pub.dev" + source: hosted + version: "7.1.0" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c918b395..c3f0e525 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: http: ^1.0.0 intl: ^0.20.2 package_info_plus: ^10.1.0 + share_plus: ^13.2.0 shared_preferences: ^2.0.17 stream_transform: ^2.0.0 timezone: ^0.11.0 @@ -36,6 +37,7 @@ dependencies: in_app_review: ^2.0.12 build_runner: ^2.15.0 intl_utils: ^2.8.14 + add_2_calendar: ^3.1.1 dev_dependencies: @@ -60,5 +62,3 @@ flutter_icons: image_path: assets/images/chabo_icon.png adaptive_icon_foreground: assets/images/chabo_icon.png adaptive_icon_background: "#FFFFFF" - - diff --git a/test/units/boat_forecast_test.dart b/test/units/boat_forecast_test.dart index f2ada957..e0cf2a52 100644 --- a/test/units/boat_forecast_test.dart +++ b/test/units/boat_forecast_test.dart @@ -6,7 +6,6 @@ import 'package:chabo_app/models/time_slot.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../localized_testable_widget.dart'; void main() { final forecast = BoatForecast( @@ -81,52 +80,6 @@ void main() { expect(forecast.closedDuration, const Duration(hours: 1)); }); - group('Info TextSpan', () { - testWidgets( - 'Same day', - (WidgetTester tester) async { - await tester.pumpWidget( - await localizedTestableWidgetEN( - child: Builder( - builder: (BuildContext context) { - final RichText richText = - forecast.getInformationWidget(context); - expect( - richText.text.toPlainText(), - 'Sunday, May 14, 2023 from  to , the Chaban bridge will be closed for the arrival of the TEST_BOAT\n\nEstimated time of crossing : 3:30 PM', - ); - - return const Placeholder(); - }, - ), - ), - ); - }, - ); - - testWidgets( - 'During tow days', - (WidgetTester tester) async { - await tester.pumpWidget( - await localizedTestableWidgetEN( - child: Builder( - builder: (BuildContext context) { - final RichText richText = - forecast2.getInformationWidget(context); - expect( - richText.text.toPlainText(), - 'From Sunday, May 14, 2023 , to Monday, May 15, 2023 , the Chaban bridge will be closed for the arrival of the TEST_BOAT\n\nEstimated time of crossing : Mon, May 15 at 2:00 AM', - ); - - return const Placeholder(); - }, - ), - ), - ); - }, - ); - }); - group('During two days or not', () { test('Is during 2 days', () { expect(forecast2.isDuringTwoDays, true); diff --git a/test/units/maintenance_forecast_test.dart b/test/units/maintenance_forecast_test.dart index 9cae836d..754374c3 100644 --- a/test/units/maintenance_forecast_test.dart +++ b/test/units/maintenance_forecast_test.dart @@ -5,7 +5,6 @@ import 'package:chabo_app/models/time_slot.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../localized_testable_widget.dart'; void main() { final forecast = MaintenanceForecast( @@ -43,52 +42,6 @@ void main() { expect(forecast.closedDuration, const Duration(hours: 1)); }); - group('Info TextSpan', () { - testWidgets( - 'Display info TextSpan (same day)', - (WidgetTester tester) async { - await tester.pumpWidget( - await localizedTestableWidgetEN( - child: Builder( - builder: (BuildContext context) { - final RichText richText = - forecast.getInformationWidget(context); - expect( - richText.text.toPlainText(), - 'Sunday, May 14, 2023 from  to , the Chaban bridge will be closed for maintenance', - ); - - return const Placeholder(); - }, - ), - ), - ); - }, - ); - - testWidgets( - 'Display info TextSpan (tow days)', - (WidgetTester tester) async { - await tester.pumpWidget( - await localizedTestableWidgetEN( - child: Builder( - builder: (BuildContext context) { - final RichText richText = - forecast2.getInformationWidget(context); - expect( - richText.text.toPlainText(), - 'From Sunday, May 14, 2023 , to Monday, May 15, 2023 , the Chaban bridge will be closed for maintenance', - ); - - return const Placeholder(); - }, - ), - ), - ); - }, - ); - }); - group('During two days or not', () { test('Is during 2 days', () { expect(forecast2.isDuringTwoDays, true); @@ -123,8 +76,8 @@ void main() { ); const days = [Day.sunday]; - final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1, days); - expect(isOverlaping, true); + final isOverlapping = forecast.isOverlappingWithTimeSlot(timeSlot1, days); + expect(isOverlapping, true); }); test('(2) Is overlaping with [TimeSlots]', () { diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 6f56494f..5ca947ed 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST sentry_flutter + share_plus url_launcher_windows )