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",