Material Components Widgets was completed by TheGarage mobile application development team. It is built using Flutter. The team used Getx framework for state management. Additionally, The primary objective of writing widget content is to ensure it results in clean code that is easy to read, maintain, understand, and modify by following a structured and consistent format.
Material Components are a collection of UI widgets and design guidelines, divided into six components:
- Actions : Interactive elements which trigger actions when clicked .
- Communication : Used to convey information to the user.
- Containment : Components for grouping in defined area.
- Navigation : User movement within the application.
- Selection : That allow users to make decisions .
- Text Inputs :Elements for entering text .
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
textStyle: const TextStyle(
fontFamily: 'GE-SS-Two',
fontWeight: FontWeight.w500,
color: Colors.white,
fontSize: 16,
),
disabledBackgroundColor: Colors.orange.shade100,
disabledForegroundColor: Colors.white,
minimumSize: const Size(398, 48),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 0.0,
backgroundColor: Colors.orange,
)
), ElevatedButton(
onPressed: () {
},
child: const Text('press here'),
) filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
backgroundColor: MaterialStatePropertyAll(Colors.orange),
foregroundColor: MaterialStatePropertyAll(Colors.white),
) FilledButton(
onPressed: () {},
child: const Text('press here'),
), outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.orange,
textStyle: const TextStyle(
fontFamily: 'GE-SS-Two',
fontWeight: FontWeight.w300,
color: Colors.white,
fontSize: 16,
),
side: const BorderSide(color: Colors.orange),
disabledBackgroundColor: Colors.orange.shade100,
minimumSize: const Size(398, 48),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 0.0,
backgroundColor: Colors.transparent,
)),OutlinedButton(
onPressed: () {}
child: const Text('press here'),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
disabledBackgroundColor: Colors.orange.shade100,
foregroundColor: Colors.orange,
),
),TextButton(
onPressed: () {},
child: Text(
'press here',
),
),floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: Colors.orange,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
elevation: 0.0,
)FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: Colors.orange,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 0.0,
) FloatingActionButton.extended(
onPressed: () {
},
icon: Icon(Icons.add),
label: Text('EXTENDED'),
), iconTheme: const IconThemeData(color: Colors.orange),
IconButton(
onPressed: () {},
icon: const Icon(Icons.save),
), segmentedButtonTheme: SegmentedButtonThemeData(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return Colors.orange;
}
return Colors.white;
},
)),
)Set <String> selection = {'S','M','L'};
List<ButtonSegment<String>> selectionSegment = [
ButtonSegment<String>(value: 'small', label: Text('S')),
ButtonSegment<String>(value: 'medium', label: Text('M')),
ButtonSegment<String>(value: 'large', label: Text('L')),
];
void updateSelection(Set<String> newSelection){
selection = newSelection;
update();
}GetBuilder<Controller>(builder: (controller) {
return SegmentedButton<String>(
segments: controller.selectionSegment,
selected: controller.selection,
onSelectionChanged: (Set<String> newSelection) {
controller.updateSelection(newSelection);
},
multiSelectionEnabled: true,
showSelectedIcon: true,
);
})
useMaterial3: true,
toggleButtonsTheme: const ToggleButtonsThemeData(
selectedColor: Colors.orange,
fillColor: Colors.transparent,
borderWidth: 0,
borderColor: Colors.transparent,
)), List<bool> get isSelectedList
{
return List.generate(2, (index) => selectedIndex == index);
}
selectIndex(int index) {
selectedIndex = index;
update();
} GetBuilder<ActionController>(builder: (controller) {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ToggleButtons(
renderBorder: false,
isSelected: controller.isSelectedList,
onPressed: (int index) => controller.updateSelectIndex(index),
constraints: BoxConstraints.expand(width: size.width * 0.45),
children: [
ToggleButtonViewWidget( controller: controller, index: 0,title: 'Option1',),
ToggleButtonViewWidget( controller: controller, index: 1,title: 'Option2',)
],
),
Divider(
height: size.height * 0.001,
thickness: 1,
color: Colors.grey,
),
SizedBox(
height: size.height * 0.014,
),
if (controller.selectedIndex == 0)
const Text(' widget 1')
else
const Text(' widget 2')
],
);
})
class ToggleButtonViewWidget extends StatelessWidget {
const ToggleButtonViewWidget({
super.key,
required this.controller,
required this.index,
required this.title,
});
final String title;
final ActionController controller;
final int index;
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final textTheme = Theme.of(context).textTheme;
return Column(
children: [
TextButton(
onPressed: () {
controller.updateSelectIndex(index);
},
child: Text(
title,
style: textTheme.headlineSmall!.copyWith(
color: controller.selectedIndex == index ? Colors.orange : null,
),
),
),
Container(
height: size.height * 0.003,
width: controller.selectedIndex == index ? 97 : 0,
decoration: buildBoxDecoration(),
),
],
);
}
}BoxDecoration buildBoxDecoration() {
return const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(100), topRight: Radius.circular(100)),
color: Colors.orange,
);
} child: GetBuilder<MyController>(
builder: (controller) {
return Badge(
addClick: () {
myController.add();
},
subClick: () {
myController.sub();
},
);
},
),class Badge extends StatelessWidget {
final Function addClick;
final Function subClick;
const Badge({
Key? key,
required this.addClick,
required this.subClick,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final myController = Get.find<MyController>();
return Container(
color: Colors.grey,
child: Stack(
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: myController.add,
icon: const Icon(Icons.add_circle),
),
IconButton(
onPressed: myController.sub,
icon: const Icon(Icons.remove_circle),
),
],
),
if (myController.list.isNotEmpty)
Positioned(
top: 0,
right: 3,
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text(
Get.find<MyController>().list.length.toString(),
style: const TextStyle(color: Colors.white),
),
),
),
],
),
);
}
}using List to counting the number in Badges.
class MyController extends GetxController {
List<String> list = [];
void add() {
list.add(' ');
update();
}
void sub() {
if (list.isNotEmpty) {
list.removeLast();
update();
}
}
}progressIndicatorTheme: const ProgressIndicatorThemeData(
linearMinHeight: 2.0,
color: Colors.white,
), GetBuilder<controller>(
builder: (controller) {
return LinearProgressIndicator(
color: Colors.white,
value: controller.progress,
);
},
),
ElevatedButton(
onPressed: () async {
await controller.startProgress();
},
child: const Text('test'),
), double progress = 0.0;
int totalSteps = 100;
Future<void> startProgress() async {
for (int currentStep = 0; currentStep < totalSteps; currentStep++) {
await Future.delayed(const Duration(milliseconds: 100));
progress = (currentStep / totalSteps).toDouble();
update();
}
}
progressIndicatorTheme: const ProgressIndicatorThemeData(
linearMinHeight: 2.0,
color: Colors.white,
), GetBuilder<controller>(
builder: (controller) {
return CircularProgressIndicator(
color: Colors.white,
value: controller.progress,
);
},
),
ElevatedButton(
onPressed: () async {
await controller.startProgress();
},
child: const Text('test'),
),
double progress = 0.0;
int totalSteps = 100;
Future<void> startProgress() async {
for (int currentStep = 0; currentStep < totalSteps; currentStep++) {
await Future.delayed(const Duration(milliseconds: 100));
progress = (currentStep / totalSteps).toDouble();
update();
}
}
We recommend using the 'SnackBarWidget' if you have only one design.
snackBarTheme: const SnackBarThemeData(
actionTextColor: Colors.white,
backgroundColor: Colors.white,
contentTextStyle: TextStyle(color: Colors.white),
elevation: 20
), Get.snackbar(
'Title',
'test',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.white,
snackStyle: SnackStyle.FLOATING,
);We recommend using the "GlobalSnackBar" class throughout the entire project, especially if there are various designs for SnackBars such as warning, error, and success messages.
snackBarTheme: const SnackBarThemeData(
actionTextColor: Colors.white,
backgroundColor: Colors.transparent,
contentTextStyle: TextStyle(color: Colors.black),
elevation: 20
),class GlobalSnackBar {
const GlobalSnackBar();
static warningSnackBar(
String message,
) {
Get.snackbar('', '',
titleText: WarningSnackBar(
message: message,
),
snackStyle: SnackStyle.FLOATING,
snackPosition: SnackPosition.BOTTOM,
barBlur: 0.0);
}
static errorSnackBar(
String message,
) {
Get.snackbar('', '',
titleText: ErrorSnackBar(
message: message,
),
snackStyle: SnackStyle.FLOATING,
snackPosition: SnackPosition.BOTTOM,
barBlur: 0.0);
}
static questionSnackBar(
String message,
) {
Get.snackbar('', '',
titleText: QuestionSnackBar(
message: message,
),
snackStyle: SnackStyle.FLOATING,
snackPosition: SnackPosition.BOTTOM,
barBlur: 0.0);
}
static successSnackBar(
String message,
) {
Get.snackbar('', '',
titleText: SuccessSnackBar(
message: message,
),
snackStyle: SnackStyle.FLOATING,
snackPosition: SnackPosition.BOTTOM,
barBlur: 0.0);
}
} dialogTheme: const DialogTheme(
backgroundColor: Colors.white,
shadowColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
)
),
)AlertDialog(
title: const Text('AlertDialog Title'),
content: const SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('This is a demo alert dialog.'),
Text('Would you like to approve of this message?'),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('Approve'),
onPressed: () {
Get.back();
},
),
],
)Dialog(
child: SizedBox(
height: 150,
child: Column(
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
onPressed: () {
Get.back();
},
icon: const Icon(Icons.close_outlined)),
)
],
),
)
) useMaterial3: true,
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: Colors.white,
elevation: 1.0,
modalElevation: 1.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(topRight: Radius.circular(28.0),
topLeft: Radius.circular(28.0),
)
),
)ElevatedButton(
child: const Text('showBottomSheet'),
onPressed: () {
showBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return SizedBox(
height: 200,
width: Get.width,
child: const Column(
children: [
Text('Bottom Sheet'),
],
),
);
},
);
},
),
ElevatedButton(
child: const Text('showModalBottomSheet'),
onPressed: () {
showModalBottomSheet<void>(
showDragHandle:true,
useSafeArea : true,
context: context,
builder: (BuildContext context) {
return const SizedBox(
height: 200,
child: Column(
children:[
Text('Modal Bottom Sheet'),
],
),
);
},
);
},
),
splashColor: Colors.blue.withAlpha(30),
cardTheme: const CardTheme(
elevation: 0.0,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
),
),Card(
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () {
},
child: const SizedBox(
width: 390,
height: 100,
child: Text('A card that can be tapped'),
),
),
) dividerColor: greyCoffeeColor,const Divider(
thickness: 2,
), listTileTheme: const ListTileThemeData(
textColor: Colors.black,
),const ListTile(
leading: CircleAvatar(child: Text('B')),
title: Text('Headline'),
subtitle: Text(
'Longer supporting text to demonstrate how the text.'),
),GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.8,
mainAxisSpacing: 8.0,
crossAxisSpacing: 10.0,
),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 4,
itemBuilder: (_, index) {
return Container(
color: Colors.grey,
);
},
);
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ListView.builder(
shrinkWrap: true,
itemCount: 3,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Container(
color: Colors.grey,
height: 150,
width: 150,
),
);}),
); appBarTheme: const AppBarTheme(
titleTextStyle: TextStyle(
fontFamily: 'Mona-Sans',
fontWeight: FontWeight.w800,
fontSize: 20,
color: Colors.orange),
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.dark, // For Android (dark icons)
),
elevation: 0.5,
backgroundColor: Colors.white,
iconTheme: IconThemeData(color: Colors.grey)),class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
const AppBarWidget({super.key});
@override
Size get preferredSize => const Size.fromHeight(60);
@override
Widget build(BuildContext context) {
return AppBar(
leading: IconButton(
onPressed: () {},
icon: const Icon(Icons.person),
),
centerTitle: true,
title: const Text('Title'),
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.menu),
),
],
);
}
} bottomAppBarTheme: const BottomAppBarTheme(
color: Colors.white,
elevation: 0.0,
height: 12,
shadowColor: Colors.black,
// shape: storage
),BottomAppBar(
child: Row(
children: [
IconButton(
tooltip: 'Search',
icon: const Icon(Icons.search),
onPressed: () {},
),
IconButton(
tooltip: 'Favorite',
icon: const Icon(Icons.favorite),
onPressed: () {},
),
],
),
) navigationBarTheme: NavigationBarThemeData(
backgroundColor: Colors.white,
elevation: 0.0,
shadowColor: Colors.grey.shade400,
indicatorColor: Colors.blue)NavigationBar(
selectedIndex: controller.pageIndex.value,
onDestinationSelected: (int index) {
controller.changeIndex(index);
},
destinations: [
NavigationDestination(icon: Icon(Icons.abc), label: 'option1'),
NavigationDestination(icon: Icon(Icons.abc), label: 'option2'),
NavigationDestination(icon: Icon(Icons.abc), label: 'option3')
],
),
body: <Widget>[
Container(
alignment: Alignment.center,
child: const Text('Page 1'),
),
Container(
alignment: Alignment.center,
child: const Text('Page 2'),
),
Container(
alignment: Alignment.center,
child: const Text('Page 3'),
),
][controller.pageIndex],var pageIndex = 0;
changeIndex(int index) {
pageIndex = index;
}navigationDrawerTheme: NavigationDrawerThemeData(
backgroundColor: Colors.blue.shade200,
tileHeight: 12,
shadowColor: Colors.black,
indicatorColor: Colors.blue.shade50,
indicatorShape: storage,
labelTextStyle: MaterialStatePropertyAll(TextStyle(fontFamily: fontFamily))
) Scaffold(
drawer: Drawer(
child: ListView(
children: [
DrawerHeader(child: Text('Header')),
ListTile(
title: Text('option1'),
onTap: () {},
),
ListTile(
title: Text('option2'),
onTap: () {},
),
],
),
),
),navigationRailTheme: NavigationRailThemeData(
backgroundColor: Colors.white,
elevation: 0,
minWidth: 12,
indicatorColor: Colors.blue,
useIndicator: true
)Row(
children: [
NavigationRail(
destinations: <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('First'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.book),
label: Text('Second'),
),
],
selectedIndex: controller.index,
onDestinationSelected: (int index) {
controller.changeIndex(index);
},
)
],
) var index = 0;
changeIndex(int index) {
index = index;
}tabBarTheme: TabBarTheme(
labelColor: Colors.white,
indicatorColor: Colors.blue.shade800,
overlayColor: MaterialStateProperty.all(Colors.blue.shade300),
)DefaultTabController(
initialIndex: 1,
length: 3,
child:Scaffold(
appBar: AppBar(
bottom:TabBar(
tabs:[
Tab(text:'Car'),
Tab(text:'Plane'),
Tab(text:'Boat'),
],
),
),
body:TabBarView(
children:[
CarWidget(),
PlaneWidget(),
BoatWidget(),
],
),
),
), checkboxTheme: CheckboxThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
checkColor: MaterialStateProperty.all(Colors.white),
fillColor: MaterialStateProperty.all(Colors.transparent),
), GetBuilder<Controller>(builder: (controller) {
return Checkbox(
activeColor: Colors.orange,
value: controller.isCheck,
onChanged: (bool? value) {
controller.updateCheckBox(value!);
},
);
});
bool isCheck = false;
void updateCheckBox(bool value) {
isCheck = value;
update();
} chipTheme: ChipThemeData(
secondarySelectedColor: whiteColor,
secondaryLabelStyle: const TextStyle(color: primaryOrangeColor),
backgroundColor: whiteColor,
disabledColor: whiteColor,
selectedColor: whiteColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: const BorderSide(
color: greySteelColor,
),
),
),ChoiceChip(
selectedColor: whiteColor,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
label: const Text('item'),
// selected chip value
selected: _value == index,
// onSelected method
onSelected: (bool selected) {},
),theme: ThemeData( // useMaterial3: true, primarySwatch: Colors.green, datePickerTheme: const DatePickerThemeData( headerForegroundColor: Colors.black, headerBackgroundColor: Colors.green, backgroundColor: Colors.white, rangePickerBackgroundColor: Colors.white, rangePickerHeaderBackgroundColor: Colors.green, rangePickerHeaderForegroundColor: Colors.black, ), ),
### Component
```bash
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
controller.selectedDateRange == null
? const Center(
child: Text('Press the button to show the picker'),
)
: Padding(
padding: const EdgeInsets.all(30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
controller.selectedDateRange?.start == controller.selectedDateRange?.end
?
// select only one date
Text(
" date: ${controller.selectedDateRange?.start.toString().split(' ')[0]}",
style: const TextStyle(
fontSize: 24, color: Colors.blue),
)
:
// select multible date
Column(
children: [
Text(
"Start date: ${controller.selectedDateRange?.start.toString().split(' ')[0]}",
style: const TextStyle( fontSize: 24, color: Colors.blue), ),
Text( "End date: ${controller.selectedDateRange?.end.toString().split(' ')[0]}",
style: const TextStyle( fontSize: 24, color: Colors.red)),
],
),
],
),
),
FloatingActionButton(
onPressed: () {
controller.dateTimeRangePicker(context);
},
child: const Icon(Icons.date_range),
)
]);
DateTimeRange? selectedDateRange;
void updateNewDateRange(DateTimeRange? newDate)
{
selectedDateRange = newDate;
update();
}
Future dateTimeRangePicker() async
{
DateTimeRange? picked = await showDateRangePicker(
context: Get.context!,
firstDate: DateTime.now(),
lastDate: DateTime(DateTime.now().year + 5),
// if you need to control size of datapicker use this :
// builder: (context, child) {
// return Center(
// child: ConstrainedBox(
// constraints: const BoxConstraints(maxWidth: 400.0, maxHeight: 560),
// child: child,
// ),
// );
// },
);
if (picked == null) return;
updateNewDateRange(picked);
// if you want use time picker use this :
final initialTime = picked == null
? TimeOfDay(hour: DateTime.now().hour, minute: DateTime.now().minute)
: TimeOfDay(
hour: picked.duration.inHours, minute: picked.duration.inMinutes);
// ignore:
final newTime = await showTimePicker(
context: Get.context!,
initialTime: initialTime,
);
if (newTime == null) return;
DateTime(
picked.start.year, picked.start.month, newTime.hour, newTime.minute);
}This is with Package:
syncfusion_flutter_datepicker: ^22.1.37class CalendarWidget extends StatelessWidget {
final DateRangePickerSelectionChangedCallback? onSelectionChanged;
final DateRangePickerSelectionMode selectionMode;
const CalendarWidget(
{super.key,
required this.onSelectionChanged,
required this.selectionMode});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return SfDateRangePicker(
minDate: DateTime.now().subtract(const Duration(days: -1)),
view: DateRangePickerView.month,
monthViewSettings: DateRangePickerMonthViewSettings(
viewHeaderStyle: DateRangePickerViewHeaderStyle(
textStyle: textTheme.bodySmall?.copyWith(fontSize: 12))),
headerStyle: DateRangePickerHeaderStyle(
textStyle: textTheme.displaySmall,
),
monthCellStyle: DateRangePickerMonthCellStyle(
todayTextStyle: textTheme.displaySmall,
textStyle: textTheme.headlineLarge?.copyWith(fontSize: 12)),
yearCellStyle: DateRangePickerYearCellStyle(
todayTextStyle: textTheme.displaySmall,
textStyle: textTheme.displaySmall),
todayHighlightColor: primaryOrangeColor,
endRangeSelectionColor: primaryOrangeColor,
rangeSelectionColor: primaryOrangeColor,
selectionColor: primaryOrangeColor,
startRangeSelectionColor: primaryOrangeColor,
onSelectionChanged: onSelectionChanged,
selectableDayPredicate: controller.disableDate,
selectionMode: selectionMode,
);
}
} String? startDate;
String? endDate;
String? rangeDate = DateFormat('yyyy-MM-dd')
.format(DateTime.now().toLocal().subtract(const Duration(days: -1)));
void onSelectionChanged(DateRangePickerSelectionChangedArgs args) {
if (args.value is PickerDateRange) {
if (args.value.endDate == null ||
args.value.startDate == args.value.endDate) {
startDate =
DateFormat('yyyy-MM-dd').format(args.value.startDate).toString();
endDate =
DateFormat('yyyy-MM-dd').format(args.value.startDate).toString();
rangeDate = startDate;
} else {
startDate = DateFormat('yyyy-MM-dd').format(args.value.startDate);
endDate = DateFormat('yyyy-MM-dd').format(args.value.endDate);
rangeDate = '$startDate - $endDate';
}
}
update();
}
bool disableDate(DateTime day) {
if ((day.isAfter(
DateTime.now().subtract(
const Duration(days: 0),
),
))) {
return true;
}
return false;
} dropdown_button2: ^2.3.9DropdownButtonHideUnderline(child:
DropdownButton2(
alignment: Alignment.centerRight,
isExpanded: true,
hint: const Text( "Chose ",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w200,
color:Colors.grey ,
),
),
items: controller.item
.map((item) => DropdownMenuItem<String>(
value: item,
child: Align(
alignment: Alignment.centerRight,
child: Text(
item,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w200,
color:Colors.black ,
),
overflow: TextOverflow.ellipsis,
),
),
))
.toList(),
value: controller.selectedValue,
onChanged: (value) {
controller.updateSelectedValue(value.toString());
},
iconStyleData: const IconStyleData(
icon: Visibility(
visible: true,
child: Icon(Icons.keyboard_arrow_down),
),
),
buttonStyleData: ButtonStyleData(
height: size.height * 0.06,
width: size.width * 0.9,
padding: const EdgeInsets.only(left: 14, right: 14),
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(5),
topLeft: Radius.circular(5),
bottomLeft: Radius.circular(0),
bottomRight: Radius.circular(0)),
color:Colors.white ,
),
dropdownStyleData: DropdownStyleData(
decoration: BoxDecoration(
color:Colors.grey ,
maxHeight: size.height * 0.9,
width: size.width * 0.9,
),
menuItemStyleData: const MenuItemStyleData(
height: 40,
),
)); List<String> items = ["Option1", "Opchion2", "Other"];
UnmodifiableListView<String> get item => UnmodifiableListView(items);
String? selectedValue;
void updateSelectedValue(String value) {
selectedValue = value;
update();
} GetBuilder<DropdownController>(builder: (_) {
return Container(
width: 350,
child: DropdownButtonFormField<String>(
validator: (value) {
if (value == null) {
return 'Please select Value';
}
return null;
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
hint: Text('Enter Your Value'),
isExpanded: true,
icon: Icon(Icons.arrow_downward_outlined),
elevation: 4,
value: dropdownController.dropdownValue,
onChanged: (String? newValue) {
dropdownController.dropdownValue = newValue!;
},
onSaved: (value) {
dropdownController.dropdownValue = value.toString();
},
items: <String>['Value1', 'Value2', 'Value3', 'Value4']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
);
}), return GetBuilder<DropdownController>(builder: (_) {
return DropdownMenu<String>(
hintText: 'Select',
trailingIcon: Icon(Icons.arrow_drop_down_outlined),
leadingIcon: Icon(Icons.calendar_month),
width: 250,
errorText: dropdownController.isOpen ? 'Error text' : null,
onSelected: (String? value) {
dropdownController.dropdownValue = value!;
},
dropdownMenuEntries: dropdownController.list
.map<DropdownMenuEntry<String>>((String value) {
return DropdownMenuEntry<String>(value: value, label: value);
}).toList(),
);
}); String? dropdownValue;
bool isOpen = false;
List<String> list = <String>['Value1', 'Value2', 'Value3', 'Value4'];
radioTheme: RadioThemeData(
overlayColor: MaterialStateProperty.all(Colors.grey),
fillColor: MaterialStateProperty.all(Colors.transparent),
), Column(
children: [
RadioButton(text: 'Option1',newValue: 1,),
RadioButton(text: 'Option2',newValue: 2,),
RadioButton(text: 'Option3',newValue: 3,),
],
)class RadioButton extends StatelessWidget {
final String text;
final int newValue;
final selectionController = Get.put(SelectionController());
RadioButton({
super.key,
required this.newValue,
required this.text,
});
@override
Widget build(BuildContext context) {
return GetBuilder<SelectionController>(builder: (controller)
{
return Row(
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
value: newValue,
groupValue: controller.radioButtonId,
onChanged: (value) {
controller.setSelectedFoodId(value = newValue);
controller.updateSelected(text);
},
activeColor: Colors.orange,
),
),
const SizedBox(
width: 10,
),
Text(
text,
),
],
);
});
}
} int radioButtonId = 0;
String selectedRadioButton = '';
void updateSelected(String value) {
selectedRadioButton = value;
update();
}
void setSelectedFoodId(int radioButtonIdValue) {
radioButtonId = radioButtonIdValue;
update();
} sliderTheme: const SliderThemeData(
thumbColor: Colors.black,
activeTrackColor: Colors.white,
trackHeight: 5,
) Slider(
value: controller.quality,
max: 100,
divisions: 5,
onChanged: (double value) {
controller.setquality(value);
},
) double quality = 0.0;
void setquality(double value) {
quality = value;
update();
}
switchTheme: SwitchThemeData(
thumbColor: MaterialStateProperty.all(whiteColor),
trackColor: MaterialStateProperty.all(blackColor),
trackOutlineColor: MaterialStateProperty.all(primaryPurpleColor),
), Switch(
value: controller.isOpen,
onChanged: (value) {
controller.changeSwitch(value);
},
), bool isOpen = false;
void changeSwitch(bool value) {
isOpen = value;
update();
}Theme"
Theme"Component"
Component"Controller"
Controller" inputDecorationTheme: const InputDecorationTheme(
contentPadding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 10),
fillColor: Color.fromRGBO(55, 64, 76, 0.05),
prefixIconColor: Color.fromRGBO(55, 64, 76, 0.05),
suffixIconColor: Color.fromRGBO(55, 64, 76, 0.05),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
width: 2,
color: Color.fromRGBO(1, 1, 1, 0.1),
),
),
border: UnderlineInputBorder(
borderSide: BorderSide(
width: 2,
),
),
enabledBorder: UnderlineInputBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(5),
topLeft: Radius.circular(5),
bottomLeft: Radius.circular(0),
bottomRight: Radius.circular(0)),
borderSide: BorderSide(
width: 2,
color: Color.fromRGBO(1, 1, 1, 0.1),
),
),
filled: true,
),class TextFieldWidget extends StatelessWidget {
final TextEditingController controller;
final FormFieldValidator? validator;
final Widget? prefixIcon, suffixIcon;
final String? text , hintText;
final bool? enabled;
final TextInputType? keyboardType;
final ValueChanged<String>? onChanged;
final Iterable<String>? autofillHints;
final List<TextInputFormatter>? inputFormatters;
final int? maxLine , maxLength;
const TextFieldWidget({
required this.controller,
this.validator,
this.onChanged,
this.prefixIcon,
this.suffixIcon,
this.enabled,
this.hintText,
this.text,
this.keyboardType,
Key? key,
this.autofillHints,
this.inputFormatters,
this.maxLine,
this.maxLength,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final textTheme = Theme.of(context).textTheme;
return Column(
children: [
Padding(
padding: const EdgeInsets.only(
right: 16,
),
child: Align(
alignment: Alignment.topRight,
child: text == null
? null
: Text(
'$text',
style: textTheme.bodyMedium,
),
),
),
const SizedBox(
height: 8,
),
TextFormField(
inputFormatters: inputFormatters,
autofillHints: autofillHints,
enabled: enabled,
style: textTheme.bodySmall,
controller: controller,
onChanged: onChanged,
maxLines: maxLine,
keyboardType: keyboardType,
cursorColor: Colors.black,
validator: validator,
maxLength: maxLength,
decoration: InputDecoration(
hintText: hintText,
hintStyle: textTheme.bodySmall,
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
),
),
],
);
}
}
TextFieldWidget(
suffixIcon:const Icon(Icons.remove_red_eye_outlined),
prefixIcon:const Icon(Icons.person),
hintText: 'Hint Text',
controller: textEditingController,
maxLength: 20,
validator: (value) {
// validation for test for GetUtils
if (!GetUtils.isLengthBetween(value, 2, 10)) {
return 'Letters must be more than two letters and less than 10';
}
return null;
},
),
),Often we use a search bar in our app to perform autocomplete-style searches via network calls. In such a case, it is not feasible to perform a network request with every character that the user types/changes; that would result in wasted network calls. it is better to search once the user pauses or stops typing. This can be achieved using a Debouncer, which uses a timer to delay the search request until it stops receiving text changes for half a second, or any duration that you set.
You have to creat this class :
class DelaySearch {
final int milliseconds;
VoidCallback? action;
Timer _timer;
DelaySearch({required this.milliseconds})
: _timer = Timer(Duration.zero, () {});
run(VoidCallback action) {
_timer.cancel();
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
}final delaySearch = DelaySearch(milliseconds: 500);
TextFieldWidget(
onChanged: (value) {
delaySearch.run(() {
searchController
.search(value);
});
},
hintText: 'Search Here',
suffixIcon: const Icon(Icons.search),
controller:
searchController.searchEditingController),
), TextEditingController searchEditingController = TextEditingController();
List<Model> searchList = [];class AuthController extends GetxController {
final AuthService service = AuthService();
final TextEditingController nameController = TextEditingController();
final TextEditingController emailController = TextEditingController();
final TextEditingController phoneNumberController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final TextEditingController firstDigit = TextEditingController();
final TextEditingController secondDigit = TextEditingController();
final TextEditingController thirdDigit = TextEditingController();
final TextEditingController fourthDigit = TextEditingController();
final TextEditingController fifthDigit = TextEditingController();
final TextEditingController sixthDigit = TextEditingController();
bool isTimerDone = false;
bool isValid = false;
num otpDigitCount = 0;
String otp = '';
bool isVisibility = false;
void visibility() {
isVisibility = !isVisibility;
update();
}
void updateTimer(bool value) {
isTimerDone = value;
update();
}
void updateValidate(bool value) {
isValid = value;
update();
}
void updateOtp(int value, String opr) {
if (opr == '+') {
otpDigitCount += value;
} else if (opr == '-') {
otpDigitCount -= 1;
}
update();
}
void updateAutofill(String value) {
otp = value;
firstDigit.text = value[0];
secondDigit.text = value[1];
thirdDigit.text = value[2];
fourthDigit.text = value[3];
fifthDigit.text = value[4];
sixthDigit.text = value[5];
update();
}
void clearOTPController() {
firstDigit.clear();
secondDigit.clear();
thirdDigit.clear();
fourthDigit.clear();
fifthDigit.clear();
sixthDigit.clear();
update();
}
}final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(
children: [
TextFieldWidget(
inputFormatters: [
FilteringTextInputFormatter.deny('+966')
],
autofillHints: const [AutofillHints.telephoneNumber],
text: 'Phone Number',
prefixIcon: const Icon(
Icons.phone,
),
keyboardType: TextInputType.number,
controller: authController.phoneNumberController,
hintText: '5xxxxxxxx',
),
ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
}
},
child: Text('Enter'),
),
],
)),final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Center(
child: Column(
children: [
TextFieldWidget(
maxLine: 1,
controller: authController.emailController,
prefixIcon: const Icon(
Icons.email_outlined,
),
),
TextFieldWidget(
maxLine: 1,
controller: authController.passwordController,
obscureText :
authController.isVisibility ? false : true,
suffixIcon: IconButton(
onPressed: () {
authController.visibility();
},
icon: authController.isVisibility
? const Icon(
Icons.visibility_off,
)
: const Icon(
Icons.visibility,
),
),
prefixIcon: const Icon(
Icons.lock,
),
),
ElevatedButton(onPressed: (){
if (_formKey.currentState!.validate()) {
// The method here
}
}, child: const Text('Enter'))
],
),
),
),
final _formKey = GlobalKey<FormState>();
Form(
key: formKey,
child: Column(
TextFieldWidget(
controller: authController.emailController,
obscureText: false,
prefixIcon: const Icon(Icons.email),
),
TextFieldWidget(
controller: authController.nameController,
maxLine: 1,
obscureText: false,
validator: (value) {
if (value.toString().isEmpty) {
return 'Enter your Name';
} else if (!RegExp(Validation.validationName)
.hasMatch(value)) {
return "Please enter a correct Name";
} else if (value.toString().length < 2) {
return 'Your Name should be at least 2 long ';
} else {
return null;
}
},
prefixIcon: const Icon(Icons.person),
),
GetBuilder<AuthController>(builder: (authController) {
return Column(
children: [
TextFieldWidget(
controller: authController.passwordController,
maxLine: 1,
obscureText:
authController.isVisibility ? false : true,
suffixIcon: IconButton(
onPressed: () {
authController.visibility();
},
icon: authController.isVisibility
? const Icon(
Icons.visibility_off,
)
: const Icon(
Icons.visibility,
)),
prefixIcon: const Icon(
Icons.lock,
),
),
TextFieldWidget(
controller:
authController.checkPasswordController,
maxLine: 1,
obscureText:
authController.isVisibility ? false : true,
validator: (value) {
if (value.toString() !=
authController.passwordController.text) {
return 'Your password not corrected';
} else {
return null;
}
},
suffixIcon: IconButton(
onPressed: () {
authController.visibility();
},
icon: authController.isVisibility
? const Icon(
Icons.visibility_off,
)
: const Icon(
Icons.visibility,
)),
prefixIcon: const Icon(
Icons.lock,
),
),
],
);
}),
ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
}
},
child: Text('Enter'),
),
],
)),class OtpScreen extends StatelessWidget {
final _formKey = GlobalKey<FormState>();
OtpScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(),
body: GetBuilder<AuthController>(
builder: (authController) {
final otpDigits = [
authController.firstDigit,
authController.secondDigit,
authController.thirdDigit,
authController.fourthDigit,
authController.fifthDigit,
authController.sixthDigit,
];
return SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: [
if (authController.isValid)
const Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'You entered an incorrect number',
style: TextStyle(color: errorColor),
),
)),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
SizedBox(
height: 40,
width: size.width * 0.95,
child: Directionality(
textDirection: TextDirection.ltr,
child: Form(
key: _formKey,
child: Center(
child: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: otpDigits.length,
itemBuilder: (context, index) {
return OTPTextField(
isFocused: true,
textController: otpDigits[index],
index: index,
);
},
),
),
),
),
),
SizedBox(
height: size.height * 0.025,
),
ElevatedButton(
onPressed: authController.otp.length ==
otpDigits.length
? () async {
authController.otp !=
otpDigits
.map((e) => e.text)
.toList()
.join()
? authController.updateValidate(true)
: authController.updateValidate(false);
if (!authController.isValid) {}
}
: null,
child: const Text('Next'))
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
authController.isTimerDone
? TextButton(
style: TextButton.styleFrom(
disabledBackgroundColor: blackColor,
foregroundColor: blackColor,
),
onPressed: () async {},
child: const Text('press here'))
: const Padding(
padding: EdgeInsets.all(2),
child: Text(
'press here',
style: TextStyle(color: greyColor),
),
),
const Text('To send the code again')
],
),
],
),
);
},
),
);
}
}
class OTPTextField extends StatelessWidget {
final _authController = Get.find<AuthController>();
final TextEditingController textController;
final bool isFocused;
final int index;
OTPTextField({
super.key,
required this.textController,
required this.isFocused,
required this.index,
});
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Container(
margin: EdgeInsets.symmetric(horizontal: size.width * 0.016),
height: size.height * 0.0563,
width: size.width*0.122,
child: TextFormField(
autofocus: isFocused,
autofillHints: const [AutofillHints.oneTimeCode],
inputFormatters: [
if (index != 0) LengthLimitingTextInputFormatter(1),
FilteringTextInputFormatter.allow(RegExp('[0-9]'))
],
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
controller: textController,
cursorColor: blackColor,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
borderSide: BorderSide(
color: _authController.isValid ? errorColor : whiteColor,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
borderSide: BorderSide(
color: _authController.isValid ? errorColor : whiteColor,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
borderSide: BorderSide(
color: _authController.isValid ? errorColor : whiteColor,
),
),
),
onChanged: (value) {
if (value.isNotEmpty) {
if (value.length == 6) {
_authController.updateAutofill(value);
FocusScope.of(context).unfocus();
} else {
if (index == 0) {
_authController.firstDigit.text =
value.substring(value.length - 1);
}
_authController.updateOtp(value.length, '+');
FocusScope.of(context).nextFocus();
}
} else {
_authController.updateOtp(value.length, '-');
FocusScope.of(context).previousFocus();
}
},
),
);
}
}class SkeletonController extends GetxController with GetSingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation gradientPosition;
@override
onInit(){
super.onInit();
_animationSetup();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _animationSetup() {
_animationController = AnimationController(duration: const Duration(milliseconds: 1500), vsync: this);
gradientPosition = Tween<double>(
begin: -3,
end: 10,
).animate(
CurvedAnimation(parent: _animationController, curve: Curves.linear),
)..addListener(update);
_animationController.repeat();
}
}class Skeleton extends StatelessWidget {
final double height;
final double width;
final BorderRadiusGeometry? borderRadius;
const Skeleton({
Key? key,
this.height = 20,
this.width = 200,
this.borderRadius,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<SkeletonController>(builder: (skeletonController) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
borderRadius: borderRadius,
gradient: LinearGradient(
begin: Alignment(skeletonController.gradientPosition.value, 0),
end: const Alignment(-1, 0),
colors: const [greyPlatinumColor, greyMysticColor, greyLightColor],
),
),
);
});
}
}
Timer? countdownTimer;
bool enableButton = true;
bool isTimeOver = false
;
Duration myduration = const Duration(minutes: 1);
startTimer() {
countdownTimer =
Timer.periodic(const Duration(seconds: 1), (timer) => setCountDown());
update();
}
String strDigits(int n) => n.toString().padLeft(2, '0');
var hours = " ".obs;
var minutes = " ".obs;
var seconds = " ".obs;
setCountDown() {
const reduceBy = 1;
final seconds = myduration.inSeconds - reduceBy;
if (seconds < 0) {
countdownTimer!.cancel();
enableButton = true;
isTimeOver = true;
myduration = const Duration(minutes: 1);
} else {
myduration = Duration(seconds: seconds);
enableButton = false;
isTimeOver = false;
}
update();
}controller.hours.value =
controller.strDigits(controller.myduration.inHours.remainder(24));
controller.minutes.value =
controller.strDigits(controller.myduration.inMinutes.remainder(60));
controller.seconds.value =
controller.strDigits(controller.myduration.inSeconds.remainder(60));
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: controller.enableButton && !controller.isTimeOver || controller.enableButton && controller.isTimeOver
? () {
controller.enableButton = false;
controller.isTimeOver = false;
controller.startTimer();
}
: null,
child: Text('Start Timer')),
SizedBox(
height: 12,
),
Text(controller.enableButton && !controller.isTimeOver
? "Press the button to start the timer"
: controller.enableButton && controller.isTimeOver
? "Time's up"
: "${controller.hours.value.toString()}:${controller.minutes.value.toString()}:${controller.seconds.value.toString()}"),
],
),
),
);
The Iphone 14 screen size in pixels is 844 x 390 :
final size = MediaQuery.of(context).size;
size.width
2 = size.width * 0.005
4 = size.width * 0.01
6 = size.width * 0.015
8 = size.width * 0.02
10 = size.width * 0.025
12 = size.width * 0.03
14 = size.width * 0.035
16 = size.width * 0.040
18 = size.width * 0.045
20 = size.width * 0.05
22 = size.width * 0.055
24 = size.width * 0.06
26 = size.width * 0.065
28 = size.width * 0.07
30 = size.width * 0.075
32 = size.width * 0.080
380 = size.width * 0.97
size.height
2 = size.height * 0.0023
4 = size.height * 0.0047
6 = size.height * 0.007
8 = size.height * 0.0094
10 = size.height * 0.011
12 = size.height * 0.014
14 = size.height * 0.016
16 = size.height * 0.018
18 = size.height * 0.021
20 = size.height * 0.023
22 = size.height * 0.026
24 = size.height * 0.028
26 = size.height * 0.03
28 = size.height * 0.033
30 = size.height * 0.035
32 = size.height * 0.037
class Controller extends GetxController {
late PageController pageController;
Timer? timer;
List<String> adsList = <String>[AppImage.businessCardImage,AppImage.businessCardImage,AppImage.businessCardImage];
@override
void onInit() async
{
super.onInit();
pageController = PageController(initialPage: 0, viewportFraction: 0.797);
// await allAds();
}
@override
onReady() {
var currentIndex = 0;
timer = Timer.periodic
(const Duration(seconds: 4), (timer) {
if (pageController.hasClients) {
if (currentIndex < adsList.length - 1) {
currentIndex++;
pageController.nextPage(
duration: const Duration(milliseconds: 2000),
curve: Curves.ease,
);
} else {
if (pageController.hasClients) {
currentIndex = 0;
pageController.jumpToPage(0);
}
}
}
});
}class CardWidget extends StatelessWidget
{
final String adsModel;
CardWidget({
Key? key,
required this.adsModel,
}) : super(key: key);
@override
Widget build(BuildContext context)
{
final size = MediaQuery.of(context).size;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 10),
height: size.height * 0.149,
child: GestureDetector(
onTap: () {},
child: SizedBox(
height: size.height * 0.149,
child: Container(
width: size.width * 0.797,
decoration: BoxDecoration(
color: errorColor,
borderRadius: const BorderRadius.all(
Radius.circular(16),
),
image: DecorationImage(
image: AssetImage(AppImage.businessCardImage),
fit: BoxFit.cover,
),
),
),
),
),
);
}
}
--------------------------
class CardListWidget extends StatelessWidget
{
const CardListWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context)
{
final size = MediaQuery.of(context).size;
return SizedBox
(
height: size.height * 0.149,
child: GetBuilder<Controller>(
builder: (controller) {
return SizedBox(
height: size.height * 0.215,
child: PageView.builder
(
controller: controller.pageController,
scrollDirection: Axis.horizontal,
itemCount: controller.adsList.length,
itemBuilder: (context, index) {
var data = controller.adsList[index];
return SizedBox(
height: size.height * 0.149,
width: size.width * 0.797,
child: CardWidget(
adsModel: data,
),
);
},
),
);
},
),
);
}
}
https://github.com/The-Garage-Tech-Team/auth_crud_map_template
https://github.com/SirinK2/sqlite_template.git
https://github.com/SirinK2/api_http_request.git
- Sirin Algadi.
- Afnan Almohammdi.
- Ahlam Albalawi.
- Hanan Asiri.
- Joud Almahdi.
- Khairiah Alayyafi،
- Razan Ajohani.




































