Assistant
AssistantChatSheet.dart (+252), Helpers/assistantSuggestions.dart,
Services/assistantService.dart — même comportement et mêmes suggestions que
visitapp-web, dérivées du contenu réel.
Parcours guidés
guided_path_content_progression_page (+72), guided_path_map_progression_page (+39),
parcours_page : alignés sur les 3 questions de progression qui remplacent les
9 booléens côté manager-app.
Carte
Helpers/mapCenter.dart + les trois vues (flutter_map, google_map, map_box) et
marker_view : centrage et icônes honorés.
Fix du build
guided_step_challenge.dart:83 — typage explicite. Avec le // @dart=2.18 de
manager_api_new côté manager-app, c'est ce qui débloque flutter build apk.
flutter build apk ✅.
Reste ouvert : M3 — meterZoneGPS toujours ignoré, une constante en dur à 100 m
remplace le rayon configuré par section.
121 lines
4.4 KiB
Dart
121 lines
4.4 KiB
Dart
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
||
|
||
/// Questions proposées au visiteur quand il ouvre l'assistant.
|
||
///
|
||
/// Dérivées du contenu réel de la configuration : les sections sont déjà en
|
||
/// mémoire (`VisitAppContext.currentSections`, alimenté au chargement de la
|
||
/// configuration) et leurs titres déjà traduits par le CMS. Un lieu sans agenda
|
||
/// ne propose donc pas de question sur l'agenda.
|
||
///
|
||
/// Miroir de `visitapp-web/src/lib/assistantSuggestions.ts` — garder les deux
|
||
/// listes de phrases alignées.
|
||
class AssistantSuggestions {
|
||
static const int _max = 3;
|
||
|
||
static const Map<String, Map<String, String>> _phrases = {
|
||
'aboutSection': {
|
||
'FR': 'Parlez-moi de « {name} »',
|
||
'NL': 'Vertel me over "{name}"',
|
||
'EN': 'Tell me about "{name}"',
|
||
'DE': 'Erzählen Sie mir von „{name}"',
|
||
'ES': 'Háblame de «{name}»',
|
||
'IT': 'Parlami di "{name}"',
|
||
},
|
||
'whatsOn': {
|
||
'FR': "Qu'est-ce qu'il y a cette semaine ?",
|
||
'NL': 'Wat is er deze week te doen?',
|
||
'EN': "What's on this week?",
|
||
'DE': 'Was gibt es diese Woche?',
|
||
'ES': '¿Qué hay esta semana?',
|
||
'IT': 'Cosa c’è questa settimana?',
|
||
},
|
||
'withKids': {
|
||
'FR': 'Que faire avec des enfants ?',
|
||
'NL': 'Wat kunnen we doen met kinderen?',
|
||
'EN': 'What can we do with children?',
|
||
'DE': 'Was können wir mit Kindern machen?',
|
||
'ES': '¿Qué hacer con niños?',
|
||
'IT': 'Cosa fare con i bambini?',
|
||
},
|
||
'highlights': {
|
||
'FR': "Qu'est-ce qu'il ne faut pas manquer ?",
|
||
'NL': 'Wat mag ik niet missen?',
|
||
'EN': "What shouldn't I miss?",
|
||
'DE': 'Was sollte ich nicht verpassen?',
|
||
'ES': '¿Qué no me puedo perder?',
|
||
'IT': 'Cosa non devo perdere?',
|
||
},
|
||
'inOneHour': {
|
||
'FR': "Que voir si je n'ai qu'une heure ?",
|
||
'NL': 'Wat zie ik als ik maar één uur heb?',
|
||
'EN': 'What should I see in one hour?',
|
||
'DE': 'Was sollte ich in einer Stunde sehen?',
|
||
'ES': '¿Qué ver si solo tengo una hora?',
|
||
'IT': 'Cosa vedere se ho solo un’ora?',
|
||
},
|
||
'openingHours': {
|
||
'FR': 'Quels sont les horaires ?',
|
||
'NL': 'Wat zijn de openingstijden?',
|
||
'EN': 'What are the opening hours?',
|
||
'DE': 'Wie sind die Öffnungszeiten?',
|
||
'ES': '¿Cuál es el horario?',
|
||
'IT': 'Quali sono gli orari?',
|
||
},
|
||
};
|
||
|
||
static String _phrase(String key, String lang, [String? name]) {
|
||
final table = _phrases[key]!;
|
||
final template = table[lang] ?? table['FR']!;
|
||
return name == null ? template : template.replaceAll('{name}', name);
|
||
}
|
||
|
||
static String _stripHtml(String value) =>
|
||
value.replaceAll(RegExp(r'<[^>]*>'), '').trim();
|
||
|
||
static String _titleOf(Map<String, dynamic> section, String lang) {
|
||
final titles = section['title'];
|
||
if (titles is! List || titles.isEmpty) return '';
|
||
final match = titles.firstWhere(
|
||
(t) => t is Map && t['language'] == lang && (t['value'] as String?)?.isNotEmpty == true,
|
||
orElse: () => titles.first,
|
||
);
|
||
if (match is! Map) return '';
|
||
return _stripHtml((match['value'] as String?) ?? '');
|
||
}
|
||
|
||
static List<String> build(VisitAppContext context, {String? currentSectionId}) {
|
||
final lang = (context.language ?? 'FR').toUpperCase();
|
||
|
||
final sections = (context.currentSections ?? [])
|
||
.whereType<Map<String, dynamic>>()
|
||
.where((s) => s['isActive'] != false && s['isSubSection'] != true)
|
||
.toList();
|
||
|
||
bool has(String type) => sections.any((s) => s['type'] == type);
|
||
|
||
final suggestions = <String>[];
|
||
|
||
// La section ouverte passe en premier : c'est ce que le visiteur a sous les yeux.
|
||
if (currentSectionId != null) {
|
||
final current = sections.firstWhere(
|
||
(s) => s['id'] == currentSectionId,
|
||
orElse: () => <String, dynamic>{},
|
||
);
|
||
if (current.isNotEmpty) {
|
||
final name = _titleOf(current, lang);
|
||
if (name.isNotEmpty) suggestions.add(_phrase('aboutSection', lang, name));
|
||
}
|
||
}
|
||
|
||
if (has('Agenda')) suggestions.add(_phrase('whatsOn', lang));
|
||
if (has('Parcours') || has('Game')) suggestions.add(_phrase('withKids', lang));
|
||
|
||
// Génériques — trois, pour qu'un lieu minimal ait quand même le compte complet.
|
||
suggestions.add(_phrase('highlights', lang));
|
||
suggestions.add(_phrase('inOneHour', lang));
|
||
suggestions.add(_phrase('openingHours', lang));
|
||
|
||
return suggestions.toSet().take(_max).toList();
|
||
}
|
||
}
|