added filter dynamic

This commit is contained in:
Komek Hayytnazarov 2023-03-28 15:13:08 +05:00
parent eac344a671
commit 4e8ef06b54
12 changed files with 372 additions and 299 deletions

View File

@ -11,6 +11,7 @@
"dili",
"fluttericon",
"fontello",
"Getx",
"harakteristiki",
"LTRB",
"roundcheckbox",

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../app.dart';
class CircleNumberWidget extends StatelessWidget {
final int number;
const CircleNumberWidget({
Key? key,
required this.number,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: HexColor('#EFF0F4'),
),
child: Text(
number.toString(),
style: new TextStyle(
color: ThemeColor.black,
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
),
);
}
}

View File

@ -1,6 +1,7 @@
library utils;
export 'calculate_aspect_ratio.dart';
export 'circle_number_widget.dart';
export 'constants.dart';
export 'device_info.dart';
export 'grid_view_load_more.dart';

View File

@ -1,7 +1,6 @@
import 'package:elektronika/app/core/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../data.dart';
import '../../app.dart';
class FilterApi {
static String className = 'FilterApi';
@ -132,11 +131,7 @@ class FilterApi {
end: 40000.0,
queryValue: '900,3000',
));
// String path = Constants.BASE_URL + 'categories/$categoryId/filters';
// final response = await HttpUtil().get(path: path);
// debugPrint('getFilters response $response');
return list;
} catch (e) {
debugPrint('ERROR: class: $className, method: $fnName');
@ -146,9 +141,43 @@ class FilterApi {
static Future<List<FilterOption>> getOptions(int page, int itemsPerPage, FilterModel model, String searchKey) async {
debugPrint('page:$page, model:${model.code}' ', id:${model.id}');
// Attribute (Brand, Size, Style, Material ...)
try {
List<FilterOption> options = [];
final String categoryId = getCategoryIdFromFilterableQueryParams();
String path = Constants.BASE_URL + 'attribute-options';
final Map<String, dynamic> params = {
'locale': await getLocale(),
'attribute_id': model.id,
'category': categoryId,
'limit': itemsPerPage,
'page': page,
'search': searchKey,
};
final response = await HttpUtil().get(path: path, queryParameters: params);
for (final option in response['data']) {
options.add(FilterOption.fromJson(option));
}
return options;
} catch (e) {
debugPrint('error : $e');
throw e;
}
}
static String getCategoryIdFromFilterableQueryParams() {
FilterController fc = Get.find();
final queryParams = fc.state.filterableQueryParams;
String categoryId = '1';
if (queryParams['category_id'] != null) {
categoryId = queryParams['category_id'].toString();
}
return categoryId;
}
}

View File

@ -1,3 +1,6 @@
/// [FilterModel] is used for filter screen
/// for example name is [Brand] options are [Mavi, Koton etc ...]
/// [selectedOptions] is selected by user.
class FilterModel {
late int id;
late String code;
@ -29,21 +32,24 @@ class FilterModel {
class FilterOption {
late int id;
late String adminName;
String? label;
String? image;
late String name;
late String label;
late String swatchValue;
late String queryValue;
FilterOption({
required this.id,
required this.adminName,
this.label,
this.image,
required this.name,
required this.label,
required this.swatchValue,
required this.queryValue,
});
FilterOption.fromJson(Map<String, dynamic> json) {
id = json['id'];
adminName = json['admin_name'];
name = json['admin_name'];
label = json['label'];
image = json['image'];
swatchValue = json['swatch_value'] ?? '';
queryValue = json['id'].toString();
}
}

View File

@ -51,8 +51,10 @@ class HomePage extends StatelessWidget {
),
),
controller.state.banners1.isNotEmpty
? Column(
children: [
AppTheme.appSizeDivider20H,
Padding(
padding: EdgeInsets.only(left: 16.w, right: 16.w),
child: controller.state.isLoading.value
@ -61,6 +63,9 @@ class HomePage extends StatelessWidget {
sliders: controller.state.banners1,
),
),
],
)
: SizedBox.shrink(),
AppTheme.appSizeDivider30H,
@ -75,6 +80,9 @@ class HomePage extends StatelessWidget {
),
),
controller.state.banners2.isNotEmpty
? Column(
children: [
AppTheme.appSizeDivider10H,
Padding(
padding: EdgeInsets.only(left: 16.w, right: 16.w),
@ -84,6 +92,9 @@ class HomePage extends StatelessWidget {
sliders: controller.state.banners2,
),
),
],
)
: SizedBox.shrink(),
AppTheme.appSizeDivider30H,

View File

@ -53,6 +53,7 @@ class FilterCard extends StatelessWidget {
context: context,
builder: (context) => BSChooseFilter(
model: model,
),
);
},
@ -85,7 +86,9 @@ class FilterCard extends StatelessWidget {
),
// Spacer(),
Expanded(
child: (model.selectedOptions.isNotEmpty || controller.state.selectedPriceFilter.value != null) ? _printSubCategory() : SizedBox.shrink(),
child: (model.selectedOptions.isNotEmpty || controller.state.selectedPriceFilter.value != null)
? _printSubCategory()
: SizedBox.shrink(),
),
],
),
@ -104,7 +107,7 @@ class FilterCard extends StatelessWidget {
final length = model.selectedOptions.length;
for (int i = 0; i < length; i++) {
final String name = model.selectedOptions[i].adminName;
final String name = model.selectedOptions[i].label;
list.add(_addText(i != length - 1 ? name + ', ' : name));
}
}

View File

@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../../library/multi_selector/multi_select_bottom_sheet.dart';
import '../../../../../library/multi_selector/multi_select_item.dart';
import '../../../../../library/library.dart';
import '../../../../data/models/models.dart';
import '../controller.dart';
import 'b_s_price_filter.dart';
@ -14,19 +13,19 @@ class BSChooseFilter extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint('model ${model.options.length}');
debugPrint('model ${model.code}');
return GetBuilder<FilterController>(
// init: FilterController(),
builder: (fc) => model.code != 'price'
? MultiSelectBottomSheet(
initialValue: model.selectedOptions,
title: model.name,
items: model.options.map((option) => MultiSelectItem<FilterOption>(option, option.adminName)).toList(),
builder: (fc) {
if (model.code == 'price') {
return BSPriceFilter(title: model.name);
} else /* if (model.code == 'brand') { */
return MultiSelectBottomSheetGetX(
model: model,
onConfirm: (selectedItems) => fc.onMultipleSelectionConfirmed(model, selectedItems),
onClear: (selectedItems) => fc.onMultipleSelectionCleared(model),
separateSelectedItems: true,
)
: BSPriceFilter(title: model.name),
);
},
);
}
}

View File

@ -3,13 +3,12 @@ library library;
export 'currency_text_input_formatter.dart';
export 'custom_icons.dart';
export 'dash_separator.dart';
export 'dash_separator.dart';
export 'fade_in_indexed.dart';
export 'gradient_progress.dart';
export 'multi_selector/multi_select_action.dart';
export 'multi_selector/multi_select_bottom_sheet.dart';
export 'multi_selector/multi_select_bottom_sheet_getx.dart';
export 'multi_selector/multi_select_item.dart';
export 'outline_gradient_button.dart';
export 'password_strength/password_strength.dart';
export 'tab_bar_material_indicator.dart';
export 'tab_bar_material_indicator.dart';

View File

@ -52,7 +52,7 @@ class MultiSelectController extends GetxController
getFirstData = true;
// convert data
var newItems = result.map((option) => MultiSelectItem<FilterOption>(option, option.adminName)).toList();
var newItems = result.map((option) => MultiSelectItem<FilterOption>(option, option.label)).toList();
debugPrint('newItems ${newItems.length}');
for (var item in newItems) {
item.selected = this.selectedValues.where((element) => element.id == item.value.id).isNotEmpty;

View File

@ -1,253 +1,253 @@
// import 'package:animate_do/animate_do.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter/services.dart';
// import 'package:flutter_screenutil/flutter_screenutil.dart';
// import 'package:flutter_svg/flutter_svg.dart';
// import 'package:get/get.dart';
import 'package:animate_do/animate_do.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
// import '../../../app/app.dart';
// import 'controller.dart';
// import 'multi_select_item.dart';
import '../../../app/app.dart';
import 'controller.dart';
import 'multi_select_item.dart';
// class MultiSelectBottomSheetGetX extends StatelessWidget {
// /// The list of selected values before interaction.
// // final List<FilterOption> initialValue;
class MultiSelectBottomSheetGetX extends StatelessWidget {
/// The list of selected values before interaction.
// final List<FilterOption> initialValue;
// /// The text at the top of the BottomSheet.
// // final String title;
/// The text at the top of the BottomSheet.
// final String title;
// /// Used to [getData] from api.
// // final String modelCode;
/// Used to [getData] from api.
// final String modelCode;
// final FilterModel model;
final FilterModel model;
// /// Fires when the an item is selected / unselected.
// final void Function(List<FilterOption>)? onSelectionChanged;
/// Fires when the an item is selected / unselected.
final void Function(List<FilterOption>)? onSelectionChanged;
// /// Fires when confirm is tapped.
// final void Function(List<FilterOption>) onConfirm;
/// Fires when confirm is tapped.
final void Function(List<FilterOption>) onConfirm;
// /// Fires when clear is tapped.
// final void Function(List<FilterOption>) onClear;
/// Fires when clear is tapped.
final void Function(List<FilterOption>) onClear;
// MultiSelectBottomSheetGetX({
// // required this.initialValue,
// // required this.title,
// // required this.modelCode,
// required this.model,
// required this.onConfirm,
// required this.onClear,
// this.onSelectionChanged,
// });
MultiSelectBottomSheetGetX({
// required this.initialValue,
// required this.title,
// required this.modelCode,
required this.model,
required this.onConfirm,
required this.onClear,
this.onSelectionChanged,
});
// /// Returns a CheckboxListTile
// Widget _buildListItem(BuildContext context, MultiSelectController msc, MultiSelectItem<FilterOption> item) {
// final theme = Theme.of(context);
// final oldCheckboxTheme = theme.checkboxTheme;
/// Returns a CheckboxListTile
Widget _buildListItem(BuildContext context, MultiSelectController msc, MultiSelectItem<FilterOption> item) {
final theme = Theme.of(context);
final oldCheckboxTheme = theme.checkboxTheme;
// final newCheckBoxTheme = oldCheckboxTheme.copyWith(
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(300)),
// );
// return Theme(
// data: theme.copyWith(checkboxTheme: newCheckBoxTheme),
// child: Transform.translate(
// offset: Offset(-24, 0),
// child: Transform.scale(
// scale: 1.h,
// child: CheckboxListTile(
// checkColor: ThemeColor.white,
// value: item.selected,
// activeColor: ThemeColor.mainColor,
// title: model.code != 'color'
// ? Text(item.label)
// : Align(
// alignment: Alignment.centerLeft,
// child: Row(
// children: [
// item.value.swatchValue.isNotEmpty
// ? CircleAvatar(
// backgroundColor: Colors.grey,
// maxRadius: 8.40.w,
// child: CircleAvatar(
// backgroundColor: HexColor(item.value.swatchValue),
// maxRadius: 8.0.w,
// ),
// )
// : SizedBox.shrink(),
// SizedBox(width: 16.w),
// Text(item.label),
// ],
// ),
// ),
// controlAffinity: ListTileControlAffinity.leading,
// onChanged: (checked) => msc.onListBoxListTileChanged(
// item,
// onSelectionChanged,
// checked,
// ),
// ),
// ),
// ),
// );
// }
final newCheckBoxTheme = oldCheckboxTheme.copyWith(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(300)),
);
return Theme(
data: theme.copyWith(checkboxTheme: newCheckBoxTheme),
child: Transform.translate(
offset: Offset(-24, 0),
child: Transform.scale(
scale: 1.h,
child: CheckboxListTile(
checkColor: ThemeColor.white,
value: item.selected,
activeColor: ThemeColor.mainColor,
title: model.code != 'color'
? Text(item.label)
: Align(
alignment: Alignment.centerLeft,
child: Row(
children: [
item.value.swatchValue.isNotEmpty
? CircleAvatar(
backgroundColor: Colors.grey,
maxRadius: 8.40.w,
child: CircleAvatar(
backgroundColor: HexColor(item.value.swatchValue),
maxRadius: 8.0.w,
),
)
: SizedBox.shrink(),
SizedBox(width: 16.w),
Text(item.label),
],
),
),
controlAffinity: ListTileControlAffinity.leading,
onChanged: (checked) => msc.onListBoxListTileChanged(
item,
onSelectionChanged,
checked,
),
),
),
),
);
}
// @override
// Widget build(BuildContext context) {
// final EdgeInsets mqPadding = MediaQuery.of(context).padding;
// var screenSize = MediaQuery.of(context).size;
// return GetBuilder<MultiSelectController>(
// init: MultiSelectController(model),
// builder: (msc) {
// final bool isLastPage = msc.lastPage.value;
// return Container(
// constraints: BoxConstraints(maxHeight: screenSize.height - mqPadding.top),
// padding: EdgeInsets.only(bottom: mqPadding.bottom + 16, top: 16, left: 16, right: 16),
// decoration: new BoxDecoration(
// color: ThemeColor.white,
// borderRadius: BorderRadius.only(topRight: Radius.circular(16), topLeft: Radius.circular(16)),
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// Container(
// padding: const EdgeInsets.only(top: 8),
// child: Center(child: SvgPicture.asset('assets/icons/drag_line.svg')),
// ),
// SizedBox(height: 8.h),
// // title
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(model.name, style: AppTheme.bottomSheetHeaderStyle),
// SizedBox(height: 16),
// // search box
// Container(
// padding: const EdgeInsets.all(8),
// height: 40.h,
// width: double.infinity,
// decoration: new BoxDecoration(
// color: ThemeColor.colorEFF0F4,
// borderRadius: BorderRadius.circular(8),
// ),
// child: TextFormField(
// cursorColor: Colors.black,
// inputFormatters: [new LengthLimitingTextInputFormatter(30)],
// decoration: InputDecoration(
// border: InputBorder.none,
// prefixIcon: Icon(
// Icons.search,
// size: 24.h,
// color: Colors.grey,
// ),
// ),
// onChanged: (val) => msc.onSearchChanged(val),
// ),
// ),
// SizedBox(height: 20),
// Row(
// children: [
// FadeIn(child: CircleNumberWidget(number: msc.selectedValues.length)),
// SizedBox(width: 8),
// Text(
// 'applied'.tr,
// style: new TextStyle(
// color: ThemeColor.color717278,
// fontSize: 16.sp,
// ),
// ),
// Spacer(),
// msc.selectedValues.isNotEmpty
// ? FadeIn(
// child: TextButton(
// child: Text('clear'.tr),
// style: TextButton.styleFrom(foregroundColor: ThemeColor.mainColor),
// onPressed: () => msc.clearFilter(context, onClear),
// ),
// )
// : SizedBox.shrink(),
// ],
// ),
// ],
// ),
@override
Widget build(BuildContext context) {
final EdgeInsets mqPadding = MediaQuery.of(context).padding;
var screenSize = MediaQuery.of(context).size;
return GetBuilder<MultiSelectController>(
init: MultiSelectController(model),
builder: (msc) {
final bool isLastPage = msc.lastPage.value;
return Container(
constraints: BoxConstraints(maxHeight: screenSize.height - mqPadding.top),
padding: EdgeInsets.only(bottom: mqPadding.bottom + 16, top: 16, left: 16, right: 16),
decoration: new BoxDecoration(
color: ThemeColor.white,
borderRadius: BorderRadius.only(topRight: Radius.circular(16), topLeft: Radius.circular(16)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.only(top: 8),
child: Center(child: SvgPicture.asset('assets/icons/drag_line.svg')),
),
SizedBox(height: 8.h),
// title
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(model.name, style: AppTheme.bottomSheetHeaderStyle),
SizedBox(height: 16),
// search box
Container(
padding: const EdgeInsets.all(8),
height: 40.h,
width: double.infinity,
decoration: new BoxDecoration(
color: ThemeColor.colorEFF0F4,
borderRadius: BorderRadius.circular(8),
),
child: TextFormField(
cursorColor: Colors.black,
inputFormatters: [new LengthLimitingTextInputFormatter(30)],
decoration: InputDecoration(
border: InputBorder.none,
prefixIcon: Icon(
Icons.search,
size: 24.h,
color: Colors.grey,
),
),
onChanged: (val) => msc.onSearchChanged(val),
),
),
SizedBox(height: 20),
Row(
children: [
FadeIn(child: CircleNumberWidget(number: msc.selectedValues.length)),
SizedBox(width: 8),
Text(
'applied'.tr,
style: new TextStyle(
color: ThemeColor.color717278,
fontSize: 16.sp,
),
),
Spacer(),
msc.selectedValues.isNotEmpty
? FadeIn(
child: TextButton(
child: Text('clear'.tr),
style: TextButton.styleFrom(foregroundColor: ThemeColor.mainColor),
onPressed: () => msc.clearFilter(context, onClear),
),
)
: SizedBox.shrink(),
],
),
],
),
// AppTheme.appDivider,
AppTheme.appSizeDivider10H,
// // body
// Expanded(
// child: ConstrainedBox(
// constraints: new BoxConstraints(minHeight: 0.3.sh),
// child: msc.obx(
// (state) => state != null
// ? RefreshIndicator(
// onRefresh: msc.getData,
// color: ThemeColor.mainColor,
// child: MediaQuery.removePadding(
// context: context,
// removeTop: true,
// child: ListView.builder(
// shrinkWrap: true,
// controller: msc.scroll,
// itemCount: state.length + 1,
// itemBuilder: (context, index) {
// if (index < state.length) {
// return FadeIn(
// child: _buildListItem(context, msc, state[index]),
// );
// } else if (index == state.length && !isLastPage)
// return Padding(
// padding: const EdgeInsets.only(top: 10, bottom: 40),
// child: Center(
// child: CustomLoader(),
// ),
// );
// else {
// return SizedBox.shrink();
// }
// },
// ),
// ),
// )
// : Center(
// child: Text(
// 'error_occurred'.tr,
// style: TextStyle(fontSize: 18),
// textAlign: TextAlign.center,
// ),
// ),
// onLoading: Center(child: CustomLoader()),
// onEmpty: Align(
// alignment: Alignment.topCenter,
// child: NotFoundWidget(),
// ),
// onError: (error) => Center(
// child: Text(
// 'error_occurred'.tr,
// style: TextStyle(fontSize: 18),
// textAlign: TextAlign.center,
// ),
// ),
// ),
// ),
// ),
// body
Expanded(
child: ConstrainedBox(
constraints: new BoxConstraints(minHeight: 0.3.sh),
child: msc.obx(
(state) => state != null
? RefreshIndicator(
onRefresh: msc.getData,
color: ThemeColor.mainColor,
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: ListView.builder(
shrinkWrap: true,
controller: msc.scroll,
itemCount: state.length + 1,
itemBuilder: (context, index) {
if (index < state.length) {
return FadeIn(
child: _buildListItem(context, msc, state[index]),
);
} else if (index == state.length && !isLastPage)
return Padding(
padding: const EdgeInsets.only(top: 10, bottom: 40),
child: Center(
child: CustomLoader(),
),
);
else {
return SizedBox.shrink();
}
},
),
),
)
: Center(
child: Text(
'error_occurred'.tr,
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
),
onLoading: Center(child: CustomLoader()),
onEmpty: Align(
alignment: Alignment.topCenter,
child: ItemNotFoundWidget(),
),
onError: (error) => Center(
child: Text(
'error_occurred'.tr,
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
),
),
),
),
// // footer
// msc.selectedValues.isNotEmpty
// ? FadeIn(
// duration: Duration(milliseconds: 900),
// child: ButtonWidthFull(
// title: 'done'.tr,
// btnColor: ThemeColor.mainColor,
// callback: () => msc.onConfirmSelection(
// context,
// onConfirm,
// ),
// ),
// )
// : SizedBox.shrink(),
// ],
// ),
// );
// },
// );
// }
// }
// footer
msc.selectedValues.isNotEmpty
? FadeIn(
duration: Duration(milliseconds: 900),
child: ColoredButton(
title: 'done'.tr,
btnColor: ThemeColor.mainColor,
callback: () => msc.onConfirmSelection(
context,
onConfirm,
),
),
)
: SizedBox.shrink(),
],
),
);
},
);
}
}

View File

@ -35,13 +35,6 @@ class ElektronikaShopApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
// systemNavigationBarColor: Colors.blue, // navigation bar color
// statusBarColor: Colors.transparent, // status bar color
// ));
// SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
// statusBarColor: ThemeColor.mainColor,
// ));
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,