From 4d18956abe0d6460e4158d91ae85f8dc882a5e85 Mon Sep 17 00:00:00 2001 From: ElPoyo Date: Thu, 28 May 2026 00:04:48 +0200 Subject: [PATCH] feat: add event details description component and environment configuration file --- .../event_details_description.dart | 137 ++++++++++++++---- 1 file changed, 107 insertions(+), 30 deletions(-) diff --git a/em2rp/lib/views/widgets/calendar_widgets/event_details_components/event_details_description.dart b/em2rp/lib/views/widgets/calendar_widgets/event_details_components/event_details_description.dart index 9538578..af992eb 100644 --- a/em2rp/lib/views/widgets/calendar_widgets/event_details_components/event_details_description.dart +++ b/em2rp/lib/views/widgets/calendar_widgets/event_details_components/event_details_description.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter/gestures.dart'; import 'package:em2rp/models/event_model.dart'; import 'package:em2rp/utils/colors.dart'; +import 'package:url_launcher/url_launcher.dart'; class EventDetailsDescription extends StatelessWidget { final EventModel event; @@ -45,17 +47,48 @@ class EventDetailsDescription extends StatelessWidget { ), ), const SizedBox(height: 8), - SelectableText( - event.address, - style: Theme.of(context).textTheme.bodyLarge, + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + event.address, + style: Theme.of(context).textTheme.bodyLarge, + ), + if (event.latitude != 0.0 || event.longitude != 0.0) ...[ + const SizedBox(height: 4), + SelectableText( + '${event.latitude}° N, ${event.longitude}° E', + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ], + ), + ), + IconButton( + icon: const Icon(Icons.map, color: AppColors.rouge), + tooltip: 'Ouvrir dans Maps', + onPressed: () async { + final query = event.address; + if (query.isEmpty) return; + + final url = Uri.parse('https://www.google.com/maps/search/?api=1&query=${Uri.encodeComponent(query)}'); + if (await canLaunchUrl(url)) { + await launchUrl(url, mode: LaunchMode.externalApplication); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Impossible d\'ouvrir l\'application de carte')), + ); + } + } + }, + ), + ], ), - if (event.latitude != 0.0 || event.longitude != 0.0) ...[ - const SizedBox(height: 4), - SelectableText( - '${event.latitude}° N, ${event.longitude}° E', - style: Theme.of(context).textTheme.bodySmall, - ), - ], ], ); } @@ -68,12 +101,22 @@ class EventDetailsDescription extends StatelessWidget { _buildInfoRow(context, Icons.people, 'Jauge', '${event.jauge} personnes'), const SizedBox(height: 8), ], - if (event.contactEmail != null) ...[ - _buildInfoRow(context, Icons.email, 'Email', event.contactEmail!), + if (event.contactEmail != null && event.contactEmail!.isNotEmpty) ...[ + _buildInfoRow(context, Icons.email, 'Email', event.contactEmail!, onTap: () async { + final url = Uri.parse('mailto:${event.contactEmail!}'); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } + }), const SizedBox(height: 8), ], - if (event.contactPhone != null) ...[ - _buildInfoRow(context, Icons.phone, 'Téléphone', event.contactPhone!), + if (event.contactPhone != null && event.contactPhone!.isNotEmpty) ...[ + _buildInfoRow(context, Icons.phone, 'Téléphone', event.contactPhone!, onTap: () async { + final url = Uri.parse('tel:${event.contactPhone!}'); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } + }), ], ], ); @@ -86,15 +129,25 @@ class EventDetailsDescription extends StatelessWidget { children: [ if (event.jauge != null) _buildInfoChip(context, Icons.people, 'Jauge', '${event.jauge} personnes'), - if (event.contactEmail != null) - _buildInfoChip(context, Icons.email, 'Email', event.contactEmail!), - if (event.contactPhone != null) - _buildInfoChip(context, Icons.phone, 'Téléphone', event.contactPhone!), + if (event.contactEmail != null && event.contactEmail!.isNotEmpty) + _buildInfoChip(context, Icons.email, 'Email', event.contactEmail!, onTap: () async { + final url = Uri.parse('mailto:${event.contactEmail!}'); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } + }), + if (event.contactPhone != null && event.contactPhone!.isNotEmpty) + _buildInfoChip(context, Icons.phone, 'Téléphone', event.contactPhone!, onTap: () async { + final url = Uri.parse('tel:${event.contactPhone!}'); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } + }), ], ); } - Widget _buildInfoRow(BuildContext context, IconData icon, String label, String value) { + Widget _buildInfoRow(BuildContext context, IconData icon, String label, String value, {VoidCallback? onTap}) { return Row( children: [ Icon(icon, size: 20, color: Theme.of(context).primaryColor), @@ -109,12 +162,24 @@ class EventDetailsDescription extends StatelessWidget { color: Colors.grey[600], ), ), - SelectableText( - value, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w500, + onTap == null + ? SelectableText( + value, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + ), + ) + : SelectableText.rich( + TextSpan( + text: value, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + color: AppColors.rouge, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer()..onTap = onTap, + ), ), - ), ], ), ), @@ -122,7 +187,7 @@ class EventDetailsDescription extends StatelessWidget { ); } - Widget _buildInfoChip(BuildContext context, IconData icon, String label, String value) { + Widget _buildInfoChip(BuildContext context, IconData icon, String label, String value, {VoidCallback? onTap}) { final primaryColor = Theme.of(context).primaryColor; return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), @@ -146,12 +211,24 @@ class EventDetailsDescription extends StatelessWidget { color: Colors.grey[600], ), ), - SelectableText( - value, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.w500, + onTap == null + ? SelectableText( + value, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + fontWeight: FontWeight.w500, + ), + ) + : SelectableText.rich( + TextSpan( + text: value, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + fontWeight: FontWeight.w500, + color: AppColors.rouge, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer()..onTap = onTap, + ), ), - ), ], ), ],