Created with <3 with dartpad.dev.
Last active
July 16, 2023 01:04
-
-
Save stephanedeluca/32c3a93eefcdaf86b73783f6cf73a0a3 to your computer and use it in GitHub Desktop.
Yassine b12
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
const _buildNumber = 12; | |
// lets define the stateless part of the screen (theme, AppBar, app title, background color...) | |
class MetaDataAdScreen extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.amber), | |
darkTheme: | |
ThemeData(useMaterial3: true, colorSchemeSeed: Colors.amber.shade100), | |
themeMode: ThemeMode.light, // Default is system | |
title: "product informations", | |
home: Scaffold( | |
appBar: AppBar( | |
title: const Text( | |
"My product caracteristics form (b$_buildNumber)", | |
style: TextStyle(color: Colors.white), | |
), | |
centerTitle: true, | |
backgroundColor: Colors.green), | |
body: ListRowsGenerator(), | |
), | |
); | |
} | |
} | |
/// A generic field widget implemented as a ListTile | |
/// TODO: I must specialize for all the types I need to support | |
class FieldInput<T> extends StatefulWidget { | |
/// Name of the field | |
final String name; | |
/// The current value | |
final T? value; | |
/// The field to edit | |
final Map<String, dynamic> field; | |
const FieldInput({ | |
super.key, | |
required this.name, | |
required this.field, | |
required this.value, | |
}); | |
@override | |
State<FieldInput<T>> createState() => _FieldInputState<T>(); | |
} | |
class _FieldInputState<T> extends State<FieldInput<T>> { | |
@override | |
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) => | |
"FieldInput: ${widget.name}: ${widget.field['value']}"; | |
/// The TextFormField | |
Key? _key; | |
/// The initial value | |
T? value; | |
/* | |
@override void initState(){ | |
super.initState(); | |
if (widget.name!="model")return; | |
debugPrint(widget.field.toString()); | |
}*/ | |
@override | |
didUpdateWidget(FieldInput<T> oldWidget){ | |
super.didUpdateWidget( oldWidget); | |
value = widget.field["value"]; | |
_key = Key("$value"); | |
if (widget.name!="model")return; | |
debugPrint("value: $value"); | |
} | |
@override | |
Widget build(BuildContext context) { | |
value = widget.field["value"]; | |
/// Provided icon widget or null | |
final icon = widget.field['icon'] == null | |
? const Icon(Icons.circle, color: Colors.transparent) | |
: Icon(widget.field['icon']); | |
/// Provided unit widget or null | |
final unit = widget.field["unit"] == null ? null : Text(widget.field["unit"]); | |
/// Returns the ListTile with potential leading icon and trailing unit. | |
return ListTile( | |
leading: icon, | |
title: TextFormField( | |
key: _key, | |
/// Provide initialValue of TextFormField the object that sits at key 'value' if and ONLY IF it exists, | |
/// otherwise provide null. Providing means: call toString() method only if field['value'] is not null | |
initialValue: value?.toString(), //widget.field['value']?.toString()??"€€€€", | |
autovalidateMode: AutovalidateMode.onUserInteraction, | |
decoration: InputDecoration( | |
labelText: widget.name, | |
), | |
/*keyboardType: switch(T){ | |
case : 'DateTime' | |
{TextInputType.datetime(); | |
} | |
break; | |
case : Phone | |
{TextInputType.phone(); | |
} | |
break; | |
}*/ | |
onChanged: (value) { | |
debugPrint("=> Field ${widget.name}: T is of $T | value: $value"); | |
try { | |
//if (T is String) { | |
setState(() { | |
debugPrint('About to set field["value"]=$value'); | |
try { | |
widget.field['value'] = value; | |
} catch (e) { | |
debugPrint("Error: $e"); | |
} | |
debugPrint('f["value"] set to ${widget.field['value']})'); | |
}); | |
//} | |
//else if (T is int) { | |
//} | |
} | |
catch(e) { | |
debugPrint("Error while copying to value: $e"); | |
} | |
}, | |
validator: (value) { | |
if (value == null || value.isEmpty) return "This field is mandatory"; | |
return null; | |
}, | |
), | |
subtitle: Text(value?.toString()??"null"), | |
trailing: unit); | |
} | |
} | |
/// The input field that lets the user pick a value from many | |
//TODO: see how to make EnumInputField share code with other InputField<T> | |
class EnumInputField extends StatefulWidget { | |
/// The list of the enums to pick from | |
final List<String> values; | |
/// The callback that is called with the selected value or null if user cancels | |
final void Function(String?) onSelected; | |
const EnumInputField( | |
{super.key, required this.values, required this.onSelected}); | |
@override | |
State<StatefulWidget> createState() => _EnumInputField(); | |
} | |
class _EnumInputField extends State<EnumInputField> { | |
@override | |
Widget build(BuildContext context) { | |
/// We display a scroll view | |
return ListView( | |
padding: const EdgeInsets.all(20), | |
physics: const BouncingScrollPhysics(), | |
children: [ | |
// == With a title | |
Text("Pick a value", style: Theme.of(context).textTheme.titleLarge), | |
// == A separator | |
const Text(" "), | |
// == The list of the values themselves | |
...widget.values | |
.map((v) => TextButton( | |
onPressed: () { | |
debugPrint("Tap on '$v'"); | |
// == Provide the user-selected value to the parent | |
widget.onSelected(v); | |
debugPrint("Tap on '$v': called onSelected()"); | |
// == Just close the bottom sheet | |
Navigator.of(context).pop(); | |
}, | |
child: Text(v), | |
)) | |
.toList(), | |
const Text(" "), | |
TextButton( | |
onPressed: () { | |
debugPrint("Tap on cancel 'null'"); | |
// == Tell parent user's cancelled | |
widget.onSelected(null); | |
// == Just close the bottom sheet | |
Navigator.of(context).pop(); | |
}, | |
child: const Text("Cancel"), | |
), | |
], | |
); | |
} | |
} | |
// Let's create a class that takes as input the CategoryCarMetaData Map and returns a ListView widget containing in each line | |
// a representation of each element of CategoryCarMetaData. | |
class ListRowsGenerator extends StatefulWidget { | |
@override | |
State<StatefulWidget> createState() => _ListListRowsGeneratorState(); | |
} | |
class _ListListRowsGeneratorState extends State<ListRowsGenerator> { | |
Map<String, dynamic> fields = {}; | |
@override | |
void initState() { | |
super.initState(); | |
fields = getCategoryCarMetaData(); | |
} | |
/// Returns the error widget | |
Widget _errorWidget(String text, Object e, StackTrace stack) { | |
return Container( | |
color: Colors.red, | |
padding: const EdgeInsets.all(20), | |
child: Text("Error $text: $e at:\n$stack", | |
style: const TextStyle(color: Colors.white)), | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
// == Select the right InputField generic according to the field type | |
final widgets = fields.keys | |
//.where((k) => k!='type') | |
.map<Widget>((k) { | |
//debugPrint("Field $k..."); | |
/// A shortcut on field k | |
/// diff 2 | |
final f = fields[k]; | |
final value = f["value"]; | |
//debugPrint("Field $k: type is ${f["type"]}"); | |
try { | |
switch (f["type"]) { | |
case "int": | |
try { | |
return FieldInput<int>(name: k, field: f, value: value,); | |
} catch (e, stack) { | |
return _errorWidget("FI<int>", e, stack); | |
} | |
case "enum": | |
//debugPrint("Field values: ${f["values"]}"); | |
try { | |
return ListTile( | |
leading: f['icon'] == null ? null : Icon(f['icon']), | |
title: Text(k), | |
//TODO: treat the case where there is unit and enum (where to put unit ?) | |
trailing: const Icon(Icons.arrow_drop_down), | |
subtitle: f['value'] == null | |
? const Text('pick a value') | |
: Text('${f["value"]}'), | |
onTap: () => showModalBottomSheet( | |
context: context, | |
builder: (context) => EnumInputField( | |
values: f["values"].toList(), | |
onSelected: (value) { | |
debugPrint( | |
"In onSelected: $value | fields[value]=${fields['value']} | Type of f[value] is ${fields['value'].runtimeType}"); | |
setState(() { | |
debugPrint('About to set f["value"]=$value'); | |
try { | |
f['value'] = value; | |
//fields[k]=f; | |
} catch (e) { | |
debugPrint("Error: $e"); | |
} | |
debugPrint('f["value"] set to ${f['value']})'); | |
}); | |
}, | |
), | |
)); | |
} catch (e, stack) { | |
return _errorWidget("FI <enum>", e, stack); | |
} | |
case "string": | |
try { | |
return FieldInput<String>(name: k, field: f, value: value,); | |
} catch (e, stack) { | |
return _errorWidget("FI<String>", e, stack); | |
} | |
case "date": | |
case "dateTime": | |
try { | |
return FieldInput<DateTime>(name: k, field: f, value: value,); | |
} catch (e, stack) { | |
return _errorWidget("FI<DateTime>", e, stack); | |
} | |
default: | |
return Text("Invalid field $k: $f"); | |
} | |
} catch (e, stack) { | |
return _errorWidget('FI<{$f["type"]}>', e, stack); | |
} | |
}).toList(); | |
return ListView( | |
children: [ | |
ListView( | |
physics: const BouncingScrollPhysics(), | |
shrinkWrap: true, | |
children: widgets, | |
), | |
//Some space | |
const Text(" "), | |
//TODO:Save Button : save all data in a Map to send it to FireBase | |
Center(child: ElevatedButton(onPressed: (){debugPrint('${fields}');}, child: Text(" Save "))), | |
//space | |
const Text(" "), | |
//TODO:Cancel Button : set to deault all value of form | |
Center(child: ElevatedButton(onPressed: () { | |
///set to null all values chosen | |
setState(() { | |
for (String key in fields.keys){fields[key].remove('value');} | |
}); | |
debugPrint('${fields}'); | |
}, | |
child: Text("Discard"))), | |
Center(child: ElevatedButton( | |
onPressed: ()=> setState((){}), | |
child: const Text("setState"))), | |
], | |
); | |
} | |
} | |
void main() { | |
runApp( MetaDataAdScreen()); | |
} | |
/// Get the meta data for the `car` category | |
Map<String, dynamic> getCategoryCarMetaData() { | |
var energies = { | |
"icon": Icons.energy_savings_leaf, | |
"type": "enum", | |
"values": { | |
"electric", | |
"hybrid", | |
"reloaded hybrid", | |
"diesel", | |
"gas", | |
"hydrogen", | |
"gpl", | |
"lng", | |
"other" | |
}, | |
}; | |
var brands = { | |
"icon": Icons.branding_watermark, | |
"type": "enum", | |
"values": { | |
"mercedes-benz", | |
"bmw", | |
"renault", | |
"citroën", | |
"peugeot", | |
"opel", | |
"vw", | |
"simca", | |
"ford", | |
"gm", | |
"cadillac", | |
"other" | |
}, | |
}; | |
var types = { | |
"icon": Icons.type_specimen, | |
"type": "enum", | |
"values": {"sedan", "suv", "pickup", "truck", "semitruck", "other"}, | |
}; | |
return { | |
"type": types, | |
"energy": energies, | |
"brand": brands, | |
"model": {"type": "string"}, | |
"date": { | |
"icon": Icons.calendar_today, | |
"type": "date", | |
}, | |
"power": {"icon": Icons.power, "type": "int", "unit": "CV"} | |
}; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment