Skip to content

Instantly share code, notes, and snippets.

@stephanedeluca
Last active July 5, 2023 23:47
Show Gist options
  • Save stephanedeluca/599fd35f9c04c6234ca0f24cebed16d3 to your computer and use it in GitHub Desktop.
Save stephanedeluca/599fd35f9c04c6234ca0f24cebed16d3 to your computer and use it in GitHub Desktop.
Shokaze Yassine b2
import 'package:flutter/material.dart';
/// The buid number
const _buildNumber = 8;
// lets define the stateless part of the screen (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: Text(
"My vehicle caracteristics form (b$_buildNumber)",
style: TextStyle(color: Colors.white),
),
centerTitle: true,
backgroundColor: Colors.red
),
body: ListRowsGenerator(),
),
);
}
}
/// A generic field widget implemented as a ListTile
/// TODO: I must specilize for all the type I need to support
class FieldInput<T> extends StatelessWidget {
/// Name of the field
final String name;
/// The field to edit
final Map<String,dynamic> field;
//T value;
const FieldInput({
super.key,
required this.name,
required this.field,
});
@override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) => "FieldInput: $name: ${field['value']}";
@override Widget build(BuildContext context) {
/// Providionned icon widget or null
final icon = field['icon']==null?null:Icon( field['icon']);
// Returns the lit tile with potential leading icon and triling unit.
return ListTile(
leading: icon,
title: TextFormField(
initialValue: field['value']?.toString(),
// == Always fires the validator
autovalidateMode:AutovalidateMode.always,
// 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
decoration: InputDecoration(
labelText: name,
),
keyboardType:
T is int ?
TextInputType.number:T is double?
const TextInputType.numberWithOptions(decimal:true)
:null
,
/// Called whenever the user strike a key. The current valye (as a string) sits in value (btw, which could be null if nothing was stoke)
onChanged: (value) {
field['value'] = value;
//setStte()
//debugPrint("Field $name: $value");
debugPrint("Field is now: $field");
},
validator:(value){
if (value==null || value.isEmpty) return "This field is mandatory";
if (value.length<4) return 'You must provide at least 4 charcaters';
return null;
}
),
trailing:field["unit"] == null ? null: Text(field["unit"]),
);
}
}
/// The input field that let the user pick a value from many
//TODO: see how to make EnumInputField shar code with other InputField<T>
class EnumInputField extends StatelessWidget {
/// The list of the enums To pick from
final List<String> values;
/// The call back 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 Widget build(BuildContext context) {
/// We display a scrollin 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 (Note that I use a stanard text instead of
// a metrics, so that everything will stand in shape whatever
// the the used)
const Text(""), // const SizedBox(10),
// == The list of the values themseleves
...values.map((v)=>
TextButton(onPressed:() {
debugPrint("tap on $v");
// == Provide the user-seleted value to the parent
onSelected(v);
// == Just close the bottom sheet
Navigator.of(context).pop();
},
child:Text(v))).toList(), // Operator `...` explodes a list into individuel items => "a", "b", "c", etc.
const Text(""),
TextButton(onPressed:(){
// == Tell parent user's cancelled
onSelected(null);
// == Just close the bottom sheet
Navigator.of(context).pop();
}, child:Text("Cancel")),
]
);
}
}
/*
/// The form definition from the server
final definition = {
"age": {
"type": "int",
}
};
/// Once the user filled in, we get:
final definition = {
"age": {
"type": "int",
"value": 43, // What the user filled in!
}
};
*/
/*
/// A person model
class Person extends Object {
/// The person's firstname
final String name;
/// The Weight of the person
final double? weight;
/// The ID of the instance
late final _id; // private
Person({required this.name, this.weight}) {
// TODO: Computes a statisically unique ID
// FIXME: This bug is referred to as http://atlassian.com/342432ZERZERZ
_id = "12";
}
@override String toString() =>
'Person "name" weights ${weight}Kg';
// Replaces [instane@12432ZERZZERZER] of Person] by Person "Yassine" weights 80Kg]
}
/// The large person (aka over 200lbs)
/// My new class is LArge Person and I reach any of its memeber via `this`.
/// As it extends Person, you can reach any accessible from PErson via `super`.
class LargePerson extends Person {
/// Create instances of larg person, with default name as Demis Roussos
LargePerson.withDefault({super.name="Demis Roussos", super.weight});
/// Create intances of large person, with sealed name to Demis Roussos
LargePerson.onlyWeight({super.weight}):super(name:"Demis Roussos");
}
final g = LargePerson.withDefault(weight:250); //Kg
final h = LargePerson.withDefault(name:"toto", weight:250);
final i = LargePerson.onlyWeight(weight:230);
//final j = LargePerson.onlyWeight(name:"toto", weight:250);// Could not provide name
/// The widget that draws the user's profile
class YassineProfile extends StatelessWidget {
/// The person's name to display
final String name;
const YassineProfile({super.key, required this.name});
@override Widget build(BuildContext context) {
return Text("Name: $name");
}
}
void explainingHowTypesWork() {
/// TextFormField: A (Text) form field => String
dynamic s = "Hello";
s = 12;
// Molded object (instance) : mold (class)
s = YassineProfile(name:"Yassine");
final yassine = Person(name:"Yassine");
s = yassine;
s = null;
//Map<dynamic, dynamic> s1;
//Map<int,String> s2;
//Map<Widget,double> s3;
Map<String, dynamic> dict = {
"age": 12, // int
"weight": 82.3, // double
"fistname": "Yassine", // String
"definition": { // Map<String, dynamic>
"type": "int",
"value": 12,
},
};
dict["int"] = 123;
// blah blah
final v = dict["definition"]; // could be null if something gone wrong
if (v is Map<String, dynamic>) { // Make sure v is of the expected type, aka. mateches specs, hourray!
//TODO:
}
// ==
else {
throw Exception("Error: Form definiton is wrong: expecting Map>String, dynamic> but got $v");
}
final s0 = "Hello $v"; //==> s0 = "Hello "+v.toString()+"";
}
/// Reads the form definition the server just sent to us. Warning could rethrow whenever something goes wrong.
void readFormDefinitionFromServer() {
/// The stream I use to update blah blah
//late final stream;
// Run the reader
try {
//stream = Stream();
explainingHowTypesWork();
}
catch(e) {
rethrow;
}
/// Finally is the code that is runf whatever the branches the code took: eitheir try or catch.
finally {
//stream.dispose();
debugPrint("Leaving readFormDefinitonFromServer()");
}
}
*/
//let's create a class that take as input the the CategoryCarMetaData Map and return a listView widget containing in each line
//a represetation of each element of CategoryCarMetaData.
class ListRowsGenerator extends StatefulWidget{
@override
State<StatefulWidget> createState() =>ListListRowsGeneratorState();
}
class ListListRowsGeneratorState extends State {
String ? valueChosen;
@override
Widget build(BuildContext context) {
final fields = getCategoryCarMetaData();
//final k = fields.keys.first;
//return FieldInput(name: k, field: fields[k]);
// == Selet the right Inputfiel generic according to the fiel type
final widgets = fields.keys.map<Widget>((k) {
debugPrint("field $k…");
/// A shortcut on field k
final f = fields[k];
debugPrint("field $k: type is ${f["type"]}");
switch(f["type"]) {
case "int": return FieldInput<int>(name: k, field: f);
case "enum":
assert(f["values"] is Set<String>,"field values is not a set of string");
assert(f["values"]!=null, "field values is null");
assert(f["values"].length>1,"field values is one length");
debugPrint("field values: ${f["values"]}");
return ListTile(
title:Text(k),
subtitle:f["value"]==null?null:Text(f["value"]),
trailing:
TextButton(onPressed:() {
showModalBottomSheet(context:context, builder:(context)=>
EnumInputField(
values: f["values"].toList(),
onSelected:(value) {
setState((){
debugPrint('f["value"]=${f["value"]}');
debugPrint('f["value"] set to $value');
f["value"] = value;
});
},
),
);
},
child: const Icon(Icons.edit)),
);
default: return FieldInput(name: k,field: f);
}
}
).toList();
//debugPrint("Field widgets: $widgets");
final w= EnumInputField(values:const ["A","b","c","d","e","f","g","h"],onSelected:(value){
//TODO: make something useful of user-picked value
// == User's cancelled? do nothing
if (value==null) return;
// == Store the value the user picked to the form
debugPrint("User picked $value");
setState((){
//field["value"]=value;
});
});
return
Column(
mainAxisSize: MainAxisSize.min,
children:[
ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap:true,
children: widgets,
),
// == Allow the user to pick from an enum
ElevatedButton(onPressed:() {
showModalBottomSheet(context:context, builder:(context)=>w);
},child:Text("Pick a letter")),
])
;
/*
List <Widget>ListViewRows=[]; //empty list of widgets that will contain the listview elements
for (String key in getCategoryCarMetaData().keys){ //for loop to navigate the CategoryCarMetaData and extract data and put it as widget in the ListViewRows
switch (getCategoryCarMetaData()[key]["type"]){ //depending on type of data, widget that represent it is different
case "enum": { //if the type is enum, we will use DropDownButton to display all values allowed so the customer can choose.
final List itemsList=getCategoryCarMetaData()[key]["values"].toList();
ListViewRows.add(Center(child: Text(key)));
ListViewRows.add(DropdownButton<String>( value: valueChosen,
isExpanded: true,
onChanged: (String ?newValue){setState(() {valueChosen=newValue;});},
items : itemsList.map((e) =>DropdownMenuItem<String>(
value: e,
child: Text(e))).toList()));
} break;
case "string": { //if the type is String, we will use a TextFormField to let the user Add values with keyboard
ListViewRows.add(Center(child: Text(key)));
ListViewRows.add(TextFormField());
} break;
case "date": { //if the type is enum, we will use a TextFormField to Add Date Value
ListViewRows.add(Center(child: Text(key)));
ListViewRows.add(TextFormField());
} break;
case "int": { //if the type is int, we will use a TextFormField to Add this Value
ListViewRows.add(Center(child: Text(key)));
ListViewRows.add(TextFormField());
} break;
case "double": { //if the type is int, we will use a TextFormField to Add this Value
ListViewRows.add(Center(child: Text(key)));
ListViewRows.add(TextFormField());
} break;
default: {
ListViewRows.add(Center(child: Text(key)));
ListViewRows.add(TextFormField());
}
break;
}
}
return(ListView(
padding: const EdgeInsets.all(20),
children: ListViewRows));
*/
}
}
/// Get the meta data for the `car` category
Map<String, dynamic> getCategoryCarMetaData() {
const energies = {
"icon": Icons.energy_savings_leaf,
"type": "enum",
"values": {
"electric",
"hybrid",
"reloaded hybrid",
"diesel",
"gas",
"hydrogen",
"gpl",
"lng",
"other"
},
};
const brands = {
"icon": Icons.branding_watermark,
"type": "enum",
"values": {
"mercedes-benz",
"bmw",
"renault",
"citroën",
"peugeot",
"opel",
"vw",
"simca",
"ford",
"gm",
"cadillac",
"other"
},
};
const 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", "value": 12,}
};
}
/* DropDown Button Template
string ? valueChosen;
final List valueChosen=["tap1","tap2","tap3"];
DropdownButton<String>( value: valueChosen,
onChanged: (newValue){setState(() {valueChosen=newValue;});},
items : itemsList.map((e) =>DropdownMenuItem<String>(value: e,
child: Text(e))).toList())
*/
/*
Intro
It's about creating a simple application, which allows a user to feed a form allowing him to create the description data relating to the car he wants to sell 🚗 .
The information will be returned to elastic search from the application.Then you'll build a screen allowing him to build a search filter, run it, and finally display the results on the screen. He can then tap on it to view all the data.
The peculiarity here is that the form is not known in advance: it will be downloaded from a remote server: outside the scope of this work, you will hard code the description in the code meanwhile as I show in the wiki .
You are asked to deliver:
Step 1: Create ClassifiedAdMetaDataFormScreen screen that takes the category metadata from a Map<String, dynamic> and presents the related form built on the fly;
Step 2: Make the final evolution of the previous screen that also stores the data in the Firestore collection testClassifiedAd.
Step 3: Create ClassifiedAdSearchEngineScreen screen that enable the user to build a filter interactively according to the category metadata;
Step 4: Make the final evolution of the previous screen to actually build and run the request from the generated filter and create the ClassifiedAdSearchEngineResultsScreen screen that displays the results.
Have fun!
for the solution explanation go to : README.md
*/
void main() {
runApp( MetaDataAdScreen());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment