Browse Source

control device

master
daivph 5 years ago
parent
commit
dd581fc038
12 changed files with 256 additions and 118 deletions
  1. +1
    -1
      ios/Flutter/.last_build_id
  2. +3
    -3
      ios/Runner.xcodeproj/project.pbxproj
  3. +5
    -1
      lib/custom_model/Device.dart
  4. +5
    -5
      lib/data/api/dio_provider.dart
  5. +2
    -2
      lib/data/api/rest_client.dart
  6. +2
    -2
      lib/data/api/rest_client.g.dart
  7. +2
    -2
      lib/data/repository/repository.dart
  8. +82
    -0
      lib/presentation/custom_widgets/widget_search.dart
  9. +16
    -1
      lib/presentation/screens/control_device/bloc/device_bloc.dart
  10. +8
    -0
      lib/presentation/screens/control_device/bloc/device_event.dart
  11. +36
    -20
      lib/presentation/screens/control_device/sc_control_device.dart
  12. +94
    -81
      lib/presentation/screens/control_device/widget_device_list.dart

+ 1
- 1
ios/Flutter/.last_build_id View File

@@ -1 +1 @@
af0eb765f94aecba228edd3e3fe3c4f4
440505e9ea1eee7043d2cbfeb318f6bc

+ 3
- 3
ios/Runner.xcodeproj/project.pbxproj View File

@@ -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";

+ 5
- 1
lib/custom_model/Device.dart View File

@@ -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;
}
}

+ 5
- 5
lib/data/api/dio_provider.dart View File

@@ -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;
}


+ 2
- 2
lib/data/api/rest_client.dart View File

@@ -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(

+ 2
- 2
lib/data/api/rest_client.g.dart View File

@@ -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',

+ 2
- 2
lib/data/repository/repository.dart View File

@@ -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

+ 82
- 0
lib/presentation/custom_widgets/widget_search.dart View File

@@ -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();
}
}

+ 16
- 1
lib/presentation/screens/control_device/bloc/device_bloc.dart View File

@@ -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);

+ 8
- 0
lib/presentation/screens/control_device/bloc/device_event.dart View File

@@ -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;

+ 36
- 20
lib/presentation/screens/control_device/sc_control_device.dart View File

@@ -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();
}
},
))
],
);
}
}

+ 94
- 81
lib/presentation/screens/control_device/widget_device_list.dart View File

@@ -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;
}
}

Loading…
Cancel
Save