Réglages nom d'app / scan QR / liens stores, et nouvelle URL de QR
Écran d'une application : nom de l'application (FR/EN/NL), case d'affichage du scan QR, et liens stores réservés au SuperAdmin. MultiStringInputContainer accepte des langues explicites, faute de configuration sélectionnée sur cet écran. Le QR d'une section pointe vers app.myinfomate.be : la page /download si l'instance a une app mobile, la section web directement sinon. web.mymuseum.be était désuet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a1701e216a
commit
0c3bc31b5e
@ -22,6 +22,9 @@ class MultiStringInputContainer extends StatelessWidget {
|
||||
final double fontSize;
|
||||
final bool isMandatory;
|
||||
final bool showPreview;
|
||||
/// Hors d'une configuration (ex. réglages d'une application), les langues ne
|
||||
/// peuvent pas venir de `selectedConfiguration` : on les passe explicitement.
|
||||
final List<String>? languages;
|
||||
|
||||
const MultiStringInputContainer({
|
||||
Key? key,
|
||||
@ -37,6 +40,7 @@ class MultiStringInputContainer extends StatelessWidget {
|
||||
this.fontSize = 18,
|
||||
this.isMandatory = true,
|
||||
this.showPreview = false,
|
||||
this.languages,
|
||||
}) : super(key: key);
|
||||
|
||||
String _plainTextPreview(String htmlOrText) {
|
||||
@ -55,7 +59,7 @@ class MultiStringInputContainer extends StatelessWidget {
|
||||
final appContext = Provider.of<AppContext>(context);
|
||||
final managerAppContext = appContext.getContext();
|
||||
|
||||
final languages = managerAppContext.selectedConfiguration!.languages!;
|
||||
final languages = this.languages ?? managerAppContext.selectedConfiguration!.languages!;
|
||||
final completed =
|
||||
initialValue.where((t) => (t.value ?? "").trim().isNotEmpty).length;
|
||||
final l = AppLocalizations.of(context)!;
|
||||
@ -85,7 +89,7 @@ class MultiStringInputContainer extends StatelessWidget {
|
||||
List<TranslationDTO> initials = initialValue;
|
||||
|
||||
// Préparer les valeurs pour toutes les langues
|
||||
managerAppContext.selectedConfiguration!.languages!.forEach((lang) {
|
||||
languages.forEach((lang) {
|
||||
if (initials.any((iv) => iv.language == lang)) {
|
||||
newValues.add(TranslationDTO.fromJson(
|
||||
jsonDecode(jsonEncode(initials.firstWhere((e) => e.language == lang)))!)!);
|
||||
|
||||
@ -9,6 +9,9 @@ import 'package:manager_app/Components/confirmation_dialog.dart';
|
||||
import 'package:manager_app/Components/message_notification.dart';
|
||||
import 'package:manager_app/Components/multi_select_dropdown_language_container.dart';
|
||||
import 'package:manager_app/Components/card_shape_selector.dart';
|
||||
import 'package:manager_app/Components/check_input_container.dart';
|
||||
import 'package:manager_app/Components/multi_string_input_container.dart';
|
||||
import 'package:manager_app/Components/string_input_container.dart';
|
||||
import 'package:manager_app/Components/reorderable_custom_list.dart';
|
||||
import 'package:manager_app/Components/resource_input_container.dart';
|
||||
import 'package:manager_app/Components/single_choice_input_container.dart';
|
||||
@ -36,6 +39,7 @@ class AppConfigurationLinkScreen extends StatefulWidget {
|
||||
class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen> {
|
||||
late ApplicationInstanceDTO _applicationInstanceDTO;
|
||||
final Map<String, Timer> _spanDebounceTimers = {};
|
||||
Timer? _storeUrlDebounceTimer;
|
||||
// Largeur de viewport simulée pour l'aperçu Web (le site est responsive,
|
||||
// pas figé — l'admin doit pouvoir vérifier les deux).
|
||||
bool _webPreviewMobileWidth = false;
|
||||
@ -51,6 +55,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
||||
for (final timer in _spanDebounceTimers.values) {
|
||||
timer.cancel();
|
||||
}
|
||||
_storeUrlDebounceTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -126,6 +131,22 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
||||
ManagerAppContext managerAppContext = appContext.getContext() as ManagerAppContext;
|
||||
final appUpdatedMsg = widget.appUpdatedLabel ?? AppLocalizations.of(context)!.appUpdatedSuccess;
|
||||
|
||||
final isVisitorApp = _applicationInstanceDTO.appType == AppType.Mobile || _applicationInstanceDTO.appType == AppType.Web;
|
||||
final canEditStoreUrls = _applicationInstanceDTO.appType == AppType.Mobile && managerAppContext.role == UserRole.SuperAdmin;
|
||||
|
||||
Future<void> saveApplicationInstance() async {
|
||||
var applicationInstance = await updateApplicationInstance(appContext, _applicationInstanceDTO);
|
||||
if (applicationInstance != null && context.mounted) {
|
||||
showNotification(kSuccess, kWhite, appUpdatedMsg, context, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Un champ texte notifie à chaque frappe : on n'enregistre qu'une fois la saisie posée.
|
||||
void saveStoreUrlDebounced() {
|
||||
_storeUrlDebounceTimer?.cancel();
|
||||
_storeUrlDebounceTimer = Timer(const Duration(milliseconds: 800), saveApplicationInstance);
|
||||
}
|
||||
|
||||
_generalInfoCard() {
|
||||
|
||||
const elementHeight = 116.0;
|
||||
@ -334,6 +355,76 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isVisitorApp)
|
||||
SizedBox(
|
||||
width: elementWidth,
|
||||
height: elementHeight,
|
||||
child: Center(
|
||||
child: MultiStringInputContainer(
|
||||
label: AppLocalizations.of(context)!.appNameLabel,
|
||||
modalLabel: AppLocalizations.of(context)!.appNameModalLabel,
|
||||
initialValue: _applicationInstanceDTO.appName ?? [],
|
||||
languages: _applicationInstanceDTO.languages ?? [],
|
||||
maxLines: 1,
|
||||
isTitle: true,
|
||||
isMandatory: false,
|
||||
onGetResult: (List<TranslationDTO> value) async {
|
||||
setState(() {
|
||||
_applicationInstanceDTO.appName = value;
|
||||
});
|
||||
await saveApplicationInstance();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isVisitorApp)
|
||||
SizedBox(
|
||||
width: elementWidth,
|
||||
height: elementHeight,
|
||||
child: Center(
|
||||
child: CheckInputContainer(
|
||||
label: AppLocalizations.of(context)!.qrScanEnabledLabel,
|
||||
subtitle: AppLocalizations.of(context)!.qrScanEnabledHint,
|
||||
isChecked: _applicationInstanceDTO.isQRCodeEnabled ?? true,
|
||||
onChanged: (value) async {
|
||||
_applicationInstanceDTO.isQRCodeEnabled = value;
|
||||
await saveApplicationInstance();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (canEditStoreUrls)
|
||||
SizedBox(
|
||||
width: elementWidth,
|
||||
height: elementHeight,
|
||||
child: Center(
|
||||
child: StringInputContainer(
|
||||
label: AppLocalizations.of(context)!.appStoreUrlLabel,
|
||||
initialValue: _applicationInstanceDTO.appStoreUrl ?? "",
|
||||
maxLength: 300,
|
||||
onChanged: (value) {
|
||||
_applicationInstanceDTO.appStoreUrl = value.trim().isEmpty ? null : value.trim();
|
||||
saveStoreUrlDebounced();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (canEditStoreUrls)
|
||||
SizedBox(
|
||||
width: elementWidth,
|
||||
height: elementHeight,
|
||||
child: Center(
|
||||
child: StringInputContainer(
|
||||
label: AppLocalizations.of(context)!.playStoreUrlLabel,
|
||||
initialValue: _applicationInstanceDTO.playStoreUrl ?? "",
|
||||
maxLength: 300,
|
||||
onChanged: (value) {
|
||||
_applicationInstanceDTO.playStoreUrl = value.trim().isEmpty ? null : value.trim();
|
||||
saveStoreUrlDebounced();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
// L'activation de l'assistant par canal se règle dans l'onglet Guide IA,
|
||||
// qui liste tous les canaux au même endroit.
|
||||
],
|
||||
|
||||
@ -273,7 +273,12 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
|
||||
|
||||
Widget _cardQR(AppContext appContext, AppLocalizations l) {
|
||||
final managerCtx = appContext.getContext() as ManagerAppContext;
|
||||
final qrData = "${managerCtx.host!.replaceFirst("api", "web")}/${managerCtx.instanceId}/${managerCtx.selectedConfiguration!.id}/${sectionDTO.id!}";
|
||||
final instanceDTO = managerCtx.instanceDTO;
|
||||
// Sans app mobile (offre web seule), la page de téléchargement n'aurait rien à proposer :
|
||||
// le QR ouvre directement la section dans l'app web.
|
||||
final qrData = instanceDTO?.isMobile != true && instanceDTO?.isWeb == true && instanceDTO?.webSlug != null
|
||||
? "$kVisitorWebUrl/${instanceDTO!.webSlug}/${managerCtx.selectedConfiguration!.id}/sections/${sectionDTO.id!}"
|
||||
: "$kVisitorWebUrl/download/${managerCtx.instanceId}/${managerCtx.selectedConfiguration!.id}/${sectionDTO.id!}";
|
||||
|
||||
return Pane(
|
||||
icon: Icons.share_outlined,
|
||||
|
||||
@ -23,6 +23,10 @@ const kSuccess = Color(0xFF8bc34a);
|
||||
const kApiBaseUrl = String.fromEnvironment('API_BASE_URL',
|
||||
defaultValue: 'http://localhost:5000');
|
||||
|
||||
// App visiteur web (visitapp-web). Les QR des sections pointent vers sa page
|
||||
// /download, qui présente l'app mobile de l'instance.
|
||||
const kVisitorWebUrl = 'https://app.myinfomate.be';
|
||||
|
||||
// Empreinte du build, injectee elle aussi au build :
|
||||
// --dart-define=GIT_SHA=$(git rev-parse --short HEAD)
|
||||
// Elle est affichee sous le titre de l'ecran de login et sert a relier un
|
||||
|
||||
@ -637,6 +637,12 @@
|
||||
"languagesLabel": "Languages:",
|
||||
"featuredEventLabel": "Featured event:",
|
||||
"chooseEvent": "Choose an event",
|
||||
"appNameLabel": "Application name:",
|
||||
"appNameModalLabel": "Name shown in the application header",
|
||||
"qrScanEnabledLabel": "Show the QR code scanner",
|
||||
"qrScanEnabledHint": "The scan button appears on the home screen and in visits",
|
||||
"appStoreUrlLabel": "App Store link:",
|
||||
"playStoreUrlLabel": "Play Store link:",
|
||||
"previewWebTitle": "Preview — Web",
|
||||
"previewMobileTitle": "Preview — Mobile",
|
||||
"previewDesktop": "Desktop",
|
||||
|
||||
@ -637,6 +637,12 @@
|
||||
"languagesLabel": "Langues :",
|
||||
"featuredEventLabel": "Evènement à l'affiche :",
|
||||
"chooseEvent": "Choisir un évènement",
|
||||
"appNameLabel": "Nom de l'application :",
|
||||
"appNameModalLabel": "Nom affiché en en-tête de l'application",
|
||||
"qrScanEnabledLabel": "Afficher le scan de QR code",
|
||||
"qrScanEnabledHint": "Le bouton de scan apparaît sur l'accueil et dans les visites",
|
||||
"appStoreUrlLabel": "Lien App Store :",
|
||||
"playStoreUrlLabel": "Lien Play Store :",
|
||||
"previewWebTitle": "Aperçu — Web",
|
||||
"previewMobileTitle": "Aperçu — Mobile",
|
||||
"previewDesktop": "Desktop",
|
||||
|
||||
@ -2860,6 +2860,42 @@ abstract class AppLocalizations {
|
||||
/// **'Choisir un évènement'**
|
||||
String get chooseEvent;
|
||||
|
||||
/// No description provided for @appNameLabel.
|
||||
///
|
||||
/// In fr, this message translates to:
|
||||
/// **'Nom de l\'application :'**
|
||||
String get appNameLabel;
|
||||
|
||||
/// No description provided for @appNameModalLabel.
|
||||
///
|
||||
/// In fr, this message translates to:
|
||||
/// **'Nom affiché en en-tête de l\'application'**
|
||||
String get appNameModalLabel;
|
||||
|
||||
/// No description provided for @qrScanEnabledLabel.
|
||||
///
|
||||
/// In fr, this message translates to:
|
||||
/// **'Afficher le scan de QR code'**
|
||||
String get qrScanEnabledLabel;
|
||||
|
||||
/// No description provided for @qrScanEnabledHint.
|
||||
///
|
||||
/// In fr, this message translates to:
|
||||
/// **'Le bouton de scan apparaît sur l\'accueil et dans les visites'**
|
||||
String get qrScanEnabledHint;
|
||||
|
||||
/// No description provided for @appStoreUrlLabel.
|
||||
///
|
||||
/// In fr, this message translates to:
|
||||
/// **'Lien App Store :'**
|
||||
String get appStoreUrlLabel;
|
||||
|
||||
/// No description provided for @playStoreUrlLabel.
|
||||
///
|
||||
/// In fr, this message translates to:
|
||||
/// **'Lien Play Store :'**
|
||||
String get playStoreUrlLabel;
|
||||
|
||||
/// No description provided for @previewWebTitle.
|
||||
///
|
||||
/// In fr, this message translates to:
|
||||
|
||||
@ -1520,6 +1520,25 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get chooseEvent => 'Choose an event';
|
||||
|
||||
@override
|
||||
String get appNameLabel => 'Application name:';
|
||||
|
||||
@override
|
||||
String get appNameModalLabel => 'Name shown in the application header';
|
||||
|
||||
@override
|
||||
String get qrScanEnabledLabel => 'Show the QR code scanner';
|
||||
|
||||
@override
|
||||
String get qrScanEnabledHint =>
|
||||
'The scan button appears on the home screen and in visits';
|
||||
|
||||
@override
|
||||
String get appStoreUrlLabel => 'App Store link:';
|
||||
|
||||
@override
|
||||
String get playStoreUrlLabel => 'Play Store link:';
|
||||
|
||||
@override
|
||||
String get previewWebTitle => 'Preview — Web';
|
||||
|
||||
|
||||
@ -1557,6 +1557,25 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get chooseEvent => 'Choisir un évènement';
|
||||
|
||||
@override
|
||||
String get appNameLabel => 'Nom de l\'application :';
|
||||
|
||||
@override
|
||||
String get appNameModalLabel => 'Nom affiché en en-tête de l\'application';
|
||||
|
||||
@override
|
||||
String get qrScanEnabledLabel => 'Afficher le scan de QR code';
|
||||
|
||||
@override
|
||||
String get qrScanEnabledHint =>
|
||||
'Le bouton de scan apparaît sur l\'accueil et dans les visites';
|
||||
|
||||
@override
|
||||
String get appStoreUrlLabel => 'Lien App Store :';
|
||||
|
||||
@override
|
||||
String get playStoreUrlLabel => 'Lien Play Store :';
|
||||
|
||||
@override
|
||||
String get previewWebTitle => 'Aperçu — Web';
|
||||
|
||||
|
||||
@ -1536,6 +1536,25 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get chooseEvent => 'Kies een evenement';
|
||||
|
||||
@override
|
||||
String get appNameLabel => 'Naam van de applicatie:';
|
||||
|
||||
@override
|
||||
String get appNameModalLabel => 'Naam in de koptekst van de applicatie';
|
||||
|
||||
@override
|
||||
String get qrScanEnabledLabel => 'QR-codescanner tonen';
|
||||
|
||||
@override
|
||||
String get qrScanEnabledHint =>
|
||||
'De scanknop verschijnt op het startscherm en in de bezoeken';
|
||||
|
||||
@override
|
||||
String get appStoreUrlLabel => 'App Store-link:';
|
||||
|
||||
@override
|
||||
String get playStoreUrlLabel => 'Play Store-link:';
|
||||
|
||||
@override
|
||||
String get previewWebTitle => 'Voorbeeld — Web';
|
||||
|
||||
|
||||
@ -637,6 +637,12 @@
|
||||
"languagesLabel": "Talen:",
|
||||
"featuredEventLabel": "Uitgelicht evenement:",
|
||||
"chooseEvent": "Kies een evenement",
|
||||
"appNameLabel": "Naam van de applicatie:",
|
||||
"appNameModalLabel": "Naam in de koptekst van de applicatie",
|
||||
"qrScanEnabledLabel": "QR-codescanner tonen",
|
||||
"qrScanEnabledHint": "De scanknop verschijnt op het startscherm en in de bezoeken",
|
||||
"appStoreUrlLabel": "App Store-link:",
|
||||
"playStoreUrlLabel": "Play Store-link:",
|
||||
"previewWebTitle": "Voorbeeld — Web",
|
||||
"previewMobileTitle": "Voorbeeld — Mobiel",
|
||||
"previewDesktop": "Desktop",
|
||||
|
||||
@ -28,6 +28,10 @@ class ApplicationInstanceDTO {
|
||||
this.sectionEventDTO,
|
||||
this.isAssistant,
|
||||
this.hasStats,
|
||||
this.isQRCodeEnabled,
|
||||
this.appName = const [],
|
||||
this.appStoreUrl,
|
||||
this.playStoreUrl,
|
||||
});
|
||||
|
||||
String? id;
|
||||
@ -66,6 +70,14 @@ class ApplicationInstanceDTO {
|
||||
|
||||
bool? hasStats;
|
||||
|
||||
bool? isQRCodeEnabled;
|
||||
|
||||
List<TranslationDTO>? appName;
|
||||
|
||||
String? appStoreUrl;
|
||||
|
||||
String? playStoreUrl;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
@ -84,7 +96,11 @@ class ApplicationInstanceDTO {
|
||||
other.sectionEventId == sectionEventId &&
|
||||
other.sectionEventDTO == sectionEventDTO &&
|
||||
other.isAssistant == isAssistant &&
|
||||
other.hasStats == hasStats;
|
||||
other.hasStats == hasStats &&
|
||||
other.isQRCodeEnabled == isQRCodeEnabled &&
|
||||
_deepEquality.equals(other.appName, appName) &&
|
||||
other.appStoreUrl == appStoreUrl &&
|
||||
other.playStoreUrl == playStoreUrl;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@ -103,11 +119,15 @@ class ApplicationInstanceDTO {
|
||||
(sectionEventId == null ? 0 : sectionEventId!.hashCode) +
|
||||
(sectionEventDTO == null ? 0 : sectionEventDTO!.hashCode) +
|
||||
(isAssistant == null ? 0 : isAssistant!.hashCode) +
|
||||
(hasStats == null ? 0 : hasStats!.hashCode);
|
||||
(hasStats == null ? 0 : hasStats!.hashCode) +
|
||||
(isQRCodeEnabled == null ? 0 : isQRCodeEnabled!.hashCode) +
|
||||
(appName == null ? 0 : appName!.hashCode) +
|
||||
(appStoreUrl == null ? 0 : appStoreUrl!.hashCode) +
|
||||
(playStoreUrl == null ? 0 : playStoreUrl!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'ApplicationInstanceDTO[id=$id, instanceId=$instanceId, appType=$appType, configurations=$configurations, mainImageId=$mainImageId, mainImageUrl=$mainImageUrl, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor, languages=$languages, sectionEventId=$sectionEventId, sectionEventDTO=$sectionEventDTO, isAssistant=$isAssistant, hasStats=$hasStats]';
|
||||
'ApplicationInstanceDTO[id=$id, instanceId=$instanceId, appType=$appType, configurations=$configurations, mainImageId=$mainImageId, mainImageUrl=$mainImageUrl, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor, languages=$languages, sectionEventId=$sectionEventId, sectionEventDTO=$sectionEventDTO, isAssistant=$isAssistant, hasStats=$hasStats, isQRCodeEnabled=$isQRCodeEnabled, appName=$appName, appStoreUrl=$appStoreUrl, playStoreUrl=$playStoreUrl]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@ -186,6 +206,24 @@ class ApplicationInstanceDTO {
|
||||
} else {
|
||||
json[r'hasStats'] = null;
|
||||
}
|
||||
if (this.isQRCodeEnabled != null) {
|
||||
json[r'isQRCodeEnabled'] = this.isQRCodeEnabled;
|
||||
}
|
||||
if (this.appName != null) {
|
||||
json[r'appName'] = this.appName;
|
||||
} else {
|
||||
json[r'appName'] = null;
|
||||
}
|
||||
if (this.appStoreUrl != null) {
|
||||
json[r'appStoreUrl'] = this.appStoreUrl;
|
||||
} else {
|
||||
json[r'appStoreUrl'] = null;
|
||||
}
|
||||
if (this.playStoreUrl != null) {
|
||||
json[r'playStoreUrl'] = this.playStoreUrl;
|
||||
} else {
|
||||
json[r'playStoreUrl'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@ -230,6 +268,10 @@ class ApplicationInstanceDTO {
|
||||
sectionEventDTO: SectionEventDTO.fromJson(json[r'sectionEventDTO']),
|
||||
isAssistant: mapValueOfType<bool>(json, r'isAssistant'),
|
||||
hasStats: mapValueOfType<bool>(json, r'hasStats'),
|
||||
isQRCodeEnabled: mapValueOfType<bool>(json, r'isQRCodeEnabled'),
|
||||
appName: TranslationDTO.listFromJson(json[r'appName']),
|
||||
appStoreUrl: mapValueOfType<String>(json, r'appStoreUrl'),
|
||||
playStoreUrl: mapValueOfType<String>(json, r'playStoreUrl'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user