| @@ -1 +1 @@ | |||
| af0eb765f94aecba228edd3e3fe3c4f4 | |||
| 440505e9ea1eee7043d2cbfeb318f6bc | |||
| @@ -391,7 +391,7 @@ | |||
| "$(inherited)", | |||
| "$(PROJECT_DIR)/Flutter", | |||
| ); | |||
| MARKETING_VERSION = 0.10.0; | |||
| MARKETING_VERSION = 0.10.1; | |||
| PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.tpfarm; | |||
| PRODUCT_NAME = Runner; | |||
| SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; | |||
| @@ -533,7 +533,7 @@ | |||
| "$(inherited)", | |||
| "$(PROJECT_DIR)/Flutter", | |||
| ); | |||
| MARKETING_VERSION = 0.10.0; | |||
| MARKETING_VERSION = 0.10.1; | |||
| PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.tpfarm; | |||
| PRODUCT_NAME = Runner; | |||
| SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; | |||
| @@ -567,7 +567,7 @@ | |||
| "$(inherited)", | |||
| "$(PROJECT_DIR)/Flutter", | |||
| ); | |||
| MARKETING_VERSION = 0.10.0; | |||
| MARKETING_VERSION = 0.10.1; | |||
| PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.tpfarm; | |||
| PRODUCT_NAME = Runner; | |||
| SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; | |||
| @@ -3,15 +3,17 @@ class Device { | |||
| String name; | |||
| String status; | |||
| String location; | |||
| num mode; | |||
| bool isSelected; | |||
| Device({this.id, this.name, this.status, this.location}); | |||
| Device({this.id, this.name, this.status, this.location, this.mode}); | |||
| Device.clone(Device device) { | |||
| this.id = device.id; | |||
| this.name = device.name; | |||
| this.status = device.status; | |||
| this.location = device.location; | |||
| this.mode = device.mode; | |||
| } | |||
| Device.fromJson(Map<String, dynamic> json) { | |||
| @@ -19,6 +21,7 @@ class Device { | |||
| name = json['name']; | |||
| status = json['status']; | |||
| location = json['location']; | |||
| mode = json['mode']; | |||
| isSelected = false; | |||
| } | |||
| @@ -28,6 +31,7 @@ class Device { | |||
| data['name'] = this.name; | |||
| data['status'] = this.status; | |||
| data['location'] = this.location; | |||
| data['mode'] = this.mode; | |||
| return data; | |||
| } | |||
| } | |||
| @@ -26,11 +26,11 @@ class HttpLogInterceptor extends InterceptorsWrapper { | |||
| var token = await pref.getString(DATA_CONST.TOKEN_KEY); | |||
| options.headers["Authorization"] = "Bearer $token"; | |||
| options.receiveTimeout = 20000; | |||
| // log("onRequest: ${options.uri}\n" | |||
| // "data=${options.data}\n" | |||
| // "method=${options.method}\n" | |||
| // "headers=${options.headers}\n" | |||
| // "queryParameters=${options.queryParameters}"); | |||
| log("onRequest: ${options.uri}\n" | |||
| "data=${options.data}\n" | |||
| "method=${options.method}\n" | |||
| "headers=${options.headers}\n" | |||
| "queryParameters=${options.queryParameters}"); | |||
| return options; | |||
| } | |||
| @@ -115,8 +115,8 @@ abstract class RestClient { | |||
| @PUT("/api/tb-crops") | |||
| Future<void> updateCrop(@Body() TbCropDTO crop); | |||
| //Device | |||
| @GET("/api/listDeviceOfUserCustomers") | |||
| Future<List<Device>> getDevices(); | |||
| @GET("/api/listDeviceOfUserCustomers?query={query}") | |||
| Future<List<Device>> getDevices({@Path() String query}); | |||
| //Get environment parameter | |||
| @GET("/api/list-environment-updates-display/{cropId}?page={page}&size={size}") | |||
| Future<List<EnvironmentParameter>> getEnvironmentParameters( | |||
| @@ -460,12 +460,12 @@ class _RestClient implements RestClient { | |||
| } | |||
| @override | |||
| getDevices() async { | |||
| getDevices({query}) async { | |||
| const _extra = <String, dynamic>{}; | |||
| final queryParameters = <String, dynamic>{}; | |||
| final _data = <String, dynamic>{}; | |||
| final Response<List<dynamic>> _result = await _dio.request( | |||
| '/api/listDeviceOfUserCustomers', | |||
| '/api/listDeviceOfUserCustomers?query=$query', | |||
| queryParameters: queryParameters, | |||
| options: RequestOptions( | |||
| method: 'GET', | |||
| @@ -199,9 +199,9 @@ class Repository { | |||
| } | |||
| //Device | |||
| Future<List<Device>> getDevices() { | |||
| Future<List<Device>> getDevices(String query) { | |||
| final client = RestClient(dio); | |||
| return client.getDevices(); | |||
| return client.getDevices(query: query); | |||
| } | |||
| //Environment Parameter | |||
| @@ -0,0 +1,82 @@ | |||
| import 'package:farm_tpf/utils/const_color.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| class SearchWidget extends StatefulWidget { | |||
| final Function(String) searchPressed; | |||
| SearchWidget({@required this.searchPressed}); | |||
| @override | |||
| _SearchWidgetState createState() => _SearchWidgetState(); | |||
| } | |||
| class _SearchWidgetState extends State<SearchWidget> { | |||
| BuildContext _blocContext; | |||
| TextEditingController _searchController = TextEditingController(); | |||
| @override | |||
| void initState() { | |||
| super.initState(); | |||
| _searchController.addListener(() { | |||
| final keyword = _searchController.text; | |||
| if (keyword.isNotEmpty) { | |||
| //search when text change | |||
| } | |||
| }); | |||
| } | |||
| Widget getSearchBarUI() { | |||
| _searchController.text = ""; | |||
| return Padding( | |||
| padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4), | |||
| child: Row( | |||
| children: <Widget>[ | |||
| Expanded( | |||
| child: Padding( | |||
| padding: const EdgeInsets.only(right: 8, top: 8, bottom: 0), | |||
| child: Container( | |||
| decoration: BoxDecoration( | |||
| color: Colors.white, | |||
| borderRadius: const BorderRadius.all( | |||
| Radius.circular(38.0), | |||
| ), | |||
| boxShadow: <BoxShadow>[ | |||
| BoxShadow( | |||
| color: Colors.grey.withOpacity(0.2), | |||
| offset: const Offset(0, 2), | |||
| blurRadius: 8.0), | |||
| ], | |||
| ), | |||
| child: Padding( | |||
| padding: const EdgeInsets.only( | |||
| left: 16, right: 16, top: 4, bottom: 4), | |||
| child: TextField( | |||
| textInputAction: TextInputAction.done, | |||
| controller: _searchController, | |||
| onChanged: (String txt) {}, | |||
| cursorColor: COLOR_CONST.GRAY1, | |||
| decoration: InputDecoration( | |||
| border: InputBorder.none, | |||
| hintText: 'Tìm kiếm ...', | |||
| ), | |||
| onSubmitted: widget.searchPressed, | |||
| ), | |||
| ), | |||
| ), | |||
| ), | |||
| ), | |||
| ], | |||
| ), | |||
| ); | |||
| } | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| _blocContext = context; | |||
| return Container(child: getSearchBarUI()); | |||
| } | |||
| @override | |||
| void dispose() { | |||
| _searchController.dispose(); | |||
| super.dispose(); | |||
| } | |||
| } | |||
| @@ -24,6 +24,21 @@ class DeviceBloc extends Bloc<DeviceEvent, DeviceState> { | |||
| } else if (event is ControlDevice) { | |||
| yield* _mapControlDeviceToState( | |||
| event.currentDevices, event.updatedDeviceId); | |||
| } else if (event is OnSearch) { | |||
| yield* _mapOnSearchToState(event.query); | |||
| } | |||
| } | |||
| Stream<DeviceState> _mapOnSearchToState(String query) async* { | |||
| yield DisplayDevice.loading(); | |||
| try { | |||
| List<Device> devices = new List<Device>(); | |||
| final response = await repository.getDevices(query); | |||
| devices = response; | |||
| devices.sort((a, b) => (a.id).compareTo(b.id)); | |||
| yield DisplayDevice.data(devices); | |||
| } catch (e) { | |||
| yield DisplayDevice.error(AppException.handleError(e)); | |||
| } | |||
| } | |||
| @@ -31,7 +46,7 @@ class DeviceBloc extends Bloc<DeviceEvent, DeviceState> { | |||
| yield DisplayDevice.loading(); | |||
| try { | |||
| List<Device> devices = new List<Device>(); | |||
| final response = await repository.getDevices(); | |||
| final response = await repository.getDevices(""); | |||
| devices = response; | |||
| devices.sort((a, b) => (a.id).compareTo(b.id)); | |||
| yield DisplayDevice.data(devices); | |||
| @@ -14,6 +14,14 @@ class OpenScreen extends DeviceEvent { | |||
| List<Object> get props => []; | |||
| } | |||
| class OnSearch extends DeviceEvent { | |||
| final String query; | |||
| OnSearch({@required this.query}); | |||
| @override | |||
| List<Object> get props => [query]; | |||
| } | |||
| class ControlDevice extends DeviceEvent { | |||
| final List<Device> currentDevices; | |||
| final int updatedDeviceId; | |||
| @@ -1,4 +1,5 @@ | |||
| import 'package:farm_tpf/data/repository/repository.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_search.dart'; | |||
| import 'package:farm_tpf/presentation/screens/control_device/widget_device_list.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| @@ -12,9 +13,11 @@ class ControlDeviceScreen extends StatefulWidget { | |||
| class _ControlDeviceScreenState extends State<ControlDeviceScreen> { | |||
| BuildContext _blocContext; | |||
| DeviceBloc _deviceBloc = DeviceBloc(repository: Repository()); | |||
| @override | |||
| void initState() { | |||
| super.initState(); | |||
| _deviceBloc.add(OpenScreen()); | |||
| } | |||
| @override | |||
| @@ -32,27 +35,40 @@ class _ControlDeviceScreenState extends State<ControlDeviceScreen> { | |||
| } | |||
| Widget _buildContent() { | |||
| return BlocBuilder<DeviceBloc, DeviceState>( | |||
| builder: (context, state) { | |||
| if (state is DisplayDevice) { | |||
| if (state.loading) { | |||
| return Container( | |||
| child: Center( | |||
| child: CircularProgressIndicator(), | |||
| ), | |||
| ); | |||
| } | |||
| return Column( | |||
| children: [ | |||
| SearchWidget(searchPressed: (value) { | |||
| FocusScope.of(context).requestFocus(FocusNode()); | |||
| _deviceBloc.add(OnSearch(query: value)); | |||
| }), | |||
| Expanded( | |||
| child: BlocBuilder<DeviceBloc, DeviceState>( | |||
| cubit: _deviceBloc, | |||
| builder: (context, state) { | |||
| if (state is DisplayDevice) { | |||
| if (state.loading) { | |||
| return Container( | |||
| child: Center( | |||
| child: CircularProgressIndicator(), | |||
| ), | |||
| ); | |||
| } | |||
| if (state.devices != null) { | |||
| return Container( | |||
| child: WidgetDeviceList(devices: state.devices), | |||
| ); | |||
| } | |||
| return Container(); | |||
| } else { | |||
| return Container(); | |||
| } | |||
| }, | |||
| if (state.devices != null) { | |||
| return Container( | |||
| child: WidgetDeviceList( | |||
| devices: state.devices, | |||
| deviceBloc: _deviceBloc, | |||
| ), | |||
| ); | |||
| } | |||
| return Container(); | |||
| } else { | |||
| return Container(); | |||
| } | |||
| }, | |||
| )) | |||
| ], | |||
| ); | |||
| } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| import 'package:dio/dio.dart'; | |||
| import 'package:farm_tpf/custom_model/Device.dart'; | |||
| import 'package:farm_tpf/data/api/app_exception.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_loading.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_utils.dart'; | |||
| import 'package:farm_tpf/presentation/screens/control_device/bloc/device_bloc.dart'; | |||
| @@ -11,8 +12,9 @@ import 'package:flutter/widgets.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| class WidgetDeviceList extends StatefulWidget { | |||
| List<Device> devices; | |||
| WidgetDeviceList({@required this.devices}); | |||
| final DeviceBloc deviceBloc; | |||
| final List<Device> devices; | |||
| WidgetDeviceList({@required this.devices, @required this.deviceBloc}); | |||
| @override | |||
| _WidgetDeviceListState createState() => _WidgetDeviceListState(); | |||
| } | |||
| @@ -58,50 +60,35 @@ class _WidgetDeviceListState extends State<WidgetDeviceList> { | |||
| } | |||
| Widget widgetDeviceList(List<ItemDeviceVM> items, BuildContext context) { | |||
| return Container( | |||
| child: GridView.count( | |||
| childAspectRatio: (10 / 7), | |||
| crossAxisCount: 2, | |||
| children: items.map( | |||
| (item) { | |||
| return _widgetItemDevice(item, context); | |||
| return RefreshIndicator( | |||
| child: ListView.builder( | |||
| itemBuilder: (BuildContext context, int index) { | |||
| return _widgetItemDevice(items[index], context); | |||
| }, | |||
| ).toList(), | |||
| )); | |||
| itemCount: items.length, | |||
| ), | |||
| onRefresh: () async { | |||
| widget.deviceBloc.add(OpenScreen()); | |||
| }, | |||
| ); | |||
| } | |||
| Widget _widgetItemDevice(ItemDeviceVM item, BuildContext context) { | |||
| return GestureDetector( | |||
| onTap: () { | |||
| //Navigator.of(context).push(MaterialPageRoute(builder: (context) => DeviceDetail(device: _device,))); | |||
| }, | |||
| child: Card( | |||
| margin: EdgeInsets.all(8.0), | |||
| shadowColor: Colors.grey, | |||
| elevation: 3, | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.center, | |||
| mainAxisAlignment: MainAxisAlignment.center, | |||
| children: <Widget>[ | |||
| Column( | |||
| children: <Widget>[ | |||
| powerBtn(item, context), | |||
| Text( | |||
| item.name, | |||
| style: TextStyle( | |||
| color: Colors.black54, | |||
| fontSize: 13, | |||
| fontWeight: FontWeight.bold), | |||
| overflow: TextOverflow.clip, | |||
| maxLines: 2, | |||
| textAlign: TextAlign.center, | |||
| ), | |||
| Text(""), | |||
| widgetStatus(item) | |||
| ], | |||
| ) | |||
| ]), | |||
| )); | |||
| onTap: () { | |||
| //Navigator.of(context).push(MaterialPageRoute(builder: (context) => DeviceDetail(device: _device,))); | |||
| }, | |||
| child: Card( | |||
| child: ListTile( | |||
| leading: powerBtn(item, context), | |||
| title: Text( | |||
| '${item.name} - ${item.location ?? ''}', | |||
| style: TextStyle( | |||
| color: Colors.black54, fontSize: 13, fontWeight: FontWeight.bold), | |||
| ), | |||
| subtitle: widgetStatus(item), | |||
| )), | |||
| ); | |||
| } | |||
| Widget powerBtn(ItemDeviceVM item, BuildContext _context) { | |||
| @@ -112,60 +99,81 @@ class _WidgetDeviceListState extends State<WidgetDeviceList> { | |||
| }, | |||
| builder: (context, state) { | |||
| if (state is DisplayDevice) { | |||
| if (state.loading) { | |||
| return Container( | |||
| child: Center( | |||
| child: CircularProgressIndicator(), | |||
| ), | |||
| ); | |||
| } | |||
| if (state.devices != null) { | |||
| currentDevices = state.devices; | |||
| if (item.status == "0") { | |||
| //tat | |||
| return IconButton( | |||
| icon: Icon(Icons.power_settings_new), | |||
| color: Colors.grey, | |||
| onPressed: () async { | |||
| //bat | |||
| _controlSwitchDevice(item, context); | |||
| }); | |||
| } else if (item.status == "1") { | |||
| //Bat | |||
| return IconButton( | |||
| icon: Icon(Icons.power_settings_new), | |||
| color: Colors.blue, | |||
| onPressed: () async { | |||
| //tat | |||
| _controlSwitchDevice(item, context); | |||
| }); | |||
| //mode = 1: auto -> cho phep dieu khien | |||
| if (item.mode == 1) { | |||
| if (item.status == "0") { | |||
| //tat | |||
| return IconButton( | |||
| icon: Icon(Icons.power_settings_new), | |||
| color: Colors.grey, | |||
| onPressed: () async { | |||
| //bat | |||
| _controlSwitchDevice(item, context); | |||
| }); | |||
| } else if (item.status == "1") { | |||
| //Bat | |||
| return IconButton( | |||
| icon: Icon(Icons.power_settings_new), | |||
| color: Colors.blue, | |||
| onPressed: () async { | |||
| //tat | |||
| _controlSwitchDevice(item, context); | |||
| }); | |||
| } else { | |||
| //loi | |||
| return IconButton( | |||
| icon: Icon(Icons.power_settings_new), | |||
| color: Colors.grey, | |||
| onPressed: () {}); | |||
| } | |||
| } else { | |||
| //loi | |||
| return IconButton( | |||
| icon: Icon(Icons.power_settings_new), | |||
| color: Colors.grey, | |||
| onPressed: () {}); | |||
| } | |||
| } | |||
| return Container(); | |||
| return IconButton( | |||
| icon: Icon(Icons.power_settings_new), | |||
| color: Colors.grey, | |||
| onPressed: () {}); | |||
| } else { | |||
| return Container(); | |||
| return IconButton( | |||
| icon: Icon(Icons.power_settings_new), | |||
| color: Colors.grey, | |||
| onPressed: () {}); | |||
| } | |||
| }, | |||
| ); | |||
| } | |||
| Widget widgetStatus(ItemDeviceVM item) { | |||
| var manualModeString = (item.mode != 1) ? ' - Điều khiển thủ công' : ''; | |||
| if (item.status == "0") { | |||
| return Text( | |||
| "Đang Tắt", | |||
| style: TextStyle(color: Colors.black54, fontSize: 13), | |||
| return RichText( | |||
| text: TextSpan( | |||
| text: 'Đang Tắt', | |||
| style: TextStyle(color: Colors.black54, fontSize: 13), | |||
| children: <TextSpan>[ | |||
| TextSpan( | |||
| text: manualModeString, | |||
| style: TextStyle(color: Colors.black54, fontSize: 13)), | |||
| ], | |||
| ), | |||
| ); | |||
| } else if (item.status == "1") { | |||
| return Text( | |||
| "Đang Bật", | |||
| style: TextStyle(color: Colors.green, fontSize: 13), | |||
| return RichText( | |||
| text: TextSpan( | |||
| text: 'Đang Bật', | |||
| style: TextStyle(color: Colors.green, fontSize: 13), | |||
| children: <TextSpan>[ | |||
| TextSpan( | |||
| text: '$manualModeString', | |||
| style: TextStyle(color: Colors.black54, fontSize: 13)), | |||
| ], | |||
| ), | |||
| ); | |||
| } else { | |||
| return Text( | |||
| @@ -197,16 +205,19 @@ class _WidgetDeviceListState extends State<WidgetDeviceList> { | |||
| var response = await client.put(urlTurnOff); | |||
| if (200 <= response.statusCode && response.statusCode < 299) { | |||
| BlocProvider.of<DeviceBloc>(_context).add(ControlDevice( | |||
| LoadingDialog.hideLoadingDialog(_context); | |||
| widget.deviceBloc.add(ControlDevice( | |||
| currentDevices: currentDevices, updatedDeviceId: item.id)); | |||
| Utils.showSnackBarSuccess( | |||
| message: 'Điều khiển thành công thiết bị ${item.name}'); | |||
| } | |||
| } catch (error) { | |||
| Utils.showSnackBarError( | |||
| message: 'Điều khiển thiết bị ${item.name} thất bại'); | |||
| LoadingDialog.hideLoadingDialog(_context); | |||
| var errorMessage = AppException.handleError(error, | |||
| customMessageError: | |||
| "Thiết bị ${item.name} đã được $statusDialogView.\nTải lại để cập nhật trạng thái mới nhất"); | |||
| Utils.showSnackBarError(message: errorMessage); | |||
| } | |||
| LoadingDialog.hideLoadingDialog(_context); | |||
| }, | |||
| ); | |||
| CupertinoAlertDialog alert = CupertinoAlertDialog( | |||
| @@ -232,13 +243,15 @@ class ItemDeviceVM { | |||
| String name; | |||
| String status; | |||
| String location; | |||
| num mode; | |||
| ItemDeviceVM(this.id, this.name, this.status); | |||
| ItemDeviceVM(this.id, this.name, this.status, this.location, this.mode); | |||
| ItemDeviceVM.fromDevice(Device device) { | |||
| this.device = device; | |||
| id = device.id; | |||
| name = device.name.toString(); | |||
| status = device.status; | |||
| location = device.location; | |||
| mode = device.mode; | |||
| } | |||
| } | |||