From a6463acce6b603432963c332519841b3db948574 Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Fri, 14 Aug 2026 15:30:14 +0200 Subject: [PATCH] =?UTF-8?q?Guide=20IA=20:=20libell=C3=A9s=20de=20canaux,?= =?UTF-8?q?=20activation=20depuis=20la=20carte,=20notifications=20topRight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La carte « Où le guide est disponible » affichait la valeur entière de l'enum AppType (0, 1, 2…) au lieu d'un libellé. Réutilise les clés statsChannel* déjà traduites en FR/EN/NL pour l'écran Statistiques. L'activation de l'assistant par canal se réglait dans chaque onglet d'application, alors que le champ vit sur ApplicationInstance et se résout par (InstanceId, AppType). Le réglage passe dans la liste des canaux du Guide IA, où tous les canaux sont visibles au même endroit ; le paramètre showAssistant de AppConfigurationLinkScreen devient inutile. Les notifications de cet écran passaient par ScaffoldMessenger (en bas), contrairement au reste de l'application qui utilise showNotification (toastification, topRight). Co-Authored-By: Claude Opus 5 --- .../app_configuration_link_screen.dart | 44 +------ lib/Screens/Applications/web_app_screen.dart | 1 - lib/Screens/GuideIa/guide_ia_screen.dart | 118 ++++++++++++------ lib/l10n/app_en.arb | 2 +- lib/l10n/app_fr.arb | 2 +- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_en.dart | 2 +- lib/l10n/app_localizations_fr.dart | 2 +- lib/l10n/app_localizations_nl.dart | 2 +- lib/l10n/app_nl.arb | 2 +- 10 files changed, 89 insertions(+), 88 deletions(-) diff --git a/lib/Screens/Applications/app_configuration_link_screen.dart b/lib/Screens/Applications/app_configuration_link_screen.dart index 1efe6a2..d0ecb46 100644 --- a/lib/Screens/Applications/app_configuration_link_screen.dart +++ b/lib/Screens/Applications/app_configuration_link_screen.dart @@ -25,10 +25,9 @@ import 'package:provider/provider.dart'; class AppConfigurationLinkScreen extends StatefulWidget { final ApplicationInstanceDTO applicationInstanceDTO; - final bool showAssistant; final String? configTitle; final String? appUpdatedLabel; - AppConfigurationLinkScreen({Key? key, required this.applicationInstanceDTO, this.showAssistant = true, this.configTitle, this.appUpdatedLabel}) : super(key: key); + AppConfigurationLinkScreen({Key? key, required this.applicationInstanceDTO, this.configTitle, this.appUpdatedLabel}) : super(key: key); @override _AppConfigurationLinkScreenState createState() => _AppConfigurationLinkScreenState(); @@ -335,45 +334,8 @@ class _AppConfigurationLinkScreenState extends State ), ), ), - // Assistant IA — visible uniquement si l'instance a la fonctionnalité - if (widget.showAssistant && managerAppContext.instanceDTO?.isAssistant == true) - Container( - width: elementWidth, - height: elementHeight, - child: Center( - child: StatefulBuilder( - builder: (context, localSetState) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(AppLocalizations.of(context)!.aiAssistantLabel, style: TextStyle(fontSize: 16)), - Switch( - activeThumbColor: kPrimaryColor, - inactiveThumbColor: kBodyTextColor, - inactiveTrackColor: kSecond, - hoverColor: kPrimaryColor.withValues(alpha: 0.2), - value: _applicationInstanceDTO.isAssistant ?? false, - onChanged: (bool newValue) async { - try { - _applicationInstanceDTO.isAssistant = newValue; - var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); - if (applicationLink != null) { - localSetState(() { - _applicationInstanceDTO.isAssistant = applicationLink.isAssistant; - }); - showNotification(kSuccess, kWhite, appUpdatedMsg, context, null); - } - } catch (e) { - showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null); - } - }, - ), - ], - ); - }, - ), - ), - ), + // L'activation de l'assistant par canal se règle dans l'onglet Guide IA, + // qui liste tous les canaux au même endroit. ], ), ), diff --git a/lib/Screens/Applications/web_app_screen.dart b/lib/Screens/Applications/web_app_screen.dart index c88a3e5..edafd29 100644 --- a/lib/Screens/Applications/web_app_screen.dart +++ b/lib/Screens/Applications/web_app_screen.dart @@ -153,7 +153,6 @@ class WebAppScreen extends StatelessWidget { Expanded( child: AppConfigurationLinkScreen( applicationInstanceDTO: applicationInstanceDTO, - showAssistant: false, configTitle: 'Contenu de l\'application web', appUpdatedLabel: 'Application web mise à jour', ), diff --git a/lib/Screens/GuideIa/guide_ia_screen.dart b/lib/Screens/GuideIa/guide_ia_screen.dart index 19a8e48..4603bcf 100644 --- a/lib/Screens/GuideIa/guide_ia_screen.dart +++ b/lib/Screens/GuideIa/guide_ia_screen.dart @@ -4,9 +4,11 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:manager_api_new/api.dart'; import 'package:manager_app/app_context.dart'; +import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; +import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart'; import 'package:manager_app/Screens/GuideIa/visitor_questions_tab.dart'; import 'package:provider/provider.dart'; @@ -33,6 +35,9 @@ class _GuideIaScreenState extends State { bool _saving = false; bool _reindexing = false; + /// Ids des canaux dont l'activation est en cours — chaque ligne bascule indépendamment. + final Set _togglingChannels = {}; + int _tab = 0; final _previewController = TextEditingController(); @@ -66,8 +71,9 @@ class _GuideIaScreenState extends State { super.dispose(); } - ManagerAppContext get _managerContext => - Provider.of(context, listen: false).getContext() as ManagerAppContext; + AppContext get _appContext => Provider.of(context, listen: false); + + ManagerAppContext get _managerContext => _appContext.getContext() as ManagerAppContext; /// Langue dans laquelle le gestionnaire saisit ses messages de repli. /// Les autres langues sont produites par la traduction automatique, comme le reste du contenu. @@ -196,14 +202,10 @@ class _GuideIaScreenState extends State { try { await _managerContext.clientAPI!.instanceApi!.instanceUpdateinstance(instance); if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l.guideIaSaved), backgroundColor: kPrimaryColor), - ); + showNotification(kSuccess, kWhite, l.guideIaSaved, context, null); } catch (_) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l.guideIaSaveError), backgroundColor: Colors.redAccent), - ); + showNotification(kError, kWhite, l.guideIaSaveError, context, null); } finally { if (mounted) setState(() => _saving = false); } @@ -244,22 +246,19 @@ class _GuideIaScreenState extends State { if (response.statusCode == 202) { final sections = (jsonDecode(response.body)['sectionsQueued'] as num?)?.toInt() ?? 0; - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text('Réindexation lancée — $sections section(s) en file. ' - 'Le détail par section part dans les logs.'), - backgroundColor: kPrimaryColor, - )); + showNotification( + kSuccess, + kWhite, + 'Réindexation lancée — $sections section(s) en file. ' + 'Le détail par section part dans les logs.', + context, + null); } else { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text('Échec (${response.statusCode}) : ${response.body}'), - backgroundColor: Colors.redAccent, - )); + showNotification(kError, kWhite, 'Échec (${response.statusCode}) : ${response.body}', context, null); } } catch (e) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Échec de la réindexation : $e'), backgroundColor: Colors.redAccent), - ); + showNotification(kError, kWhite, 'Échec de la réindexation : $e', context, null); } finally { if (mounted) setState(() => _reindexing = false); } @@ -531,6 +530,44 @@ class _GuideIaScreenState extends State { ); } + String _channelLabel(AppLocalizations l, AppType? channel) { + switch (channel?.value) { + case 0: + return l.statsChannelMobile; + case 1: + return l.statsChannelTablet; + case 2: + return l.statsChannelWeb; + case 3: + return l.statsChannelVR; + case 4: + return l.statsChannelVoice; + default: + return '—'; + } + } + + Future _toggleChannel(ApplicationInstanceDTO app, AppLocalizations l) async { + final previous = app.isAssistant; + setState(() { + app.isAssistant = !(previous ?? false); + _togglingChannels.add(app.id); + }); + + try { + final updated = await updateApplicationInstance(_appContext, app); + if (!mounted) return; + setState(() => app.isAssistant = updated?.isAssistant ?? app.isAssistant); + showNotification(kSuccess, kWhite, l.guideIaSaved, context, null); + } catch (_) { + if (!mounted) return; + setState(() => app.isAssistant = previous); + showNotification(kError, kWhite, l.guideIaSaveError, context, null); + } finally { + if (mounted) setState(() => _togglingChannels.remove(app.id)); + } + } + Widget _channelsCard(AppLocalizations l) { final apps = _instance?.applicationInstanceDTOs ?? []; @@ -540,19 +577,27 @@ class _GuideIaScreenState extends State { child: Column( children: apps.map((app) { final on = app.isAssistant == true; - return Padding( - padding: const EdgeInsets.symmetric(vertical: 7), - child: Row( - children: [ - Icon(on ? Icons.check_circle : Icons.remove_circle_outline, - size: 18, color: on ? kPrimaryColor : kSecond), - const SizedBox(width: 10), - Expanded( - child: Text((app.appType?.value ?? '—').toString(), - style: const TextStyle(fontSize: 14))), - Text(on ? l.guideIaChannelOn : l.guideIaChannelOff, - style: TextStyle(fontSize: 12.5, color: kBodyTextColor.withValues(alpha: 0.7))), - ], + final busy = _togglingChannels.contains(app.id); + return InkWell( + onTap: busy ? null : () => _toggleChannel(app, l), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 7), + child: Row( + children: [ + if (busy) + const SizedBox( + width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2)) + else + Icon(on ? Icons.check_circle : Icons.remove_circle_outline, + size: 18, color: on ? kPrimaryColor : kSecond), + const SizedBox(width: 10), + Expanded( + child: Text(_channelLabel(l, app.appType), + style: const TextStyle(fontSize: 14))), + Text(on ? l.guideIaChannelOn : l.guideIaChannelOff, + style: TextStyle(fontSize: 12.5, color: kBodyTextColor.withValues(alpha: 0.7))), + ], + ), ), ); }).toList(), @@ -690,12 +735,7 @@ class _GuideIaScreenState extends State { _previewTurns.last = _PreviewTurn(message, null); _previewPending = false; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(AppLocalizations.of(context)!.guideIaPreviewError), - backgroundColor: Colors.redAccent, - ), - ); + showNotification(kError, kWhite, AppLocalizations.of(context)!.guideIaPreviewError, context, null); } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ea15ee3..5e75602 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -62,7 +62,7 @@ "guideIaUsageQuestions": "{count} questions asked", "guideIaUsageNoQuota": "No cap set on your plan.", "guideIaChannelsTitle": "Where the guide is available", - "guideIaChannelsSub": "Enabled per channel, from each application's settings.", + "guideIaChannelsSub": "Click a channel to enable or disable the guide there.", "guideIaChannelOn": "Enabled", "guideIaChannelOff": "Disabled", "guideIaIdentityTitle": "Guide identity", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 3721283..af22d7f 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -62,7 +62,7 @@ "guideIaUsageQuestions": "{count} questions posées", "guideIaUsageNoQuota": "Aucun plafond défini sur votre offre.", "guideIaChannelsTitle": "Où le guide est disponible", - "guideIaChannelsSub": "Activable canal par canal, depuis la configuration de chaque application.", + "guideIaChannelsSub": "Cliquez sur un canal pour y activer ou désactiver le guide.", "guideIaChannelOn": "Activé", "guideIaChannelOff": "Désactivé", "guideIaIdentityTitle": "Identité du guide", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 58a9c47..6e6cc9c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -445,7 +445,7 @@ abstract class AppLocalizations { /// No description provided for @guideIaChannelsSub. /// /// In fr, this message translates to: - /// **'Activable canal par canal, depuis la configuration de chaque application.'** + /// **'Cliquez sur un canal pour y activer ou désactiver le guide.'** String get guideIaChannelsSub; /// No description provided for @guideIaChannelOn. diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 6625886..99ded49 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -195,7 +195,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get guideIaChannelsSub => - 'Enabled per channel, from each application\'s settings.'; + 'Click a channel to enable or disable the guide there.'; @override String get guideIaChannelOn => 'Enabled'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 902e518..25fdb37 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -198,7 +198,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get guideIaChannelsSub => - 'Activable canal par canal, depuis la configuration de chaque application.'; + 'Cliquez sur un canal pour y activer ou désactiver le guide.'; @override String get guideIaChannelOn => 'Activé'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index c2f37e6..fc81918 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -198,7 +198,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get guideIaChannelsSub => - 'Per kanaal in te schakelen, vanuit de instellingen van elke toepassing.'; + 'Klik op een kanaal om de gids daar in of uit te schakelen.'; @override String get guideIaChannelOn => 'Ingeschakeld'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 1c06167..0d456b0 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -62,7 +62,7 @@ "guideIaUsageQuestions": "{count} gestelde vragen", "guideIaUsageNoQuota": "Geen limiet ingesteld op uw abonnement.", "guideIaChannelsTitle": "Waar de gids beschikbaar is", - "guideIaChannelsSub": "Per kanaal in te schakelen, vanuit de instellingen van elke toepassing.", + "guideIaChannelsSub": "Klik op een kanaal om de gids daar in of uit te schakelen.", "guideIaChannelOn": "Ingeschakeld", "guideIaChannelOff": "Uitgeschakeld", "guideIaIdentityTitle": "Identiteit van de gids",