251 lines
9.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.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/number_stepper_field.dart';
import 'package:manager_app/Components/resource_input_container.dart';
import 'package:manager_app/Components/section_card.dart';
import '../progression_mode.dart';
/// Les champs propres au parcours, sans son contenant ni sa liste d'étapes.
///
/// Le DTO est modifié en place ; `onChanged` prévient l'hôte, à charge pour lui
/// de se reconstruire et de déclencher l'enregistrement.
class ParcoursFields extends StatelessWidget {
const ParcoursFields({
Key? key,
required this.path,
required this.onChanged,
}) : super(key: key);
final GuidedPathDTO path;
final VoidCallback onChanged;
/// Réécrit une liste de traductions en conservant la ressource déjà associée
/// à chaque langue : `MultiStringInputContainer` ne manipule que le texte.
List<TranslationAndResourceDTO> _mergeResources(
List<TranslationDTO> values,
List<TranslationAndResourceDTO>? previous,
) {
return values.map((t) {
final prev = (previous ?? []).firstWhere(
(e) => e.language == t.language,
orElse: () => TranslationAndResourceDTO(),
);
return TranslationAndResourceDTO(
language: t.language,
value: t.value,
resourceId: prev.resourceId,
resource: prev.resource,
);
}).toList();
}
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context)!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ResourceInputContainer(
label: l.imageLabel,
color: kPrimaryColor,
initialValue: path.imageResourceId,
onChanged: (resourceDTO) {
path.imageResourceId = resourceDTO.id;
onChanged();
},
),
SizedBox(height: kSpace5),
Row(
children: [
Expanded(
child: MultiStringInputContainer(
label: l.titleColonLabel,
modalLabel: l.pathTitleLabel,
initialValue: path.title ?? [],
onGetResult: (val) {
path.title = val;
onChanged();
},
maxLines: 1,
isTitle: true,
isHTML: true,
showPreview: true,
),
),
SizedBox(width: kSpace7),
Expanded(
child: MultiStringInputContainer(
label: l.descriptionColonLabel,
modalLabel: l.pathDescriptionLabel,
initialValue: path.description ?? [],
onGetResult: (val) {
path.description = val;
onChanged();
},
maxLines: 1,
isTitle: false,
isHTML: true,
showPreview: true,
),
),
],
),
SizedBox(height: kSpace5),
SectionCard(
icon: Icons.sports_esports,
title: l.pathMoodTitle,
subtitle: l.pathMoodSub,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l.pathMoodQuestion, style: kLabelField),
RadioGroup<bool>(
groupValue: path.isGameMode ?? false,
onChanged: (val) {
path.isGameMode = val;
onChanged();
},
child: Column(
children: [
RadioListTile<bool>(
dense: true,
contentPadding: EdgeInsets.zero,
activeColor: kPrimaryColor,
value: false,
title: Text(l.pathMoodVisit, style: kTextBody),
subtitle: Text(
l.pathMoodVisitDesc,
style: kTextHint,
),
),
RadioListTile<bool>(
dense: true,
contentPadding: EdgeInsets.zero,
activeColor: kPrimaryColor,
value: true,
title: Text(l.pathMoodGame, style: kTextBody),
subtitle: Text(
l.pathMoodGameDesc,
style: kTextHint,
),
),
],
),
),
if (path.isGameMode == true) ...[
SizedBox(height: kSpace3),
Row(
children: [
Expanded(
child: MultiStringInputContainer(
label: l.startMessageLabel,
modalLabel: l.gameStartMessageModalLabel,
color: kPrimaryColor,
initialValue: (path.gameMessageDebut ?? [])
.map((t) => TranslationDTO(
language: t.language, value: t.value))
.toList(),
onGetResult: (val) {
path.gameMessageDebut =
_mergeResources(val, path.gameMessageDebut);
onChanged();
},
maxLines: 2,
isTitle: false,
isHTML: true,
showPreview: true,
),
),
SizedBox(width: kSpace7),
Expanded(
child: MultiStringInputContainer(
label: l.endMessageLabel,
modalLabel: l.gameEndMessageModalLabel,
color: kPrimaryColor,
initialValue: (path.gameMessageFin ?? [])
.map((t) => TranslationDTO(
language: t.language, value: t.value))
.toList(),
onGetResult: (val) {
path.gameMessageFin =
_mergeResources(val, path.gameMessageFin);
onChanged();
},
maxLines: 2,
isTitle: false,
isHTML: true,
showPreview: true,
),
),
],
),
],
],
),
),
SizedBox(height: kSpace5),
SectionCard(
icon: Icons.settings,
title: l.pathOptionsTitle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l.pathProgressionQuestion, style: kLabelField),
RadioGroup<ProgressionMode>(
groupValue: progressionModeOf(path),
onChanged: (val) {
applyProgressionMode(path, val!);
onChanged();
},
child: Column(
children: [
for (final mode in ProgressionMode.values)
RadioListTile<ProgressionMode>(
dense: true,
contentPadding: EdgeInsets.zero,
activeColor: kPrimaryColor,
value: mode,
title: Text(mode.label(l), style: kTextBody),
subtitle: Text(mode.description(l), style: kTextHint),
),
],
),
),
if (progressionModeOf(path) == ProgressionMode.stepByStep)
Padding(
padding: const EdgeInsets.only(left: kSpace5),
child: CheckInputContainer(
label: l.pathHideNextSteps,
subtitle:
l.pathHideNextStepsDesc,
isChecked: path.hideNextStepsUntilComplete ?? false,
onChanged: (val) {
path.hideNextStepsUntilComplete = val;
onChanged();
},
),
),
SizedBox(height: kSpace3),
NumberStepperField(
label: l.pathEstimatedDuration,
value: path.estimatedDurationMinutes,
min: 0,
max: 600,
unit: l.minutesAbbr,
onChanged: (val) {
path.estimatedDurationMinutes = val.toInt();
onChanged();
},
),
],
),
),
],
);
}
}