Update fix for parcours, event bloc call apis + update layout for game (tabs)

This commit is contained in:
Thomas Fransolet 2026-03-04 16:42:45 +01:00
parent ded4620bf2
commit e05e5234c8
9 changed files with 574 additions and 280 deletions

View File

@ -3,6 +3,10 @@ import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart';
import 'package:intl/intl.dart';
import 'showNewOrUpdateProgrammeBlock.dart';
import 'package:provider/provider.dart';
import 'package:manager_app/app_context.dart';
import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Components/message_notification.dart';
class EventConfig extends StatefulWidget {
final SectionEventDTO initialValue;
@ -25,6 +29,22 @@ class _EventConfigState extends State<EventConfig> {
void initState() {
super.initState();
eventDTO = widget.initialValue;
WidgetsBinding.instance.addPostFrameCallback((_) => _loadProgrammeBlocks());
}
Future<void> _loadProgrammeBlocks() async {
if (eventDTO.id == null || !mounted) return;
final appContext = Provider.of<AppContext>(context, listen: false);
final api = (appContext.getContext() as ManagerAppContext).clientAPI!.sectionEventApi!;
try {
final blocks = await api.sectionEventGetAllProgrammeBlockFromSection(eventDTO.id!);
if (blocks == null || !mounted) return;
setState(() {
eventDTO.programme = blocks;
});
} catch (e) {
// Silently keep initial value on error
}
}
@override
@ -41,11 +61,11 @@ class _EventConfigState extends State<EventConfig> {
title: Text("Date de début"),
subtitle: Text(eventDTO.startDate != null
? DateFormat('dd/MM/yyyy HH:mm')
.format(eventDTO.startDate!)
.format(eventDTO.startDate!.toLocal())
: "Non définie"),
trailing: Icon(Icons.calendar_today),
onTap: () async {
DateTime initialDate = eventDTO.startDate ?? DateTime.now();
DateTime initialDate = eventDTO.startDate?.toLocal() ?? DateTime.now();
if (initialDate.isBefore(DateTime(2000))) {
initialDate = DateTime.now();
}
@ -71,7 +91,7 @@ class _EventConfigState extends State<EventConfig> {
TimeOfDay? time = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(
eventDTO.startDate ?? DateTime.now()),
eventDTO.startDate?.toLocal() ?? DateTime.now()),
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
@ -100,11 +120,11 @@ class _EventConfigState extends State<EventConfig> {
child: ListTile(
title: Text("Date de fin"),
subtitle: Text(eventDTO.endDate != null
? DateFormat('dd/MM/yyyy HH:mm').format(eventDTO.endDate!)
? DateFormat('dd/MM/yyyy HH:mm').format(eventDTO.endDate!.toLocal())
: "Non définie"),
trailing: Icon(Icons.calendar_today),
onTap: () async {
DateTime initialDate = eventDTO.endDate ??
DateTime initialDate = eventDTO.endDate?.toLocal() ??
DateTime.now().add(Duration(days: 1));
if (initialDate.isBefore(DateTime(2000))) {
initialDate = DateTime.now().add(Duration(days: 1));
@ -130,7 +150,7 @@ class _EventConfigState extends State<EventConfig> {
if (picked != null) {
TimeOfDay? time = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(eventDTO.endDate ??
initialTime: TimeOfDay.fromDateTime(eventDTO.endDate?.toLocal() ??
DateTime.now().add(Duration(days: 1))),
builder: (context, child) {
return Theme(
@ -171,17 +191,54 @@ class _EventConfigState extends State<EventConfig> {
icon: Icon(Icons.add),
label: Text("Ajouter un bloc"),
onPressed: () {
final appContext =
Provider.of<AppContext>(context, listen: false);
showNewOrUpdateProgrammeBlock(
context,
null,
(newBlock) {
(newBlock) async {
try {
// The API expects ProgrammeBlockDTO, while eventDTO.programme is List<ProgrammeBlock>
// Usually they are structural equivalents in these generated APIs, but let's be safe.
final programmeBlockDTO =
ProgrammeBlockDTO.fromJson(newBlock.toJson());
if (programmeBlockDTO == null) return;
final createdBlockDTO =
await (appContext.getContext() as ManagerAppContext)
.clientAPI!
.sectionEventApi!
.sectionEventCreateProgrammeBlock(
eventDTO.id!, programmeBlockDTO);
if (createdBlockDTO != null) {
// Convert back if necessary
final createdBlock =
ProgrammeBlock.fromJson(createdBlockDTO.toJson());
if (createdBlock != null) {
setState(() {
eventDTO.programme = [
...(eventDTO.programme ?? []),
newBlock
createdBlock
];
widget.onChanged(eventDTO);
});
showNotification(
kSuccess,
kWhite,
'Bloc de programme créé avec succès',
context,
null);
}
}
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la création du bloc',
context,
null);
}
},
);
},
@ -219,25 +276,89 @@ class _EventConfigState extends State<EventConfig> {
IconButton(
icon: Icon(Icons.edit, color: kPrimaryColor),
onPressed: () {
final appContext = Provider.of<AppContext>(
context,
listen: false);
showNewOrUpdateProgrammeBlock(
context,
block,
(updatedBlock) {
(updatedBlock) async {
try {
final programmeBlockDTO =
ProgrammeBlockDTO.fromJson(
updatedBlock.toJson());
if (programmeBlockDTO == null) return;
final resultDTO =
await (appContext.getContext()
as ManagerAppContext)
.clientAPI!
.sectionEventApi!
.sectionEventUpdateProgrammeBlock(
programmeBlockDTO);
if (resultDTO != null) {
final result = ProgrammeBlock.fromJson(
resultDTO.toJson());
if (result != null) {
setState(() {
eventDTO.programme![index] = updatedBlock;
eventDTO.programme![index] = result;
widget.onChanged(eventDTO);
});
showNotification(
kSuccess,
kWhite,
'Bloc mis à jour avec succès',
context,
null);
}
}
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la mise à jour du bloc',
context,
null);
}
},
);
},
),
IconButton(
icon: Icon(Icons.delete, color: kError),
onPressed: () {
onPressed: () async {
final appContext = Provider.of<AppContext>(
context,
listen: false);
try {
if (block.id != null) {
await (appContext.getContext()
as ManagerAppContext)
.clientAPI!
.sectionEventApi!
.sectionEventDeleteProgrammeBlock(
block.id!);
}
setState(() {
eventDTO.programme!.removeAt(index);
widget.onChanged(eventDTO);
});
showNotification(
kSuccess,
kWhite,
'Bloc supprimé avec succès',
context,
null);
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la suppression du bloc',
context,
null);
}
},
),
],

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/Components/multi_string_input_and_resource_container.dart';
import 'package:manager_app/Components/number_input_container.dart';
import 'package:manager_app/Components/resource_input_container.dart';
@ -32,45 +32,75 @@ class _GameConfigState extends State<GameConfig> {
gameDTO = widget.initialValue;
gameDTO.rows = gameDTO.rows ?? 3;
gameDTO.cols = gameDTO.cols ?? 3;
gameDTO.gameType = gameDTO.gameType ?? GameTypes.number0;
gameDTO.gameType = gameDTO.gameType ?? GameTypes.Puzzle;
gameDTO.messageDebut = gameDTO.messageDebut ?? [];
gameDTO.messageFin = gameDTO.messageFin ?? [];
gameDTO.guidedPaths = gameDTO.guidedPaths ?? [];
super.initState();
}
@override
void didUpdateWidget(GameConfig oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.initialValue.id != oldWidget.initialValue.id) {
setState(() {
gameDTO = widget.initialValue;
gameDTO.rows = gameDTO.rows ?? 3;
gameDTO.cols = gameDTO.cols ?? 3;
gameDTO.gameType = gameDTO.gameType ?? GameTypes.Puzzle;
});
}
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
int initialIndex = gameDTO.gameType?.value ?? 0;
return DefaultTabController(
key: ValueKey("${gameDTO.id}_$initialIndex"),
length: 3,
initialIndex: initialIndex,
child: Builder(builder: (context) {
final TabController controller = DefaultTabController.of(context);
// Attach listener to sync gameType when tab changes
controller.addListener(() {
if (!controller.indexIsChanging) {
GameTypes newType = GameTypes.values[controller.index];
if (gameDTO.gameType != newType) {
setState(() {
gameDTO.gameType = newType;
});
widget.onChanged(gameDTO);
}
}
});
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Type de Jeu : ",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
DropdownButton<GameTypes>(
value: gameDTO.gameType,
items: [
DropdownMenuItem(
value: GameTypes.number0, child: Text("Puzzle")),
DropdownMenuItem(
value: GameTypes.number1, child: Text("Puzzle Glissant")),
DropdownMenuItem(
value: GameTypes.number2, child: Text("Escape Game")),
],
onChanged: (val) {
setState(() {
gameDTO.gameType = val;
widget.onChanged(gameDTO);
});
},
),
TabBar(
labelColor: kPrimaryColor,
unselectedLabelColor: Colors.grey,
indicatorColor: kPrimaryColor,
tabs: [
Tab(icon: Icon(Icons.extension), text: "Puzzle"),
Tab(icon: Icon(Icons.grid_on), text: "Puzzle Glissant"),
Tab(icon: Icon(Icons.door_front_door), text: "Escape Game"),
],
),
),
if (gameDTO.gameType == GameTypes.number2) ...[
// Escape Mode: Parcours Config
Expanded(
child: ParcoursConfig(
child: Container(
height: 500,
child: TabBarView(
children: [
_buildPuzzleConfig(),
_buildPuzzleConfig(),
ParcoursConfig(
initialValue: gameDTO.guidedPaths ?? [],
parentId: gameDTO.id!,
isEvent: false,
@ -82,17 +112,29 @@ class _GameConfigState extends State<GameConfig> {
});
},
),
],
),
] else ...[
// Regular Puzzle Mode
Column(
),
),
],
);
}),
);
}
Widget _buildPuzzleConfig() {
return SingleChildScrollView(
child: Column(
children: [
Row(
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ResourceInputContainer(
label: "Image du puzzle :",
initialValue: gameDTO.puzzleImageId ?? '',
color: kPrimaryColor,
onChanged: (ResourceDTO resourceDTO) {
setState(() {
if (resourceDTO.id == null) {
@ -106,8 +148,7 @@ class _GameConfigState extends State<GameConfig> {
});
},
),
Container(
height: 100,
Flexible(
child: MultiStringInputAndResourceContainer(
label: "Message départ :",
modalLabel: "Message départ",
@ -131,8 +172,7 @@ class _GameConfigState extends State<GameConfig> {
isTitle: false,
),
),
Container(
height: 100,
Flexible(
child: MultiStringInputAndResourceContainer(
label: "Message fin :",
modalLabel: "Message fin",
@ -158,55 +198,38 @@ class _GameConfigState extends State<GameConfig> {
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
height: 100,
child: NumberInputContainer(
label: "Nombre de lignes :",
NumberInputContainer(
label: "Lignes :",
initialValue: gameDTO.rows ?? 3,
color: kPrimaryColor,
isSmall: true,
maxLength: 2,
onChanged: (value) {
try {
gameDTO.rows = int.parse(value);
onChanged: (String value) {
setState(() {
gameDTO.rows = int.tryParse(value) ?? 3;
widget.onChanged(gameDTO);
});
} catch (e) {
showNotification(Colors.orange, kWhite,
'Cela doit être un chiffre', context, null);
}
},
),
),
Container(
height: 100,
child: NumberInputContainer(
label: "Nombre de colonnes :",
NumberInputContainer(
label: "Colonnes :",
initialValue: gameDTO.cols ?? 3,
color: kPrimaryColor,
isSmall: true,
maxLength: 2,
onChanged: (value) {
try {
gameDTO.cols = int.parse(value);
onChanged: (String value) {
setState(() {
gameDTO.cols = int.tryParse(value) ?? 3;
widget.onChanged(gameDTO);
});
} catch (e) {
showNotification(Colors.orange, kWhite,
'Cela doit être un chiffre', context, null);
}
},
),
),
],
),
],
),
],
],
);
}
}

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:manager_app/Components/geometry_input_container.dart';
import 'package:manager_app/Components/dropDown_input_container_categories.dart';
@ -13,9 +14,9 @@ import 'package:manager_api_new/api.dart';
void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
Function getResult, AppContext appContext, BuildContext context) {
GeoPointDTO geoPointDTO = GeoPointDTO();
if (inputGeoPointDTO != null) {
geoPointDTO = inputGeoPointDTO;
geoPointDTO =
GeoPointDTO.fromJson(jsonDecode(jsonEncode(inputGeoPointDTO)))!;
} else {
geoPointDTO.title = <TranslationDTO>[];
geoPointDTO.description = <TranslationDTO>[];

View File

@ -2,6 +2,10 @@ import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart';
import 'package:provider/provider.dart';
import 'package:manager_app/app_context.dart';
import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Components/message_notification.dart';
class ParcoursConfig extends StatefulWidget {
final List<GuidedPathDTO> initialValue;
@ -31,6 +35,24 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
super.initState();
paths = List.from(widget.initialValue);
paths.sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0));
WidgetsBinding.instance.addPostFrameCallback((_) => _loadFromApi());
}
Future<void> _loadFromApi() async {
final appContext = Provider.of<AppContext>(context, listen: false);
final api = (appContext.getContext() as ManagerAppContext).clientAPI!.sectionMapApi!;
try {
// Backend already includes steps + quiz questions via .Include()
final fetchedPaths = await api.sectionMapGetAllGuidedPathFromSection(widget.parentId);
if (fetchedPaths == null || !mounted) return;
fetchedPaths.sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0));
setState(() {
paths = fetchedPaths;
});
} catch (e) {
// Silently keep initial value on error
}
}
@override
@ -48,18 +70,48 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
icon: Icon(Icons.add),
label: Text("Ajouter un parcours"),
onPressed: () {
final appContext =
Provider.of<AppContext>(context, listen: false);
showNewOrUpdateGuidedPath(
context,
null,
widget.parentId,
widget.isEvent,
widget.isEscapeMode,
(newPath) {
setState(() {
(newPath) async {
try {
newPath.order = paths.length;
paths.add(newPath);
newPath.instanceId = (appContext.getContext() as ManagerAppContext).instanceId;
if (widget.isEscapeMode) {
newPath.sectionGameId = widget.parentId;
} else if (widget.isEvent) {
newPath.sectionEventId = widget.parentId;
} else {
newPath.sectionMapId = widget.parentId;
}
final createdPath =
await (appContext.getContext() as ManagerAppContext)
.clientAPI!
.sectionMapApi!
.sectionMapCreateGuidedPath(
widget.parentId, newPath);
if (createdPath != null) {
setState(() {
paths.add(createdPath);
widget.onChanged(paths);
});
showNotification(kSuccess, kWhite,
'Parcours créé avec succès', context, null);
}
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la création du parcours',
context,
null);
}
},
);
},
@ -77,16 +129,35 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
: ReorderableListView.builder(
buildDefaultDragHandles: false,
itemCount: paths.length,
onReorder: (oldIndex, newIndex) {
setState(() {
onReorder: (oldIndex, newIndex) async {
if (newIndex > oldIndex) newIndex -= 1;
final item = paths.removeAt(oldIndex);
paths.insert(newIndex, item);
for (int i = 0; i < paths.length; i++) {
paths[i].order = i;
}
setState(() {});
widget.onChanged(paths);
});
final appContext =
Provider.of<AppContext>(context, listen: false);
final api = (appContext.getContext() as ManagerAppContext)
.clientAPI!
.sectionMapApi!;
try {
// Update all affected paths orders
await Future.wait(
paths.map((p) => api.sectionMapUpdateGuidedPath(p)));
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la mise à jour de l\'ordre',
context,
null);
}
},
itemBuilder: (context, index) {
final path = paths[index];
@ -112,24 +183,64 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
IconButton(
icon: Icon(Icons.edit, color: kPrimaryColor),
onPressed: () {
final appContext = Provider.of<AppContext>(
context,
listen: false);
showNewOrUpdateGuidedPath(
context,
path,
widget.parentId,
widget.isEvent,
widget.isEscapeMode,
(updatedPath) {
(updatedPath) async {
try {
final result =
await (appContext.getContext()
as ManagerAppContext)
.clientAPI!
.sectionMapApi!
.sectionMapUpdateGuidedPath(
updatedPath);
if (result != null) {
setState(() {
paths[index] = updatedPath;
paths[index] = result;
widget.onChanged(paths);
});
showNotification(
kSuccess,
kWhite,
'Parcours mis à jour avec succès',
context,
null);
}
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la mise à jour du parcours',
context,
null);
}
},
);
},
),
IconButton(
icon: Icon(Icons.delete, color: kError),
onPressed: () {
onPressed: () async {
final appContext = Provider.of<AppContext>(
context,
listen: false);
try {
if (path.id != null) {
await (appContext.getContext()
as ManagerAppContext)
.clientAPI!
.sectionMapApi!
.sectionMapDeleteGuidedPath(path.id!);
}
setState(() {
paths.removeAt(index);
for (int i = 0; i < paths.length; i++) {
@ -137,6 +248,20 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
}
widget.onChanged(paths);
});
showNotification(
kSuccess,
kWhite,
'Parcours supprimé avec succès',
context,
null);
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la suppression du parcours',
context,
null);
}
},
),
ReorderableDragStartListener(

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart';
@ -15,7 +16,7 @@ void showNewOrUpdateGuidedPath(
Function(GuidedPathDTO) onSave,
) {
GuidedPathDTO workingPath = path != null
? GuidedPathDTO.fromJson(path.toJson())!
? GuidedPathDTO.fromJson(jsonDecode(jsonEncode(path)))!
: GuidedPathDTO(
title: [],
description: [],
@ -193,8 +194,10 @@ void showNewOrUpdateGuidedPath(
"Étape $index"
: "Étape $index",
),
subtitle: Text(
"${step.quizQuestions?.length ?? 0} question(s)"),
subtitle: isEscapeMode
? Text(
"${step.quizQuestions?.length ?? 0} question(s)")
: null,
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
@ -254,6 +257,16 @@ void showNewOrUpdateGuidedPath(
child: RoundedButton(
text: "Sauvegarder",
press: () {
// Initialise les booleans null false
workingPath.isLinear ??= false;
workingPath.requireSuccessToAdvance ??= false;
workingPath.hideNextStepsUntilComplete ??= false;
// Initialise les booleans nuls dans chaque étape
for (final s in workingPath.steps ?? []) {
s.isHiddenInitially ??= false;
s.isStepTimer ??= false;
s.isStepLocked ??= false;
}
onSave(workingPath);
Navigator.pop(context);
},

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/Components/confirmation_dialog.dart';
@ -14,8 +15,9 @@ void showNewOrUpdateGuidedStep(
bool isEscapeMode,
Function(GuidedStepDTO) onSave,
) {
// Use jsonEncode/jsonDecode for a robust deep copy that handles nested DTOs correctly
GuidedStepDTO workingStep = step != null
? GuidedStepDTO.fromJson(step.toJson())!
? GuidedStepDTO.fromJson(jsonDecode(jsonEncode(step)))!
: GuidedStepDTO(
title: [],
description: [],
@ -23,17 +25,6 @@ void showNewOrUpdateGuidedStep(
order: 0,
);
// Convert EventAddressDTOGeometry to GeometryDTO via JSON for the geometry picker
GeometryDTO? _toGeometryDTO(EventAddressDTOGeometry? geo) {
if (geo == null) return null;
return GeometryDTO.fromJson(geo.toJson());
}
EventAddressDTOGeometry? _toEventGeometry(GeometryDTO? geo) {
if (geo == null) return null;
return EventAddressDTOGeometry.fromJson(geo.toJson());
}
showDialog(
context: context,
builder: (BuildContext context) {
@ -100,16 +91,14 @@ void showNewOrUpdateGuidedStep(
],
),
Divider(height: 24),
// Géométrie conversion JSON entre les deux types GeoDTO
// Géométrie Directement avec GeometryDTO
GeometryInputContainer(
label: "Emplacement de l'étape :",
initialGeometry:
_toGeometryDTO(workingStep.geometry),
initialGeometry: workingStep.geometry,
initialColor: null,
onSave: (geometry, color) {
setState(() {
workingStep.geometry =
_toEventGeometry(geometry);
workingStep.geometry = geometry;
});
},
),
@ -244,6 +233,11 @@ void showNewOrUpdateGuidedStep(
child: RoundedButton(
text: "Sauvegarder",
press: () {
// Initialise les booleans null false
// pour éviter l'erreur backend "Error converting null to Boolean"
workingStep.isHiddenInitially ??= false;
workingStep.isStepTimer ??= false;
workingStep.isStepLocked ??= false;
onSave(workingStep);
Navigator.pop(context);
},

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart';
@ -34,30 +35,24 @@ void showNewOrUpdateQuizQuestion(
bool isEscapeMode,
Function(QuizQuestion) onSave,
) {
// QuizQuestion.label is List<TranslationAndResourceDTO> convert for display
// Use JSON cloning for a robust deep copy
QuizQuestion? clonedQuestion = question != null
? QuizQuestion.fromJson(jsonDecode(jsonEncode(question)))
: null;
List<TranslationDTO> workingLabel = _toTranslationList(
question != null && question.label.isNotEmpty
? question.label
clonedQuestion != null && clonedQuestion.label.isNotEmpty
? clonedQuestion.label
: [TranslationAndResourceDTO(language: 'FR', value: '')]);
List<ResponseDTO> workingResponses =
question != null && question.responses.isNotEmpty
? question.responses
.map((r) => ResponseDTO.fromJson(r.toJson())!)
.toList()
: [];
List<ResponseDTO> workingResponses = clonedQuestion?.responses ?? [];
QuizQuestion workingQuestion = QuizQuestion(
id: question?.id ?? 0,
label: _fromTranslationList(workingLabel), // kept in sync below
QuizQuestion workingQuestion = clonedQuestion ??
QuizQuestion(
id: 0,
label: _fromTranslationList(workingLabel),
responses: workingResponses,
validationQuestionType:
question?.validationQuestionType ?? QuestionType.number0,
puzzleImageId: question?.puzzleImageId,
puzzleImage: question?.puzzleImage,
puzzleRows: question?.puzzleRows,
puzzleCols: question?.puzzleCols,
isSlidingPuzzle: question?.isSlidingPuzzle,
validationQuestionType: QuestionType.number0,
order: question?.order ?? 0,
guidedStepId: question?.guidedStepId ?? stepId,
);

View File

@ -35,9 +35,14 @@ class Client {
SectionQuizApi? _sectionQuizApi;
SectionQuizApi? get sectionQuizApi => _sectionQuizApi;
SectionAgendaApi? _sectionAgendaApi;
SectionAgendaApi? get sectionAgendaApi => _sectionAgendaApi;
SectionEventApi? _sectionEventApi;
SectionEventApi? get sectionEventApi => _sectionEventApi;
Client(String path) {
_apiClient = ApiClient(
basePath: path);
_apiClient = ApiClient(basePath: path);
//basePath: "https://192.168.31.140");
//basePath: "https://localhost:44339");
_apiClient!.addDefaultHeader("Access-Control_Allow_Origin", "*");
@ -51,5 +56,7 @@ class Client {
_deviceApi = DeviceApi(_apiClient);
_sectionMapApi = SectionMapApi(_apiClient);
_sectionQuizApi = SectionQuizApi(_apiClient);
_sectionAgendaApi = SectionAgendaApi(_apiClient);
_sectionEventApi = SectionEventApi(_apiClient);
}
}

View File

@ -23,15 +23,15 @@ class GameTypes {
int toJson() => value;
static const number0 = GameTypes._(0);
static const number1 = GameTypes._(1);
static const number2 = GameTypes._(2);
static const Puzzle = GameTypes._(0);
static const SlidingPuzzle = GameTypes._(1);
static const Escape = GameTypes._(2);
/// List of all possible values in this [enum][GameTypes].
static const values = <GameTypes>[
number0,
number1,
number2,
Puzzle,
SlidingPuzzle,
Escape,
];
static GameTypes? fromJson(dynamic value) =>
@ -74,18 +74,33 @@ class GameTypesTypeTransformer {
/// and users are still using an old app with the old code.
GameTypes? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case 0:
return GameTypes.number0;
case 1:
return GameTypes.number1;
case 2:
return GameTypes.number2;
if (data.runtimeType == String) {
switch (data.toString()) {
case r'Puzzle':
return GameTypes.Puzzle;
case r'SlidingPuzzle':
return GameTypes.SlidingPuzzle;
case r'Escape':
return GameTypes.Escape;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
} else if (data is int) {
switch (data) {
case 0:
return GameTypes.Puzzle;
case 1:
return GameTypes.SlidingPuzzle;
case 2:
return GameTypes.Escape;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
}
return null;
}