Compare commits
2 Commits
a54952e5f4
...
e851929dbc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e851929dbc | ||
|
|
0f2fa9bf3e |
@ -34,11 +34,15 @@ lib/
|
|||||||
## Client API généré (manager_api_new/)
|
## Client API généré (manager_api_new/)
|
||||||
**C'est ici que le client est généré.** Il est consommé via dépendance locale par `tablet-app` et `mymuseum-visitapp`.
|
**C'est ici que le client est généré.** Il est consommé via dépendance locale par `tablet-app` et `mymuseum-visitapp`.
|
||||||
|
|
||||||
- **Ne pas modifier manuellement** les fichiers dans `manager_api_new/`
|
- **Éditer manuellement** les fichiers de `manager_api_new/` en miroir des changements backend — **ne pas relancer le générateur OpenAPI**, il écraserait des patches locaux déjà accumulés dans ce client.
|
||||||
- Régénérer via OpenAPI generator quand le backend change : `openapi_generator_config.json` à la racine
|
|
||||||
- 16 classes API : AuthenticationApi, ConfigurationApi, SectionApi, SectionMapApi, SectionEventApi, ResourceApi, DeviceApi, UserApi, StatsApi, AIApi, etc.
|
- 16 classes API : AuthenticationApi, ConfigurationApi, SectionApi, SectionMapApi, SectionEventApi, ResourceApi, DeviceApi, UserApi, StatsApi, AIApi, etc.
|
||||||
- 130+ DTOs générés dans `manager_api_new/lib/model/`
|
- 130+ DTOs générés dans `manager_api_new/lib/model/`
|
||||||
|
|
||||||
|
## Package layout partagé (myinfomate_layout/)
|
||||||
|
Package Dart pur (pas de dépendance Flutter) contenant `bentoLayout()` — le placement "bento" dense (chaque card occupe colSpan × lignes rowSpan sur une grille à N colonnes, même principe que `grid-auto-flow: dense` en CSS). Utilisé pour l'aperçu live de `app_configuration_link_screen.dart` ET par `mymuseum-visitapp` (même placement, pour que l'aperçu soit fidèle au rendu réel). Côté web (`visitapp-web`), le même rendu est obtenu nativement via CSS Grid (pas de portage JS nécessaire). Tests dans `myinfomate_layout/test/` (fixtures `bento_layout_cases.json`).
|
||||||
|
|
||||||
|
La taille d'une card = deux entiers `gridColSpan` / `gridRowSpan` (1 ou 2), portés par `AppConfigurationLinkDTO` (donc réglables indépendamment par plateforme). Dans l'aperçu, ils se règlent via un sélecteur de forme par ligne (`card_shape_selector.dart`) OU par glisser du coin d'une card (drag 2D) — les deux écrivent le même champ.
|
||||||
|
|
||||||
## État central (ManagerAppContext)
|
## État central (ManagerAppContext)
|
||||||
Contient : credentials, accessToken, instanceId, instanceDTO, référence au client API, configuration/section sélectionnée.
|
Contient : credentials, accessToken, instanceId, instanceDTO, référence au client API, configuration/section sélectionnée.
|
||||||
Accès via `context.read<AppContext>()` ou `context.watch<AppContext>()`.
|
Accès via `context.read<AppContext>()` ou `context.watch<AppContext>()`.
|
||||||
|
|||||||
69
lib/Components/card_shape_selector.dart
Normal file
69
lib/Components/card_shape_selector.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:manager_app/constants.dart';
|
||||||
|
|
||||||
|
class CardShape {
|
||||||
|
final int col;
|
||||||
|
final int row;
|
||||||
|
const CardShape(this.col, this.row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Les 4 formes proposées pour une card dans la grille bento.
|
||||||
|
const List<CardShape> kCardShapes = [
|
||||||
|
CardShape(1, 1), // standard
|
||||||
|
CardShape(2, 1), // large
|
||||||
|
CardShape(1, 2), // haute
|
||||||
|
CardShape(2, 2), // grande vedette
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Sélecteur de forme compact — raccourci équivalent au drag de coin dans
|
||||||
|
/// l'aperçu (les deux écrivent gridColSpan/gridRowSpan sur le même lien).
|
||||||
|
class CardShapeSelector extends StatelessWidget {
|
||||||
|
final int colSpan;
|
||||||
|
final int rowSpan;
|
||||||
|
final void Function(int col, int row) onChanged;
|
||||||
|
|
||||||
|
const CardShapeSelector({
|
||||||
|
super.key,
|
||||||
|
required this.colSpan,
|
||||||
|
required this.rowSpan,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kSecond.withValues(alpha: 0.4),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: kCardShapes.map((shape) {
|
||||||
|
final isSelected = shape.col == colSpan && shape.row == rowSpan;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => onChanged(shape.col, shape.row),
|
||||||
|
child: Container(
|
||||||
|
width: 26,
|
||||||
|
height: 26,
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 2),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? kPrimaryColor : Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
width: shape.col == 2 ? 16 : 8,
|
||||||
|
height: shape.row == 2 ? 16 : 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? kWhite : kBodyTextColor,
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ class CheckInputContainer extends StatefulWidget {
|
|||||||
final bool? isChecked;
|
final bool? isChecked;
|
||||||
final IconData? icon;
|
final IconData? icon;
|
||||||
final String label;
|
final String label;
|
||||||
|
final String? subtitle;
|
||||||
final ValueChanged<bool> onChanged;
|
final ValueChanged<bool> onChanged;
|
||||||
final double fontSize;
|
final double fontSize;
|
||||||
const CheckInputContainer({
|
const CheckInputContainer({
|
||||||
@ -14,6 +15,7 @@ class CheckInputContainer extends StatefulWidget {
|
|||||||
this.isChecked,
|
this.isChecked,
|
||||||
this.icon,
|
this.icon,
|
||||||
required this.label,
|
required this.label,
|
||||||
|
this.subtitle,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
this.fontSize = 18
|
this.fontSize = 18
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -51,10 +53,24 @@ class _CheckInputContainerState extends State<CheckInputContainer> {
|
|||||||
size: 25.0,
|
size: 25.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.label,
|
widget.label,
|
||||||
style: new TextStyle(fontSize: widget.fontSize, fontWeight: FontWeight.w300)
|
style: new TextStyle(fontSize: widget.fontSize, fontWeight: FontWeight.w300)
|
||||||
),
|
),
|
||||||
|
if (widget.subtitle != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 2),
|
||||||
|
child: Text(
|
||||||
|
widget.subtitle!,
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|||||||
@ -65,7 +65,7 @@ class _MultiSelectDropdownLanguageContainerState
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8, width: 10),
|
const SizedBox(height: 8, width: 10),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 35),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
/*decoration: BoxDecoration(
|
/*decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: Colors.grey.shade400, width: 1.2),
|
border: Border.all(color: Colors.grey.shade400, width: 1.2),
|
||||||
|
|||||||
@ -36,11 +36,12 @@ class MultiStringInputAndResourceContainer extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appContext = Provider.of<AppContext>(context);
|
final appContext = Provider.of<AppContext>(context);
|
||||||
ManagerAppContext managerAppContext = appContext.getContext();
|
ManagerAppContext managerAppContext = appContext.getContext();
|
||||||
Size size = MediaQuery.of(context).size;
|
|
||||||
return Container(
|
return Container(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Align(
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: 150),
|
||||||
|
child: Align(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: AutoSizeText(
|
child: AutoSizeText(
|
||||||
label,
|
label,
|
||||||
@ -50,10 +51,11 @@ class MultiStringInputAndResourceContainer extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(10.0),
|
padding: const EdgeInsets.all(10.0),
|
||||||
child: Container(
|
child: ConstrainedBox(
|
||||||
width: size.width *0.15,
|
constraints: BoxConstraints(maxWidth: 140),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
List<TranslationAndResourceDTO> newValues = <TranslationAndResourceDTO>[];
|
List<TranslationAndResourceDTO> newValues = <TranslationAndResourceDTO>[];
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:html/parser.dart' as html_parser;
|
||||||
import 'package:manager_api_new/api.dart';
|
import 'package:manager_api_new/api.dart';
|
||||||
import 'package:manager_app/Components/multi_input_modal.dart';
|
import 'package:manager_app/Components/multi_input_modal.dart';
|
||||||
import 'package:manager_app/Components/multi_string_input_html_modal.dart';
|
import 'package:manager_app/Components/multi_string_input_html_modal.dart';
|
||||||
@ -21,6 +22,7 @@ class MultiStringInputContainer extends StatelessWidget {
|
|||||||
final bool isHTML;
|
final bool isHTML;
|
||||||
final double fontSize;
|
final double fontSize;
|
||||||
final bool isMandatory;
|
final bool isMandatory;
|
||||||
|
final bool showPreview;
|
||||||
|
|
||||||
const MultiStringInputContainer({
|
const MultiStringInputContainer({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -35,8 +37,20 @@ class MultiStringInputContainer extends StatelessWidget {
|
|||||||
this.isHTML = false,
|
this.isHTML = false,
|
||||||
this.fontSize = 18,
|
this.fontSize = 18,
|
||||||
this.isMandatory = true,
|
this.isMandatory = true,
|
||||||
|
this.showPreview = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
String _plainTextPreview(String htmlOrText) {
|
||||||
|
if (htmlOrText.trim().isEmpty) return "";
|
||||||
|
try {
|
||||||
|
final doc = html_parser.parse(htmlOrText);
|
||||||
|
final text = (doc.body?.text ?? htmlOrText).trim();
|
||||||
|
return text.isEmpty ? htmlOrText.trim() : text;
|
||||||
|
} catch (_) {
|
||||||
|
return htmlOrText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appContext = Provider.of<AppContext>(context);
|
final appContext = Provider.of<AppContext>(context);
|
||||||
@ -106,7 +120,58 @@ class MultiStringInputContainer extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: showPreview
|
||||||
|
? Builder(builder: (context) {
|
||||||
|
final languages = managerAppContext.selectedConfiguration!.languages!;
|
||||||
|
final completed = initialValue
|
||||||
|
.where((t) => (t.value ?? "").trim().isNotEmpty)
|
||||||
|
.length;
|
||||||
|
final currentTranslation = initialValue.isNotEmpty
|
||||||
|
? (initialValue.firstWhere(
|
||||||
|
(t) => t.language == 'FR',
|
||||||
|
orElse: () => initialValue.first)
|
||||||
|
.value ??
|
||||||
|
"")
|
||||||
|
: "";
|
||||||
|
final preview = _plainTextPreview(currentTranslation);
|
||||||
|
return Container(
|
||||||
|
height: 54,
|
||||||
|
width: 200,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: BorderRadius.circular(50),
|
||||||
|
),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
preview.isEmpty ? "Aucun texte —" : preview,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: kWhite,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 13,
|
||||||
|
fontStyle: preview.isEmpty ? FontStyle.italic : FontStyle.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
"$completed / ${languages.length} langues traduites",
|
||||||
|
style: const TextStyle(
|
||||||
|
color: kWhite,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: Container(
|
||||||
height: 50,
|
height: 50,
|
||||||
width: 180,
|
width: 180,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
73
lib/Components/number_stepper_field.dart
Normal file
73
lib/Components/number_stepper_field.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../constants.dart';
|
||||||
|
|
||||||
|
class NumberStepperField extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final num? value;
|
||||||
|
final num min;
|
||||||
|
final num max;
|
||||||
|
final num step;
|
||||||
|
final String? unit;
|
||||||
|
final ValueChanged<num> onChanged;
|
||||||
|
|
||||||
|
const NumberStepperField({
|
||||||
|
Key? key,
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
this.min = 0,
|
||||||
|
this.max = 999999,
|
||||||
|
this.step = 1,
|
||||||
|
this.unit,
|
||||||
|
required this.onChanged,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
num get _current => (value ?? min).clamp(min, max);
|
||||||
|
|
||||||
|
String _format(num n) => n.truncateToDouble() == n ? n.toInt().toString() : n.toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final current = _current;
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: kSecond),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.remove_circle_outline),
|
||||||
|
color: current <= min ? Colors.grey[400] : kPrimaryColor,
|
||||||
|
onPressed: current <= min
|
||||||
|
? null
|
||||||
|
: () => onChanged((current - step).clamp(min, max)),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 56,
|
||||||
|
child: Text(
|
||||||
|
unit != null ? "${_format(current)} $unit" : _format(current),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.add_circle_outline),
|
||||||
|
color: current >= max ? Colors.grey[400] : kPrimaryColor,
|
||||||
|
onPressed: current >= max
|
||||||
|
? null
|
||||||
|
: () => onChanged((current + step).clamp(min, max)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,19 +34,18 @@ class _PDFFileInputContainerState extends State<PDFFileInputContainer> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Size size = MediaQuery.of(context).size;
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Align(
|
Expanded(
|
||||||
|
child: Align(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: Text(widget.label, style: TextStyle(fontSize: widget.fontSize, fontWeight: FontWeight.w300))
|
child: Text(widget.label, style: TextStyle(fontSize: widget.fontSize, fontWeight: FontWeight.w300), overflow: TextOverflow.ellipsis)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: size.width *0.16,
|
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
List<OrderedTranslationAndResourceDTO> newValues = <OrderedTranslationAndResourceDTO>[];
|
List<OrderedTranslationAndResourceDTO> newValues = <OrderedTranslationAndResourceDTO>[];
|
||||||
|
|||||||
@ -60,7 +60,7 @@ class _ReorderableCustomListState<T> extends State<ReorderableCustomList<T>> {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = itemsMiddle[index];
|
final item = itemsMiddle[index];
|
||||||
return Container(
|
return Container(
|
||||||
key: ValueKey(item),
|
key: ValueKey(identityHashCode(item)),
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|||||||
70
lib/Components/section_card.dart
Normal file
70
lib/Components/section_card.dart
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../constants.dart';
|
||||||
|
|
||||||
|
class SectionCard extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String title;
|
||||||
|
final String? subtitle;
|
||||||
|
final Widget child;
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
|
||||||
|
const SectionCard({
|
||||||
|
Key? key,
|
||||||
|
required this.icon,
|
||||||
|
required this.title,
|
||||||
|
this.subtitle,
|
||||||
|
required this.child,
|
||||||
|
this.padding = const EdgeInsets.all(16),
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: kSecond),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
padding: padding,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: kPrimaryColor, size: 22),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (subtitle != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 2),
|
||||||
|
child: Text(
|
||||||
|
subtitle!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,19 +1,21 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:auto_size_text/auto_size_text.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||||
import 'package:manager_app/Components/color_picker_input_container.dart';
|
import 'package:manager_app/Components/color_picker_input_container.dart';
|
||||||
import 'package:manager_app/Components/common_loader.dart';
|
import 'package:manager_app/Components/common_loader.dart';
|
||||||
import 'package:manager_app/Components/confirmation_dialog.dart';
|
import 'package:manager_app/Components/confirmation_dialog.dart';
|
||||||
import 'package:manager_app/Components/message_notification.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/multi_select_dropdown_language_container.dart';
|
||||||
import 'package:manager_app/Components/multi_string_input_container.dart';
|
import 'package:manager_app/Components/card_shape_selector.dart';
|
||||||
import 'package:manager_app/Components/reorderable_custom_list.dart';
|
import 'package:manager_app/Components/reorderable_custom_list.dart';
|
||||||
import 'package:manager_app/Components/resource_input_container.dart';
|
import 'package:manager_app/Components/resource_input_container.dart';
|
||||||
import 'package:manager_app/Components/segmented_enum_input_container.dart';
|
|
||||||
import 'package:manager_app/Components/single_choice_input_container.dart';
|
import 'package:manager_app/Components/single_choice_input_container.dart';
|
||||||
import 'package:manager_app/Models/managerContext.dart';
|
import 'package:manager_app/Models/managerContext.dart';
|
||||||
import 'package:manager_app/Screens/Applications/add_configuration_link_popup.dart';
|
import 'package:manager_app/Screens/Applications/add_configuration_link_popup.dart';
|
||||||
|
import 'package:manager_app/Screens/Applications/bento_preview_grid.dart';
|
||||||
|
import 'package:manager_app/Screens/Applications/browser_frame.dart';
|
||||||
import 'package:manager_app/Screens/Applications/phone_mockup.dart';
|
import 'package:manager_app/Screens/Applications/phone_mockup.dart';
|
||||||
import 'package:manager_app/app_context.dart';
|
import 'package:manager_app/app_context.dart';
|
||||||
import 'package:manager_app/constants.dart';
|
import 'package:manager_app/constants.dart';
|
||||||
@ -34,6 +36,10 @@ class AppConfigurationLinkScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen> {
|
class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen> {
|
||||||
late ApplicationInstanceDTO _applicationInstanceDTO;
|
late ApplicationInstanceDTO _applicationInstanceDTO;
|
||||||
|
final Map<String, Timer> _spanDebounceTimers = {};
|
||||||
|
// 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;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -41,6 +47,79 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
_applicationInstanceDTO = widget.applicationInstanceDTO;
|
_applicationInstanceDTO = widget.applicationInstanceDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (final timer in _spanDebounceTimers.values) {
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Met à jour les spans localement (sélecteur de forme et aperçu restent
|
||||||
|
// synchronisés sur le même objet link), puis débounce l'appel réseau pendant
|
||||||
|
// un glisser de coin continu.
|
||||||
|
void _onSpanChanged(
|
||||||
|
AppConfigurationLinkDTO link,
|
||||||
|
int colSpan,
|
||||||
|
int rowSpan,
|
||||||
|
AppContext appContext,
|
||||||
|
BuildContext context,
|
||||||
|
String appUpdatedMsg,
|
||||||
|
void Function(void Function()) localSetState,
|
||||||
|
) {
|
||||||
|
link.gridColSpan = colSpan;
|
||||||
|
link.gridRowSpan = rowSpan;
|
||||||
|
localSetState(() {});
|
||||||
|
_spanDebounceTimers[link.id!]?.cancel();
|
||||||
|
// Débounce le temps du glisser ; la notif n'arrive qu'une fois la
|
||||||
|
// sauvegarde réellement effectuée, pas à chaque pixel de resize.
|
||||||
|
_spanDebounceTimers[link.id!] = Timer(const Duration(milliseconds: 400), () async {
|
||||||
|
try {
|
||||||
|
await updateApplicationLink(appContext, link);
|
||||||
|
if (context.mounted) showNotification(kSuccess, kWhite, appUpdatedMsg, context, null);
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glisser une card sur une autre dans l'aperçu échange leur position dans la
|
||||||
|
// liste (donc leur `order`) ; le dense packing recalcule le reste tout seul.
|
||||||
|
// La liste de gauche se resynchronise via sa clé (basée sur la séquence des
|
||||||
|
// ids), puisqu'elle travaille sur sa propre copie interne de `links`.
|
||||||
|
void _onSwap(
|
||||||
|
String fromId,
|
||||||
|
String toId,
|
||||||
|
List<AppConfigurationLinkDTO> appConfigurationLinks,
|
||||||
|
AppContext appContext,
|
||||||
|
BuildContext context,
|
||||||
|
String appUpdatedMsg,
|
||||||
|
void Function(void Function()) localSetState,
|
||||||
|
) async {
|
||||||
|
final fromIndex = appConfigurationLinks.indexWhere((l) => l.id == fromId);
|
||||||
|
final toIndex = appConfigurationLinks.indexWhere((l) => l.id == toId);
|
||||||
|
if (fromIndex == -1 || toIndex == -1) return;
|
||||||
|
|
||||||
|
final tmp = appConfigurationLinks[fromIndex];
|
||||||
|
appConfigurationLinks[fromIndex] = appConfigurationLinks[toIndex];
|
||||||
|
appConfigurationLinks[toIndex] = tmp;
|
||||||
|
|
||||||
|
int order = 0;
|
||||||
|
for (final item in appConfigurationLinks) {
|
||||||
|
item.order = order;
|
||||||
|
order++;
|
||||||
|
}
|
||||||
|
localSetState(() {});
|
||||||
|
try {
|
||||||
|
await updateAppConfigurationOrder(appContext, appConfigurationLinks);
|
||||||
|
showNotification(kSuccess, kWhite, appUpdatedMsg, context, null);
|
||||||
|
} catch (e) {
|
||||||
|
showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appContext = Provider.of<AppContext>(context);
|
final appContext = Provider.of<AppContext>(context);
|
||||||
@ -50,7 +129,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
|
|
||||||
_generalInfoCard() {
|
_generalInfoCard() {
|
||||||
|
|
||||||
const elementHeight = 125.0;
|
const elementHeight = 116.0;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
@ -73,8 +152,8 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
runAlignment: WrapAlignment.center,
|
runAlignment: WrapAlignment.center,
|
||||||
spacing: 16,
|
spacing: 12,
|
||||||
runSpacing: 16,
|
runSpacing: 12,
|
||||||
children: [
|
children: [
|
||||||
// Image principale
|
// Image principale
|
||||||
Container(
|
Container(
|
||||||
@ -184,32 +263,6 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Layout (Grid or Mansonry)
|
|
||||||
SizedBox(
|
|
||||||
width: elementWidth,
|
|
||||||
height: elementHeight,
|
|
||||||
child: Center(
|
|
||||||
child: SegmentedEnumInputContainer(
|
|
||||||
label: AppLocalizations.of(context)!.layoutLabel,
|
|
||||||
selected: _applicationInstanceDTO.layoutMainPage,
|
|
||||||
values: LayoutMainPageType.values,
|
|
||||||
inputValues: { LayoutMainPageType.SimpleGrid: {'label': AppLocalizations.of(context)!.layoutGrid, 'icon': Icons.grid_view}, LayoutMainPageType.MasonryGrid : {'label': 'Masonry', 'icon': Icons.view_quilt }},
|
|
||||||
onChanged: (value) async {
|
|
||||||
var tempOutput = value;
|
|
||||||
_applicationInstanceDTO.layoutMainPage = tempOutput;
|
|
||||||
// automatic save
|
|
||||||
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
|
|
||||||
if(applicationLink != null) {
|
|
||||||
showNotification(kSuccess, kWhite, appUpdatedMsg, context, null);
|
|
||||||
/*setState(() {
|
|
||||||
|
|
||||||
});*/
|
|
||||||
}
|
|
||||||
//print(configurationDTO.languages);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
// Langues
|
// Langues
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: elementWidth,
|
width: elementWidth,
|
||||||
@ -254,9 +307,6 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
|
|
||||||
sectionEvents.add(SectionEventDTO(id: null, label: AppLocalizations.of(context)!.noneOption));
|
sectionEvents.add(SectionEventDTO(id: null, label: AppLocalizations.of(context)!.noneOption));
|
||||||
|
|
||||||
print(_applicationInstanceDTO.sectionEventId);
|
|
||||||
print(_applicationInstanceDTO.sectionEventDTO);
|
|
||||||
|
|
||||||
return SingleChoiceInputContainer<SectionEventDTO?>(
|
return SingleChoiceInputContainer<SectionEventDTO?>(
|
||||||
label: AppLocalizations.of(context)!.featuredEventLabel,
|
label: AppLocalizations.of(context)!.featuredEventLabel,
|
||||||
selectLabel: AppLocalizations.of(context)!.chooseEvent,
|
selectLabel: AppLocalizations.of(context)!.chooseEvent,
|
||||||
@ -265,26 +315,19 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
valueExtractor: (SectionEventDTO? dto) => dto?.id ?? "",
|
valueExtractor: (SectionEventDTO? dto) => dto?.id ?? "",
|
||||||
labelExtractor: (SectionEventDTO? dto) => dto?.label ?? "Aucun",
|
labelExtractor: (SectionEventDTO? dto) => dto?.label ?? "Aucun",
|
||||||
onChanged: (SectionEventDTO? sectionEvent) async {
|
onChanged: (SectionEventDTO? sectionEvent) async {
|
||||||
if(sectionEvent == null) {
|
// "Aucun" est un DTO sentinelle (id: null), pas un vrai
|
||||||
_applicationInstanceDTO.sectionEventId = null;
|
// null Dart : sectionEvent?.id couvre les deux cas.
|
||||||
_applicationInstanceDTO.sectionEventDTO = null;
|
_applicationInstanceDTO.sectionEventId = sectionEvent?.id;
|
||||||
return;
|
|
||||||
}
|
|
||||||
print("Sélectionné: $sectionEvent");
|
|
||||||
print(sectionEvent.label);
|
|
||||||
print(sectionEvent.id);
|
|
||||||
_applicationInstanceDTO.sectionEventId = sectionEvent.id;
|
|
||||||
|
|
||||||
print(_applicationInstanceDTO.sectionEventId);
|
|
||||||
|
|
||||||
// automatic save
|
|
||||||
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
|
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
|
||||||
if(applicationLink != null) {
|
if(applicationLink != null) {
|
||||||
showNotification(kSuccess, kWhite, appUpdatedMsg, context, null);
|
// setState (pas localSetState) : l'aperçu (_previewHero)
|
||||||
//setState(() {
|
// lit _applicationInstanceDTO en dehors du StatefulBuilder
|
||||||
|
// de la liste/aperçu, il faut rebuild tout l'écran.
|
||||||
|
setState(() {
|
||||||
_applicationInstanceDTO.sectionEventDTO = applicationLink.sectionEventDTO;
|
_applicationInstanceDTO.sectionEventDTO = applicationLink.sectionEventDTO;
|
||||||
//_applicationInstanceDTO = applicationLink;
|
});
|
||||||
//});
|
showNotification(kSuccess, kWhite, appUpdatedMsg, context, null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -344,14 +387,15 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_phoneConfigCard(List<AppConfigurationLinkDTO>? appConfigurationLinks) {
|
_phoneConfigCard(
|
||||||
|
List<AppConfigurationLinkDTO>? appConfigurationLinks,
|
||||||
|
void Function(void Function()) localSetState,
|
||||||
|
) {
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
color: kWhite,
|
color: kWhite,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
child: StatefulBuilder(
|
child: Stack(
|
||||||
builder: (context, localSetState) {
|
|
||||||
return Stack(
|
|
||||||
//crossAxisAlignment: CrossAxisAlignment.start,
|
//crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
@ -362,16 +406,18 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
padding: const EdgeInsets.only(left: 32, right: 32, top: 75),
|
padding: const EdgeInsets.only(left: 32, right: 32, top: 75),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: size.height * 0.6,
|
height: size.height * 0.6,
|
||||||
width: size.width * 0.8,
|
width: size.width * 0.9,
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
minHeight: 300,
|
minHeight: 300,
|
||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
maxHeight: 500
|
maxHeight: 500
|
||||||
),
|
),
|
||||||
//color: Colors.blue,
|
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: ReorderableCustomList<AppConfigurationLinkDTO>(
|
child: ReorderableCustomList<AppConfigurationLinkDTO>(
|
||||||
key: ValueKey(appConfigurationLinks),
|
// Basé sur la séquence des ids (pas juste la référence de la
|
||||||
|
// liste, mutée en place par le swap dans l'aperçu) pour que
|
||||||
|
// la liste se resynchronise dès que l'ordre change ailleurs.
|
||||||
|
key: ValueKey(appConfigurationLinks.map((l) => l.id).join(',')),
|
||||||
items: appConfigurationLinks,
|
items: appConfigurationLinks,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
onChanged: (updatedList) async {
|
onChanged: (updatedList) async {
|
||||||
@ -418,6 +464,21 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
if (_applicationInstanceDTO.appType != AppType.Tablet)
|
||||||
|
(BuildContext context, int index, AppConfigurationLinkDTO link) {
|
||||||
|
return Container(
|
||||||
|
height: 50,
|
||||||
|
width: 130,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: CardShapeSelector(
|
||||||
|
colSpan: (link.gridColSpan ?? 1).clamp(1, 2),
|
||||||
|
rowSpan: (link.gridRowSpan ?? 1).clamp(1, 2),
|
||||||
|
onChanged: (col, row) {
|
||||||
|
_onSpanChanged(link, col, row, appContext, context, appUpdatedMsg, localSetState);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
(BuildContext context, int index, AppConfigurationLinkDTO link) {
|
(BuildContext context, int index, AppConfigurationLinkDTO link) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 50,
|
height: 50,
|
||||||
@ -481,60 +542,6 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
/*PhoneMockup(
|
|
||||||
child: Center(
|
|
||||||
child: GridView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount),
|
|
||||||
itemCount: appConfigurationLinks.length,
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
AppConfigurationLinkDTO appConfigurationLink = appConfigurationLinks[index];
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(15),
|
|
||||||
margin: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(appConfigurationLink.configuration!.label!),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
showConfirmationDialog(
|
|
||||||
AppLocalizations.of(context)!.configRemoveConfirm,
|
|
||||||
() {},
|
|
||||||
() async {
|
|
||||||
try {
|
|
||||||
var result = await deleteConfigurationToApp(appContext, appConfigurationLink, _applicationInstanceDTO);
|
|
||||||
|
|
||||||
showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null);
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
// for refresh ui
|
|
||||||
});
|
|
||||||
} catch(e) {
|
|
||||||
showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
context
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Icon(Icons.delete, color: kError, size: 25),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),*/
|
|
||||||
): Center(child: Text(AppLocalizations.of(context)!.noData)),
|
): Center(child: Text(AppLocalizations.of(context)!.noData)),
|
||||||
appConfigurationLinks != null ? Positioned(
|
appConfigurationLinks != null ? Positioned(
|
||||||
top: 8,
|
top: 8,
|
||||||
@ -551,8 +558,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
isActive: true,
|
isActive: true,
|
||||||
isDate: false,
|
isDate: false,
|
||||||
isHour: false,
|
isHour: false,
|
||||||
isSectionImageBackground: false,
|
isSectionImageBackground: false
|
||||||
layoutMainPage: LayoutMainPageType.SimpleGrid
|
|
||||||
);
|
);
|
||||||
await addConfigurationToApp(appContext, appConfigurationLinkDTO, _applicationInstanceDTO);
|
await addConfigurationToApp(appContext, appConfigurationLinkDTO, _applicationInstanceDTO);
|
||||||
}
|
}
|
||||||
@ -576,8 +582,223 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
),
|
),
|
||||||
) : SizedBox(),
|
) : SizedBox(),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reproduit le header de la vraie home (event en vedette, sinon image
|
||||||
|
// principale de l'app) — pour que l'aperçu soit fidèle à ce que le
|
||||||
|
// visiteur voit réellement, pas juste la grille de sections.
|
||||||
|
// [heroHeight] est fixe (indépendant du nombre d'items du bento en dessous)
|
||||||
|
// pour matcher le vrai comportement : SliverAppBar.expandedHeight figé côté
|
||||||
|
// mymuseum-visitapp, `height: 50vh` figé côté HomeHero.tsx (visitapp-web).
|
||||||
|
_previewHero(double heroHeight) {
|
||||||
|
final event = _applicationInstanceDTO.sectionEventDTO;
|
||||||
|
final imageUrl = event?.imageSource ?? _applicationInstanceDTO.mainImageUrl;
|
||||||
|
final title = (event?.title?.isNotEmpty ?? false) ? event!.title!.first.value : null;
|
||||||
|
|
||||||
|
if (imageUrl == null && title == null) return const SizedBox();
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: heroHeight,
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
color: kSecond.withValues(alpha: 0.4),
|
||||||
|
image: imageUrl != null
|
||||||
|
? DecorationImage(image: NetworkImage(imageUrl), fit: BoxFit.cover)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [Colors.transparent, Colors.black.withValues(alpha: 0.75)],
|
||||||
|
stops: const [0.4, 1.0],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (title != null)
|
||||||
|
Positioned(
|
||||||
|
bottom: 10,
|
||||||
|
left: 12,
|
||||||
|
right: 12,
|
||||||
|
// Titre en HTML (contenu Quill) — hauteur bornée + clip pour
|
||||||
|
// ne jamais déborder, même si le moteur ignore line-clamp.
|
||||||
|
child: ClipRect(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 20,
|
||||||
|
child: HtmlWidget(
|
||||||
|
title,
|
||||||
|
textStyle: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w700),
|
||||||
|
customStylesBuilder: (_) => {'font-family': 'Roboto'},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _previewWidthChip(String label, bool isSelected, VoidCallback onTap) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 1),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? kPrimaryColor : Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: isSelected ? kWhite : kBodyTextColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panneau d'aperçu — Card distincte, séparée de la liste (pas de conflit
|
||||||
|
// avec le bouton "+" qui reste positionné en absolu dans _phoneConfigCard).
|
||||||
|
_previewCard(
|
||||||
|
List<AppConfigurationLinkDTO>? appConfigurationLinks,
|
||||||
|
void Function(void Function()) localSetState,
|
||||||
|
) {
|
||||||
|
if (appConfigurationLinks == null || _applicationInstanceDTO.appType == AppType.Tablet) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
final isWeb = _applicationInstanceDTO.appType == AppType.Web;
|
||||||
|
// Nombre de colonnes de l'aperçu = celui de la cible réelle : mobile & web
|
||||||
|
// mobile = 2, web desktop = 4 (breakpoint lg de ConfigurationGrid.tsx).
|
||||||
|
final previewColumns = isWeb ? (_webPreviewMobileWidth ? 2 : 4) : 2;
|
||||||
|
// visitapp-web est un site responsive, pas figé à une largeur — on doit
|
||||||
|
// pouvoir vérifier le rendu desktop ET mobile web, pas juste une seule
|
||||||
|
// largeur de cadre arbitraire.
|
||||||
|
const mobileWebWidth = 390.0;
|
||||||
|
// Ratio largeur/hauteur approximatif d'un smartphone (~9:19.5).
|
||||||
|
const phoneAspect = 0.46;
|
||||||
|
// Largeur de référence pour dériver le hero mobile à l'échelle du cadre
|
||||||
|
// (voir realMobileHeroHeight ci-dessous).
|
||||||
|
const referencePhoneWidth = mobileWebWidth;
|
||||||
|
// Hauteur réelle du hero sur mymuseum-visitapp : SliverAppBar.expandedHeight
|
||||||
|
// fixe (lib/Screens/Home/home_3.0.dart), indépendante de la taille d'écran
|
||||||
|
// et du nombre d'items du bento en dessous — donc on la met à l'échelle du
|
||||||
|
// cadre au lieu de la recalculer depuis la hauteur dispo.
|
||||||
|
const realMobileHeroHeight = 235.0;
|
||||||
|
|
||||||
|
// Le hero garde une taille fixe (fidèle à la vraie app, cf. commentaire de
|
||||||
|
// _previewHero), mais scrolle AVEC le reste du contenu — le vrai
|
||||||
|
// SliverAppBar est `pinned: false` côté mymuseum-visitapp (home_3.0.dart),
|
||||||
|
// il disparaît donc au scroll, ce n'est pas un header épinglé.
|
||||||
|
Widget buildPreviewContent(double frameWidth, double frameHeight, bool isWebFrame) {
|
||||||
|
final heroHeight = isWebFrame
|
||||||
|
? (frameHeight * 0.5).clamp(110.0, 320.0)
|
||||||
|
: realMobileHeroHeight * (frameWidth / referencePhoneWidth);
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
_previewHero(heroHeight),
|
||||||
|
BentoPreviewGrid(
|
||||||
|
links: appConfigurationLinks,
|
||||||
|
columns: previewColumns,
|
||||||
|
onSpanChanged: (link, col, row) => _onSpanChanged(
|
||||||
|
link, col, row, appContext, context, appUpdatedMsg, localSetState),
|
||||||
|
onSwap: (fromId, toId) => _onSwap(
|
||||||
|
fromId, toId, appConfigurationLinks, appContext, context, appUpdatedMsg, localSetState),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
color: kWhite,
|
||||||
|
elevation: 0,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(isWeb ? AppLocalizations.of(context)!.previewWebTitle : AppLocalizations.of(context)!.previewMobileTitle, style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)),
|
||||||
|
const Spacer(),
|
||||||
|
if (isWeb)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kSecond.withValues(alpha: 0.4),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_previewWidthChip(AppLocalizations.of(context)!.previewDesktop, !_webPreviewMobileWidth, () => setState(() => _webPreviewMobileWidth = false)),
|
||||||
|
_previewWidthChip(AppLocalizations.of(context)!.previewMobile, _webPreviewMobileWidth, () => setState(() => _webPreviewMobileWidth = true)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Le cadre est dimensionné sur l'espace réellement disponible ici
|
||||||
|
// (pas sur tout l'écran) pour qu'il soit toujours visible en
|
||||||
|
// entier, sans avoir à scroller pour voir le bas du smartphone.
|
||||||
|
Expanded(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, frameConstraints) {
|
||||||
|
final availableHeight = frameConstraints.maxHeight;
|
||||||
|
final availableWidth = frameConstraints.maxWidth;
|
||||||
|
|
||||||
|
// Le mode "Mobile" de l'onglet Web simule un visiteur qui
|
||||||
|
// consulte le site sur son téléphone : même silhouette de
|
||||||
|
// téléphone que l'onglet Mobile, mais avec le contenu/hero
|
||||||
|
// rendu comme visitapp-web (50vh, colonnes web-mobile).
|
||||||
|
final useDesktopBrowser = isWeb && !_webPreviewMobileWidth;
|
||||||
|
|
||||||
|
final Widget frame;
|
||||||
|
if (useDesktopBrowser) {
|
||||||
|
frame = BrowserFrame(
|
||||||
|
width: availableWidth,
|
||||||
|
height: availableHeight,
|
||||||
|
child: buildPreviewContent(availableWidth, availableHeight, true),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
var phoneHeight = availableHeight;
|
||||||
|
var phoneWidth = phoneHeight * phoneAspect;
|
||||||
|
if (phoneWidth > availableWidth) {
|
||||||
|
phoneWidth = availableWidth;
|
||||||
|
phoneHeight = phoneWidth / phoneAspect;
|
||||||
|
}
|
||||||
|
frame = PhoneMockup(
|
||||||
|
width: phoneWidth,
|
||||||
|
height: phoneHeight,
|
||||||
|
child: buildPreviewContent(phoneWidth, phoneHeight, isWeb),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Align(alignment: Alignment.topCenter, child: frame);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -588,11 +809,6 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
List<AppConfigurationLinkDTO>? appConfigurationLinks = snapshot.data;
|
List<AppConfigurationLinkDTO>? appConfigurationLinks = snapshot.data;
|
||||||
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
|
||||||
final itemWidth = 175;
|
|
||||||
|
|
||||||
final crossAxisCount = (screenWidth / itemWidth).floor().clamp(1, 6);
|
|
||||||
|
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final maxHeight = constraints.maxHeight;
|
final maxHeight = constraints.maxHeight;
|
||||||
@ -616,383 +832,34 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
maxHeight: maxHeight * 0.53,
|
maxHeight: maxHeight * 0.53,
|
||||||
minHeight: 150,
|
minHeight: 150,
|
||||||
),
|
),
|
||||||
child: _phoneConfigCard(appConfigurationLinks),
|
// Un seul StatefulBuilder partagé entre la liste et l'aperçu :
|
||||||
),
|
// les deux Card restent synchronisées sur le même état de poids.
|
||||||
],
|
child: StatefulBuilder(
|
||||||
),
|
builder: (context, localSetState) {
|
||||||
);
|
final showPreview = _applicationInstanceDTO.appType != AppType.Tablet;
|
||||||
},
|
return Row(
|
||||||
);
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
|
||||||
return Align(
|
|
||||||
alignment: AlignmentDirectional.topCenter,
|
|
||||||
child: Container(
|
|
||||||
color: Colors.pink,
|
|
||||||
width: size.width * 0.85,
|
|
||||||
height: size.height * 0.65,
|
|
||||||
constraints: BoxConstraints(minWidth: 200, minHeight: 200),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Align(
|
Expanded(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
flex: showPreview ? 3 : 1,
|
||||||
child: Padding(
|
child: _phoneConfigCard(appConfigurationLinks, localSetState),
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(AppLocalizations.of(context)!.generalInfo),
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
Align(
|
if (showPreview) ...[
|
||||||
alignment: AlignmentDirectional.centerStart,
|
const SizedBox(width: 8),
|
||||||
child: Container(
|
Expanded(
|
||||||
child: Column(
|
flex: 2,
|
||||||
children: [
|
child: _previewCard(appConfigurationLinks, localSetState),
|
||||||
// Titre affiché main
|
|
||||||
/*MultiStringInputContainer(
|
|
||||||
label: "Titre affiché:",
|
|
||||||
modalLabel: "Titre",
|
|
||||||
color: kPrimaryColor,
|
|
||||||
initialValue: _applicationInstanceDTO.title,
|
|
||||||
onGetResult: (value) {
|
|
||||||
if (sectionDTO.title! != value) {
|
|
||||||
sectionDTO.title = value;
|
|
||||||
save(true, appContext);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
maxLines: 1,
|
|
||||||
isHTML: true,
|
|
||||||
isTitle: true,
|
|
||||||
),*/
|
|
||||||
// Image principale
|
|
||||||
ResourceInputContainer(
|
|
||||||
label: AppLocalizations.of(context)!.mainImageLabel,
|
|
||||||
initialValue: _applicationInstanceDTO.mainImageId,
|
|
||||||
color: kPrimaryColor,
|
|
||||||
onChanged: (ResourceDTO resource) {
|
|
||||||
if(resource.id == null) {
|
|
||||||
_applicationInstanceDTO.mainImageId = null;
|
|
||||||
_applicationInstanceDTO.mainImageUrl = null;
|
|
||||||
} else {
|
|
||||||
_applicationInstanceDTO.mainImageId = resource.id;
|
|
||||||
_applicationInstanceDTO.mainImageUrl = resource.url;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
// Image Loader
|
|
||||||
ResourceInputContainer(
|
|
||||||
label: AppLocalizations.of(context)!.loaderLabel,
|
|
||||||
initialValue: _applicationInstanceDTO.loaderImageId,
|
|
||||||
color: kPrimaryColor,
|
|
||||||
onChanged: (ResourceDTO resource) {
|
|
||||||
if(resource.id == null) {
|
|
||||||
_applicationInstanceDTO.loaderImageId = null;
|
|
||||||
_applicationInstanceDTO.loaderImageUrl = null;
|
|
||||||
} else {
|
|
||||||
_applicationInstanceDTO.loaderImageId = resource.id;
|
|
||||||
_applicationInstanceDTO.loaderImageUrl = resource.url;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// Primary color
|
|
||||||
ColorPickerInputContainer(
|
|
||||||
label: AppLocalizations.of(context)!.primaryColorLabel,
|
|
||||||
fontSize: 20,
|
|
||||||
color: _applicationInstanceDTO.primaryColor,
|
|
||||||
onChanged: (value) {
|
|
||||||
_applicationInstanceDTO.primaryColor = value;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// Secondary color
|
|
||||||
ColorPickerInputContainer(
|
|
||||||
label: AppLocalizations.of(context)!.secondaryColorLabel,
|
|
||||||
fontSize: 20,
|
|
||||||
color: _applicationInstanceDTO.secondaryColor,
|
|
||||||
onChanged: (value) {
|
|
||||||
_applicationInstanceDTO.secondaryColor = value;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// Layout (Grid or Mansonry)
|
|
||||||
Text('Todo Type selector Grid or Mansonry'),
|
|
||||||
// Langues
|
|
||||||
MultiSelectDropdownLanguageContainer(
|
|
||||||
label: AppLocalizations.of(context)!.languagesLabel,
|
|
||||||
initialValue: _applicationInstanceDTO.languages != null ? _applicationInstanceDTO.languages!: [],
|
|
||||||
values: languages,
|
|
||||||
isMultiple: true,
|
|
||||||
fontSize: 20,
|
|
||||||
isAtLeastOne: true,
|
|
||||||
onChanged: (value) {
|
|
||||||
var tempOutput = new List<String>.from(value);
|
|
||||||
_applicationInstanceDTO.languages = tempOutput;
|
|
||||||
//print(configurationDTO.languages);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// Highlight / Event principal -> Remplace l'image principale
|
|
||||||
Text('Todo, event section selector')
|
|
||||||
],
|
],
|
||||||
), // Elements
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(AppLocalizations.of(context)!.phoneConfigTitle),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: AlignmentDirectional.topCenter,
|
|
||||||
child: appConfigurationLinks != null ? SingleChildScrollView(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: size.height * 0.6,
|
|
||||||
width: size.width * 0.8,
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: 300,
|
|
||||||
minWidth: 300,
|
|
||||||
maxHeight: 500
|
|
||||||
),
|
|
||||||
color: Colors.blue,
|
|
||||||
child: ReorderableCustomList<AppConfigurationLinkDTO>(
|
|
||||||
items: appConfigurationLinks,
|
|
||||||
shrinkWrap: true,
|
|
||||||
onChanged: (updatedList) async {
|
|
||||||
int order = 0;
|
|
||||||
// update order manually
|
|
||||||
for(var item in updatedList) {
|
|
||||||
item.order = order;
|
|
||||||
order++;
|
|
||||||
}
|
|
||||||
// TODO use order put method
|
|
||||||
var result = await updateAppConfigurationOrder(appContext, updatedList);
|
|
||||||
setState(() {
|
|
||||||
// for refresh
|
|
||||||
});
|
|
||||||
},
|
|
||||||
actions: [
|
|
||||||
/*(BuildContext context, int index, AppConfigurationLinkDTO link) {
|
|
||||||
return Container(
|
|
||||||
height: 50,
|
|
||||||
width: 50,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
var applicationInstance = _applicationInstanceDTO;
|
|
||||||
applicationInstance.
|
|
||||||
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
|
|
||||||
if(applicationLink != null) {
|
|
||||||
if(newValue) {
|
|
||||||
showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configActivatedSuccess, context, null);
|
|
||||||
} else {
|
|
||||||
showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeactivatedSuccess, context, null);
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
link.isActive = applicationLink.isActive;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Icon(Icons.star, color: kError, size: 25),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},*/
|
|
||||||
(BuildContext context, int index, AppConfigurationLinkDTO link) {
|
|
||||||
return Container(
|
|
||||||
height: 50,
|
|
||||||
width: 70,
|
|
||||||
child: Switch(
|
|
||||||
activeThumbColor: kPrimaryColor,
|
|
||||||
inactiveThumbColor: kBodyTextColor,
|
|
||||||
inactiveTrackColor: kSecond,
|
|
||||||
hoverColor: kPrimaryColor.withValues(alpha: 0.2),
|
|
||||||
value: link.isActive ?? false,
|
|
||||||
onChanged: (bool newValue) async {
|
|
||||||
try {
|
|
||||||
link.isActive = newValue;
|
|
||||||
var applicationLink = await updateApplicationLink(appContext, link);
|
|
||||||
if(applicationLink != null) {
|
|
||||||
if(newValue) {
|
|
||||||
showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configActivatedSuccess, context, null);
|
|
||||||
} else {
|
|
||||||
showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeactivatedSuccess, context, null);
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
link.isActive = applicationLink.isActive;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(BuildContext context, int index, AppConfigurationLinkDTO link) {
|
|
||||||
return Container(
|
|
||||||
height: 50,
|
|
||||||
width: 50,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
showConfirmationDialog(
|
|
||||||
AppLocalizations.of(context)!.configRemoveConfirm,
|
|
||||||
() {},
|
|
||||||
() async {
|
|
||||||
try {
|
|
||||||
var result = await deleteConfigurationToApp(appContext, link, _applicationInstanceDTO);
|
|
||||||
|
|
||||||
showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null);
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
// for refresh ui
|
|
||||||
});
|
|
||||||
} catch(e) {
|
|
||||||
showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
context
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Icon(Icons.delete, color: kError, size: 25),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
padding: const EdgeInsets.all(8),
|
);
|
||||||
itemBuilder: (context, index, appConfigurationLink) {
|
},
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
|
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.symmetric(vertical: 3),
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
if(appConfigurationLink.configuration!.imageId != null)
|
|
||||||
Container(
|
|
||||||
width: 50,
|
|
||||||
height: 50,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: kSecond.withValues(alpha: 0.65),
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
|
||||||
image: DecorationImage(
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
image: NetworkImage(appConfigurationLink.configuration!.imageSource!)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(appConfigurationLink.configuration?.label ?? ""),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: 20,
|
|
||||||
right: 20,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
// Show configuration selector to link with !
|
|
||||||
var result = await showAddConfigurationLink(context, appContext, managerAppContext.instanceDTO!, appConfigurationLinks.map((acl) => acl.configurationId!).toList() ?? []);
|
|
||||||
if(result != null) {
|
|
||||||
for(var configurationId in result) {
|
|
||||||
AppConfigurationLinkDTO appConfigurationLinkDTO = AppConfigurationLinkDTO(
|
|
||||||
applicationInstanceId: _applicationInstanceDTO.id,
|
|
||||||
configurationId: configurationId,
|
|
||||||
isActive: true,
|
|
||||||
isDate: false,
|
|
||||||
isHour: false,
|
|
||||||
isSectionImageBackground: false,
|
|
||||||
layoutMainPage: LayoutMainPageType.SimpleGrid
|
|
||||||
);
|
|
||||||
await addConfigurationToApp(appContext, appConfigurationLinkDTO, _applicationInstanceDTO);
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
// Refresh ui
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 85,
|
|
||||||
width: 85,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: kSuccess,
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Icon(Icons.add, size: 25, color: kWhite),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
/*PhoneMockup(
|
|
||||||
child: Center(
|
|
||||||
child: GridView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount),
|
|
||||||
itemCount: appConfigurationLinks.length,
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
AppConfigurationLinkDTO appConfigurationLink = appConfigurationLinks[index];
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(15),
|
|
||||||
margin: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(appConfigurationLink.configuration!.label!),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
showConfirmationDialog(
|
|
||||||
AppLocalizations.of(context)!.configRemoveConfirm,
|
|
||||||
() {},
|
|
||||||
() async {
|
|
||||||
try {
|
|
||||||
var result = await deleteConfigurationToApp(appContext, appConfigurationLink, _applicationInstanceDTO);
|
|
||||||
|
|
||||||
showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null);
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
// for refresh ui
|
|
||||||
});
|
|
||||||
} catch(e) {
|
|
||||||
showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
context
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Icon(Icons.delete, color: kError, size: 25),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),*/
|
|
||||||
],
|
|
||||||
),
|
|
||||||
): Center(child: Text(AppLocalizations.of(context)!.noData)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else if (snapshot.connectionState == ConnectionState.none) {
|
} else if (snapshot.connectionState == ConnectionState.none) {
|
||||||
return Text(AppLocalizations.of(context)!.noData);
|
return Text(AppLocalizations.of(context)!.noData);
|
||||||
@ -1058,7 +925,6 @@ Future<ApplicationInstanceDTO?> updateApplicationInstance(AppContext appContext,
|
|||||||
applicationInstanceToSend?.sectionEventDTO = null;
|
applicationInstanceToSend?.sectionEventDTO = null;
|
||||||
applicationInstanceToSend?.sectionEventId = applicationInstanceDTO.sectionEventId;
|
applicationInstanceToSend?.sectionEventId = applicationInstanceDTO.sectionEventId;
|
||||||
applicationInstanceToSend?.appType = applicationInstanceDTO.appType;
|
applicationInstanceToSend?.appType = applicationInstanceDTO.appType;
|
||||||
applicationInstanceToSend?.layoutMainPage = applicationInstanceDTO.layoutMainPage;
|
|
||||||
|
|
||||||
ApplicationInstanceDTO? result = await (appContext.getContext() as ManagerAppContext).clientAPI!.applicationInstanceApi!.applicationInstanceUpdate(applicationInstanceToSend!);
|
ApplicationInstanceDTO? result = await (appContext.getContext() as ManagerAppContext).clientAPI!.applicationInstanceApi!.applicationInstanceUpdate(applicationInstanceToSend!);
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
243
lib/Screens/Applications/bento_preview_grid.dart
Normal file
243
lib/Screens/Applications/bento_preview_grid.dart
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:manager_api_new/api.dart';
|
||||||
|
import 'package:manager_app/constants.dart';
|
||||||
|
import 'package:myinfomate_layout/myinfomate_layout.dart';
|
||||||
|
|
||||||
|
const double _kGap = 8;
|
||||||
|
|
||||||
|
/// Aperçu live de la grille bento (même placement dense que web/mobile en prod).
|
||||||
|
/// Glisser le coin d'une card change ses spans (colonnes × lignes) ; le résultat
|
||||||
|
/// snappe sur des cellules entières. [onSpanChanged] doit rester synchronisé avec
|
||||||
|
/// le même état que le sélecteur de forme de la liste.
|
||||||
|
/// Glisser la poignée (coin haut-gauche) d'une card sur une autre échange leur
|
||||||
|
/// position dans la liste — le dense packing recalcule le reste automatiquement.
|
||||||
|
class BentoPreviewGrid extends StatelessWidget {
|
||||||
|
final List<AppConfigurationLinkDTO> links;
|
||||||
|
final int columns;
|
||||||
|
final void Function(AppConfigurationLinkDTO link, int colSpan, int rowSpan) onSpanChanged;
|
||||||
|
final void Function(String fromId, String toId) onSwap;
|
||||||
|
|
||||||
|
const BentoPreviewGrid({
|
||||||
|
super.key,
|
||||||
|
required this.links,
|
||||||
|
required this.columns,
|
||||||
|
required this.onSpanChanged,
|
||||||
|
required this.onSwap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
|
final width = constraints.maxWidth;
|
||||||
|
// Seuls les blocs actifs apparaissent réellement dans l'app — sinon le
|
||||||
|
// switch "actif/inactif" de la liste n'aurait aucun effet sur l'aperçu.
|
||||||
|
final activeLinks = links.where((l) => l.isActive == true).toList();
|
||||||
|
if (width <= 0 || activeLinks.isEmpty) return const SizedBox();
|
||||||
|
|
||||||
|
final cols = columns < 1 ? 1 : columns;
|
||||||
|
final maxSpan = cols < 2 ? 1 : 2;
|
||||||
|
final cellWidth = (width - (cols - 1) * _kGap) / cols;
|
||||||
|
final cellHeight = cellWidth * 0.78;
|
||||||
|
|
||||||
|
// Trié par `order` plutôt que par l'ordre du tableau reçu : la liste de
|
||||||
|
// gauche (ReorderableCustomList) travaille sur sa propre copie interne et
|
||||||
|
// ne réordonne que les DTO (leur champ `order`), jamais le tableau `links`
|
||||||
|
// lui-même — sans ce tri, l'aperçu ignorerait le reorder de la liste.
|
||||||
|
final orderedLinks = [...activeLinks]..sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0));
|
||||||
|
|
||||||
|
final result = bentoLayout(
|
||||||
|
orderedLinks
|
||||||
|
.map((l) => BentoItem(
|
||||||
|
id: l.id!,
|
||||||
|
colSpan: (l.gridColSpan ?? 1).clamp(1, maxSpan),
|
||||||
|
rowSpan: (l.gridRowSpan ?? 1).clamp(1, 2),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
cols,
|
||||||
|
);
|
||||||
|
|
||||||
|
final totalHeight =
|
||||||
|
result.rowCount * cellHeight + (result.rowCount - 1) * _kGap;
|
||||||
|
final byId = {for (final l in activeLinks) l.id!: l};
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: totalHeight < 0 ? 0 : totalHeight,
|
||||||
|
child: Stack(
|
||||||
|
children: result.placements.map((p) {
|
||||||
|
final link = byId[p.id]!;
|
||||||
|
return Positioned(
|
||||||
|
left: p.col * (cellWidth + _kGap),
|
||||||
|
top: p.row * (cellHeight + _kGap),
|
||||||
|
width: p.colSpan * cellWidth + (p.colSpan - 1) * _kGap,
|
||||||
|
height: p.rowSpan * cellHeight + (p.rowSpan - 1) * _kGap,
|
||||||
|
child: DragTarget<String>(
|
||||||
|
onWillAcceptWithDetails: (details) => details.data != link.id,
|
||||||
|
onAcceptWithDetails: (details) => onSwap(details.data, link.id!),
|
||||||
|
builder: (context, candidateData, rejectedData) {
|
||||||
|
return _BentoCard(
|
||||||
|
link: link,
|
||||||
|
cellWidth: cellWidth,
|
||||||
|
cellHeight: cellHeight,
|
||||||
|
maxSpan: maxSpan,
|
||||||
|
isDropTarget: candidateData.isNotEmpty,
|
||||||
|
onSpanChanged: (c, r) => onSpanChanged(link, c, r),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BentoCard extends StatefulWidget {
|
||||||
|
final AppConfigurationLinkDTO link;
|
||||||
|
final double cellWidth;
|
||||||
|
final double cellHeight;
|
||||||
|
final int maxSpan;
|
||||||
|
final bool isDropTarget;
|
||||||
|
final void Function(int colSpan, int rowSpan) onSpanChanged;
|
||||||
|
|
||||||
|
const _BentoCard({
|
||||||
|
required this.link,
|
||||||
|
required this.cellWidth,
|
||||||
|
required this.cellHeight,
|
||||||
|
required this.maxSpan,
|
||||||
|
required this.isDropTarget,
|
||||||
|
required this.onSpanChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_BentoCard> createState() => _BentoCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BentoCardState extends State<_BentoCard> {
|
||||||
|
double _accDx = 0;
|
||||||
|
double _accDy = 0;
|
||||||
|
int _startCol = 1;
|
||||||
|
int _startRow = 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final link = widget.link;
|
||||||
|
final hasImage = link.configuration?.imageSource != null;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: hasImage ? null : kPrimaryColor.withValues(alpha: 0.55),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
image: hasImage
|
||||||
|
? DecorationImage(image: NetworkImage(link.configuration!.imageSource!), fit: BoxFit.cover)
|
||||||
|
: null,
|
||||||
|
border: widget.isDropTarget ? Border.all(color: kPrimaryColor, width: 3) : null,
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [Colors.transparent, Colors.black.withValues(alpha: 0.7)],
|
||||||
|
stops: const [0.4, 1.0],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 6,
|
||||||
|
left: 8,
|
||||||
|
right: 8,
|
||||||
|
child: Text(
|
||||||
|
link.configuration?.label ?? '',
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Poignée de déplacement — glisser sur une autre card échange leur position.
|
||||||
|
// Coin opposé à la poignée de resize pour éviter tout conflit de geste.
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
child: Draggable<String>(
|
||||||
|
data: link.id,
|
||||||
|
feedback: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: 0.85,
|
||||||
|
child: SizedBox(
|
||||||
|
width: widget.cellWidth,
|
||||||
|
height: widget.cellHeight,
|
||||||
|
child: _dragHandle(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _dragHandle(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Poignée de coin — drag 2D : largeur = colonnes, hauteur = lignes.
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.resizeDownRight,
|
||||||
|
child: GestureDetector(
|
||||||
|
onPanStart: (_) {
|
||||||
|
_accDx = 0;
|
||||||
|
_accDy = 0;
|
||||||
|
_startCol = (link.gridColSpan ?? 1).clamp(1, widget.maxSpan);
|
||||||
|
_startRow = (link.gridRowSpan ?? 1).clamp(1, 2);
|
||||||
|
},
|
||||||
|
onPanUpdate: (details) {
|
||||||
|
_accDx += details.delta.dx;
|
||||||
|
_accDy += details.delta.dy;
|
||||||
|
final newCol = (_startCol + (_accDx / (widget.cellWidth + _kGap)).round())
|
||||||
|
.clamp(1, widget.maxSpan);
|
||||||
|
final newRow = (_startRow + (_accDy / (widget.cellHeight + _kGap)).round())
|
||||||
|
.clamp(1, 2);
|
||||||
|
if (newCol != (link.gridColSpan ?? 1) || newRow != (link.gridRowSpan ?? 1)) {
|
||||||
|
widget.onSpanChanged(newCol, newRow);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
margin: const EdgeInsets.all(3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kWhite,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(color: kPrimaryColor, width: 2),
|
||||||
|
),
|
||||||
|
child: Icon(Icons.open_in_full, size: 11, color: kPrimaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _dragHandle() {
|
||||||
|
return MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.grab,
|
||||||
|
child: Container(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
margin: const EdgeInsets.all(3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kWhite,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(color: kPrimaryColor, width: 2),
|
||||||
|
),
|
||||||
|
child: Icon(Icons.drag_indicator, size: 11, color: kPrimaryColor),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
lib/Screens/Applications/browser_frame.dart
Normal file
63
lib/Screens/Applications/browser_frame.dart
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Cadre navigateur — pendant de [PhoneMockup] pour l'aperçu de l'onglet Web.
|
||||||
|
/// [width]/[height] sont imposés par l'appelant (espace réellement disponible
|
||||||
|
/// dans le panneau d'aperçu), [width] permettant en plus de simuler une largeur
|
||||||
|
/// de viewport donnée (desktop vs mobile web) — visitapp-web est un site
|
||||||
|
/// responsive, pas une app figée à une taille.
|
||||||
|
class BrowserFrame extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
final double width;
|
||||||
|
final double height;
|
||||||
|
|
||||||
|
const BrowserFrame({super.key, required this.child, required this.width, required this.height});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: Colors.black, width: 6),
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 28,
|
||||||
|
color: const Color(0xFFE8EAED),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: Row(
|
||||||
|
children: const [
|
||||||
|
_Dot(color: Color(0xFFFF5F57)),
|
||||||
|
SizedBox(width: 6),
|
||||||
|
_Dot(color: Color(0xFFFEBC2E)),
|
||||||
|
SizedBox(width: 6),
|
||||||
|
_Dot(color: Color(0xFF28C840)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: Container(color: Colors.white, child: child)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Dot extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
const _Dot({required this.color});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,17 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// [width]/[height] sont imposés par l'appelant (espace réellement disponible
|
||||||
|
/// dans le panneau d'aperçu) plutôt que dérivés de tout l'écran, pour que le
|
||||||
|
/// cadre entier reste visible sans avoir à scroller pour le voir en entier.
|
||||||
class PhoneMockup extends StatelessWidget {
|
class PhoneMockup extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
final double width;
|
||||||
|
final double height;
|
||||||
|
|
||||||
const PhoneMockup({super.key, required this.child});
|
const PhoneMockup({super.key, required this.child, required this.width, required this.height});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Size size = MediaQuery.of(context).size;
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: size.width * 0.4,
|
width: width,
|
||||||
height: size.height * 0.6,
|
height: height,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
borderRadius: BorderRadius.circular(40),
|
borderRadius: BorderRadius.circular(40),
|
||||||
@ -32,8 +35,8 @@ class PhoneMockup extends StatelessWidget {
|
|||||||
// Notch
|
// Notch
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 8,
|
top: 8,
|
||||||
left:(size.width * 0.35) / 2,
|
left:(width * 0.35) / 2,
|
||||||
right: (size.width * 0.35) / 2,
|
right: (width * 0.35) / 2,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 20,
|
height: 20,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
@ -52,7 +52,6 @@ class _WebInstanceLoaderState extends State<WebInstanceLoader> {
|
|||||||
instanceId: instanceDTO.id,
|
instanceId: instanceDTO.id,
|
||||||
appType: AppType.Web,
|
appType: AppType.Web,
|
||||||
languages: ['fr'],
|
languages: ['fr'],
|
||||||
layoutMainPage: LayoutMainPageType.MasonryGrid,
|
|
||||||
isAssistant: false,
|
isAssistant: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -119,7 +118,7 @@ class WebAppScreen extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (instanceDTO?.webSlug != null || instanceDTO?.publicApiKey != null)
|
if (instanceDTO?.webSlug != null || instanceDTO?.publicApiKey != null)
|
||||||
Card(
|
Card(
|
||||||
margin: const EdgeInsets.fromLTRB(0, 0, 0, 8),
|
margin: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||||
color: kWhite,
|
color: kWhite,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@ -129,15 +128,24 @@ class WebAppScreen extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text('Accès web', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)),
|
Text('Accès web', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_InfoRow(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _InfoRow(
|
||||||
label: 'URL visiteurs',
|
label: 'URL visiteurs',
|
||||||
value: 'app.myinfomate.be/${instanceDTO?.webSlug ?? '—'}',
|
value: 'app.myinfomate.be/${instanceDTO?.webSlug ?? '—'}',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
),
|
||||||
_InfoRow(
|
const SizedBox(width: 24),
|
||||||
|
Expanded(
|
||||||
|
child: _InfoRow(
|
||||||
label: 'Clé publique API',
|
label: 'Clé publique API',
|
||||||
value: instanceDTO?.publicApiKey ?? '—',
|
value: instanceDTO?.publicApiKey ?? '—',
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:manager_api_new/api.dart';
|
import 'package:manager_api_new/api.dart';
|
||||||
import 'package:manager_app/constants.dart';
|
import 'package:manager_app/constants.dart';
|
||||||
import 'package:manager_app/Components/check_input_container.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/multi_string_input_container.dart';
|
||||||
import 'package:manager_app/Components/single_select_container.dart';
|
import 'package:manager_app/Components/single_select_container.dart';
|
||||||
|
import 'package:manager_app/Components/string_input_container.dart';
|
||||||
import 'package:manager_app/Components/message_notification.dart';
|
import 'package:manager_app/Components/message_notification.dart';
|
||||||
|
import 'package:manager_app/Components/confirmation_dialog.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:manager_app/app_context.dart';
|
import 'package:manager_app/app_context.dart';
|
||||||
import 'package:manager_app/l10n/app_localizations.dart';
|
import 'package:manager_app/l10n/app_localizations.dart';
|
||||||
@ -12,6 +15,8 @@ import 'package:manager_app/Models/managerContext.dart';
|
|||||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||||
import 'showNewOrUpdateEventAgenda.dart';
|
import 'showNewOrUpdateEventAgenda.dart';
|
||||||
|
|
||||||
|
enum _AgendaDateFilter { all, thisMonth, thisYear, past }
|
||||||
|
|
||||||
class AgendaConfig extends StatefulWidget {
|
class AgendaConfig extends StatefulWidget {
|
||||||
final String? color;
|
final String? color;
|
||||||
final String? label;
|
final String? label;
|
||||||
@ -33,6 +38,94 @@ class AgendaConfig extends StatefulWidget {
|
|||||||
class _AgendaConfigState extends State<AgendaConfig> {
|
class _AgendaConfigState extends State<AgendaConfig> {
|
||||||
late AgendaDTO agendaDTO;
|
late AgendaDTO agendaDTO;
|
||||||
List<EventAgendaDTO> events = [];
|
List<EventAgendaDTO> events = [];
|
||||||
|
String _searchFilter = '';
|
||||||
|
_AgendaDateFilter _dateFilter = _AgendaDateFilter.all;
|
||||||
|
|
||||||
|
List<EventAgendaDTO> get _filteredEvents {
|
||||||
|
final now = DateTime.now();
|
||||||
|
var filtered = events.where((event) {
|
||||||
|
switch (_dateFilter) {
|
||||||
|
case _AgendaDateFilter.thisMonth:
|
||||||
|
return event.dateFrom != null &&
|
||||||
|
event.dateFrom!.year == now.year &&
|
||||||
|
event.dateFrom!.month == now.month;
|
||||||
|
case _AgendaDateFilter.thisYear:
|
||||||
|
return event.dateFrom != null && event.dateFrom!.year == now.year;
|
||||||
|
case _AgendaDateFilter.past:
|
||||||
|
return event.dateFrom != null && event.dateFrom!.isBefore(now);
|
||||||
|
case _AgendaDateFilter.all:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
if (_searchFilter.isNotEmpty) {
|
||||||
|
filtered = filtered.where((event) {
|
||||||
|
final label = _labelFor(event);
|
||||||
|
return label.toLowerCase().contains(_searchFilter.toLowerCase());
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered.sort((a, b) {
|
||||||
|
if (a.dateFrom == null && b.dateFrom == null) return 0;
|
||||||
|
if (a.dateFrom == null) return 1;
|
||||||
|
if (b.dateFrom == null) return -1;
|
||||||
|
return a.dateFrom!.compareTo(b.dateFrom!);
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _labelFor(EventAgendaDTO event) {
|
||||||
|
if (event.label != null && event.label!.isNotEmpty) {
|
||||||
|
return (event.label!.firstWhere((t) => t.language == 'FR',
|
||||||
|
orElse: () => event.label![0]))
|
||||||
|
.value ??
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Groups events synced from the same external agenda that share the same
|
||||||
|
/// address and dates: the sync creates one EventAgendaDTO per language
|
||||||
|
/// instead of one multilingual event, so this recombines them for display.
|
||||||
|
String? _syncGroupKey(EventAgendaDTO event) {
|
||||||
|
if (event.isSynced != true) return null;
|
||||||
|
final dates =
|
||||||
|
"${event.dateFrom?.toIso8601String()}|${event.dateTo?.toIso8601String()}";
|
||||||
|
final address = event.address?.address?.trim();
|
||||||
|
if (address != null && address.isNotEmpty) {
|
||||||
|
return "addr:$address|$dates";
|
||||||
|
}
|
||||||
|
// No address to match on (common for these synced events): fall back to
|
||||||
|
// an identical title, which only happens when the source repeats the
|
||||||
|
// same untranslated name per language pass.
|
||||||
|
final label = _labelFor(event).trim();
|
||||||
|
if (label.isNotEmpty) {
|
||||||
|
return "label:$label|$dates";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<EventAgendaDTO>> get _displayGroups {
|
||||||
|
final groups = <List<EventAgendaDTO>>[];
|
||||||
|
final groupByKey = <String, List<EventAgendaDTO>>{};
|
||||||
|
for (final event in _filteredEvents) {
|
||||||
|
final key = _syncGroupKey(event);
|
||||||
|
if (key == null) {
|
||||||
|
groups.add([event]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final existing = groupByKey[key];
|
||||||
|
if (existing != null) {
|
||||||
|
existing.add(event);
|
||||||
|
} else {
|
||||||
|
final group = [event];
|
||||||
|
groupByKey[key] = group;
|
||||||
|
groups.add(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -142,6 +235,39 @@ class _AgendaConfigState extends State<AgendaConfig> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 280,
|
||||||
|
child: StringInputContainer(
|
||||||
|
label: AppLocalizations.of(context)!.searchLabel,
|
||||||
|
onChanged: (v) => setState(() => _searchFilter = v),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
_buildDateFilterChip(
|
||||||
|
_AgendaDateFilter.all,
|
||||||
|
AppLocalizations.of(context)!.agendaFilterAll),
|
||||||
|
_buildDateFilterChip(
|
||||||
|
_AgendaDateFilter.thisMonth,
|
||||||
|
AppLocalizations.of(context)!.agendaFilterThisMonth),
|
||||||
|
_buildDateFilterChip(
|
||||||
|
_AgendaDateFilter.thisYear,
|
||||||
|
AppLocalizations.of(context)!.agendaFilterThisYear),
|
||||||
|
_buildDateFilterChip(
|
||||||
|
_AgendaDateFilter.past,
|
||||||
|
AppLocalizations.of(context)!.agendaFilterPast),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 600,
|
height: 600,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
@ -149,10 +275,16 @@ class _AgendaConfigState extends State<AgendaConfig> {
|
|||||||
? Center(
|
? Center(
|
||||||
child: Text(AppLocalizations.of(context)!.noEvents,
|
child: Text(AppLocalizations.of(context)!.noEvents,
|
||||||
style: TextStyle(fontStyle: FontStyle.italic)))
|
style: TextStyle(fontStyle: FontStyle.italic)))
|
||||||
: ListView.builder(
|
: Builder(builder: (context) {
|
||||||
itemCount: events.length,
|
final groups = _displayGroups;
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: groups.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final event = events[index];
|
final group = groups[index];
|
||||||
|
final primary = group.firstWhere(
|
||||||
|
(e) => e.label?.any((t) => t.language == 'FR') ??
|
||||||
|
false,
|
||||||
|
orElse: () => group.first);
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
margin: const EdgeInsets.symmetric(
|
margin: const EdgeInsets.symmetric(
|
||||||
@ -164,99 +296,141 @@ class _AgendaConfigState extends State<AgendaConfig> {
|
|||||||
horizontal: 16, vertical: 8),
|
horizontal: 16, vertical: 8),
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
backgroundColor: kPrimaryColor.withOpacity(0.1),
|
backgroundColor: kPrimaryColor.withOpacity(0.1),
|
||||||
child:
|
child: const Icon(Icons.event,
|
||||||
const Icon(Icons.event, color: kPrimaryColor),
|
color: kPrimaryColor),
|
||||||
),
|
),
|
||||||
title: HtmlWidget(
|
title: HtmlWidget(
|
||||||
(event.label != null && event.label!.isNotEmpty)
|
(primary.label != null &&
|
||||||
? (event.label!.firstWhere(
|
primary.label!.isNotEmpty)
|
||||||
|
? (primary.label!.firstWhere(
|
||||||
(t) => t.language == 'FR',
|
(t) => t.language == 'FR',
|
||||||
orElse: () => event.label![0])).value ?? "${AppLocalizations.of(context)!.agendaEventFallback} $index"
|
orElse: () => primary.label![0]))
|
||||||
|
.value ??
|
||||||
|
"${AppLocalizations.of(context)!.agendaEventFallback} $index"
|
||||||
: "${AppLocalizations.of(context)!.agendaEventFallback} $index",
|
: "${AppLocalizations.of(context)!.agendaEventFallback} $index",
|
||||||
textStyle: const TextStyle(fontWeight: FontWeight.bold),
|
textStyle:
|
||||||
|
const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
subtitle: Padding(
|
subtitle: Padding(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 4),
|
||||||
child:
|
child: Text([
|
||||||
Text(event.address?.address ?? AppLocalizations.of(context)!.noAddress),
|
_formatEventDate(primary),
|
||||||
|
primary.address?.address ??
|
||||||
|
AppLocalizations.of(context)!.noAddress,
|
||||||
|
].where((s) => s.isNotEmpty).join(' • ')),
|
||||||
),
|
),
|
||||||
trailing: Row(
|
trailing: group.length > 1
|
||||||
|
? _buildLanguageChips(group)
|
||||||
|
: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.edit,
|
icon: const Icon(Icons.edit,
|
||||||
color: kPrimaryColor),
|
color: kPrimaryColor),
|
||||||
onPressed: () {
|
onPressed: () =>
|
||||||
|
_editEvent(context, primary),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete,
|
||||||
|
color: kError),
|
||||||
|
onPressed: () =>
|
||||||
|
_deleteEvent(context, primary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _editEvent(BuildContext context, EventAgendaDTO event) {
|
||||||
showNewOrUpdateEventAgenda(
|
showNewOrUpdateEventAgenda(
|
||||||
context,
|
context,
|
||||||
event,
|
event,
|
||||||
agendaDTO.id ?? "",
|
agendaDTO.id ?? "",
|
||||||
(updatedEvent) async {
|
(updatedEvent) async {
|
||||||
try {
|
try {
|
||||||
final result = await _api(context)
|
final result =
|
||||||
.sectionAgendaUpdateEventAgenda(
|
await _api(context).sectionAgendaUpdateEventAgenda(updatedEvent);
|
||||||
updatedEvent);
|
|
||||||
if (result != null && mounted) {
|
if (result != null && mounted) {
|
||||||
setState(
|
setState(() =>
|
||||||
() => events[index] = result);
|
events[events.indexWhere((e) => e.id == event.id)] = result);
|
||||||
showNotification(
|
showNotification(kSuccess, kWhite,
|
||||||
kSuccess,
|
AppLocalizations.of(context)!.agendaEventUpdatedSuccess, context, null);
|
||||||
kWhite,
|
|
||||||
AppLocalizations.of(context)!.agendaEventUpdatedSuccess,
|
|
||||||
context,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showNotification(
|
showNotification(kError, kWhite,
|
||||||
kError,
|
AppLocalizations.of(context)!.agendaEventUpdateError, context, null);
|
||||||
kWhite,
|
|
||||||
AppLocalizations.of(context)!.agendaEventUpdateError,
|
|
||||||
context,
|
|
||||||
null);
|
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
),
|
|
||||||
IconButton(
|
void _deleteEvent(BuildContext context, EventAgendaDTO event) {
|
||||||
icon: const Icon(Icons.delete, color: kError),
|
showConfirmationDialog(
|
||||||
onPressed: () async {
|
AppLocalizations.of(context)!.agendaEventDeleteConfirm,
|
||||||
|
() {},
|
||||||
|
() async {
|
||||||
try {
|
try {
|
||||||
if (event.id != null) {
|
if (event.id != null) {
|
||||||
await _api(context)
|
await _api(context).sectionAgendaDeleteEventAgenda(event.id!);
|
||||||
.sectionAgendaDeleteEventAgenda(
|
|
||||||
event.id!);
|
|
||||||
}
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => events.removeAt(index));
|
setState(() => events.removeWhere((e) => e.id == event.id));
|
||||||
showNotification(
|
showNotification(kSuccess, kWhite,
|
||||||
kSuccess,
|
AppLocalizations.of(context)!.agendaEventDeletedSuccess, context, null);
|
||||||
kWhite,
|
|
||||||
AppLocalizations.of(context)!.agendaEventDeletedSuccess,
|
|
||||||
context,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showNotification(
|
showNotification(kError, kWhite,
|
||||||
kError,
|
AppLocalizations.of(context)!.agendaEventDeleteError, context, null);
|
||||||
kWhite,
|
|
||||||
AppLocalizations.of(context)!.agendaEventDeleteError,
|
|
||||||
context,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
context,
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
),
|
|
||||||
),
|
Widget _buildLanguageChips(List<EventAgendaDTO> group) {
|
||||||
],
|
return Wrap(
|
||||||
],
|
spacing: 4,
|
||||||
|
children: group.map((event) {
|
||||||
|
final language = (event.label != null && event.label!.isNotEmpty)
|
||||||
|
? event.label![0].language ?? '?'
|
||||||
|
: '?';
|
||||||
|
return InputChip(
|
||||||
|
label: Text(language.toUpperCase(),
|
||||||
|
style: const TextStyle(fontSize: 12)),
|
||||||
|
onPressed: () => _editEvent(context, event),
|
||||||
|
onDeleted: () => _deleteEvent(context, event),
|
||||||
|
deleteIconColor: kError,
|
||||||
);
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDateFilterChip(_AgendaDateFilter filter, String label) {
|
||||||
|
return ChoiceChip(
|
||||||
|
label: Text(label),
|
||||||
|
selected: _dateFilter == filter,
|
||||||
|
selectedColor: kPrimaryColor.withOpacity(0.2),
|
||||||
|
onSelected: (_) => setState(() => _dateFilter = filter),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatEventDate(EventAgendaDTO event) {
|
||||||
|
if (event.dateFrom == null) return '';
|
||||||
|
final dateFormat = DateFormat('dd/MM/yyyy');
|
||||||
|
final from = dateFormat.format(event.dateFrom!.toLocal());
|
||||||
|
if (event.dateTo == null || event.dateTo!.isAtSameMomentAs(event.dateFrom!)) {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
return "$from - ${dateFormat.format(event.dateTo!.toLocal())}";
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAgendaHeader(Size size, String mapProviderIn) {
|
Widget _buildAgendaHeader(Size size, String mapProviderIn) {
|
||||||
|
|||||||
@ -78,6 +78,7 @@ void showNewOrUpdateEventAgenda(
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -92,6 +93,7 @@ void showNewOrUpdateEventAgenda(
|
|||||||
maxLines: 5,
|
maxLines: 5,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -72,9 +72,10 @@ class _ArticleConfigState extends State<ArticleConfig> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
height: size.height * 0.15,
|
constraints: BoxConstraints(minHeight: size.height * 0.15),
|
||||||
//width: size.width * 0.5,//,
|
//width: size.width * 0.5,//,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
@ -96,6 +97,7 @@ class _ArticleConfigState extends State<ArticleConfig> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
CheckInputContainer(
|
CheckInputContainer(
|
||||||
label: "Contenu au-dessus :",
|
label: "Contenu au-dessus :",
|
||||||
|
|||||||
@ -97,6 +97,7 @@ void showNewOrUpdateMapAnnotation(
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
isHTML: false,
|
isHTML: false,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
|
|||||||
@ -86,6 +86,7 @@ void showNewOrUpdateProgrammeBlock(
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -100,6 +101,7 @@ void showNewOrUpdateProgrammeBlock(
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -74,6 +74,7 @@ Future<CategorieDTO?> showNewOrUpdateCategory(CategorieDTO? inputCategorieDTO, A
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
|||||||
@ -111,6 +111,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
|
|||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -129,6 +130,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
|
|||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -147,6 +149,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
|
|||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -170,6 +173,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
|
|||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -188,6 +192,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
|
|||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -206,6 +211,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
|
|||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -230,6 +236,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
|
|||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
|
|||||||
@ -210,6 +210,7 @@ class _SubSectionEditScreenState extends State<SubSectionEditScreen> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
MultiStringInputContainer(
|
MultiStringInputContainer(
|
||||||
@ -226,6 +227,7 @@ class _SubSectionEditScreenState extends State<SubSectionEditScreen> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -8,9 +8,10 @@ import 'package:manager_app/l10n/app_localizations.dart';
|
|||||||
import 'package:manager_app/Components/rounded_button.dart';
|
import 'package:manager_app/Components/rounded_button.dart';
|
||||||
import 'package:manager_app/Components/multi_string_input_container.dart';
|
import 'package:manager_app/Components/multi_string_input_container.dart';
|
||||||
import 'package:manager_app/Components/check_input_container.dart';
|
import 'package:manager_app/Components/check_input_container.dart';
|
||||||
import 'package:manager_app/Components/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/resource_input_container.dart';
|
||||||
import 'package:manager_app/Components/reorderable_custom_list.dart';
|
import 'package:manager_app/Components/reorderable_custom_list.dart';
|
||||||
|
import 'package:manager_app/Components/section_card.dart';
|
||||||
import 'showNewOrUpdateGuidedStep.dart';
|
import 'showNewOrUpdateGuidedStep.dart';
|
||||||
|
|
||||||
void showNewOrUpdateGuidedPath(
|
void showNewOrUpdateGuidedPath(
|
||||||
@ -44,7 +45,6 @@ void showNewOrUpdateGuidedPath(
|
|||||||
// contentWidth = dialogWidth minus the 24px padding on each side
|
// contentWidth = dialogWidth minus the 24px padding on each side
|
||||||
final double contentWidth = dialogWidth - 48;
|
final double contentWidth = dialogWidth - 48;
|
||||||
final double halfWidth = (contentWidth - 20) / 2;
|
final double halfWidth = (contentWidth - 20) / 2;
|
||||||
final double thirdWidth = (contentWidth - 40) / 3;
|
|
||||||
|
|
||||||
return Dialog(
|
return Dialog(
|
||||||
shape:
|
shape:
|
||||||
@ -95,6 +95,7 @@ void showNewOrUpdateGuidedPath(
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -109,63 +110,69 @@ void showNewOrUpdateGuidedPath(
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Divider(height: 24),
|
SizedBox(height: 16),
|
||||||
// Options
|
// Options
|
||||||
Row(
|
SectionCard(
|
||||||
|
icon: Icons.settings,
|
||||||
|
title: "Options du parcours",
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
CheckInputContainer(
|
||||||
width: thirdWidth,
|
|
||||||
child: CheckInputContainer(
|
|
||||||
label: AppLocalizations.of(context)!.linearLabel,
|
label: AppLocalizations.of(context)!.linearLabel,
|
||||||
|
subtitle: "Les étapes doivent être suivies dans l'ordre.",
|
||||||
isChecked: workingPath.isLinear ?? false,
|
isChecked: workingPath.isLinear ?? false,
|
||||||
onChanged: (val) => setState(
|
onChanged: (val) => setState(
|
||||||
() => workingPath.isLinear = val),
|
() => workingPath.isLinear = val),
|
||||||
),
|
),
|
||||||
),
|
CheckInputContainer(
|
||||||
SizedBox(width: 20),
|
|
||||||
SizedBox(
|
|
||||||
width: thirdWidth,
|
|
||||||
child: CheckInputContainer(
|
|
||||||
label: AppLocalizations.of(context)!.requiredSuccessLabel,
|
label: AppLocalizations.of(context)!.requiredSuccessLabel,
|
||||||
|
subtitle: "Le quiz de chaque étape doit être validé pour débloquer la suivante.",
|
||||||
isChecked:
|
isChecked:
|
||||||
workingPath.requireSuccessToAdvance ??
|
workingPath.requireSuccessToAdvance ??
|
||||||
false,
|
false,
|
||||||
onChanged: (val) => setState(() => workingPath
|
onChanged: (val) => setState(() => workingPath
|
||||||
.requireSuccessToAdvance = val),
|
.requireSuccessToAdvance = val),
|
||||||
),
|
),
|
||||||
),
|
CheckInputContainer(
|
||||||
SizedBox(width: 20),
|
|
||||||
SizedBox(
|
|
||||||
width: thirdWidth,
|
|
||||||
child: CheckInputContainer(
|
|
||||||
label: "Cacher les suivantes :",
|
label: "Cacher les suivantes :",
|
||||||
|
subtitle: "Seule l'étape en cours est visible sur la carte.",
|
||||||
isChecked:
|
isChecked:
|
||||||
workingPath.hideNextStepsUntilComplete ??
|
workingPath.hideNextStepsUntilComplete ??
|
||||||
false,
|
false,
|
||||||
onChanged: (val) => setState(() => workingPath
|
onChanged: (val) => setState(() => workingPath
|
||||||
.hideNextStepsUntilComplete = val),
|
.hideNextStepsUntilComplete = val),
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
NumberStepperField(
|
||||||
|
label: "Durée estimée",
|
||||||
|
value: workingPath.estimatedDurationMinutes,
|
||||||
|
min: 0,
|
||||||
|
max: 600,
|
||||||
|
unit: "min",
|
||||||
|
onChanged: (val) => setState(() =>
|
||||||
|
workingPath.estimatedDurationMinutes = val.toInt()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 8),
|
|
||||||
SizedBox(
|
|
||||||
width: thirdWidth,
|
|
||||||
child: StringInputContainer(
|
|
||||||
label: "Durée estimée (min) :",
|
|
||||||
initialValue: workingPath.estimatedDurationMinutes?.toString() ?? "",
|
|
||||||
onChanged: (val) => setState(() =>
|
|
||||||
workingPath.estimatedDurationMinutes = int.tryParse(val)),
|
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 16),
|
||||||
Divider(height: 24),
|
|
||||||
// Mode jeu
|
// Mode jeu
|
||||||
|
SectionCard(
|
||||||
|
icon: Icons.sports_esports,
|
||||||
|
title: "Mode jeu",
|
||||||
|
subtitle: "Escape game ou chasse au trésor",
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
CheckInputContainer(
|
CheckInputContainer(
|
||||||
label: "Mode jeu (escape game / chasse au trésor)",
|
label: "Mode jeu (escape game / chasse au trésor)",
|
||||||
|
subtitle: "Active les messages de jeu ci-dessous et le vocabulaire escape game sur les étapes.",
|
||||||
isChecked: workingPath.isGameMode ?? false,
|
isChecked: workingPath.isGameMode ?? false,
|
||||||
onChanged: (val) =>
|
onChanged: (val) =>
|
||||||
setState(() => workingPath.isGameMode = val),
|
setState(() => workingPath.isGameMode = val),
|
||||||
@ -201,6 +208,7 @@ void showNewOrUpdateGuidedPath(
|
|||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -231,21 +239,26 @@ void showNewOrUpdateGuidedPath(
|
|||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
Divider(height: 24),
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
// Étapes
|
// Étapes
|
||||||
Row(
|
SectionCard(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
icon: Icons.list_alt,
|
||||||
|
title: AppLocalizations.of(context)!.pathStepsLabel,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(AppLocalizations.of(context)!.pathStepsLabel,
|
Align(
|
||||||
style: TextStyle(
|
alignment: Alignment.centerRight,
|
||||||
fontWeight: FontWeight.bold,
|
child: IconButton(
|
||||||
fontSize: 15)),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.add_circle_outline,
|
icon: Icon(Icons.add_circle_outline,
|
||||||
color: kSuccess),
|
color: kSuccess),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -268,7 +281,6 @@ void showNewOrUpdateGuidedPath(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
if (workingPath.steps?.isEmpty ?? true)
|
if (workingPath.steps?.isEmpty ?? true)
|
||||||
Padding(
|
Padding(
|
||||||
@ -349,6 +361,9 @@ void showNewOrUpdateGuidedPath(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
// -- Boutons --
|
// -- Boutons --
|
||||||
|
|||||||
@ -5,10 +5,11 @@ import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
|||||||
import 'package:manager_api_new/api.dart';
|
import 'package:manager_api_new/api.dart';
|
||||||
import 'package:manager_app/Components/confirmation_dialog.dart';
|
import 'package:manager_app/Components/confirmation_dialog.dart';
|
||||||
import 'package:manager_app/Components/geometry_input_container.dart';
|
import 'package:manager_app/Components/geometry_input_container.dart';
|
||||||
|
import 'package:manager_app/Components/number_stepper_field.dart';
|
||||||
import 'package:manager_app/Components/reorderable_custom_list.dart';
|
import 'package:manager_app/Components/reorderable_custom_list.dart';
|
||||||
import 'package:manager_app/Components/multi_string_input_container.dart';
|
import 'package:manager_app/Components/multi_string_input_container.dart';
|
||||||
import 'package:manager_app/Components/rounded_button.dart';
|
import 'package:manager_app/Components/rounded_button.dart';
|
||||||
import 'package:manager_app/Components/string_input_container.dart';
|
import 'package:manager_app/Components/section_card.dart';
|
||||||
import 'package:manager_app/Screens/Configurations/Section/SubSection/Slider/listView_card_image.dart';
|
import 'package:manager_app/Screens/Configurations/Section/SubSection/Slider/listView_card_image.dart';
|
||||||
import 'package:manager_app/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart';
|
import 'package:manager_app/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart';
|
||||||
import 'package:manager_app/app_context.dart';
|
import 'package:manager_app/app_context.dart';
|
||||||
@ -93,6 +94,7 @@ void showNewOrUpdateGuidedStep(
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -107,12 +109,20 @@ void showNewOrUpdateGuidedStep(
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Divider(height: 24),
|
SizedBox(height: 16),
|
||||||
// Géométrie — Directement avec GeometryDTO
|
// Emplacement — Directement avec GeometryDTO
|
||||||
|
SectionCard(
|
||||||
|
icon: Icons.location_on,
|
||||||
|
title: "Emplacement",
|
||||||
|
subtitle: "Position et déclenchement géolocalisé de l'étape",
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
GeometryInputContainer(
|
GeometryInputContainer(
|
||||||
label: AppLocalizations.of(context)!.stepLocationLabel,
|
label: AppLocalizations.of(context)!.stepLocationLabel,
|
||||||
initialGeometry: workingStep.geometry,
|
initialGeometry: workingStep.geometry,
|
||||||
@ -124,36 +134,68 @@ void showNewOrUpdateGuidedStep(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
SwitchListTile(
|
Row(
|
||||||
dense: true,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
contentPadding: EdgeInsets.zero,
|
children: [
|
||||||
title: Text("Déclenchement géolocalisé"),
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Switch(
|
||||||
value: workingStep.isGeoTriggered ?? false,
|
value: workingStep.isGeoTriggered ?? false,
|
||||||
onChanged: (val) => setState(() => workingStep.isGeoTriggered = val),
|
onChanged: (val) => setState(() => workingStep.isGeoTriggered = val),
|
||||||
activeThumbColor: kPrimaryColor,
|
activeThumbColor: kPrimaryColor,
|
||||||
),
|
),
|
||||||
if (workingStep.isGeoTriggered == true) ...[
|
SizedBox(width: 8),
|
||||||
SizedBox(height: 8),
|
Expanded(
|
||||||
SizedBox(
|
child: Column(
|
||||||
width: halfWidth,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: StringInputContainer(
|
mainAxisSize: MainAxisSize.min,
|
||||||
label: "Rayon (mètres) :",
|
children: [
|
||||||
initialValue: workingStep.zoneRadiusMeters?.toString() ?? "",
|
Text("Déclenchement géolocalisé", style: TextStyle(fontSize: 14)),
|
||||||
onChanged: (val) => setState(() =>
|
Text(
|
||||||
workingStep.zoneRadiusMeters = double.tryParse(val)),
|
"L'étape se débloque automatiquement quand le visiteur entre dans la zone.",
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
Divider(height: 24),
|
),
|
||||||
|
),
|
||||||
|
if (workingStep.isGeoTriggered == true)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 12),
|
||||||
|
child: NumberStepperField(
|
||||||
|
label: "Rayon",
|
||||||
|
value: workingStep.zoneRadiusMeters,
|
||||||
|
min: 1,
|
||||||
|
max: 500,
|
||||||
|
unit: "m",
|
||||||
|
onChanged: (val) => setState(() =>
|
||||||
|
workingStep.zoneRadiusMeters = val.toDouble()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
// Comportement de l'étape
|
// Comportement de l'étape
|
||||||
Text("Comportement", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15)),
|
SectionCard(
|
||||||
SizedBox(height: 8),
|
icon: Icons.tune,
|
||||||
Row(
|
title: "Comportement",
|
||||||
|
subtitle: "Visibilité et verrouillage de l'étape",
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SwitchListTile(
|
child: SwitchListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
title: Text(AppLocalizations.of(context)!.initiallyHiddenLabel),
|
title: Text(AppLocalizations.of(context)!.initiallyHiddenLabel),
|
||||||
|
subtitle: Text(
|
||||||
|
"N'apparaît pas sur la carte tant qu'elle n'a pas été débloquée.",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
value: workingStep.isHiddenInitially ?? false,
|
value: workingStep.isHiddenInitially ?? false,
|
||||||
onChanged: (val) => setState(() => workingStep.isHiddenInitially = val),
|
onChanged: (val) => setState(() => workingStep.isHiddenInitially = val),
|
||||||
activeThumbColor: kPrimaryColor,
|
activeThumbColor: kPrimaryColor,
|
||||||
@ -163,6 +205,10 @@ void showNewOrUpdateGuidedStep(
|
|||||||
child: SwitchListTile(
|
child: SwitchListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
title: Text(AppLocalizations.of(context)!.lockedLabel),
|
title: Text(AppLocalizations.of(context)!.lockedLabel),
|
||||||
|
subtitle: Text(
|
||||||
|
"Visible mais non accessible tant que les étapes précédentes ne sont pas terminées.",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
value: workingStep.isStepLocked ?? false,
|
value: workingStep.isStepLocked ?? false,
|
||||||
onChanged: (val) => setState(() => workingStep.isStepLocked = val),
|
onChanged: (val) => setState(() => workingStep.isStepLocked = val),
|
||||||
activeThumbColor: kPrimaryColor,
|
activeThumbColor: kPrimaryColor,
|
||||||
@ -170,9 +216,15 @@ void showNewOrUpdateGuidedStep(
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Divider(height: 24),
|
),
|
||||||
Text("Contenu riche", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15)),
|
SizedBox(height: 16),
|
||||||
SizedBox(height: 8),
|
SectionCard(
|
||||||
|
icon: Icons.perm_media,
|
||||||
|
title: "Contenu riche",
|
||||||
|
subtitle: "Audio guide et images affichés sur la fiche de l'étape",
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
// Audio
|
// Audio
|
||||||
MultiStringInputContainer(
|
MultiStringInputContainer(
|
||||||
label: "Audio :",
|
label: "Audio :",
|
||||||
@ -237,17 +289,23 @@ void showNewOrUpdateGuidedStep(
|
|||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Questions — uniquement en mode Escape Game
|
],
|
||||||
if (isEscapeMode) ...[
|
),
|
||||||
Divider(height: 24),
|
),
|
||||||
Row(
|
SizedBox(height: 16),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// Questions — disponibles pour tous les parcours (guidés et escape game)
|
||||||
|
SectionCard(
|
||||||
|
icon: Icons.quiz,
|
||||||
|
title: AppLocalizations.of(context)!.questionsChallengesLabel,
|
||||||
|
subtitle: isEscapeMode
|
||||||
|
? "Énigme à résoudre pour progresser"
|
||||||
|
: "Quiz optionnel pour cette étape",
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(AppLocalizations.of(context)!.questionsChallengesLabel,
|
Align(
|
||||||
style: TextStyle(
|
alignment: Alignment.centerRight,
|
||||||
fontWeight: FontWeight.bold,
|
child: IconButton(
|
||||||
fontSize: 15)),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.add_circle_outline,
|
icon: Icon(Icons.add_circle_outline,
|
||||||
color: kSuccess),
|
color: kSuccess),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -271,35 +329,54 @@ void showNewOrUpdateGuidedStep(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
SizedBox(
|
Row(
|
||||||
width: halfWidth,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
child: SwitchListTile(
|
children: [
|
||||||
dense: true,
|
Expanded(
|
||||||
contentPadding: EdgeInsets.zero,
|
child: Row(
|
||||||
title: Text("Timer"),
|
children: [
|
||||||
|
Switch(
|
||||||
value: workingStep.isStepTimer ?? false,
|
value: workingStep.isStepTimer ?? false,
|
||||||
onChanged: (val) => setState(() => workingStep.isStepTimer = val),
|
onChanged: (val) => setState(() => workingStep.isStepTimer = val),
|
||||||
activeThumbColor: kPrimaryColor,
|
activeThumbColor: kPrimaryColor,
|
||||||
),
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text("Timer", style: TextStyle(fontSize: 14)),
|
||||||
|
Text(
|
||||||
|
"Chronomètre la réponse ; passé le délai, le message d'expiration s'affiche.",
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (workingStep.isStepTimer == true)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 12),
|
||||||
|
child: NumberStepperField(
|
||||||
|
label: AppLocalizations.of(context)!.durationSecondsLabel,
|
||||||
|
value: workingStep.timerSeconds,
|
||||||
|
min: 0,
|
||||||
|
max: 3600,
|
||||||
|
unit: "sec",
|
||||||
|
onChanged: (val) => setState(() => workingStep.timerSeconds = val.toInt()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
if (workingStep.isStepTimer == true) ...[
|
if (workingStep.isStepTimer == true) ...[
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
Expanded(
|
||||||
width: halfWidth,
|
|
||||||
child: StringInputContainer(
|
|
||||||
label: AppLocalizations.of(context)!.durationSecondsLabel,
|
|
||||||
initialValue: workingStep.timerSeconds?.toString() ?? "",
|
|
||||||
onChanged: (val) => setState(() =>
|
|
||||||
workingStep.timerSeconds = int.tryParse(val)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 20),
|
|
||||||
SizedBox(
|
|
||||||
width: halfWidth,
|
|
||||||
child: MultiStringInputContainer(
|
child: MultiStringInputContainer(
|
||||||
label: "Message d'expiration :",
|
label: "Message d'expiration :",
|
||||||
modalLabel: "Message d'expiration du timer",
|
modalLabel: "Message d'expiration du timer",
|
||||||
@ -309,6 +386,7 @@ void showNewOrUpdateGuidedStep(
|
|||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -401,6 +479,8 @@ void showNewOrUpdateGuidedStep(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -436,11 +516,6 @@ void showNewOrUpdateGuidedStep(
|
|||||||
if (workingStep.isGeoTriggered != true) {
|
if (workingStep.isGeoTriggered != true) {
|
||||||
workingStep.zoneRadiusMeters = null;
|
workingStep.zoneRadiusMeters = null;
|
||||||
}
|
}
|
||||||
if (!isEscapeMode) {
|
|
||||||
workingStep.isStepTimer = false;
|
|
||||||
workingStep.timerSeconds = null;
|
|
||||||
workingStep.timerExpiredMessage = null;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await onSave(workingStep);
|
await onSave(workingStep);
|
||||||
if (context.mounted) Navigator.pop(context);
|
if (context.mounted) Navigator.pop(context);
|
||||||
|
|||||||
@ -115,6 +115,7 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
|
|
||||||
@ -167,6 +168,7 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
SizedBox(height: 4),
|
SizedBox(height: 4),
|
||||||
@ -256,6 +258,7 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Supprimer
|
// Supprimer
|
||||||
|
|||||||
@ -97,7 +97,8 @@ Future<ContentDTO?> showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true
|
isTitle: true,
|
||||||
|
showPreview: true
|
||||||
),
|
),
|
||||||
MultiStringInputContainer(
|
MultiStringInputContainer(
|
||||||
label: AppLocalizations.of(context)!.displayedDescriptionLabel,
|
label: AppLocalizations.of(context)!.displayedDescriptionLabel,
|
||||||
@ -113,7 +114,8 @@ Future<ContentDTO?> showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: false
|
isTitle: false,
|
||||||
|
showPreview: true
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@ -34,19 +34,43 @@ class _VideoConfigState extends State<VideoConfig> {
|
|||||||
late VideoDTO resourceSource;
|
late VideoDTO resourceSource;
|
||||||
late _VideoSourceMode _mode;
|
late _VideoSourceMode _mode;
|
||||||
String? _selectedResourceLabel;
|
String? _selectedResourceLabel;
|
||||||
|
String? _youtubeUrl;
|
||||||
|
String? _vimeoUrl;
|
||||||
|
String? _resourceUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
resourceSource = widget.initialValue;
|
resourceSource = widget.initialValue;
|
||||||
_mode = _detectMode(resourceSource.source_);
|
_mode = _detectMode(resourceSource.source_);
|
||||||
|
switch (_mode) {
|
||||||
|
case _VideoSourceMode.youtube:
|
||||||
|
_youtubeUrl = resourceSource.source_;
|
||||||
|
break;
|
||||||
|
case _VideoSourceMode.vimeo:
|
||||||
|
_vimeoUrl = resourceSource.source_;
|
||||||
|
break;
|
||||||
|
case _VideoSourceMode.resource:
|
||||||
|
_resourceUrl = resourceSource.source_;
|
||||||
|
break;
|
||||||
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onModeChanged(_VideoSourceMode mode) {
|
void _onModeChanged(_VideoSourceMode mode) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_mode = mode;
|
_mode = mode;
|
||||||
resourceSource.source_ = null;
|
|
||||||
_selectedResourceLabel = null;
|
_selectedResourceLabel = null;
|
||||||
|
switch (mode) {
|
||||||
|
case _VideoSourceMode.youtube:
|
||||||
|
resourceSource.source_ = _youtubeUrl;
|
||||||
|
break;
|
||||||
|
case _VideoSourceMode.vimeo:
|
||||||
|
resourceSource.source_ = _vimeoUrl;
|
||||||
|
break;
|
||||||
|
case _VideoSourceMode.resource:
|
||||||
|
resourceSource.source_ = _resourceUrl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
widget.onChanged(resourceSource);
|
widget.onChanged(resourceSource);
|
||||||
}
|
}
|
||||||
@ -68,9 +92,11 @@ class _VideoConfigState extends State<VideoConfig> {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
if (_mode == _VideoSourceMode.youtube)
|
if (_mode == _VideoSourceMode.youtube)
|
||||||
StringInputContainer(
|
StringInputContainer(
|
||||||
|
key: const ValueKey('youtube'),
|
||||||
label: 'URL YouTube :',
|
label: 'URL YouTube :',
|
||||||
initialValue: _mode == _VideoSourceMode.youtube ? resourceSource.source_ : null,
|
initialValue: _youtubeUrl,
|
||||||
onChanged: (url) {
|
onChanged: (url) {
|
||||||
|
_youtubeUrl = url;
|
||||||
resourceSource.source_ = url;
|
resourceSource.source_ = url;
|
||||||
widget.onChanged(resourceSource);
|
widget.onChanged(resourceSource);
|
||||||
},
|
},
|
||||||
@ -79,9 +105,11 @@ class _VideoConfigState extends State<VideoConfig> {
|
|||||||
),
|
),
|
||||||
if (_mode == _VideoSourceMode.vimeo)
|
if (_mode == _VideoSourceMode.vimeo)
|
||||||
StringInputContainer(
|
StringInputContainer(
|
||||||
|
key: const ValueKey('vimeo'),
|
||||||
label: 'URL Vimeo :',
|
label: 'URL Vimeo :',
|
||||||
initialValue: _mode == _VideoSourceMode.vimeo ? resourceSource.source_ : null,
|
initialValue: _vimeoUrl,
|
||||||
onChanged: (url) {
|
onChanged: (url) {
|
||||||
|
_vimeoUrl = url;
|
||||||
resourceSource.source_ = url;
|
resourceSource.source_ = url;
|
||||||
widget.onChanged(resourceSource);
|
widget.onChanged(resourceSource);
|
||||||
},
|
},
|
||||||
@ -91,9 +119,10 @@ class _VideoConfigState extends State<VideoConfig> {
|
|||||||
if (_mode == _VideoSourceMode.resource)
|
if (_mode == _VideoSourceMode.resource)
|
||||||
_ResourceVideoPicker(
|
_ResourceVideoPicker(
|
||||||
selectedLabel: _selectedResourceLabel,
|
selectedLabel: _selectedResourceLabel,
|
||||||
selectedUrl: _mode == _VideoSourceMode.resource ? resourceSource.source_ : null,
|
selectedUrl: _resourceUrl,
|
||||||
onChanged: (resource) {
|
onChanged: (resource) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_resourceUrl = resource.url;
|
||||||
resourceSource.source_ = resource.url;
|
resourceSource.source_ = resource.url;
|
||||||
_selectedResourceLabel = resource.label;
|
_selectedResourceLabel = resource.label;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -273,6 +273,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isHTML: true,
|
isHTML: true,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -200,7 +200,8 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
|
|||||||
isHTML: true,
|
isHTML: true,
|
||||||
isMandatory: false,
|
isMandatory: false,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
onGetResult: (value) => config.title = value,
|
onGetResult: (value) => setState(() => config.title = value),
|
||||||
|
showPreview: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Wrap(
|
Wrap(
|
||||||
|
|||||||
@ -117,7 +117,6 @@ class _DeviceElementState extends State<DeviceElement> {
|
|||||||
DeviceDTO? device = await managerAppContext.clientAPI!.deviceApi!.deviceUpdateMainInfos(deviceToUpdate);
|
DeviceDTO? device = await managerAppContext.clientAPI!.deviceApi!.deviceUpdateMainInfos(deviceToUpdate);
|
||||||
|
|
||||||
appConfigurationToUpdate.configurationId = deviceToUpdate.configurationId;
|
appConfigurationToUpdate.configurationId = deviceToUpdate.configurationId;
|
||||||
appConfigurationToUpdate.layoutMainPage = LayoutMainPageType.SimpleGrid; // Hardcoded for now as not supported
|
|
||||||
AppConfigurationLinkDTO? result = await managerAppContext.clientAPI!.applicationInstanceApi!.applicationInstanceUpdateApplicationLink(appConfigurationToUpdate);
|
AppConfigurationLinkDTO? result = await managerAppContext.clientAPI!.applicationInstanceApi!.applicationInstanceUpdateApplicationLink(appConfigurationToUpdate);
|
||||||
|
|
||||||
//print(device);
|
//print(device);
|
||||||
|
|||||||
@ -289,6 +289,11 @@
|
|||||||
"addEvent": "Add an event",
|
"addEvent": "Add an event",
|
||||||
"noEvents": "No events",
|
"noEvents": "No events",
|
||||||
"noAddress": "No address",
|
"noAddress": "No address",
|
||||||
|
"agendaFilterAll": "All",
|
||||||
|
"agendaFilterThisMonth": "This month",
|
||||||
|
"agendaFilterThisYear": "This year",
|
||||||
|
"agendaFilterPast": "Past",
|
||||||
|
"agendaEventDeleteConfirm": "Are you sure you want to delete this event?",
|
||||||
"onlineLabel": "Online:",
|
"onlineLabel": "Online:",
|
||||||
"mapViewLabel": "Map view:",
|
"mapViewLabel": "Map view:",
|
||||||
"mapServiceLabel": "Map service:",
|
"mapServiceLabel": "Map service:",
|
||||||
@ -362,6 +367,10 @@
|
|||||||
"languagesLabel": "Languages:",
|
"languagesLabel": "Languages:",
|
||||||
"featuredEventLabel": "Featured event:",
|
"featuredEventLabel": "Featured event:",
|
||||||
"chooseEvent": "Choose an event",
|
"chooseEvent": "Choose an event",
|
||||||
|
"previewWebTitle": "Preview — Web",
|
||||||
|
"previewMobileTitle": "Preview — Mobile",
|
||||||
|
"previewDesktop": "Desktop",
|
||||||
|
"previewMobile": "Mobile",
|
||||||
"noneOption": "None",
|
"noneOption": "None",
|
||||||
"aiAssistantLabel": "AI assistant:",
|
"aiAssistantLabel": "AI assistant:",
|
||||||
"appUpdatedSuccess": "Mobile application updated",
|
"appUpdatedSuccess": "Mobile application updated",
|
||||||
|
|||||||
@ -289,6 +289,11 @@
|
|||||||
"addEvent": "Ajouter un évènement",
|
"addEvent": "Ajouter un évènement",
|
||||||
"noEvents": "Aucun évènement",
|
"noEvents": "Aucun évènement",
|
||||||
"noAddress": "Pas d'adresse",
|
"noAddress": "Pas d'adresse",
|
||||||
|
"agendaFilterAll": "Tous",
|
||||||
|
"agendaFilterThisMonth": "Ce mois-ci",
|
||||||
|
"agendaFilterThisYear": "Cette année",
|
||||||
|
"agendaFilterPast": "Passés",
|
||||||
|
"agendaEventDeleteConfirm": "Êtes-vous sûr de vouloir supprimer cet évènement ?",
|
||||||
"onlineLabel": "En ligne :",
|
"onlineLabel": "En ligne :",
|
||||||
"mapViewLabel": "Vue carte :",
|
"mapViewLabel": "Vue carte :",
|
||||||
"mapServiceLabel": "Service carte :",
|
"mapServiceLabel": "Service carte :",
|
||||||
@ -362,6 +367,10 @@
|
|||||||
"languagesLabel": "Langues :",
|
"languagesLabel": "Langues :",
|
||||||
"featuredEventLabel": "Evènement à l'affiche :",
|
"featuredEventLabel": "Evènement à l'affiche :",
|
||||||
"chooseEvent": "Choisir un évènement",
|
"chooseEvent": "Choisir un évènement",
|
||||||
|
"previewWebTitle": "Aperçu — Web",
|
||||||
|
"previewMobileTitle": "Aperçu — Mobile",
|
||||||
|
"previewDesktop": "Desktop",
|
||||||
|
"previewMobile": "Mobile",
|
||||||
"noneOption": "Aucun",
|
"noneOption": "Aucun",
|
||||||
"aiAssistantLabel": "Assistant IA :",
|
"aiAssistantLabel": "Assistant IA :",
|
||||||
"appUpdatedSuccess": "Application mobile mise à jour",
|
"appUpdatedSuccess": "Application mobile mise à jour",
|
||||||
|
|||||||
@ -1462,6 +1462,36 @@ abstract class AppLocalizations {
|
|||||||
/// **'Pas d\'adresse'**
|
/// **'Pas d\'adresse'**
|
||||||
String get noAddress;
|
String get noAddress;
|
||||||
|
|
||||||
|
/// No description provided for @agendaFilterAll.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Tous'**
|
||||||
|
String get agendaFilterAll;
|
||||||
|
|
||||||
|
/// No description provided for @agendaFilterThisMonth.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Ce mois-ci'**
|
||||||
|
String get agendaFilterThisMonth;
|
||||||
|
|
||||||
|
/// No description provided for @agendaFilterThisYear.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Cette année'**
|
||||||
|
String get agendaFilterThisYear;
|
||||||
|
|
||||||
|
/// No description provided for @agendaFilterPast.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Passés'**
|
||||||
|
String get agendaFilterPast;
|
||||||
|
|
||||||
|
/// No description provided for @agendaEventDeleteConfirm.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Êtes-vous sûr de vouloir supprimer cet évènement ?'**
|
||||||
|
String get agendaEventDeleteConfirm;
|
||||||
|
|
||||||
/// No description provided for @onlineLabel.
|
/// No description provided for @onlineLabel.
|
||||||
///
|
///
|
||||||
/// In fr, this message translates to:
|
/// In fr, this message translates to:
|
||||||
@ -1864,6 +1894,30 @@ abstract class AppLocalizations {
|
|||||||
/// **'Choisir un évènement'**
|
/// **'Choisir un évènement'**
|
||||||
String get chooseEvent;
|
String get chooseEvent;
|
||||||
|
|
||||||
|
/// No description provided for @previewWebTitle.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Aperçu — Web'**
|
||||||
|
String get previewWebTitle;
|
||||||
|
|
||||||
|
/// No description provided for @previewMobileTitle.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Aperçu — Mobile'**
|
||||||
|
String get previewMobileTitle;
|
||||||
|
|
||||||
|
/// No description provided for @previewDesktop.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Desktop'**
|
||||||
|
String get previewDesktop;
|
||||||
|
|
||||||
|
/// No description provided for @previewMobile.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Mobile'**
|
||||||
|
String get previewMobile;
|
||||||
|
|
||||||
/// No description provided for @noneOption.
|
/// No description provided for @noneOption.
|
||||||
///
|
///
|
||||||
/// In fr, this message translates to:
|
/// In fr, this message translates to:
|
||||||
|
|||||||
@ -737,6 +737,22 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get noAddress => 'No address';
|
String get noAddress => 'No address';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterThisMonth => 'This month';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterThisYear => 'This year';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterPast => 'Past';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaEventDeleteConfirm =>
|
||||||
|
'Are you sure you want to delete this event?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get onlineLabel => 'Online:';
|
String get onlineLabel => 'Online:';
|
||||||
|
|
||||||
@ -943,6 +959,18 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get chooseEvent => 'Choose an event';
|
String get chooseEvent => 'Choose an event';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewWebTitle => 'Preview — Web';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewMobileTitle => 'Preview — Mobile';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewDesktop => 'Desktop';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewMobile => 'Mobile';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noneOption => 'None';
|
String get noneOption => 'None';
|
||||||
|
|
||||||
|
|||||||
@ -760,6 +760,22 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get noAddress => 'Pas d\'adresse';
|
String get noAddress => 'Pas d\'adresse';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterAll => 'Tous';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterThisMonth => 'Ce mois-ci';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterThisYear => 'Cette année';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterPast => 'Passés';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaEventDeleteConfirm =>
|
||||||
|
'Êtes-vous sûr de vouloir supprimer cet évènement ?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get onlineLabel => 'En ligne :';
|
String get onlineLabel => 'En ligne :';
|
||||||
|
|
||||||
@ -973,6 +989,18 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get chooseEvent => 'Choisir un évènement';
|
String get chooseEvent => 'Choisir un évènement';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewWebTitle => 'Aperçu — Web';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewMobileTitle => 'Aperçu — Mobile';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewDesktop => 'Desktop';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewMobile => 'Mobile';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noneOption => 'Aucun';
|
String get noneOption => 'Aucun';
|
||||||
|
|
||||||
|
|||||||
@ -746,6 +746,22 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get noAddress => 'Geen adres';
|
String get noAddress => 'Geen adres';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterAll => 'Alle';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterThisMonth => 'Deze maand';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterThisYear => 'Dit jaar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaFilterPast => 'Verleden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get agendaEventDeleteConfirm =>
|
||||||
|
'Weet u zeker dat u dit evenement wilt verwijderen?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get onlineLabel => 'Online:';
|
String get onlineLabel => 'Online:';
|
||||||
|
|
||||||
@ -954,6 +970,18 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get chooseEvent => 'Kies een evenement';
|
String get chooseEvent => 'Kies een evenement';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewWebTitle => 'Voorbeeld — Web';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewMobileTitle => 'Voorbeeld — Mobiel';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewDesktop => 'Desktop';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previewMobile => 'Mobiel';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noneOption => 'Geen';
|
String get noneOption => 'Geen';
|
||||||
|
|
||||||
|
|||||||
@ -289,6 +289,11 @@
|
|||||||
"addEvent": "Evenement toevoegen",
|
"addEvent": "Evenement toevoegen",
|
||||||
"noEvents": "Geen evenementen",
|
"noEvents": "Geen evenementen",
|
||||||
"noAddress": "Geen adres",
|
"noAddress": "Geen adres",
|
||||||
|
"agendaFilterAll": "Alle",
|
||||||
|
"agendaFilterThisMonth": "Deze maand",
|
||||||
|
"agendaFilterThisYear": "Dit jaar",
|
||||||
|
"agendaFilterPast": "Verleden",
|
||||||
|
"agendaEventDeleteConfirm": "Weet u zeker dat u dit evenement wilt verwijderen?",
|
||||||
"onlineLabel": "Online:",
|
"onlineLabel": "Online:",
|
||||||
"mapViewLabel": "Kaartweergave:",
|
"mapViewLabel": "Kaartweergave:",
|
||||||
"mapServiceLabel": "Kaartservice:",
|
"mapServiceLabel": "Kaartservice:",
|
||||||
@ -362,6 +367,10 @@
|
|||||||
"languagesLabel": "Talen:",
|
"languagesLabel": "Talen:",
|
||||||
"featuredEventLabel": "Uitgelicht evenement:",
|
"featuredEventLabel": "Uitgelicht evenement:",
|
||||||
"chooseEvent": "Kies een evenement",
|
"chooseEvent": "Kies een evenement",
|
||||||
|
"previewWebTitle": "Voorbeeld — Web",
|
||||||
|
"previewMobileTitle": "Voorbeeld — Mobiel",
|
||||||
|
"previewDesktop": "Desktop",
|
||||||
|
"previewMobile": "Mobiel",
|
||||||
"noneOption": "Geen",
|
"noneOption": "Geen",
|
||||||
"aiAssistantLabel": "AI-assistent:",
|
"aiAssistantLabel": "AI-assistent:",
|
||||||
"appUpdatedSuccess": "Mobiele applicatie bijgewerkt",
|
"appUpdatedSuccess": "Mobiele applicatie bijgewerkt",
|
||||||
|
|||||||
@ -238,7 +238,6 @@ Class | Method | HTTP request | Description
|
|||||||
- [GuidedStepTriggerGeoPoint](doc//GuidedStepTriggerGeoPoint.md)
|
- [GuidedStepTriggerGeoPoint](doc//GuidedStepTriggerGeoPoint.md)
|
||||||
- [Instance](doc//Instance.md)
|
- [Instance](doc//Instance.md)
|
||||||
- [InstanceDTO](doc//InstanceDTO.md)
|
- [InstanceDTO](doc//InstanceDTO.md)
|
||||||
- [LayoutMainPageType](doc//LayoutMainPageType.md)
|
|
||||||
- [LoginDTO](doc//LoginDTO.md)
|
- [LoginDTO](doc//LoginDTO.md)
|
||||||
- [MapAnnotation](doc//MapAnnotation.md)
|
- [MapAnnotation](doc//MapAnnotation.md)
|
||||||
- [MapAnnotationDTO](doc//MapAnnotationDTO.md)
|
- [MapAnnotationDTO](doc//MapAnnotationDTO.md)
|
||||||
|
|||||||
@ -13,13 +13,13 @@ Name | Type | Description | Notes
|
|||||||
**id** | **String** | | [optional]
|
**id** | **String** | | [optional]
|
||||||
**order** | **int** | | [optional]
|
**order** | **int** | | [optional]
|
||||||
**isActive** | **bool** | | [optional]
|
**isActive** | **bool** | | [optional]
|
||||||
**weightMasonryGrid** | **int** | | [optional]
|
**gridColSpan** | **int** | | [optional]
|
||||||
|
**gridRowSpan** | **int** | | [optional]
|
||||||
**isDate** | **bool** | | [optional]
|
**isDate** | **bool** | | [optional]
|
||||||
**isHour** | **bool** | | [optional]
|
**isHour** | **bool** | | [optional]
|
||||||
**roundedValue** | **int** | | [optional]
|
**roundedValue** | **int** | | [optional]
|
||||||
**screenPercentageSectionsMainPage** | **int** | | [optional]
|
**screenPercentageSectionsMainPage** | **int** | | [optional]
|
||||||
**isSectionImageBackground** | **bool** | | [optional]
|
**isSectionImageBackground** | **bool** | | [optional]
|
||||||
**layoutMainPage** | [**LayoutMainPageType**](LayoutMainPageType.md) | | [optional]
|
|
||||||
**loaderImageId** | **String** | | [optional]
|
**loaderImageId** | **String** | | [optional]
|
||||||
**loaderImageUrl** | **String** | | [optional]
|
**loaderImageUrl** | **String** | | [optional]
|
||||||
**primaryColor** | **String** | | [optional]
|
**primaryColor** | **String** | | [optional]
|
||||||
|
|||||||
@ -18,7 +18,6 @@ Name | Type | Description | Notes
|
|||||||
**loaderImageUrl** | **String** | | [optional]
|
**loaderImageUrl** | **String** | | [optional]
|
||||||
**primaryColor** | **String** | | [optional]
|
**primaryColor** | **String** | | [optional]
|
||||||
**secondaryColor** | **String** | | [optional]
|
**secondaryColor** | **String** | | [optional]
|
||||||
**layoutMainPage** | [**LayoutMainPageType**](LayoutMainPageType.md) | | [optional]
|
|
||||||
**languages** | **List<String>** | | [optional] [default to const []]
|
**languages** | **List<String>** | | [optional] [default to const []]
|
||||||
**sectionEventId** | **String** | | [optional]
|
**sectionEventId** | **String** | | [optional]
|
||||||
**sectionEvent** | [**ApplicationInstanceSectionEvent**](ApplicationInstanceSectionEvent.md) | | [optional]
|
**sectionEvent** | [**ApplicationInstanceSectionEvent**](ApplicationInstanceSectionEvent.md) | | [optional]
|
||||||
|
|||||||
@ -14,7 +14,8 @@ Name | Type | Description | Notes
|
|||||||
**applicationInstanceId** | **String** | | [optional]
|
**applicationInstanceId** | **String** | | [optional]
|
||||||
**order** | **int** | | [optional]
|
**order** | **int** | | [optional]
|
||||||
**isActive** | **bool** | | [optional]
|
**isActive** | **bool** | | [optional]
|
||||||
**weightMasonryGrid** | **int** | | [optional]
|
**gridColSpan** | **int** | | [optional]
|
||||||
|
**gridRowSpan** | **int** | | [optional]
|
||||||
**isDate** | **bool** | | [optional]
|
**isDate** | **bool** | | [optional]
|
||||||
**isHour** | **bool** | | [optional]
|
**isHour** | **bool** | | [optional]
|
||||||
**roundedValue** | **int** | | [optional]
|
**roundedValue** | **int** | | [optional]
|
||||||
@ -22,7 +23,6 @@ Name | Type | Description | Notes
|
|||||||
**isSectionImageBackground** | **bool** | | [optional]
|
**isSectionImageBackground** | **bool** | | [optional]
|
||||||
**deviceId** | **String** | | [optional]
|
**deviceId** | **String** | | [optional]
|
||||||
**device** | [**AppConfigurationLinkDTODevice**](AppConfigurationLinkDTODevice.md) | | [optional]
|
**device** | [**AppConfigurationLinkDTODevice**](AppConfigurationLinkDTODevice.md) | | [optional]
|
||||||
**layoutMainPage** | [**LayoutMainPageType**](LayoutMainPageType.md) | | [optional]
|
|
||||||
**loaderImageId** | **String** | | [optional]
|
**loaderImageId** | **String** | | [optional]
|
||||||
**loaderImageUrl** | **String** | | [optional]
|
**loaderImageUrl** | **String** | | [optional]
|
||||||
**primaryColor** | **String** | | [optional]
|
**primaryColor** | **String** | | [optional]
|
||||||
|
|||||||
@ -18,7 +18,6 @@ Name | Type | Description | Notes
|
|||||||
**loaderImageUrl** | **String** | | [optional]
|
**loaderImageUrl** | **String** | | [optional]
|
||||||
**primaryColor** | **String** | | [optional]
|
**primaryColor** | **String** | | [optional]
|
||||||
**secondaryColor** | **String** | | [optional]
|
**secondaryColor** | **String** | | [optional]
|
||||||
**layoutMainPage** | [**LayoutMainPageType**](LayoutMainPageType.md) | | [optional]
|
|
||||||
**languages** | **List<String>** | | [optional] [default to const []]
|
**languages** | **List<String>** | | [optional] [default to const []]
|
||||||
**sectionEventId** | **String** | | [optional]
|
**sectionEventId** | **String** | | [optional]
|
||||||
**sectionEvent** | [**ApplicationInstanceSectionEvent**](ApplicationInstanceSectionEvent.md) | | [optional]
|
**sectionEvent** | [**ApplicationInstanceSectionEvent**](ApplicationInstanceSectionEvent.md) | | [optional]
|
||||||
|
|||||||
@ -18,7 +18,6 @@ Name | Type | Description | Notes
|
|||||||
**loaderImageUrl** | **String** | | [optional]
|
**loaderImageUrl** | **String** | | [optional]
|
||||||
**primaryColor** | **String** | | [optional]
|
**primaryColor** | **String** | | [optional]
|
||||||
**secondaryColor** | **String** | | [optional]
|
**secondaryColor** | **String** | | [optional]
|
||||||
**layoutMainPage** | [**LayoutMainPageType**](LayoutMainPageType.md) | | [optional]
|
|
||||||
**languages** | **List<String>** | | [optional] [default to const []]
|
**languages** | **List<String>** | | [optional] [default to const []]
|
||||||
**sectionEventId** | **String** | | [optional]
|
**sectionEventId** | **String** | | [optional]
|
||||||
**sectionEventDTO** | [**ApplicationInstanceDTOSectionEventDTO**](ApplicationInstanceDTOSectionEventDTO.md) | | [optional]
|
**sectionEventDTO** | [**ApplicationInstanceDTOSectionEventDTO**](ApplicationInstanceDTOSectionEventDTO.md) | | [optional]
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
# manager_api_new.model.LayoutMainPageType
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:manager_api_new/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@ -118,7 +118,6 @@ part 'model/instance.dart';
|
|||||||
part 'model/instance_dto.dart';
|
part 'model/instance_dto.dart';
|
||||||
part 'model/instance_quota_dto.dart';
|
part 'model/instance_quota_dto.dart';
|
||||||
part 'model/subscription_plan_dto.dart';
|
part 'model/subscription_plan_dto.dart';
|
||||||
part 'model/layout_main_page_type.dart';
|
|
||||||
part 'model/login_dto.dart';
|
part 'model/login_dto.dart';
|
||||||
part 'model/map_annotation.dart';
|
part 'model/map_annotation.dart';
|
||||||
part 'model/map_annotation_dto.dart';
|
part 'model/map_annotation_dto.dart';
|
||||||
|
|||||||
@ -361,8 +361,6 @@ class ApiClient {
|
|||||||
return InstanceDTO.fromJson(value);
|
return InstanceDTO.fromJson(value);
|
||||||
case 'InstanceQuotaDTO':
|
case 'InstanceQuotaDTO':
|
||||||
return InstanceQuotaDTO.fromJson(value);
|
return InstanceQuotaDTO.fromJson(value);
|
||||||
case 'LayoutMainPageType':
|
|
||||||
return LayoutMainPageTypeTypeTransformer().decode(value);
|
|
||||||
case 'LoginDTO':
|
case 'LoginDTO':
|
||||||
return LoginDTO.fromJson(value);
|
return LoginDTO.fromJson(value);
|
||||||
case 'MapAnnotation':
|
case 'MapAnnotation':
|
||||||
|
|||||||
@ -80,9 +80,6 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is GeometryType) {
|
if (value is GeometryType) {
|
||||||
return GeometryTypeTypeTransformer().encode(value).toString();
|
return GeometryTypeTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
if (value is LayoutMainPageType) {
|
|
||||||
return LayoutMainPageTypeTypeTransformer().encode(value).toString();
|
|
||||||
}
|
|
||||||
if (value is MapProvider) {
|
if (value is MapProvider) {
|
||||||
return MapProviderTypeTransformer().encode(value).toString();
|
return MapProviderTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,13 +18,13 @@ class AppConfigurationLink {
|
|||||||
this.id,
|
this.id,
|
||||||
this.order,
|
this.order,
|
||||||
this.isActive,
|
this.isActive,
|
||||||
this.weightMasonryGrid,
|
this.gridColSpan,
|
||||||
|
this.gridRowSpan,
|
||||||
this.isDate,
|
this.isDate,
|
||||||
this.isHour,
|
this.isHour,
|
||||||
this.roundedValue,
|
this.roundedValue,
|
||||||
this.screenPercentageSectionsMainPage,
|
this.screenPercentageSectionsMainPage,
|
||||||
this.isSectionImageBackground,
|
this.isSectionImageBackground,
|
||||||
this.layoutMainPage,
|
|
||||||
this.loaderImageId,
|
this.loaderImageId,
|
||||||
this.loaderImageUrl,
|
this.loaderImageUrl,
|
||||||
this.primaryColor,
|
this.primaryColor,
|
||||||
@ -51,7 +51,9 @@ class AppConfigurationLink {
|
|||||||
///
|
///
|
||||||
bool? isActive;
|
bool? isActive;
|
||||||
|
|
||||||
int? weightMasonryGrid;
|
int? gridColSpan;
|
||||||
|
|
||||||
|
int? gridRowSpan;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
@ -81,14 +83,6 @@ class AppConfigurationLink {
|
|||||||
///
|
///
|
||||||
bool? isSectionImageBackground;
|
bool? isSectionImageBackground;
|
||||||
|
|
||||||
///
|
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
|
||||||
/// source code must fall back to having a nullable type.
|
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
|
||||||
///
|
|
||||||
LayoutMainPageType? layoutMainPage;
|
|
||||||
|
|
||||||
String? loaderImageId;
|
String? loaderImageId;
|
||||||
|
|
||||||
String? loaderImageUrl;
|
String? loaderImageUrl;
|
||||||
@ -114,14 +108,14 @@ class AppConfigurationLink {
|
|||||||
other.id == id &&
|
other.id == id &&
|
||||||
other.order == order &&
|
other.order == order &&
|
||||||
other.isActive == isActive &&
|
other.isActive == isActive &&
|
||||||
other.weightMasonryGrid == weightMasonryGrid &&
|
other.gridColSpan == gridColSpan &&
|
||||||
|
other.gridRowSpan == gridRowSpan &&
|
||||||
other.isDate == isDate &&
|
other.isDate == isDate &&
|
||||||
other.isHour == isHour &&
|
other.isHour == isHour &&
|
||||||
other.roundedValue == roundedValue &&
|
other.roundedValue == roundedValue &&
|
||||||
other.screenPercentageSectionsMainPage ==
|
other.screenPercentageSectionsMainPage ==
|
||||||
screenPercentageSectionsMainPage &&
|
screenPercentageSectionsMainPage &&
|
||||||
other.isSectionImageBackground == isSectionImageBackground &&
|
other.isSectionImageBackground == isSectionImageBackground &&
|
||||||
other.layoutMainPage == layoutMainPage &&
|
|
||||||
other.loaderImageId == loaderImageId &&
|
other.loaderImageId == loaderImageId &&
|
||||||
other.loaderImageUrl == loaderImageUrl &&
|
other.loaderImageUrl == loaderImageUrl &&
|
||||||
other.primaryColor == primaryColor &&
|
other.primaryColor == primaryColor &&
|
||||||
@ -139,7 +133,8 @@ class AppConfigurationLink {
|
|||||||
(id == null ? 0 : id!.hashCode) +
|
(id == null ? 0 : id!.hashCode) +
|
||||||
(order == null ? 0 : order!.hashCode) +
|
(order == null ? 0 : order!.hashCode) +
|
||||||
(isActive == null ? 0 : isActive!.hashCode) +
|
(isActive == null ? 0 : isActive!.hashCode) +
|
||||||
(weightMasonryGrid == null ? 0 : weightMasonryGrid!.hashCode) +
|
(gridColSpan == null ? 0 : gridColSpan!.hashCode) +
|
||||||
|
(gridRowSpan == null ? 0 : gridRowSpan!.hashCode) +
|
||||||
(isDate == null ? 0 : isDate!.hashCode) +
|
(isDate == null ? 0 : isDate!.hashCode) +
|
||||||
(isHour == null ? 0 : isHour!.hashCode) +
|
(isHour == null ? 0 : isHour!.hashCode) +
|
||||||
(roundedValue == null ? 0 : roundedValue!.hashCode) +
|
(roundedValue == null ? 0 : roundedValue!.hashCode) +
|
||||||
@ -149,7 +144,6 @@ class AppConfigurationLink {
|
|||||||
(isSectionImageBackground == null
|
(isSectionImageBackground == null
|
||||||
? 0
|
? 0
|
||||||
: isSectionImageBackground!.hashCode) +
|
: isSectionImageBackground!.hashCode) +
|
||||||
(layoutMainPage == null ? 0 : layoutMainPage!.hashCode) +
|
|
||||||
(loaderImageId == null ? 0 : loaderImageId!.hashCode) +
|
(loaderImageId == null ? 0 : loaderImageId!.hashCode) +
|
||||||
(loaderImageUrl == null ? 0 : loaderImageUrl!.hashCode) +
|
(loaderImageUrl == null ? 0 : loaderImageUrl!.hashCode) +
|
||||||
(primaryColor == null ? 0 : primaryColor!.hashCode) +
|
(primaryColor == null ? 0 : primaryColor!.hashCode) +
|
||||||
@ -161,7 +155,7 @@ class AppConfigurationLink {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'AppConfigurationLink[configurationId=$configurationId, applicationInstanceId=$applicationInstanceId, id=$id, order=$order, isActive=$isActive, weightMasonryGrid=$weightMasonryGrid, isDate=$isDate, isHour=$isHour, roundedValue=$roundedValue, screenPercentageSectionsMainPage=$screenPercentageSectionsMainPage, isSectionImageBackground=$isSectionImageBackground, layoutMainPage=$layoutMainPage, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor, configuration=$configuration, applicationInstance=$applicationInstance, deviceId=$deviceId, device=$device]';
|
'AppConfigurationLink[configurationId=$configurationId, applicationInstanceId=$applicationInstanceId, id=$id, order=$order, isActive=$isActive, gridColSpan=$gridColSpan, gridRowSpan=$gridRowSpan, isDate=$isDate, isHour=$isHour, roundedValue=$roundedValue, screenPercentageSectionsMainPage=$screenPercentageSectionsMainPage, isSectionImageBackground=$isSectionImageBackground, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor, configuration=$configuration, applicationInstance=$applicationInstance, deviceId=$deviceId, device=$device]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -182,10 +176,15 @@ class AppConfigurationLink {
|
|||||||
} else {
|
} else {
|
||||||
json[r'isActive'] = null;
|
json[r'isActive'] = null;
|
||||||
}
|
}
|
||||||
if (this.weightMasonryGrid != null) {
|
if (this.gridColSpan != null) {
|
||||||
json[r'weightMasonryGrid'] = this.weightMasonryGrid;
|
json[r'gridColSpan'] = this.gridColSpan;
|
||||||
} else {
|
} else {
|
||||||
json[r'weightMasonryGrid'] = null;
|
json[r'gridColSpan'] = null;
|
||||||
|
}
|
||||||
|
if (this.gridRowSpan != null) {
|
||||||
|
json[r'gridRowSpan'] = this.gridRowSpan;
|
||||||
|
} else {
|
||||||
|
json[r'gridRowSpan'] = null;
|
||||||
}
|
}
|
||||||
if (this.isDate != null) {
|
if (this.isDate != null) {
|
||||||
json[r'isDate'] = this.isDate;
|
json[r'isDate'] = this.isDate;
|
||||||
@ -213,11 +212,6 @@ class AppConfigurationLink {
|
|||||||
} else {
|
} else {
|
||||||
json[r'isSectionImageBackground'] = null;
|
json[r'isSectionImageBackground'] = null;
|
||||||
}
|
}
|
||||||
if (this.layoutMainPage != null) {
|
|
||||||
json[r'layoutMainPage'] = this.layoutMainPage;
|
|
||||||
} else {
|
|
||||||
json[r'layoutMainPage'] = null;
|
|
||||||
}
|
|
||||||
if (this.loaderImageId != null) {
|
if (this.loaderImageId != null) {
|
||||||
json[r'loaderImageId'] = this.loaderImageId;
|
json[r'loaderImageId'] = this.loaderImageId;
|
||||||
} else {
|
} else {
|
||||||
@ -288,7 +282,8 @@ class AppConfigurationLink {
|
|||||||
id: mapValueOfType<String>(json, r'id'),
|
id: mapValueOfType<String>(json, r'id'),
|
||||||
order: mapValueOfType<int>(json, r'order'),
|
order: mapValueOfType<int>(json, r'order'),
|
||||||
isActive: mapValueOfType<bool>(json, r'isActive'),
|
isActive: mapValueOfType<bool>(json, r'isActive'),
|
||||||
weightMasonryGrid: mapValueOfType<int>(json, r'weightMasonryGrid'),
|
gridColSpan: mapValueOfType<int>(json, r'gridColSpan'),
|
||||||
|
gridRowSpan: mapValueOfType<int>(json, r'gridRowSpan'),
|
||||||
isDate: mapValueOfType<bool>(json, r'isDate'),
|
isDate: mapValueOfType<bool>(json, r'isDate'),
|
||||||
isHour: mapValueOfType<bool>(json, r'isHour'),
|
isHour: mapValueOfType<bool>(json, r'isHour'),
|
||||||
roundedValue: mapValueOfType<int>(json, r'roundedValue'),
|
roundedValue: mapValueOfType<int>(json, r'roundedValue'),
|
||||||
@ -296,7 +291,6 @@ class AppConfigurationLink {
|
|||||||
mapValueOfType<int>(json, r'screenPercentageSectionsMainPage'),
|
mapValueOfType<int>(json, r'screenPercentageSectionsMainPage'),
|
||||||
isSectionImageBackground:
|
isSectionImageBackground:
|
||||||
mapValueOfType<bool>(json, r'isSectionImageBackground'),
|
mapValueOfType<bool>(json, r'isSectionImageBackground'),
|
||||||
layoutMainPage: LayoutMainPageType.fromJson(json[r'layoutMainPage']),
|
|
||||||
loaderImageId: mapValueOfType<String>(json, r'loaderImageId'),
|
loaderImageId: mapValueOfType<String>(json, r'loaderImageId'),
|
||||||
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
||||||
primaryColor: mapValueOfType<String>(json, r'primaryColor'),
|
primaryColor: mapValueOfType<String>(json, r'primaryColor'),
|
||||||
|
|||||||
@ -23,7 +23,6 @@ class AppConfigurationLinkApplicationInstance {
|
|||||||
this.loaderImageUrl,
|
this.loaderImageUrl,
|
||||||
this.primaryColor,
|
this.primaryColor,
|
||||||
this.secondaryColor,
|
this.secondaryColor,
|
||||||
this.layoutMainPage,
|
|
||||||
this.languages = const [],
|
this.languages = const [],
|
||||||
this.sectionEventId,
|
this.sectionEventId,
|
||||||
this.sectionEvent,
|
this.sectionEvent,
|
||||||
@ -50,14 +49,6 @@ class AppConfigurationLinkApplicationInstance {
|
|||||||
|
|
||||||
String? secondaryColor;
|
String? secondaryColor;
|
||||||
|
|
||||||
///
|
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
|
||||||
/// source code must fall back to having a nullable type.
|
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
|
||||||
///
|
|
||||||
LayoutMainPageType? layoutMainPage;
|
|
||||||
|
|
||||||
List<String>? languages;
|
List<String>? languages;
|
||||||
|
|
||||||
String? sectionEventId;
|
String? sectionEventId;
|
||||||
@ -86,7 +77,6 @@ class AppConfigurationLinkApplicationInstance {
|
|||||||
other.loaderImageUrl == loaderImageUrl &&
|
other.loaderImageUrl == loaderImageUrl &&
|
||||||
other.primaryColor == primaryColor &&
|
other.primaryColor == primaryColor &&
|
||||||
other.secondaryColor == secondaryColor &&
|
other.secondaryColor == secondaryColor &&
|
||||||
other.layoutMainPage == layoutMainPage &&
|
|
||||||
_deepEquality.equals(other.languages, languages) &&
|
_deepEquality.equals(other.languages, languages) &&
|
||||||
other.sectionEventId == sectionEventId &&
|
other.sectionEventId == sectionEventId &&
|
||||||
other.sectionEvent == sectionEvent &&
|
other.sectionEvent == sectionEvent &&
|
||||||
@ -105,7 +95,6 @@ class AppConfigurationLinkApplicationInstance {
|
|||||||
(loaderImageUrl == null ? 0 : loaderImageUrl!.hashCode) +
|
(loaderImageUrl == null ? 0 : loaderImageUrl!.hashCode) +
|
||||||
(primaryColor == null ? 0 : primaryColor!.hashCode) +
|
(primaryColor == null ? 0 : primaryColor!.hashCode) +
|
||||||
(secondaryColor == null ? 0 : secondaryColor!.hashCode) +
|
(secondaryColor == null ? 0 : secondaryColor!.hashCode) +
|
||||||
(layoutMainPage == null ? 0 : layoutMainPage!.hashCode) +
|
|
||||||
(languages == null ? 0 : languages!.hashCode) +
|
(languages == null ? 0 : languages!.hashCode) +
|
||||||
(sectionEventId == null ? 0 : sectionEventId!.hashCode) +
|
(sectionEventId == null ? 0 : sectionEventId!.hashCode) +
|
||||||
(sectionEvent == null ? 0 : sectionEvent!.hashCode) +
|
(sectionEvent == null ? 0 : sectionEvent!.hashCode) +
|
||||||
@ -113,7 +102,7 @@ class AppConfigurationLinkApplicationInstance {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'AppConfigurationLinkApplicationInstance[instanceId=$instanceId, appType=$appType, id=$id, configurations=$configurations, mainImageId=$mainImageId, mainImageUrl=$mainImageUrl, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor, layoutMainPage=$layoutMainPage, languages=$languages, sectionEventId=$sectionEventId, sectionEvent=$sectionEvent, isAssistant=$isAssistant]';
|
'AppConfigurationLinkApplicationInstance[instanceId=$instanceId, appType=$appType, id=$id, configurations=$configurations, mainImageId=$mainImageId, mainImageUrl=$mainImageUrl, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor, languages=$languages, sectionEventId=$sectionEventId, sectionEvent=$sectionEvent, isAssistant=$isAssistant]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -159,11 +148,6 @@ class AppConfigurationLinkApplicationInstance {
|
|||||||
} else {
|
} else {
|
||||||
json[r'secondaryColor'] = null;
|
json[r'secondaryColor'] = null;
|
||||||
}
|
}
|
||||||
if (this.layoutMainPage != null) {
|
|
||||||
json[r'layoutMainPage'] = this.layoutMainPage;
|
|
||||||
} else {
|
|
||||||
json[r'layoutMainPage'] = null;
|
|
||||||
}
|
|
||||||
if (this.languages != null) {
|
if (this.languages != null) {
|
||||||
json[r'languages'] = this.languages;
|
json[r'languages'] = this.languages;
|
||||||
} else {
|
} else {
|
||||||
@ -219,7 +203,6 @@ class AppConfigurationLinkApplicationInstance {
|
|||||||
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
||||||
primaryColor: mapValueOfType<String>(json, r'primaryColor'),
|
primaryColor: mapValueOfType<String>(json, r'primaryColor'),
|
||||||
secondaryColor: mapValueOfType<String>(json, r'secondaryColor'),
|
secondaryColor: mapValueOfType<String>(json, r'secondaryColor'),
|
||||||
layoutMainPage: LayoutMainPageType.fromJson(json[r'layoutMainPage']),
|
|
||||||
languages: json[r'languages'] is Iterable
|
languages: json[r'languages'] is Iterable
|
||||||
? (json[r'languages'] as Iterable)
|
? (json[r'languages'] as Iterable)
|
||||||
.cast<String>()
|
.cast<String>()
|
||||||
|
|||||||
@ -19,7 +19,8 @@ class AppConfigurationLinkDTO {
|
|||||||
this.applicationInstanceId,
|
this.applicationInstanceId,
|
||||||
this.order,
|
this.order,
|
||||||
this.isActive,
|
this.isActive,
|
||||||
this.weightMasonryGrid,
|
this.gridColSpan,
|
||||||
|
this.gridRowSpan,
|
||||||
this.isDate,
|
this.isDate,
|
||||||
this.isHour,
|
this.isHour,
|
||||||
this.roundedValue,
|
this.roundedValue,
|
||||||
@ -27,7 +28,6 @@ class AppConfigurationLinkDTO {
|
|||||||
this.isSectionImageBackground,
|
this.isSectionImageBackground,
|
||||||
this.deviceId,
|
this.deviceId,
|
||||||
this.device,
|
this.device,
|
||||||
this.layoutMainPage,
|
|
||||||
this.loaderImageId,
|
this.loaderImageId,
|
||||||
this.loaderImageUrl,
|
this.loaderImageUrl,
|
||||||
this.primaryColor,
|
this.primaryColor,
|
||||||
@ -52,7 +52,9 @@ class AppConfigurationLinkDTO {
|
|||||||
///
|
///
|
||||||
bool? isActive;
|
bool? isActive;
|
||||||
|
|
||||||
int? weightMasonryGrid;
|
int? gridColSpan;
|
||||||
|
|
||||||
|
int? gridRowSpan;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
@ -86,14 +88,6 @@ class AppConfigurationLinkDTO {
|
|||||||
|
|
||||||
DeviceDTO? device;
|
DeviceDTO? device;
|
||||||
|
|
||||||
///
|
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
|
||||||
/// source code must fall back to having a nullable type.
|
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
|
||||||
///
|
|
||||||
LayoutMainPageType? layoutMainPage;
|
|
||||||
|
|
||||||
String? loaderImageId;
|
String? loaderImageId;
|
||||||
|
|
||||||
String? loaderImageUrl;
|
String? loaderImageUrl;
|
||||||
@ -112,7 +106,8 @@ class AppConfigurationLinkDTO {
|
|||||||
other.applicationInstanceId == applicationInstanceId &&
|
other.applicationInstanceId == applicationInstanceId &&
|
||||||
other.order == order &&
|
other.order == order &&
|
||||||
other.isActive == isActive &&
|
other.isActive == isActive &&
|
||||||
other.weightMasonryGrid == weightMasonryGrid &&
|
other.gridColSpan == gridColSpan &&
|
||||||
|
other.gridRowSpan == gridRowSpan &&
|
||||||
other.isDate == isDate &&
|
other.isDate == isDate &&
|
||||||
other.isHour == isHour &&
|
other.isHour == isHour &&
|
||||||
other.roundedValue == roundedValue &&
|
other.roundedValue == roundedValue &&
|
||||||
@ -121,7 +116,6 @@ class AppConfigurationLinkDTO {
|
|||||||
other.isSectionImageBackground == isSectionImageBackground &&
|
other.isSectionImageBackground == isSectionImageBackground &&
|
||||||
other.deviceId == deviceId &&
|
other.deviceId == deviceId &&
|
||||||
other.device == device &&
|
other.device == device &&
|
||||||
other.layoutMainPage == layoutMainPage &&
|
|
||||||
other.loaderImageId == loaderImageId &&
|
other.loaderImageId == loaderImageId &&
|
||||||
other.loaderImageUrl == loaderImageUrl &&
|
other.loaderImageUrl == loaderImageUrl &&
|
||||||
other.primaryColor == primaryColor &&
|
other.primaryColor == primaryColor &&
|
||||||
@ -136,7 +130,8 @@ class AppConfigurationLinkDTO {
|
|||||||
(applicationInstanceId == null ? 0 : applicationInstanceId!.hashCode) +
|
(applicationInstanceId == null ? 0 : applicationInstanceId!.hashCode) +
|
||||||
(order == null ? 0 : order!.hashCode) +
|
(order == null ? 0 : order!.hashCode) +
|
||||||
(isActive == null ? 0 : isActive!.hashCode) +
|
(isActive == null ? 0 : isActive!.hashCode) +
|
||||||
(weightMasonryGrid == null ? 0 : weightMasonryGrid!.hashCode) +
|
(gridColSpan == null ? 0 : gridColSpan!.hashCode) +
|
||||||
|
(gridRowSpan == null ? 0 : gridRowSpan!.hashCode) +
|
||||||
(isDate == null ? 0 : isDate!.hashCode) +
|
(isDate == null ? 0 : isDate!.hashCode) +
|
||||||
(isHour == null ? 0 : isHour!.hashCode) +
|
(isHour == null ? 0 : isHour!.hashCode) +
|
||||||
(roundedValue == null ? 0 : roundedValue!.hashCode) +
|
(roundedValue == null ? 0 : roundedValue!.hashCode) +
|
||||||
@ -148,7 +143,6 @@ class AppConfigurationLinkDTO {
|
|||||||
: isSectionImageBackground!.hashCode) +
|
: isSectionImageBackground!.hashCode) +
|
||||||
(deviceId == null ? 0 : deviceId!.hashCode) +
|
(deviceId == null ? 0 : deviceId!.hashCode) +
|
||||||
(device == null ? 0 : device!.hashCode) +
|
(device == null ? 0 : device!.hashCode) +
|
||||||
(layoutMainPage == null ? 0 : layoutMainPage!.hashCode) +
|
|
||||||
(loaderImageId == null ? 0 : loaderImageId!.hashCode) +
|
(loaderImageId == null ? 0 : loaderImageId!.hashCode) +
|
||||||
(loaderImageUrl == null ? 0 : loaderImageUrl!.hashCode) +
|
(loaderImageUrl == null ? 0 : loaderImageUrl!.hashCode) +
|
||||||
(primaryColor == null ? 0 : primaryColor!.hashCode) +
|
(primaryColor == null ? 0 : primaryColor!.hashCode) +
|
||||||
@ -156,7 +150,7 @@ class AppConfigurationLinkDTO {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'AppConfigurationLinkDTO[id=$id, configurationId=$configurationId, configuration=$configuration, applicationInstanceId=$applicationInstanceId, order=$order, isActive=$isActive, weightMasonryGrid=$weightMasonryGrid, isDate=$isDate, isHour=$isHour, roundedValue=$roundedValue, screenPercentageSectionsMainPage=$screenPercentageSectionsMainPage, isSectionImageBackground=$isSectionImageBackground, deviceId=$deviceId, device=$device, layoutMainPage=$layoutMainPage, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor]';
|
'AppConfigurationLinkDTO[id=$id, configurationId=$configurationId, configuration=$configuration, applicationInstanceId=$applicationInstanceId, order=$order, isActive=$isActive, gridColSpan=$gridColSpan, gridRowSpan=$gridRowSpan, isDate=$isDate, isHour=$isHour, roundedValue=$roundedValue, screenPercentageSectionsMainPage=$screenPercentageSectionsMainPage, isSectionImageBackground=$isSectionImageBackground, deviceId=$deviceId, device=$device, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -190,10 +184,15 @@ class AppConfigurationLinkDTO {
|
|||||||
} else {
|
} else {
|
||||||
json[r'isActive'] = null;
|
json[r'isActive'] = null;
|
||||||
}
|
}
|
||||||
if (this.weightMasonryGrid != null) {
|
if (this.gridColSpan != null) {
|
||||||
json[r'weightMasonryGrid'] = this.weightMasonryGrid;
|
json[r'gridColSpan'] = this.gridColSpan;
|
||||||
} else {
|
} else {
|
||||||
json[r'weightMasonryGrid'] = null;
|
json[r'gridColSpan'] = null;
|
||||||
|
}
|
||||||
|
if (this.gridRowSpan != null) {
|
||||||
|
json[r'gridRowSpan'] = this.gridRowSpan;
|
||||||
|
} else {
|
||||||
|
json[r'gridRowSpan'] = null;
|
||||||
}
|
}
|
||||||
if (this.isDate != null) {
|
if (this.isDate != null) {
|
||||||
json[r'isDate'] = this.isDate;
|
json[r'isDate'] = this.isDate;
|
||||||
@ -231,11 +230,6 @@ class AppConfigurationLinkDTO {
|
|||||||
} else {
|
} else {
|
||||||
json[r'device'] = null;
|
json[r'device'] = null;
|
||||||
}
|
}
|
||||||
if (this.layoutMainPage != null) {
|
|
||||||
json[r'layoutMainPage'] = this.layoutMainPage;
|
|
||||||
} else {
|
|
||||||
json[r'layoutMainPage'] = null;
|
|
||||||
}
|
|
||||||
if (this.loaderImageId != null) {
|
if (this.loaderImageId != null) {
|
||||||
json[r'loaderImageId'] = this.loaderImageId;
|
json[r'loaderImageId'] = this.loaderImageId;
|
||||||
} else {
|
} else {
|
||||||
@ -288,7 +282,8 @@ class AppConfigurationLinkDTO {
|
|||||||
mapValueOfType<String>(json, r'applicationInstanceId'),
|
mapValueOfType<String>(json, r'applicationInstanceId'),
|
||||||
order: mapValueOfType<int>(json, r'order'),
|
order: mapValueOfType<int>(json, r'order'),
|
||||||
isActive: mapValueOfType<bool>(json, r'isActive'),
|
isActive: mapValueOfType<bool>(json, r'isActive'),
|
||||||
weightMasonryGrid: mapValueOfType<int>(json, r'weightMasonryGrid'),
|
gridColSpan: mapValueOfType<int>(json, r'gridColSpan'),
|
||||||
|
gridRowSpan: mapValueOfType<int>(json, r'gridRowSpan'),
|
||||||
isDate: mapValueOfType<bool>(json, r'isDate'),
|
isDate: mapValueOfType<bool>(json, r'isDate'),
|
||||||
isHour: mapValueOfType<bool>(json, r'isHour'),
|
isHour: mapValueOfType<bool>(json, r'isHour'),
|
||||||
roundedValue: mapValueOfType<int>(json, r'roundedValue'),
|
roundedValue: mapValueOfType<int>(json, r'roundedValue'),
|
||||||
@ -298,7 +293,6 @@ class AppConfigurationLinkDTO {
|
|||||||
mapValueOfType<bool>(json, r'isSectionImageBackground'),
|
mapValueOfType<bool>(json, r'isSectionImageBackground'),
|
||||||
deviceId: mapValueOfType<String>(json, r'deviceId'),
|
deviceId: mapValueOfType<String>(json, r'deviceId'),
|
||||||
device: DeviceDTO.fromJson(json[r'device']),
|
device: DeviceDTO.fromJson(json[r'device']),
|
||||||
layoutMainPage: LayoutMainPageType.fromJson(json[r'layoutMainPage']),
|
|
||||||
loaderImageId: mapValueOfType<String>(json, r'loaderImageId'),
|
loaderImageId: mapValueOfType<String>(json, r'loaderImageId'),
|
||||||
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
||||||
primaryColor: mapValueOfType<String>(json, r'primaryColor'),
|
primaryColor: mapValueOfType<String>(json, r'primaryColor'),
|
||||||
|
|||||||
@ -23,7 +23,6 @@ class ApplicationInstance {
|
|||||||
this.loaderImageUrl,
|
this.loaderImageUrl,
|
||||||
this.primaryColor,
|
this.primaryColor,
|
||||||
this.secondaryColor,
|
this.secondaryColor,
|
||||||
this.layoutMainPage,
|
|
||||||
this.languages = const [],
|
this.languages = const [],
|
||||||
this.sectionEventId,
|
this.sectionEventId,
|
||||||
this.sectionEvent,
|
this.sectionEvent,
|
||||||
@ -50,14 +49,6 @@ class ApplicationInstance {
|
|||||||
|
|
||||||
String? secondaryColor;
|
String? secondaryColor;
|
||||||
|
|
||||||
///
|
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
|
||||||
/// source code must fall back to having a nullable type.
|
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
|
||||||
///
|
|
||||||
LayoutMainPageType? layoutMainPage;
|
|
||||||
|
|
||||||
List<String>? languages;
|
List<String>? languages;
|
||||||
|
|
||||||
String? sectionEventId;
|
String? sectionEventId;
|
||||||
@ -86,7 +77,6 @@ class ApplicationInstance {
|
|||||||
other.loaderImageUrl == loaderImageUrl &&
|
other.loaderImageUrl == loaderImageUrl &&
|
||||||
other.primaryColor == primaryColor &&
|
other.primaryColor == primaryColor &&
|
||||||
other.secondaryColor == secondaryColor &&
|
other.secondaryColor == secondaryColor &&
|
||||||
other.layoutMainPage == layoutMainPage &&
|
|
||||||
_deepEquality.equals(other.languages, languages) &&
|
_deepEquality.equals(other.languages, languages) &&
|
||||||
other.sectionEventId == sectionEventId &&
|
other.sectionEventId == sectionEventId &&
|
||||||
other.sectionEvent == sectionEvent &&
|
other.sectionEvent == sectionEvent &&
|
||||||
@ -105,7 +95,6 @@ class ApplicationInstance {
|
|||||||
(loaderImageUrl == null ? 0 : loaderImageUrl!.hashCode) +
|
(loaderImageUrl == null ? 0 : loaderImageUrl!.hashCode) +
|
||||||
(primaryColor == null ? 0 : primaryColor!.hashCode) +
|
(primaryColor == null ? 0 : primaryColor!.hashCode) +
|
||||||
(secondaryColor == null ? 0 : secondaryColor!.hashCode) +
|
(secondaryColor == null ? 0 : secondaryColor!.hashCode) +
|
||||||
(layoutMainPage == null ? 0 : layoutMainPage!.hashCode) +
|
|
||||||
(languages == null ? 0 : languages!.hashCode) +
|
(languages == null ? 0 : languages!.hashCode) +
|
||||||
(sectionEventId == null ? 0 : sectionEventId!.hashCode) +
|
(sectionEventId == null ? 0 : sectionEventId!.hashCode) +
|
||||||
(sectionEvent == null ? 0 : sectionEvent!.hashCode) +
|
(sectionEvent == null ? 0 : sectionEvent!.hashCode) +
|
||||||
@ -113,7 +102,7 @@ class ApplicationInstance {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'ApplicationInstance[instanceId=$instanceId, appType=$appType, id=$id, configurations=$configurations, mainImageId=$mainImageId, mainImageUrl=$mainImageUrl, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor, layoutMainPage=$layoutMainPage, languages=$languages, sectionEventId=$sectionEventId, sectionEvent=$sectionEvent, isAssistant=$isAssistant]';
|
'ApplicationInstance[instanceId=$instanceId, appType=$appType, id=$id, configurations=$configurations, mainImageId=$mainImageId, mainImageUrl=$mainImageUrl, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor, languages=$languages, sectionEventId=$sectionEventId, sectionEvent=$sectionEvent, isAssistant=$isAssistant]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -159,11 +148,6 @@ class ApplicationInstance {
|
|||||||
} else {
|
} else {
|
||||||
json[r'secondaryColor'] = null;
|
json[r'secondaryColor'] = null;
|
||||||
}
|
}
|
||||||
if (this.layoutMainPage != null) {
|
|
||||||
json[r'layoutMainPage'] = this.layoutMainPage;
|
|
||||||
} else {
|
|
||||||
json[r'layoutMainPage'] = null;
|
|
||||||
}
|
|
||||||
if (this.languages != null) {
|
if (this.languages != null) {
|
||||||
json[r'languages'] = this.languages;
|
json[r'languages'] = this.languages;
|
||||||
} else {
|
} else {
|
||||||
@ -219,7 +203,6 @@ class ApplicationInstance {
|
|||||||
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
||||||
primaryColor: mapValueOfType<String>(json, r'primaryColor'),
|
primaryColor: mapValueOfType<String>(json, r'primaryColor'),
|
||||||
secondaryColor: mapValueOfType<String>(json, r'secondaryColor'),
|
secondaryColor: mapValueOfType<String>(json, r'secondaryColor'),
|
||||||
layoutMainPage: LayoutMainPageType.fromJson(json[r'layoutMainPage']),
|
|
||||||
languages: json[r'languages'] is Iterable
|
languages: json[r'languages'] is Iterable
|
||||||
? (json[r'languages'] as Iterable)
|
? (json[r'languages'] as Iterable)
|
||||||
.cast<String>()
|
.cast<String>()
|
||||||
|
|||||||
@ -23,7 +23,6 @@ class ApplicationInstanceDTO {
|
|||||||
this.loaderImageUrl,
|
this.loaderImageUrl,
|
||||||
this.primaryColor,
|
this.primaryColor,
|
||||||
this.secondaryColor,
|
this.secondaryColor,
|
||||||
this.layoutMainPage,
|
|
||||||
this.languages = const [],
|
this.languages = const [],
|
||||||
this.sectionEventId,
|
this.sectionEventId,
|
||||||
this.sectionEventDTO,
|
this.sectionEventDTO,
|
||||||
@ -57,14 +56,6 @@ class ApplicationInstanceDTO {
|
|||||||
|
|
||||||
String? secondaryColor;
|
String? secondaryColor;
|
||||||
|
|
||||||
///
|
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
|
||||||
/// source code must fall back to having a nullable type.
|
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
|
||||||
///
|
|
||||||
LayoutMainPageType? layoutMainPage;
|
|
||||||
|
|
||||||
List<String>? languages;
|
List<String>? languages;
|
||||||
|
|
||||||
String? sectionEventId;
|
String? sectionEventId;
|
||||||
@ -89,7 +80,6 @@ class ApplicationInstanceDTO {
|
|||||||
other.loaderImageUrl == loaderImageUrl &&
|
other.loaderImageUrl == loaderImageUrl &&
|
||||||
other.primaryColor == primaryColor &&
|
other.primaryColor == primaryColor &&
|
||||||
other.secondaryColor == secondaryColor &&
|
other.secondaryColor == secondaryColor &&
|
||||||
other.layoutMainPage == layoutMainPage &&
|
|
||||||
_deepEquality.equals(other.languages, languages) &&
|
_deepEquality.equals(other.languages, languages) &&
|
||||||
other.sectionEventId == sectionEventId &&
|
other.sectionEventId == sectionEventId &&
|
||||||
other.sectionEventDTO == sectionEventDTO &&
|
other.sectionEventDTO == sectionEventDTO &&
|
||||||
@ -109,7 +99,6 @@ class ApplicationInstanceDTO {
|
|||||||
(loaderImageUrl == null ? 0 : loaderImageUrl!.hashCode) +
|
(loaderImageUrl == null ? 0 : loaderImageUrl!.hashCode) +
|
||||||
(primaryColor == null ? 0 : primaryColor!.hashCode) +
|
(primaryColor == null ? 0 : primaryColor!.hashCode) +
|
||||||
(secondaryColor == null ? 0 : secondaryColor!.hashCode) +
|
(secondaryColor == null ? 0 : secondaryColor!.hashCode) +
|
||||||
(layoutMainPage == null ? 0 : layoutMainPage!.hashCode) +
|
|
||||||
(languages == null ? 0 : languages!.hashCode) +
|
(languages == null ? 0 : languages!.hashCode) +
|
||||||
(sectionEventId == null ? 0 : sectionEventId!.hashCode) +
|
(sectionEventId == null ? 0 : sectionEventId!.hashCode) +
|
||||||
(sectionEventDTO == null ? 0 : sectionEventDTO!.hashCode) +
|
(sectionEventDTO == null ? 0 : sectionEventDTO!.hashCode) +
|
||||||
@ -118,7 +107,7 @@ class ApplicationInstanceDTO {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'ApplicationInstanceDTO[id=$id, instanceId=$instanceId, appType=$appType, configurations=$configurations, mainImageId=$mainImageId, mainImageUrl=$mainImageUrl, loaderImageId=$loaderImageId, loaderImageUrl=$loaderImageUrl, primaryColor=$primaryColor, secondaryColor=$secondaryColor, layoutMainPage=$layoutMainPage, 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]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -172,11 +161,6 @@ class ApplicationInstanceDTO {
|
|||||||
} else {
|
} else {
|
||||||
json[r'secondaryColor'] = null;
|
json[r'secondaryColor'] = null;
|
||||||
}
|
}
|
||||||
if (this.layoutMainPage != null) {
|
|
||||||
json[r'layoutMainPage'] = this.layoutMainPage;
|
|
||||||
} else {
|
|
||||||
json[r'layoutMainPage'] = null;
|
|
||||||
}
|
|
||||||
if (this.languages != null) {
|
if (this.languages != null) {
|
||||||
json[r'languages'] = this.languages;
|
json[r'languages'] = this.languages;
|
||||||
} else {
|
} else {
|
||||||
@ -237,7 +221,6 @@ class ApplicationInstanceDTO {
|
|||||||
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
||||||
primaryColor: mapValueOfType<String>(json, r'primaryColor'),
|
primaryColor: mapValueOfType<String>(json, r'primaryColor'),
|
||||||
secondaryColor: mapValueOfType<String>(json, r'secondaryColor'),
|
secondaryColor: mapValueOfType<String>(json, r'secondaryColor'),
|
||||||
layoutMainPage: LayoutMainPageType.fromJson(json[r'layoutMainPage']),
|
|
||||||
languages: json[r'languages'] is Iterable
|
languages: json[r'languages'] is Iterable
|
||||||
? (json[r'languages'] as Iterable)
|
? (json[r'languages'] as Iterable)
|
||||||
.cast<String>()
|
.cast<String>()
|
||||||
|
|||||||
@ -1,102 +0,0 @@
|
|||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.18
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
part of openapi.api;
|
|
||||||
|
|
||||||
/// 0 = SimpleGrid 1 = MasonryGrid
|
|
||||||
class LayoutMainPageType {
|
|
||||||
/// Instantiate a new enum with the provided [value].
|
|
||||||
const LayoutMainPageType._(this.value);
|
|
||||||
|
|
||||||
/// The underlying value of this enum member.
|
|
||||||
final int value;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => value.toString();
|
|
||||||
|
|
||||||
int toJson() => value;
|
|
||||||
|
|
||||||
static const SimpleGrid = LayoutMainPageType._(0);
|
|
||||||
static const MasonryGrid = LayoutMainPageType._(1);
|
|
||||||
|
|
||||||
/// List of all possible values in this [enum][LayoutMainPageType].
|
|
||||||
static const values = <LayoutMainPageType>[
|
|
||||||
SimpleGrid,
|
|
||||||
MasonryGrid,
|
|
||||||
];
|
|
||||||
|
|
||||||
static LayoutMainPageType? fromJson(dynamic value) =>
|
|
||||||
LayoutMainPageTypeTypeTransformer().decode(value);
|
|
||||||
|
|
||||||
static List<LayoutMainPageType> listFromJson(
|
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final result = <LayoutMainPageType>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = LayoutMainPageType.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transformation class that can [encode] an instance of [LayoutMainPageType] to int,
|
|
||||||
/// and [decode] dynamic data back to [LayoutMainPageType].
|
|
||||||
class LayoutMainPageTypeTypeTransformer {
|
|
||||||
factory LayoutMainPageTypeTypeTransformer() =>
|
|
||||||
_instance ??= const LayoutMainPageTypeTypeTransformer._();
|
|
||||||
|
|
||||||
const LayoutMainPageTypeTypeTransformer._();
|
|
||||||
|
|
||||||
int encode(LayoutMainPageType data) => data.value;
|
|
||||||
|
|
||||||
/// Decodes a [dynamic value][data] to a LayoutMainPageType.
|
|
||||||
///
|
|
||||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
|
||||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
|
||||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
|
||||||
///
|
|
||||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
|
||||||
/// and users are still using an old app with the old code.
|
|
||||||
LayoutMainPageType? decode(dynamic data, {bool allowNull = true}) {
|
|
||||||
if (data != null) {
|
|
||||||
if(data.runtimeType == String) {
|
|
||||||
switch (data.toString()) {
|
|
||||||
case r'SimpleGrid': return LayoutMainPageType.SimpleGrid;
|
|
||||||
case r'MasonryGrid': return LayoutMainPageType.MasonryGrid;
|
|
||||||
default:
|
|
||||||
if (!allowNull) {
|
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(data.runtimeType == int) {
|
|
||||||
switch (data) {
|
|
||||||
case 0: return LayoutMainPageType.SimpleGrid;
|
|
||||||
case 1: return LayoutMainPageType.MasonryGrid;
|
|
||||||
default:
|
|
||||||
if (!allowNull) {
|
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Singleton [LayoutMainPageTypeTypeTransformer] instance.
|
|
||||||
static LayoutMainPageTypeTypeTransformer? _instance;
|
|
||||||
}
|
|
||||||
@ -91,11 +91,6 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
// LayoutMainPageType layoutMainPage
|
|
||||||
test('to test the property `layoutMainPage`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// List<String> languages (default value: const [])
|
// List<String> languages (default value: const [])
|
||||||
test('to test the property `languages`', () async {
|
test('to test the property `languages`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
|||||||
@ -41,8 +41,13 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
// int weightMasonryGrid
|
// int gridColSpan
|
||||||
test('to test the property `weightMasonryGrid`', () async {
|
test('to test the property `gridColSpan`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// int gridRowSpan
|
||||||
|
test('to test the property `gridRowSpan`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -41,8 +41,13 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
// int weightMasonryGrid
|
// int gridColSpan
|
||||||
test('to test the property `weightMasonryGrid`', () async {
|
test('to test the property `gridColSpan`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// int gridRowSpan
|
||||||
|
test('to test the property `gridRowSpan`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -91,11 +91,6 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
// LayoutMainPageType layoutMainPage
|
|
||||||
test('to test the property `layoutMainPage`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// List<String> languages (default value: const [])
|
// List<String> languages (default value: const [])
|
||||||
test('to test the property `languages`', () async {
|
test('to test the property `languages`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.18
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
import 'package:manager_api_new/api.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
// tests for LayoutMainPageType
|
|
||||||
void main() {
|
|
||||||
group('test LayoutMainPageType', () {});
|
|
||||||
}
|
|
||||||
1
myinfomate_layout/lib/myinfomate_layout.dart
Normal file
1
myinfomate_layout/lib/myinfomate_layout.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
export 'src/bento_layout.dart';
|
||||||
94
myinfomate_layout/lib/src/bento_layout.dart
Normal file
94
myinfomate_layout/lib/src/bento_layout.dart
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
class BentoItem {
|
||||||
|
final String id;
|
||||||
|
final int colSpan;
|
||||||
|
final int rowSpan;
|
||||||
|
|
||||||
|
const BentoItem({required this.id, this.colSpan = 1, this.rowSpan = 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
class BentoPlacement {
|
||||||
|
final String id;
|
||||||
|
final int col;
|
||||||
|
final int row;
|
||||||
|
final int colSpan;
|
||||||
|
final int rowSpan;
|
||||||
|
|
||||||
|
const BentoPlacement({
|
||||||
|
required this.id,
|
||||||
|
required this.col,
|
||||||
|
required this.row,
|
||||||
|
required this.colSpan,
|
||||||
|
required this.rowSpan,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class BentoResult {
|
||||||
|
final List<BentoPlacement> placements;
|
||||||
|
final int rowCount;
|
||||||
|
|
||||||
|
const BentoResult({required this.placements, required this.rowCount});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Placement "bento" dense sur une grille de [columns] colonnes.
|
||||||
|
/// Chaque item occupe colSpan × rowSpan cellules ; on place chaque item à la
|
||||||
|
/// première cellule libre (parcours ligne par ligne) qui l'accueille — même
|
||||||
|
/// principe que `grid-auto-flow: dense` en CSS, pour que l'aperçu Flutter et le
|
||||||
|
/// rendu web restent cohérents.
|
||||||
|
BentoResult bentoLayout(List<BentoItem> items, int columns) {
|
||||||
|
final cols = columns < 1 ? 1 : columns;
|
||||||
|
final occupied = <List<bool>>[];
|
||||||
|
|
||||||
|
void ensureRow(int r) {
|
||||||
|
while (occupied.length <= r) {
|
||||||
|
occupied.add(List<bool>.filled(cols, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fits(int r, int c, int cs, int rs) {
|
||||||
|
if (c + cs > cols) return false;
|
||||||
|
for (var rr = r; rr < r + rs; rr++) {
|
||||||
|
ensureRow(rr);
|
||||||
|
for (var cc = c; cc < c + cs; cc++) {
|
||||||
|
if (occupied[rr][cc]) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fill(int r, int c, int cs, int rs) {
|
||||||
|
for (var rr = r; rr < r + rs; rr++) {
|
||||||
|
ensureRow(rr);
|
||||||
|
for (var cc = c; cc < c + cs; cc++) {
|
||||||
|
occupied[rr][cc] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final placements = <BentoPlacement>[];
|
||||||
|
|
||||||
|
for (final item in items) {
|
||||||
|
final cs = item.colSpan.clamp(1, cols);
|
||||||
|
final rs = item.rowSpan < 1 ? 1 : item.rowSpan;
|
||||||
|
|
||||||
|
var placed = false;
|
||||||
|
var r = 0;
|
||||||
|
while (!placed) {
|
||||||
|
for (var c = 0; c + cs <= cols; c++) {
|
||||||
|
if (fits(r, c, cs, rs)) {
|
||||||
|
fill(r, c, cs, rs);
|
||||||
|
placements.add(BentoPlacement(id: item.id, col: c, row: r, colSpan: cs, rowSpan: rs));
|
||||||
|
placed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!placed) r++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rowCount = occupied.length;
|
||||||
|
while (rowCount > 0 && occupied[rowCount - 1].every((cell) => !cell)) {
|
||||||
|
rowCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BentoResult(placements: placements, rowCount: rowCount);
|
||||||
|
}
|
||||||
381
myinfomate_layout/pubspec.lock
Normal file
381
myinfomate_layout/pubspec.lock
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: f6526c100095fd63a916824e3da344bbbd50c25c8f56bcd52d13c59d35bbe422
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "104.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "6c6d751533496152e78f71c46ad001260c5e74e0f9ec1f1cbc165a0467c086d6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "14.0.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.1"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
cli_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_config
|
||||||
|
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.15.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.20"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.2"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.13"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.2"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: "0d5ba5602ec3baa28c8ce365e1efc5575969c765f45c554a3e167dc7945b9c30"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.31.2"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "475610b2aa23c19687cce2961e44b0cc57cafe220f67c2b80201231b2a07fbe7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.13"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: a39c204a4fc7a7ccb04a2b985e359fda3cc37e45e0b8ac61c3fb1a05aa832132
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.19"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.2.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.11.0 <4.0.0"
|
||||||
7
myinfomate_layout/pubspec.yaml
Normal file
7
myinfomate_layout/pubspec.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
name: myinfomate_layout
|
||||||
|
version: 1.0.0
|
||||||
|
description: Placement "bento" partagé (grille à spans colSpan x rowSpan, dense) pour les apps MyInfoMate.
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.17.0 <4.0.0'
|
||||||
|
dev_dependencies:
|
||||||
|
test: ^1.24.0
|
||||||
44
myinfomate_layout/test/bento_layout_test.dart
Normal file
44
myinfomate_layout/test/bento_layout_test.dart
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:myinfomate_layout/myinfomate_layout.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final fixturePath =
|
||||||
|
'${Directory.current.path}/test/fixtures/bento_layout_cases.json';
|
||||||
|
final fixture =
|
||||||
|
jsonDecode(File(fixturePath).readAsStringSync()) as Map<String, dynamic>;
|
||||||
|
final cases = fixture['cases'] as List<dynamic>;
|
||||||
|
|
||||||
|
for (final rawCase in cases) {
|
||||||
|
final testCase = rawCase as Map<String, dynamic>;
|
||||||
|
test(testCase['name'], () {
|
||||||
|
final items = (testCase['items'] as List<dynamic>)
|
||||||
|
.map((raw) => BentoItem(
|
||||||
|
id: raw['id'] as String,
|
||||||
|
colSpan: raw['colSpan'] as int,
|
||||||
|
rowSpan: raw['rowSpan'] as int,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final result = bentoLayout(items, testCase['columns'] as int);
|
||||||
|
final expected = testCase['expected'] as Map<String, dynamic>;
|
||||||
|
|
||||||
|
expect(result.rowCount, expected['rowCount']);
|
||||||
|
|
||||||
|
final expectedPlacements = expected['placements'] as List<dynamic>;
|
||||||
|
expect(result.placements.length, expectedPlacements.length);
|
||||||
|
|
||||||
|
for (var i = 0; i < result.placements.length; i++) {
|
||||||
|
final p = result.placements[i];
|
||||||
|
final e = expectedPlacements[i] as Map<String, dynamic>;
|
||||||
|
expect(p.id, e['id']);
|
||||||
|
expect(p.col, e['col']);
|
||||||
|
expect(p.row, e['row']);
|
||||||
|
expect(p.colSpan, e['colSpan']);
|
||||||
|
expect(p.rowSpan, e['rowSpan']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
72
myinfomate_layout/test/fixtures/bento_layout_cases.json
vendored
Normal file
72
myinfomate_layout/test/fixtures/bento_layout_cases.json
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"name": "two_columns_uniform",
|
||||||
|
"columns": 2,
|
||||||
|
"items": [
|
||||||
|
{ "id": "a", "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "b", "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "c", "colSpan": 1, "rowSpan": 1 }
|
||||||
|
],
|
||||||
|
"expected": {
|
||||||
|
"rowCount": 2,
|
||||||
|
"placements": [
|
||||||
|
{ "id": "a", "col": 0, "row": 0, "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "b", "col": 1, "row": 0, "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "c", "col": 0, "row": 1, "colSpan": 1, "rowSpan": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wide_then_dense_backfill",
|
||||||
|
"columns": 4,
|
||||||
|
"items": [
|
||||||
|
{ "id": "big", "colSpan": 2, "rowSpan": 2 },
|
||||||
|
{ "id": "a", "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "b", "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "c", "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "d", "colSpan": 1, "rowSpan": 1 }
|
||||||
|
],
|
||||||
|
"expected": {
|
||||||
|
"rowCount": 2,
|
||||||
|
"placements": [
|
||||||
|
{ "id": "big", "col": 0, "row": 0, "colSpan": 2, "rowSpan": 2 },
|
||||||
|
{ "id": "a", "col": 2, "row": 0, "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "b", "col": 3, "row": 0, "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "c", "col": 2, "row": 1, "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "d", "col": 3, "row": 1, "colSpan": 1, "rowSpan": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tall_card",
|
||||||
|
"columns": 2,
|
||||||
|
"items": [
|
||||||
|
{ "id": "tall", "colSpan": 1, "rowSpan": 2 },
|
||||||
|
{ "id": "a", "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "b", "colSpan": 1, "rowSpan": 1 }
|
||||||
|
],
|
||||||
|
"expected": {
|
||||||
|
"rowCount": 2,
|
||||||
|
"placements": [
|
||||||
|
{ "id": "tall", "col": 0, "row": 0, "colSpan": 1, "rowSpan": 2 },
|
||||||
|
{ "id": "a", "col": 1, "row": 0, "colSpan": 1, "rowSpan": 1 },
|
||||||
|
{ "id": "b", "col": 1, "row": 1, "colSpan": 1, "rowSpan": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "colspan_clamped_to_columns",
|
||||||
|
"columns": 2,
|
||||||
|
"items": [
|
||||||
|
{ "id": "wide", "colSpan": 3, "rowSpan": 1 }
|
||||||
|
],
|
||||||
|
"expected": {
|
||||||
|
"rowCount": 1,
|
||||||
|
"placements": [
|
||||||
|
{ "id": "wide", "col": 0, "row": 0, "colSpan": 2, "rowSpan": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1028,6 +1028,13 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.7"
|
version: "0.1.7"
|
||||||
|
myinfomate_layout:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: myinfomate_layout
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "1.0.0"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -67,6 +67,8 @@ dependencies:
|
|||||||
package_info_plus: ^4.2.0
|
package_info_plus: ^4.2.0
|
||||||
manager_api_new:
|
manager_api_new:
|
||||||
path: manager_api_new
|
path: manager_api_new
|
||||||
|
myinfomate_layout:
|
||||||
|
path: myinfomate_layout
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user