From 9a1303fc5e993df0ce5c25e194a00adcbed70c9c Mon Sep 17 00:00:00 2001 From: nextwo <1234> Date: Fri, 19 Jul 2024 22:48:11 +0300 Subject: [PATCH] search asset ui --- .../providers/api/devices_provider.dart | 17 +- lib/l10n/app_ar.arb | 3 +- lib/l10n/app_en.arb | 3 +- .../common_widgets/custom_app_bar.dart | 2 +- .../device_transfer/search_asset_page.dart | 228 ++++++++++++++++++ .../equipment/single_device_picker.dart | 5 +- lib/views/widgets/horizontal_list_widget.dart | 51 ++++ lib/views/widgets/qr/scan_qr_widget.dart | 4 +- 8 files changed, 300 insertions(+), 13 deletions(-) create mode 100644 lib/views/pages/device_transfer/search_asset_page.dart create mode 100644 lib/views/widgets/horizontal_list_widget.dart diff --git a/lib/controllers/providers/api/devices_provider.dart b/lib/controllers/providers/api/devices_provider.dart index 1e9e6c80..af9f9796 100644 --- a/lib/controllers/providers/api/devices_provider.dart +++ b/lib/controllers/providers/api/devices_provider.dart @@ -20,7 +20,14 @@ class AssetProvider extends ChangeNotifier { _stateCode = null; } + void searchReset() { + _stateCode = null; + _searchDevices = []; + } + final pageItemNumber = 10; + final searchPageItemNumber = 10; + bool nextPage = true; int _stateCode; @@ -30,8 +37,10 @@ class AssetProvider extends ChangeNotifier { set stateCode(int code) => _stateCode = code; List _devices = []; + List _searchDevices = []; List get devices => _devices; + List get searchDevices => _searchDevices; // when categories in-process _loading = true // done _loading = true @@ -49,15 +58,15 @@ class AssetProvider extends ChangeNotifier { notifyListeners(); } - Future getAssets({AssetSearch search, bool isQr = false}) async { + Future getAssets({AssetSearch search, bool isQr = false, bool isSearchBy = false}) async { if (_loading == true) return -2; _loading = true; notifyListeners(); Response response; try { final Map body = { - "pageSize": pageItemNumber, - "pageNumber": devices.length ~/ pageItemNumber + 1, + "pageSize": isSearchBy ? searchPageItemNumber : pageItemNumber, + "pageNumber": isSearchBy ? searchDevices.length ~/ searchPageItemNumber + 1 : devices.length ~/ pageItemNumber + 1, }; if (search != null) body.addAll(search.toJson()); response = await ApiManager.instance.post(URLs.getAssets, body: body); @@ -79,7 +88,7 @@ class AssetProvider extends ChangeNotifier { } catch (e) { print(e); } - _devices.addAll(dList); + isSearchBy ? _searchDevices.addAll(dList) : _devices.addAll(dList); nextPage = true; } else { nextPage = false; diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index e48c80e1..33f48a18 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -386,5 +386,6 @@ "nextPmDate" : "موعد الزيارة الوقائية التالية", "lastPmDate" : "موعد الزيارة الوقائية الأخيرة", "assetScan" : "مسح الجهاز", - "pickManually" : "اختر يدويا" + "pickManually" : "اختر يدويا", + "searchAsset" : "ابحث عن جهاز" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 01322a8a..2c157481 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -389,5 +389,6 @@ "nextPmDate" : "Next PM Date", "lastPmDate" : "Last PM Date", "assetScan" : "Asset Scan", - "pickManually" : "Pick Manually" + "pickManually" : "Pick Manually", + "searchAsset" : "Search Asset" } \ No newline at end of file diff --git a/lib/new_views/common_widgets/custom_app_bar.dart b/lib/new_views/common_widgets/custom_app_bar.dart index c34e5cb3..06a54551 100644 --- a/lib/new_views/common_widgets/custom_app_bar.dart +++ b/lib/new_views/common_widgets/custom_app_bar.dart @@ -32,7 +32,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { actions: actions, shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( - bottom: Radius.circular(bottomCorner??0), + bottom: Radius.circular(bottomCorner??10), ), ), ); diff --git a/lib/views/pages/device_transfer/search_asset_page.dart b/lib/views/pages/device_transfer/search_asset_page.dart new file mode 100644 index 00000000..b4c65133 --- /dev/null +++ b/lib/views/pages/device_transfer/search_asset_page.dart @@ -0,0 +1,228 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/providers/api/devices_provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; + +import '../../../models/device/asset.dart'; +import '../../../models/device/asset_search.dart'; +import '../../../new_views/app_style/app_color.dart'; +import '../../../new_views/common_widgets/app_lazy_loading.dart'; +import '../../../new_views/common_widgets/custom_app_bar.dart'; +import '../../widgets/bottom_sheets/asset_detail_bottom_sheet.dart'; +import '../../widgets/equipment/asset_item_listview.dart'; +import '../../widgets/horizontal_list_widget.dart'; +import '../../widgets/loaders/lazy_loading.dart'; +import '../../widgets/loaders/no_item_found.dart'; + +class SearchAssetPage extends StatefulWidget { + /// add on route + static const String id = "asset_search_page"; + final AssetSearch data; + + const SearchAssetPage({Key key, this.data}) : super(key: key); + + @override + State createState() => _SearchAssetPageState(); +} + +class _SearchAssetPageState extends State { + int _selectedIndex = 0; + AssetSearch search; + TextEditingController _searchController; + AssetProvider _deviceProvider; + List _searchableList = []; + final GlobalKey _formKey = GlobalKey(); + bool _isFirst = true; + + @override + void initState() { + search = widget.data ?? AssetSearch(); + _searchController = TextEditingController(text: search.assetName); + super.initState(); + } + + @override + void dispose() { + _searchController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + List searchBy = [ + context.translation.assetName, + context.translation.assetNumber, + context.translation.oracleCode, + context.translation.snNumber, + context.translation.model, + context.translation.supplier, + context.translation.site, + context.translation.manufacture, + context.translation.md, + context.translation.location, + ]; + + _deviceProvider = Provider.of(context, listen: false); + + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: CustomAppBar( + title: context.translation.searchAsset, + + /// comment reset button because it isn't exist in new design + // actions: [ + // if (_showResetButton()) + // Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // Text( + // context.translation.reset, + // style: AppTextStyles.bodyText2.copyWith(color: const Color(0xFF4A8DB7)), + // ).paddingAll(8).onPress(() { + // setState(() { + // _searchController.text = ""; + // }); + // }), + // ], + // ) + // ], + ), + body: Column( + children: [ + Expanded( + flex: 2, + child: HorizontalListWidget( + list: searchBy, + callBackFunction: (index) { + setState(() { + _selectedIndex = index; + }); + }, + ).paddingOnly(top: 16), + ), + Expanded( + flex: 3, + child: Form( + key: _formKey, + child: AppTextFormField( + controller: _searchController, + labelText: "${context.translation.searchBy} ${searchBy[_selectedIndex]}", + onChange: (text) { + _searchController.text = text; + _searchController.selection = TextSelection.fromPosition(TextPosition(offset: _searchController.text.length)); + setState(() {}); + }, + onSaved: (value) { + setState(() { + search = AssetSearch(); + }); + _setValue(value); + }, + suffixIcon: IconButton( + icon: const Icon(Icons.search), + onPressed: _searchController.text.isNotEmpty ? _search : null, + color: AppColor.neutral20, + ).paddingOnly(end: 6), + ).paddingOnly(top: 18, start: 18, end: 18), + ), + ), + Expanded( + flex: 25, + child: _searchableList.isEmpty + ? _isFirst + ? const SizedBox() + : NoItemFound(message: context.translation.noDeviceFound) + : LazyLoading( + nextPage: _deviceProvider.nextPage, + onLazyLoad: () async { + if (_searchController.text.isNotEmpty) { + await _deviceProvider.getAssets(search: search, isSearchBy: true); + setState(() { + _searchableList.clear(); + _searchableList.addAll(_deviceProvider.devices); + }); + } + }, + child: ListView.separated( + itemCount: _searchableList.length, + separatorBuilder: (listContext, itemIndex) => 8.height, + itemBuilder: (listContext, itemIndex) { + return AssetItemListView( + device: _searchableList[itemIndex], + onPressed: (device) { + Navigator.of(context).pop(); + Navigator.of(context).pop(device); + // showModalBottomSheet( + // context: context, + // isScrollControlled: true, + // shape: const RoundedRectangleBorder( + // borderRadius: BorderRadius.vertical( + // top: Radius.circular(20), + // ), + // ), + // clipBehavior: Clip.antiAliasWithSaveLayer, + // builder: (BuildContext context) => AssetDetailBottomSheet(device), + // ); + }, + ); + }, + ).paddingOnly(start: 16, end: 16), + ), + ) + ], + )); + } + + void _search() async { + FocusScope.of(context).unfocus(); + _formKey.currentState.save(); + _deviceProvider.searchReset(); + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + await _deviceProvider.getAssets(search: search, isSearchBy: true); + setState(() { + _searchableList.clear(); + _searchableList.addAll(_deviceProvider.searchDevices); + _isFirst = false; + }); + Navigator.pop(context); + } + + _setValue(value) { + /// todo : check oracle code and location (no matched parameter) + switch (_selectedIndex) { + case 0: + search.assetName = value; + break; + case 1: + search.assetNo = value; + break; + case 3: + search.assetSerialNumber = value; + break; + case 4: + search.model = value; + break; + case 5: + search.supplier = value; + break; + case 6: + search.site = value; + break; + case 7: + search.manufacturer = value; + break; + case 8: + search.modelDefinition = value; + break; + default: + break; + } + } + + // bool _showResetButton() { + // return (_searchController?.text?.isNotEmpty ?? false); + // } +} diff --git a/lib/views/widgets/equipment/single_device_picker.dart b/lib/views/widgets/equipment/single_device_picker.dart index 3e58b5c6..443a251f 100644 --- a/lib/views/widgets/equipment/single_device_picker.dart +++ b/lib/views/widgets/equipment/single_device_picker.dart @@ -18,8 +18,7 @@ import '../../../models/device/asset.dart'; import '../../../models/device/asset_search.dart'; import '../../../new_views/app_style/app_color.dart'; import '../../pages/device_transfer/asset_filter_screen.dart'; -import '../../pages/device_transfer/asset_search_screen.dart'; -import '../qr/scan_qr.dart'; +import '../../pages/device_transfer/search_asset_page.dart'; import '../qr/scan_qr_widget.dart'; class MyAssetsPage extends StatefulWidget { @@ -115,7 +114,7 @@ class _MyAssetsPageState extends State { ], ), ).onPress(() async { - final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => AssetSearchScreen(data: _searchAsset))); + final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => SearchAssetPage(data: _searchAsset))); if (result != null) { _searchAsset = result; } diff --git a/lib/views/widgets/horizontal_list_widget.dart b/lib/views/widgets/horizontal_list_widget.dart new file mode 100644 index 00000000..4e814949 --- /dev/null +++ b/lib/views/widgets/horizontal_list_widget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; + +import '../../new_views/app_style/app_color.dart'; + +class HorizontalListWidget extends StatefulWidget { + const HorizontalListWidget({Key key, @required this.list,this.callBackFunction}) : super(key: key); + final List list; + final Function(int index) callBackFunction; + + @override + State createState() => _HorizontalListWidgetState(); +} + +class _HorizontalListWidgetState extends State { + int _selectedIndex = 0; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 35.toScreenHeight, + child: ListView.builder( + itemCount: widget.list.length, + scrollDirection: Axis.horizontal, + shrinkWrap: true, + padding: EdgeInsetsDirectional.only(start: 16.toScreenWidth), + itemBuilder: (context, index) => Container( + margin: EdgeInsets.symmetric(horizontal: 4.toScreenWidth), + padding: EdgeInsets.symmetric(horizontal: 20.toScreenWidth), + alignment: Alignment.center, + decoration: ShapeDecoration( + color: _selectedIndex == index ? Colors.transparent : AppColor.selectedButtonColor(context), + shape: RoundedRectangleBorder( + side: _selectedIndex == index ? BorderSide(width: 2, color: (context.isDark ? AppColor.backgroundLight : AppColor.backgroundDark)) : BorderSide.none, + borderRadius: BorderRadius.circular(7), + ), + ), + child: (widget.list[index]).tinyFont(context).custom(color: AppColor.filterButtonTextColor(context)), + ).onPress(() { + setState(() { + _selectedIndex = index; + }); + widget.callBackFunction?.call(index); + }), + ), + ); + } +} diff --git a/lib/views/widgets/qr/scan_qr_widget.dart b/lib/views/widgets/qr/scan_qr_widget.dart index 4f4d6306..b826e66f 100644 --- a/lib/views/widgets/qr/scan_qr_widget.dart +++ b/lib/views/widgets/qr/scan_qr_widget.dart @@ -43,7 +43,6 @@ class _ScanQrWidgetState extends State { _controller?.dispose(); } - _pickManually() async { final picker = ImagePicker(); final pickedFile = await picker.pickImage(source: ImageSource.gallery); @@ -93,8 +92,7 @@ class _ScanQrWidgetState extends State { SizedBox( height: 60.toScreenHeight, child: CustomAppBar( - title: widget.title ?? '', - bottomCorner: 12, + title: widget.title, )), ], ),