Complete refactoring SFM App Source Code
This commit is contained in:
97
lib/feature/devices/add_new_device_widget.dart
Normal file
97
lib/feature/devices/add_new_device_widget.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../product/utils/response_status_utils.dart';
|
||||
import '../../product/constant/enums/role_enums.dart';
|
||||
import '../../product/services/api_services.dart';
|
||||
import '../../product/utils/qr_utils.dart';
|
||||
import '../../product/constant/icon/icon_constants.dart';
|
||||
import '../../product/extention/context_extention.dart';
|
||||
import '../../product/services/language_services.dart';
|
||||
|
||||
addNewDevice(BuildContext context, String role) async {
|
||||
TextEditingController extIDController = TextEditingController(text: "");
|
||||
TextEditingController deviceNameController = TextEditingController(text: "");
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
dismissDirection: DismissDirection.none,
|
||||
duration: context.dynamicMinutesDuration(1),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${appLocalization(context).add_device_title}: ',
|
||||
style: context.titleMediumTextStyle),
|
||||
Container(
|
||||
alignment: Alignment.centerRight,
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
},
|
||||
icon: IconConstants.instance.getMaterialIcon(Icons.close)),
|
||||
)
|
||||
],
|
||||
),
|
||||
TextField(
|
||||
textInputAction: TextInputAction.next,
|
||||
controller: extIDController,
|
||||
decoration: InputDecoration(
|
||||
hintText: appLocalization(context).input_extID_device_hintText,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () async {
|
||||
String result =
|
||||
await QRScanUtils.instance.scanQR(context);
|
||||
extIDController.text = result;
|
||||
},
|
||||
icon: IconConstants.instance
|
||||
.getMaterialIcon(Icons.qr_code_scanner_outlined))),
|
||||
),
|
||||
role == RoleEnums.ADMIN.name
|
||||
? TextField(
|
||||
textInputAction: TextInputAction.done,
|
||||
controller: deviceNameController,
|
||||
decoration: InputDecoration(
|
||||
hintText:
|
||||
appLocalization(context).input_name_device_hintText),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
String extID = extIDController.text;
|
||||
String deviceName = deviceNameController.text;
|
||||
addDevices(context, role, extID, deviceName);
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
},
|
||||
child: Text(appLocalization(context).add_button_content)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void addDevices(
|
||||
BuildContext context, String role, String extID, String deviceName) async {
|
||||
APIServices apiServices = APIServices();
|
||||
Map<String, dynamic> body = {};
|
||||
if (role == RoleEnums.ADMIN.name) {
|
||||
body = {"ext_id": extID, "name": deviceName};
|
||||
int statusCode = await apiServices.createDeviceByAdmin(body);
|
||||
showSnackBarResponseByStatusCode(
|
||||
context,
|
||||
statusCode,
|
||||
appLocalization(context).notification_create_device_success,
|
||||
appLocalization(context).notification_create_device_failed);
|
||||
} else {
|
||||
body = {"ext_id": extID};
|
||||
int statusCode = await apiServices.registerDevice(body);
|
||||
showSnackBarResponseByStatusCode(
|
||||
context,
|
||||
statusCode,
|
||||
appLocalization(context).notification_add_device_success,
|
||||
appLocalization(context).notification_device_not_exist);
|
||||
}
|
||||
}
|
||||
61
lib/feature/devices/delete_device_widget.dart
Normal file
61
lib/feature/devices/delete_device_widget.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../product/constant/enums/role_enums.dart';
|
||||
import '../../product/services/api_services.dart';
|
||||
import '../../product/services/language_services.dart';
|
||||
import '../../product/utils/response_status_utils.dart';
|
||||
|
||||
handleDeleteDevice(BuildContext context, String thingID, String role) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertDialog(
|
||||
title: Text(appLocalization(dialogContext).delete_device_dialog_title),
|
||||
content:
|
||||
Text(appLocalization(dialogContext).delete_device_dialog_content),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text(appLocalization(dialogContext).cancel_button_content),
|
||||
onPressed: () {
|
||||
Navigator.of(dialogContext).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
appLocalization(dialogContext).delete_button_content,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
onPressed: () {
|
||||
deleteOrUnregisterDevice(context, thingID, role);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
deleteOrUnregisterDevice(
|
||||
BuildContext context, String thingID, String role) async {
|
||||
APIServices apiServices = APIServices();
|
||||
if (role == RoleEnums.USER.name) {
|
||||
Map<String, dynamic> body = {
|
||||
"thing_id": thingID,
|
||||
};
|
||||
int statusCode = await apiServices.unregisterDevice(body);
|
||||
showSnackBarResponseByStatusCode(
|
||||
context,
|
||||
statusCode,
|
||||
appLocalization(context).notification_delete_device_success,
|
||||
appLocalization(context).notification_delete_device_failed);
|
||||
} else {
|
||||
int statusCode = await apiServices.deleteDeviceByAdmin(thingID);
|
||||
showSnackBarResponseByStatusCode(
|
||||
context,
|
||||
statusCode,
|
||||
appLocalization(context).notification_delete_device_success,
|
||||
appLocalization(context).notification_delete_device_failed);
|
||||
}
|
||||
}
|
||||
79
lib/feature/devices/device_detail/device_detail_bloc.dart
Normal file
79
lib/feature/devices/device_detail/device_detail_bloc.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:sfm_app/product/services/api_services.dart';
|
||||
import 'package:sfm_app/product/utils/device_utils.dart';
|
||||
|
||||
import '../device_model.dart';
|
||||
|
||||
import '../../../product/base/bloc/base_bloc.dart';
|
||||
|
||||
class DetailDeviceBloc extends BlocBase {
|
||||
APIServices apiServices = APIServices();
|
||||
|
||||
final deviceInfo = StreamController<Device>.broadcast();
|
||||
StreamSink<Device> get sinkDeviceInfo => deviceInfo.sink;
|
||||
Stream<Device> get streamDeviceInfo => deviceInfo.stream;
|
||||
|
||||
final deviceSensor = StreamController<Map<String, dynamic>>.broadcast();
|
||||
StreamSink<Map<String, dynamic>> get sinkDeviceSensor => deviceSensor.sink;
|
||||
Stream<Map<String, dynamic>> get streamDeviceSensor => deviceSensor.stream;
|
||||
|
||||
final deviceLocation = StreamController<String>.broadcast();
|
||||
StreamSink<String> get sinkDeviceLocation => deviceLocation.sink;
|
||||
Stream<String> get streamDeviceLocation => deviceLocation.stream;
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
|
||||
void getDeviceDetail(
|
||||
BuildContext context,
|
||||
String thingID,
|
||||
Completer<GoogleMapController> controller,
|
||||
) async {
|
||||
String body = await apiServices.getDeviceInfomation(thingID);
|
||||
if (body != "") {
|
||||
final data = jsonDecode(body);
|
||||
Device device = Device.fromJson(data);
|
||||
sinkDeviceInfo.add(device);
|
||||
if (device.areaPath != null) {
|
||||
String fullLocation = await DeviceUtils.instance
|
||||
.getFullDeviceLocation(context, device.areaPath!);
|
||||
log("Location: $fullLocation");
|
||||
sinkDeviceLocation.add(fullLocation);
|
||||
}
|
||||
Map<String, dynamic> sensorMap = {};
|
||||
if (device.status!.sensors != null) {
|
||||
sensorMap = DeviceUtils.instance
|
||||
.getDeviceSensors(context, device.status!.sensors!);
|
||||
} else {
|
||||
sensorMap = DeviceUtils.instance.getDeviceSensors(context, []);
|
||||
}
|
||||
sinkDeviceSensor.add(sensorMap);
|
||||
if (device.settings!.latitude! != "" &&
|
||||
device.settings!.longitude! != "") {
|
||||
final CameraPosition cameraPosition = CameraPosition(
|
||||
target: LatLng(
|
||||
double.parse(device.settings!.latitude!),
|
||||
double.parse(device.settings!.longitude!),
|
||||
),
|
||||
zoom: 13,
|
||||
);
|
||||
final GoogleMapController mapController = await controller.future;
|
||||
mapController
|
||||
.animateCamera(CameraUpdate.newCameraPosition(cameraPosition));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void findLocation(BuildContext context, String areaPath) async {
|
||||
String fullLocation =
|
||||
await DeviceUtils.instance.getFullDeviceLocation(context, areaPath);
|
||||
sinkDeviceLocation.add(fullLocation);
|
||||
}
|
||||
}
|
||||
361
lib/feature/devices/device_detail/device_detail_screen.dart
Normal file
361
lib/feature/devices/device_detail/device_detail_screen.dart
Normal file
@@ -0,0 +1,361 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:simple_ripple_animation/simple_ripple_animation.dart';
|
||||
import 'dart:math' as math;
|
||||
import '../device_model.dart';
|
||||
import '../../../product/base/bloc/base_bloc.dart';
|
||||
import '../../../product/extention/context_extention.dart';
|
||||
import '../../../product/services/language_services.dart';
|
||||
import '../../../product/utils/device_utils.dart';
|
||||
|
||||
import '../../../product/constant/icon/icon_constants.dart';
|
||||
import 'device_detail_bloc.dart';
|
||||
|
||||
class DetailDeviceScreen extends StatefulWidget {
|
||||
const DetailDeviceScreen({super.key, required this.thingID});
|
||||
final String thingID;
|
||||
@override
|
||||
State<DetailDeviceScreen> createState() => _DetailDeviceScreenState();
|
||||
}
|
||||
|
||||
class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
|
||||
List<String> imageAssets = [
|
||||
IconConstants.instance.getIcon("normal_icon"),
|
||||
IconConstants.instance.getIcon("offline_icon"),
|
||||
IconConstants.instance.getIcon("flame_icon"),
|
||||
];
|
||||
String stateImgAssets(int state) {
|
||||
String imgStringAsset;
|
||||
if (state == 0) {
|
||||
imgStringAsset = imageAssets[0];
|
||||
} else if (state == 1) {
|
||||
imgStringAsset = imageAssets[1];
|
||||
} else {
|
||||
imgStringAsset = imageAssets[2];
|
||||
}
|
||||
return imgStringAsset;
|
||||
}
|
||||
|
||||
late DetailDeviceBloc detailDeviceBloc;
|
||||
Completer<GoogleMapController> controller = Completer();
|
||||
CameraPosition initialCamera = const CameraPosition(
|
||||
target: LatLng(20.966048511844402, 105.74977710843086), zoom: 15);
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
detailDeviceBloc = BlocProvider.of(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double screenWidth = MediaQuery.of(context).size.width;
|
||||
return StreamBuilder<Device>(
|
||||
stream: detailDeviceBloc.streamDeviceInfo,
|
||||
builder: (context, deviceSnapshot) {
|
||||
if (deviceSnapshot.data?.extId == null) {
|
||||
detailDeviceBloc.getDeviceDetail(context, widget.thingID, controller);
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
} else {
|
||||
return StreamBuilder<Map<String, dynamic>>(
|
||||
stream: detailDeviceBloc.streamDeviceSensor,
|
||||
builder: (context, sensorSnapshot) {
|
||||
if (sensorSnapshot.data != null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(appLocalization(context).detail_message),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// device Name
|
||||
Card(
|
||||
child: Container(
|
||||
width: context.dynamicWidth(1),
|
||||
height: context.highValue,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${appLocalization(context).device_title}: ${deviceSnapshot.data!.name}',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Tinh trang va nhiet do
|
||||
Row(
|
||||
children: [
|
||||
Card(
|
||||
child: Container(
|
||||
width: (screenWidth - 20) / 2,
|
||||
height: context.highValue,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(5, 5, 0, 5),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 25,
|
||||
width: 25,
|
||||
child: RippleAnimation(
|
||||
color: DeviceUtils.instance
|
||||
.getColorRiple(
|
||||
deviceSnapshot.data!.state!),
|
||||
delay:
|
||||
const Duration(milliseconds: 800),
|
||||
repeat: true,
|
||||
minRadius: 40,
|
||||
ripplesCount: 6,
|
||||
duration: const Duration(
|
||||
milliseconds: 6 * 300),
|
||||
child: CircleAvatar(
|
||||
minRadius: 20,
|
||||
maxRadius: 20,
|
||||
backgroundImage: AssetImage(
|
||||
stateImgAssets(
|
||||
deviceSnapshot.data!.state!,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: context.lowValue,
|
||||
),
|
||||
Text(
|
||||
DeviceUtils.instance.checkStateDevice(
|
||||
context,
|
||||
deviceSnapshot.data!.state!,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Card(
|
||||
child: SizedBox(
|
||||
width: (screenWidth - 20) / 2,
|
||||
height: context.highValue,
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(5, 5, 0, 5),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.thermostat,
|
||||
color: Colors.blue,
|
||||
size: 30,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).paginated_data_table_column_deviceTemperature}: ${sensorSnapshot.data?['sensorTemp'] ?? 100}",
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Card(
|
||||
child: Container(
|
||||
width: (screenWidth - 20) / 2,
|
||||
height: context.highValue,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(10, 5, 0, 5),
|
||||
child: Row(
|
||||
children: [
|
||||
Transform.rotate(
|
||||
angle: 90 * math.pi / 180,
|
||||
child: Icon(
|
||||
DeviceUtils.instance.getBatteryIcon(
|
||||
int.parse(
|
||||
sensorSnapshot
|
||||
.data!['sensorBattery'],
|
||||
),
|
||||
),
|
||||
color: Colors.blue,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: context.lowValue,
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).paginated_data_table_column_deviceBaterry}: ${sensorSnapshot.data!['sensorBattery']}%",
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Card(
|
||||
child: Container(
|
||||
width: (screenWidth - 20) / 2,
|
||||
height: context.highValue,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(10, 5, 0, 5),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
DeviceUtils.instance.getSignalIcon(
|
||||
context,
|
||||
sensorSnapshot.data!['sensorCsq'],
|
||||
),
|
||||
color: Colors.blue,
|
||||
size: 30,
|
||||
),
|
||||
SizedBox(
|
||||
width: context.lowValue,
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).paginated_data_table_column_deviceSignal}: ${sensorSnapshot.data!['sensorCsq']}",
|
||||
style: const TextStyle(fontSize: 15),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Card(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
height: context.highValue,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.location_on,
|
||||
color: Colors.blue,
|
||||
size: 30,
|
||||
),
|
||||
SizedBox(
|
||||
width: context.lowValue,
|
||||
),
|
||||
Expanded(
|
||||
child: StreamBuilder<String>(
|
||||
stream:
|
||||
detailDeviceBloc.streamDeviceLocation,
|
||||
builder: (context, locationSnapshot) {
|
||||
if (locationSnapshot.data != null) {
|
||||
return Text(
|
||||
locationSnapshot.data ?? "",
|
||||
style:
|
||||
const TextStyle(fontSize: 13),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
);
|
||||
} else {
|
||||
detailDeviceBloc.findLocation(context,
|
||||
deviceSnapshot.data!.areaPath!);
|
||||
return Text(appLocalization(context)
|
||||
.undefine_message);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Card(
|
||||
child: Container(
|
||||
height: 300,
|
||||
padding: const EdgeInsets.fromLTRB(5, 5, 5, 5),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(15),
|
||||
),
|
||||
),
|
||||
child: deviceSnapshot.data!.settings!.latitude !=
|
||||
""
|
||||
? GoogleMap(
|
||||
initialCameraPosition: initialCamera,
|
||||
mapType: MapType.normal,
|
||||
markers: {
|
||||
Marker(
|
||||
markerId: MarkerId(
|
||||
deviceSnapshot.data!.thingId!),
|
||||
position: LatLng(
|
||||
double.parse(deviceSnapshot
|
||||
.data!.settings!.latitude!),
|
||||
double.parse(deviceSnapshot
|
||||
.data!.settings!.longitude!),
|
||||
),
|
||||
),
|
||||
},
|
||||
onMapCreated: (mapcontroller) {
|
||||
controller.complete(mapcontroller);
|
||||
},
|
||||
mapToolbarEnabled: false,
|
||||
zoomControlsEnabled: false,
|
||||
liteModeEnabled: true,
|
||||
)
|
||||
: Center(
|
||||
child: Text(
|
||||
appLocalization(context)
|
||||
.detail_device_dont_has_location_message,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
171
lib/feature/devices/device_model.dart
Normal file
171
lib/feature/devices/device_model.dart
Normal file
@@ -0,0 +1,171 @@
|
||||
import 'package:google_maps_cluster_manager/google_maps_cluster_manager.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
|
||||
// class Device {
|
||||
// int? offset;
|
||||
// List<Item>? items;
|
||||
|
||||
// Device({
|
||||
// this.offset,
|
||||
// this.items,
|
||||
// });
|
||||
// Device.fromJson(Map<String, dynamic> json) {
|
||||
// offset = json['offset'];
|
||||
// if (json['items'] != null) {
|
||||
// items = [];
|
||||
// json['items'].forEach((v) {
|
||||
// items!.add(Item.fromJson(v));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
class Device with ClusterItem {
|
||||
String? extId;
|
||||
String? thingId;
|
||||
String? thingKey;
|
||||
List<String?>? channels;
|
||||
String? areaPath;
|
||||
String? fvers;
|
||||
String? name;
|
||||
HardwareInfo? hardwareInfo;
|
||||
Settings? settings;
|
||||
Status? status;
|
||||
DateTime? connectionTime;
|
||||
int? state;
|
||||
String? visibility;
|
||||
DateTime? createdAt;
|
||||
DateTime? updatedAt;
|
||||
|
||||
Device({
|
||||
this.extId,
|
||||
this.thingId,
|
||||
this.thingKey,
|
||||
this.channels,
|
||||
this.areaPath,
|
||||
this.fvers,
|
||||
this.name,
|
||||
this.hardwareInfo,
|
||||
this.settings,
|
||||
this.status,
|
||||
this.connectionTime,
|
||||
this.state,
|
||||
this.visibility,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
Device.fromJson(Map<String, dynamic> json) {
|
||||
extId = json['ext_id'];
|
||||
thingId = json['thing_id'];
|
||||
thingKey = json['thing_key'];
|
||||
if (json['channels'] != null) {
|
||||
channels = [];
|
||||
json['channels'].forEach((v) {
|
||||
channels!.add(v);
|
||||
});
|
||||
}
|
||||
areaPath = json['area_path'];
|
||||
fvers = json['fvers'];
|
||||
name = json['name'];
|
||||
hardwareInfo = json['hardware_info'] != null
|
||||
? HardwareInfo.fromJson(json['hardware_info'])
|
||||
: null;
|
||||
settings =
|
||||
json['settings'] != null ? Settings.fromJson(json['settings']) : null;
|
||||
status = json['status'] != null ? Status.fromJson(json['status']) : null;
|
||||
connectionTime = DateTime.parse(json['connection_time']);
|
||||
state = json['state'];
|
||||
visibility = json['visibility'];
|
||||
createdAt = DateTime.parse(json['created_at']);
|
||||
updatedAt = DateTime.parse(json['updated_at']);
|
||||
}
|
||||
|
||||
static List<Device> fromJsonDynamicList(List<dynamic> list) {
|
||||
return list.map((e) => Device.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
LatLng get location {
|
||||
double latitude = double.tryParse(settings?.latitude ?? '') ?? 34.639971;
|
||||
double longitude = double.tryParse(settings?.longitude ?? '') ?? -39.664027;
|
||||
return LatLng(latitude, longitude);
|
||||
}
|
||||
}
|
||||
|
||||
class HardwareInfo {
|
||||
String? gsm;
|
||||
String? imei;
|
||||
String? imsi;
|
||||
String? phone;
|
||||
DateTime? updatedAt;
|
||||
|
||||
HardwareInfo({
|
||||
this.gsm,
|
||||
this.imei,
|
||||
this.imsi,
|
||||
this.phone,
|
||||
this.updatedAt,
|
||||
});
|
||||
HardwareInfo.fromJson(Map<String, dynamic> json) {
|
||||
gsm = json['gsm'];
|
||||
imei = json['imei'];
|
||||
imsi = json['imsi'];
|
||||
phone = json['phone'];
|
||||
updatedAt = DateTime.parse(json['updated_at']);
|
||||
}
|
||||
}
|
||||
|
||||
class Settings {
|
||||
String? latitude;
|
||||
String? longitude;
|
||||
DateTime? updatedAt;
|
||||
|
||||
Settings({
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
this.updatedAt,
|
||||
});
|
||||
Settings.fromJson(Map<String, dynamic> json) {
|
||||
latitude = json['latitude'];
|
||||
longitude = json['longitude'];
|
||||
updatedAt = DateTime.parse(json['updated_at']);
|
||||
}
|
||||
}
|
||||
|
||||
class Status {
|
||||
int? readOffset;
|
||||
List<Sensor>? sensors;
|
||||
DateTime? updatedAt;
|
||||
|
||||
Status({
|
||||
this.readOffset,
|
||||
this.sensors,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
Status.fromJson(Map<String, dynamic> json) {
|
||||
readOffset = json['read_offset'];
|
||||
if (json['sensors'] != null) {
|
||||
sensors = [];
|
||||
json['sensors'].forEach((v) {
|
||||
sensors!.add(Sensor.fromJson(v));
|
||||
});
|
||||
}
|
||||
updatedAt = DateTime.parse(json['updated_at']);
|
||||
}
|
||||
}
|
||||
|
||||
class Sensor {
|
||||
String? name;
|
||||
int? value;
|
||||
int? time;
|
||||
dynamic x;
|
||||
|
||||
Sensor({this.name, this.value, this.time, this.x});
|
||||
Sensor.fromJson(Map<String, dynamic> json) {
|
||||
name = json['n'];
|
||||
value = json['v'];
|
||||
time = json['t'];
|
||||
x = json['x'];
|
||||
}
|
||||
}
|
||||
255
lib/feature/devices/device_update/device_update_bloc.dart
Normal file
255
lib/feature/devices/device_update/device_update_bloc.dart
Normal file
@@ -0,0 +1,255 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../product/services/api_services.dart';
|
||||
import '../../../product/services/language_services.dart';
|
||||
import '../../../product/shared/model/ward_model.dart';
|
||||
import '../../../product/utils/response_status_utils.dart';
|
||||
|
||||
import '../../../product/shared/model/district_model.dart';
|
||||
import '../../../product/shared/model/province_model.dart';
|
||||
import '../device_model.dart';
|
||||
import '../../../product/base/bloc/base_bloc.dart';
|
||||
|
||||
class DeviceUpdateBloc extends BlocBase {
|
||||
APIServices apiServices = APIServices();
|
||||
|
||||
final deviceInfo = StreamController<Device>.broadcast();
|
||||
StreamSink<Device> get sinkDeviceInfo => deviceInfo.sink;
|
||||
Stream<Device> get streamDeviceInfo => deviceInfo.stream;
|
||||
|
||||
// DeviceUpdateScreen
|
||||
final isChanged = StreamController<bool>.broadcast();
|
||||
StreamSink<bool> get sinkIsChanged => isChanged.sink;
|
||||
Stream<bool> get streamIsChanged => isChanged.stream;
|
||||
|
||||
final provinceData = StreamController<Map<String, String>>.broadcast();
|
||||
StreamSink<Map<String, String>> get sinkProvinceData => provinceData.sink;
|
||||
Stream<Map<String, String>> get streamProvinceData => provinceData.stream;
|
||||
|
||||
final listProvinces =
|
||||
StreamController<List<DropdownMenuItem<Province>>>.broadcast();
|
||||
StreamSink<List<DropdownMenuItem<Province>>> get sinkListProvinces =>
|
||||
listProvinces.sink;
|
||||
Stream<List<DropdownMenuItem<Province>>> get streamListProvinces =>
|
||||
listProvinces.stream;
|
||||
|
||||
final districtData = StreamController<Map<String, String>>.broadcast();
|
||||
StreamSink<Map<String, String>> get sinkDistrictData => districtData.sink;
|
||||
Stream<Map<String, String>> get streamDistrictData => districtData.stream;
|
||||
|
||||
final listDistricts =
|
||||
StreamController<List<DropdownMenuItem<District>>>.broadcast();
|
||||
StreamSink<List<DropdownMenuItem<District>>> get sinkListDistricts =>
|
||||
listDistricts.sink;
|
||||
Stream<List<DropdownMenuItem<District>>> get streamListDistricts =>
|
||||
listDistricts.stream;
|
||||
|
||||
final wardData = StreamController<Map<String, String>>.broadcast();
|
||||
StreamSink<Map<String, String>> get sinkWardData => wardData.sink;
|
||||
Stream<Map<String, String>> get streamWardData => wardData.stream;
|
||||
|
||||
final listWards = StreamController<List<DropdownMenuItem<Ward>>>.broadcast();
|
||||
StreamSink<List<DropdownMenuItem<Ward>>> get sinkListWards => listWards.sink;
|
||||
Stream<List<DropdownMenuItem<Ward>>> get streamListWards => listWards.stream;
|
||||
|
||||
// Show Maps in DeviceUpdateScreen
|
||||
|
||||
final markers = StreamController<Set<Marker>>.broadcast();
|
||||
StreamSink<Set<Marker>> get sinkMarkers => markers.sink;
|
||||
Stream<Set<Marker>> get streamMarkers => markers.stream;
|
||||
|
||||
final searchLocation = StreamController<TextEditingController>.broadcast();
|
||||
StreamSink<TextEditingController> get sinkSearchLocation =>
|
||||
searchLocation.sink;
|
||||
Stream<TextEditingController> get streamSearchLocation =>
|
||||
searchLocation.stream;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// deviceInfo.done;
|
||||
}
|
||||
|
||||
Future<void> getAllProvinces() async {
|
||||
List<DropdownMenuItem<Province>> provincesData = [];
|
||||
provincesData.clear();
|
||||
sinkListProvinces.add(provincesData);
|
||||
final body = await apiServices.getAllProvinces();
|
||||
final data = jsonDecode(body);
|
||||
List<dynamic> items = data["items"];
|
||||
|
||||
final provinces = Province.fromJsonDynamicList(items);
|
||||
for (var province in provinces) {
|
||||
provincesData.add(
|
||||
DropdownMenuItem(value: province, child: Text(province.fullName!)));
|
||||
}
|
||||
sinkListProvinces.add(provincesData);
|
||||
}
|
||||
|
||||
Future<void> getAllDistricts(String provinceID) async {
|
||||
List<DropdownMenuItem<District>> districtsData = [];
|
||||
districtsData.clear();
|
||||
sinkListDistricts.add(districtsData);
|
||||
final body = await apiServices.getAllDistricts(provinceID);
|
||||
final data = jsonDecode(body);
|
||||
List<dynamic> items = data["items"];
|
||||
final districts = District.fromJsonDynamicList(items);
|
||||
for (var district in districts) {
|
||||
districtsData.add(
|
||||
DropdownMenuItem(value: district, child: Text(district.fullName!)));
|
||||
}
|
||||
sinkListDistricts.add(districtsData);
|
||||
}
|
||||
|
||||
Future<void> getAllWards(String districtID) async {
|
||||
List<DropdownMenuItem<Ward>> wardsData = [];
|
||||
wardsData.clear();
|
||||
sinkListWards.add(wardsData);
|
||||
final body = await apiServices.getAllWards(districtID);
|
||||
final data = jsonDecode(body);
|
||||
List<dynamic> items = data["items"];
|
||||
final wards = Ward.fromJsonDynamicList(items);
|
||||
for (var ward in wards) {
|
||||
wardsData.add(DropdownMenuItem(value: ward, child: Text(ward.fullName!)));
|
||||
}
|
||||
sinkListWards.add(wardsData);
|
||||
}
|
||||
|
||||
Future<void> getDeviceInfomation(
|
||||
String thingID,
|
||||
List<DropdownMenuItem<District>> districtsData,
|
||||
List<DropdownMenuItem<Ward>> wardsData,
|
||||
TextEditingController deviceNameController,
|
||||
TextEditingController latitudeController,
|
||||
TextEditingController longitudeController) async {
|
||||
String body = await apiServices.getDeviceInfomation(thingID);
|
||||
final data = jsonDecode(body);
|
||||
Device device = Device.fromJson(data);
|
||||
sinkDeviceInfo.add(device);
|
||||
deviceNameController.text = device.name ?? "";
|
||||
latitudeController.text = device.settings!.latitude ?? "";
|
||||
longitudeController.text = device.settings!.longitude ?? "";
|
||||
if (device.areaPath != "") {
|
||||
List<String> areaPath = device.areaPath!.split('_');
|
||||
String provinceCode = areaPath[0];
|
||||
String districtCode = areaPath[1];
|
||||
String wardCode = areaPath[2];
|
||||
getAllDistricts(provinceCode);
|
||||
getAllWards(districtCode);
|
||||
final provinceResponse = await apiServices.getProvinceByID(provinceCode);
|
||||
final provincesData = jsonDecode(provinceResponse);
|
||||
Province province = Province.fromJson(provincesData['data']);
|
||||
final districtResponse = await apiServices.getDistrictByID(districtCode);
|
||||
final districtData = jsonDecode(districtResponse);
|
||||
District district = District.fromJson(districtData['data']);
|
||||
final wardResponse = await apiServices.getWardByID(wardCode);
|
||||
final wardData = jsonDecode(wardResponse);
|
||||
Ward ward = Ward.fromJson(wardData['data']);
|
||||
Map<String, String> provinceData = {
|
||||
"name": province.fullName!,
|
||||
"code": province.code!
|
||||
};
|
||||
sinkProvinceData.add(provinceData);
|
||||
Map<String, String> districData = {
|
||||
"name": district.fullName!,
|
||||
"code": district.code!,
|
||||
};
|
||||
sinkDistrictData.add(districData);
|
||||
Map<String, String> wardMap = {
|
||||
"name": ward.fullName!,
|
||||
"code": ward.code!,
|
||||
};
|
||||
sinkWardData.add(wardMap);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Province> getProvinceByName(String name) async {
|
||||
final response = await apiServices.getProvincesByName(name);
|
||||
final data = jsonDecode(response);
|
||||
if (data != null &&
|
||||
data.containsKey('items') &&
|
||||
data['items'] != null &&
|
||||
data['items'].isNotEmpty) {
|
||||
List<dynamic> items = data['items'];
|
||||
List<Province> provinces = Province.fromJsonDynamicList(items);
|
||||
if (provinces.isNotEmpty) {
|
||||
return provinces[0];
|
||||
}
|
||||
}
|
||||
return Province(name: "null");
|
||||
}
|
||||
|
||||
Future<District> getDistrictByName(String name, String provinceCode) async {
|
||||
final response = await apiServices.getDistrictsByName(name);
|
||||
if (response != "") {
|
||||
final data = jsonDecode(response);
|
||||
List<dynamic> items = data['items'];
|
||||
if (items.isNotEmpty) {
|
||||
List<District> districts = District.fromJsonDynamicList(items);
|
||||
if (districts.isNotEmpty) {
|
||||
for (var district in districts) {
|
||||
if (district.provinceCode == provinceCode) {
|
||||
return district;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return District(name: "null");
|
||||
}
|
||||
|
||||
Future<Ward> getWardByName(String name, String districtCode) async {
|
||||
final response = await apiServices.getWarsdByName(name);
|
||||
final data = jsonDecode(response);
|
||||
if (data != null && data['items'] != null) {
|
||||
List<dynamic> items = data['items'];
|
||||
if (items.isNotEmpty) {
|
||||
List<Ward> wards = Ward.fromJsonDynamicList(items);
|
||||
if (wards.isNotEmpty) {
|
||||
for (var ward in wards) {
|
||||
if (ward.districtCode == districtCode) {
|
||||
return ward;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ward(name: "null");
|
||||
}
|
||||
|
||||
Future<void> updateDevice(
|
||||
BuildContext context,
|
||||
String thingID,
|
||||
String name,
|
||||
String latitude,
|
||||
String longitude,
|
||||
String provinceCode,
|
||||
String districtCode,
|
||||
String wardCode,
|
||||
) async {
|
||||
DateTime dateTime = DateTime.now();
|
||||
String formattedDateTime =
|
||||
DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
|
||||
Map<String, dynamic> body = {
|
||||
"name": name,
|
||||
"area_province": provinceCode,
|
||||
"area_district": districtCode,
|
||||
"area_ward": wardCode,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"note": "User updated device infomation at $formattedDateTime",
|
||||
};
|
||||
int statusCode = await apiServices.updateOwnerDevice(thingID, body);
|
||||
showSnackBarResponseByStatusCodeNoIcon(
|
||||
context,
|
||||
statusCode,
|
||||
appLocalization(context).notification_update_device_success,
|
||||
appLocalization(context).notification_update_device_failed,
|
||||
);
|
||||
}
|
||||
}
|
||||
515
lib/feature/devices/device_update/device_update_screen.dart
Normal file
515
lib/feature/devices/device_update/device_update_screen.dart
Normal file
@@ -0,0 +1,515 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:search_choices/search_choices.dart';
|
||||
|
||||
import '../device_model.dart';
|
||||
import 'device_update_bloc.dart';
|
||||
import 'map_dialog.dart';
|
||||
import '../../../product/base/bloc/base_bloc.dart';
|
||||
import '../../../product/extention/context_extention.dart';
|
||||
import '../../../product/services/api_services.dart';
|
||||
import '../../../product/services/language_services.dart';
|
||||
|
||||
import '../../../product/shared/model/district_model.dart';
|
||||
import '../../../product/shared/model/province_model.dart';
|
||||
import '../../../product/shared/model/ward_model.dart';
|
||||
|
||||
class DeviceUpdateScreen extends StatefulWidget {
|
||||
const DeviceUpdateScreen({super.key, required this.thingID});
|
||||
final String thingID;
|
||||
@override
|
||||
State<DeviceUpdateScreen> createState() => _DeviceUpdateScreenState();
|
||||
}
|
||||
|
||||
class _DeviceUpdateScreenState extends State<DeviceUpdateScreen> {
|
||||
late DeviceUpdateBloc deviceUpdateBloc;
|
||||
APIServices apiServices = APIServices();
|
||||
Device device = Device();
|
||||
bool isChanged = false;
|
||||
TextEditingController deviceNameController = TextEditingController();
|
||||
TextEditingController deviceLatitudeController = TextEditingController();
|
||||
TextEditingController deviceLongitudeController = TextEditingController();
|
||||
List<DropdownMenuItem<Province>> provincesData = [];
|
||||
String? selectedProvince = "";
|
||||
List<DropdownMenuItem<District>> districtsData = [];
|
||||
String? selectedDistrict = "";
|
||||
List<DropdownMenuItem<Ward>> wardsData = [];
|
||||
String? selectedWard = "";
|
||||
|
||||
// static String provinceCode = "";
|
||||
// static String districtCode = "";
|
||||
// static String wardCode = "";
|
||||
|
||||
Map<String, String> provinceData = {};
|
||||
Map<String, String> districtData = {};
|
||||
Map<String, String> wardData = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
deviceUpdateBloc = BlocProvider.of(context);
|
||||
deviceUpdateBloc.getAllProvinces();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(appLocalization(context).device_update_title),
|
||||
),
|
||||
body: StreamBuilder<Map<String, String>>(
|
||||
stream: deviceUpdateBloc.streamProvinceData,
|
||||
builder: (context, provinceNameSnapshot) {
|
||||
return StreamBuilder<Map<String, String>>(
|
||||
stream: deviceUpdateBloc.streamDistrictData,
|
||||
builder: (context, districtNameSnapshot) {
|
||||
return StreamBuilder<Map<String, String>>(
|
||||
stream: deviceUpdateBloc.streamWardData,
|
||||
builder: (context, wardNameSnapshot) {
|
||||
return SafeArea(
|
||||
child: StreamBuilder<Device>(
|
||||
stream: deviceUpdateBloc.streamDeviceInfo,
|
||||
initialData: device,
|
||||
builder: (context, deviceInfoSnapshot) {
|
||||
if (deviceInfoSnapshot.data!.thingId == null) {
|
||||
deviceUpdateBloc.getDeviceInfomation(
|
||||
widget.thingID,
|
||||
districtsData,
|
||||
wardsData,
|
||||
deviceNameController,
|
||||
deviceLatitudeController,
|
||||
deviceLongitudeController);
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
} else {
|
||||
return StreamBuilder<bool>(
|
||||
stream: deviceUpdateBloc.streamIsChanged,
|
||||
initialData: isChanged,
|
||||
builder: (context, isChangedSnapshot) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: context.paddingLow,
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${appLocalization(context).input_name_device_device}:",
|
||||
style: context
|
||||
.titleMediumTextStyle),
|
||||
Padding(
|
||||
padding:
|
||||
context.paddingLowVertical,
|
||||
child: TextField(
|
||||
onChanged: (value) {
|
||||
isChangedListener();
|
||||
},
|
||||
textInputAction:
|
||||
TextInputAction.next,
|
||||
controller:
|
||||
deviceNameController,
|
||||
decoration: InputDecoration(
|
||||
hintText: appLocalization(
|
||||
context)
|
||||
.input_name_device_hintText,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).device_update_location}:",
|
||||
style: context
|
||||
.titleMediumTextStyle),
|
||||
Padding(
|
||||
padding:
|
||||
context.paddingLowVertical,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: context
|
||||
.dynamicWidth(0.6),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
child: TextField(
|
||||
onChanged: (value) {
|
||||
isChangedListener();
|
||||
},
|
||||
textInputAction:
|
||||
TextInputAction
|
||||
.next,
|
||||
controller:
|
||||
deviceLatitudeController,
|
||||
decoration:
|
||||
InputDecoration(
|
||||
label: Text(
|
||||
"${appLocalization(context).update_device_dialog_location_longitude}:"),
|
||||
hintText: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_longitude_hintText,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: context
|
||||
.paddingLowVertical,
|
||||
child: SizedBox(
|
||||
child: TextField(
|
||||
onChanged:
|
||||
(value) {
|
||||
isChangedListener();
|
||||
},
|
||||
controller:
|
||||
deviceLongitudeController,
|
||||
textInputAction:
|
||||
TextInputAction
|
||||
.next,
|
||||
decoration:
|
||||
InputDecoration(
|
||||
label: Text(
|
||||
"${appLocalization(context).update_device_dialog_location_latitude}:"),
|
||||
hintText: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_latitude_hintText,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: IconButton.filled(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStatePropertyAll(
|
||||
Colors
|
||||
.lightGreen)),
|
||||
// iconSize: 24,
|
||||
onPressed: () async {
|
||||
showMapDialog(
|
||||
context,
|
||||
deviceUpdateBloc,
|
||||
deviceLatitudeController,
|
||||
deviceLongitudeController);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.map_outlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: context.lowValue),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).device_update_province}:",
|
||||
style: context
|
||||
.titleMediumTextStyle),
|
||||
Padding(
|
||||
padding:
|
||||
context.paddingLowVertical,
|
||||
child: StreamBuilder<
|
||||
List<
|
||||
DropdownMenuItem<
|
||||
Province>>>(
|
||||
stream: deviceUpdateBloc
|
||||
.streamListProvinces,
|
||||
builder: (dialogContext,
|
||||
listProvinces) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius
|
||||
.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
border: Border.all(),
|
||||
),
|
||||
child: SearchChoices.single(
|
||||
items:
|
||||
listProvinces.data ??
|
||||
provincesData,
|
||||
hint: provinceNameSnapshot
|
||||
.data !=
|
||||
null
|
||||
? Text(
|
||||
provinceNameSnapshot
|
||||
.data![
|
||||
'name'] ??
|
||||
"",
|
||||
)
|
||||
: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_province_hintText,
|
||||
searchHint: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_province_searchHint,
|
||||
displayClearIcon: false,
|
||||
onChanged: (value) {
|
||||
isChangedListener();
|
||||
// provinceCode =
|
||||
// value.code;
|
||||
selectedProvince =
|
||||
value.fullName;
|
||||
provinceData['name'] =
|
||||
value.fullName;
|
||||
provinceData['code'] =
|
||||
value.code;
|
||||
deviceUpdateBloc
|
||||
.sinkProvinceData
|
||||
.add(provinceData);
|
||||
deviceUpdateBloc
|
||||
.getAllDistricts(
|
||||
value.code);
|
||||
selectedDistrict = "";
|
||||
districtData['name'] =
|
||||
selectedDistrict!;
|
||||
// deviceUpdateBloc.sinkDistrictName
|
||||
// .add(selectedDistrict);
|
||||
deviceUpdateBloc
|
||||
.sinkDistrictData
|
||||
.add(districtData);
|
||||
selectedWard = "";
|
||||
wardData['name'] =
|
||||
selectedWard!;
|
||||
deviceUpdateBloc
|
||||
.sinkWardData
|
||||
.add(wardData);
|
||||
},
|
||||
isExpanded: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).device_update_district}:",
|
||||
style: context
|
||||
.titleMediumTextStyle),
|
||||
Padding(
|
||||
padding:
|
||||
context.paddingLowVertical,
|
||||
child: StreamBuilder<
|
||||
List<
|
||||
DropdownMenuItem<
|
||||
District>>>(
|
||||
stream: deviceUpdateBloc
|
||||
.streamListDistricts,
|
||||
builder: (dialogContext,
|
||||
listDistricts) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius
|
||||
.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
border: Border.all(),
|
||||
),
|
||||
child: SearchChoices.single(
|
||||
items:
|
||||
listDistricts.data ??
|
||||
districtsData,
|
||||
hint: districtNameSnapshot
|
||||
.data !=
|
||||
null
|
||||
? Text(
|
||||
districtNameSnapshot
|
||||
.data![
|
||||
'name'] ??
|
||||
selectedDistrict!,
|
||||
)
|
||||
: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_district_hintText,
|
||||
searchHint: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_district_searchHint,
|
||||
displayClearIcon: false,
|
||||
onChanged: (value) {
|
||||
isChangedListener();
|
||||
// districtCode =
|
||||
// value.code;
|
||||
selectedDistrict =
|
||||
value.fullName;
|
||||
districtData['name'] =
|
||||
value.fullName!;
|
||||
districtData['code'] =
|
||||
value.code;
|
||||
deviceUpdateBloc
|
||||
.sinkDistrictData
|
||||
.add(districtData);
|
||||
deviceUpdateBloc
|
||||
.getAllWards(
|
||||
value.code);
|
||||
selectedWard = "";
|
||||
wardData['name'] =
|
||||
selectedWard!;
|
||||
deviceUpdateBloc
|
||||
.sinkWardData
|
||||
.add(wardData);
|
||||
},
|
||||
isExpanded: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).device_update_ward}:",
|
||||
style: context
|
||||
.titleMediumTextStyle),
|
||||
Padding(
|
||||
padding:
|
||||
context.paddingLowVertical,
|
||||
child: StreamBuilder<
|
||||
List<DropdownMenuItem<Ward>>>(
|
||||
stream: deviceUpdateBloc
|
||||
.streamListWards,
|
||||
builder:
|
||||
(dialogContext, listWards) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius
|
||||
.all(
|
||||
Radius.circular(
|
||||
20)),
|
||||
border: Border.all()),
|
||||
child: SearchChoices.single(
|
||||
items: listWards.data ??
|
||||
wardsData,
|
||||
hint: wardNameSnapshot
|
||||
.data !=
|
||||
null
|
||||
? Text(
|
||||
wardNameSnapshot
|
||||
.data![
|
||||
'name'] ??
|
||||
selectedWard!,
|
||||
)
|
||||
: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_ward_hintText,
|
||||
searchHint: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_ward_searchHint,
|
||||
displayClearIcon: false,
|
||||
onChanged: (value) {
|
||||
isChangedListener();
|
||||
// wardCode = value.code;
|
||||
selectedWard =
|
||||
value.fullName;
|
||||
wardData['name'] =
|
||||
value.fullName!;
|
||||
wardData['code'] =
|
||||
value.code!;
|
||||
deviceUpdateBloc
|
||||
.sinkWardData
|
||||
.add(wardData);
|
||||
},
|
||||
isExpanded: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (isChangedSnapshot.data == true)
|
||||
Center(
|
||||
child: SizedBox(
|
||||
width:
|
||||
context.dynamicWidth(0.6),
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
foregroundColor:
|
||||
MaterialStateProperty
|
||||
.all(Colors
|
||||
.white),
|
||||
backgroundColor:
|
||||
MaterialStateProperty
|
||||
.all(Colors
|
||||
.blue)),
|
||||
onPressed: () async {
|
||||
String provinceCode =
|
||||
provinceNameSnapshot
|
||||
.data![
|
||||
"code"] ??
|
||||
"";
|
||||
String districtCode =
|
||||
districtNameSnapshot
|
||||
.data![
|
||||
"code"] ??
|
||||
"";
|
||||
String wardCode =
|
||||
wardNameSnapshot
|
||||
.data![
|
||||
"code"] ??
|
||||
"";
|
||||
String latitude =
|
||||
deviceLatitudeController
|
||||
.value.text;
|
||||
String longitude =
|
||||
deviceLongitudeController
|
||||
.value.text;
|
||||
String deviceName =
|
||||
deviceNameController
|
||||
.value.text;
|
||||
// log("ProvinceCode: $provinceCode");
|
||||
// log("DistrictCode: $districtCode");
|
||||
// log("WardCode: $wardCode");
|
||||
// log("Latitude: $latitude");
|
||||
// log("Longitude: $longitude");
|
||||
// log("Device Name: $deviceName");
|
||||
await deviceUpdateBloc
|
||||
.updateDevice(
|
||||
context,
|
||||
deviceInfoSnapshot
|
||||
.data!.thingId!,
|
||||
deviceName,
|
||||
latitude,
|
||||
longitude,
|
||||
provinceCode,
|
||||
districtCode,
|
||||
wardCode,
|
||||
);
|
||||
Future.delayed(
|
||||
// ignore: use_build_context_synchronously
|
||||
context.lowDuration,
|
||||
() {
|
||||
Navigator.pop(
|
||||
context);
|
||||
});
|
||||
},
|
||||
child: Text(appLocalization(
|
||||
context)
|
||||
.update_button_content)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void isChangedListener() {
|
||||
isChanged = true;
|
||||
deviceUpdateBloc.sinkIsChanged.add(isChanged);
|
||||
}
|
||||
}
|
||||
47
lib/feature/devices/device_update/geocode_model.dart
Normal file
47
lib/feature/devices/device_update/geocode_model.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
class Geocode {
|
||||
List<AddressComponent>? addressComponents;
|
||||
String? formattedAddress;
|
||||
Geocode({
|
||||
this.addressComponents,
|
||||
this.formattedAddress,
|
||||
});
|
||||
|
||||
Geocode.fromJson(Map<String, dynamic> json) {
|
||||
formattedAddress = json['formatted_address'];
|
||||
if (json['address_components'] != null) {
|
||||
addressComponents = [];
|
||||
json['address_components'].forEach((v) {
|
||||
addressComponents!.add(AddressComponent.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['formatted_address'] = formattedAddress;
|
||||
if (addressComponents != null) {
|
||||
data['address_components'] =
|
||||
addressComponents!.map((e) => e.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class AddressComponent {
|
||||
String? longName;
|
||||
String? shortName;
|
||||
List<String>? types;
|
||||
AddressComponent({this.longName, this.shortName, this.types});
|
||||
|
||||
AddressComponent.fromJson(Map<String, dynamic> json) {
|
||||
longName = json['long_name'];
|
||||
shortName = json['short_name'];
|
||||
types = json['types'].cast<String>();
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['long_name'] = longName;
|
||||
data['short_name'] = shortName;
|
||||
data['type'] = types;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
298
lib/feature/devices/device_update/map_dialog.dart
Normal file
298
lib/feature/devices/device_update/map_dialog.dart
Normal file
@@ -0,0 +1,298 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'device_update_bloc.dart';
|
||||
import '../../../product/constant/app/app_constants.dart';
|
||||
import '../../../product/extention/context_extention.dart';
|
||||
import '../../../product/services/language_services.dart';
|
||||
import '../../../product/shared/find_location_maps/shared_map_search_location.dart';
|
||||
|
||||
import '../../../product/shared/find_location_maps/model/prediction_model.dart';
|
||||
import '../../../product/shared/shared_transition.dart';
|
||||
import 'geocode_model.dart';
|
||||
|
||||
showMapDialog(
|
||||
BuildContext context,
|
||||
DeviceUpdateBloc deviceUpdateBloc,
|
||||
TextEditingController latitudeController,
|
||||
TextEditingController longitudeController) async {
|
||||
const CameraPosition defaultPosition = CameraPosition(
|
||||
target: LatLng(20.985424, 105.738354),
|
||||
zoom: 12,
|
||||
);
|
||||
TextEditingController searchLocationController = TextEditingController();
|
||||
TextEditingController mapDialogLatitudeController = TextEditingController();
|
||||
TextEditingController mapDialogLongitudeController = TextEditingController();
|
||||
Completer<GoogleMapController> ggmapController = Completer();
|
||||
final streamController = StreamController<GoogleMapController>.broadcast();
|
||||
showGeneralDialog(
|
||||
barrierDismissible: false,
|
||||
transitionDuration: context.normalDuration,
|
||||
transitionBuilder: transitionsLeftToRight,
|
||||
context: context,
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return StreamBuilder<Set<Marker>>(
|
||||
stream: deviceUpdateBloc.streamMarkers,
|
||||
builder: (context, markerSnapshot) {
|
||||
if (!markerSnapshot.hasData) {
|
||||
if (latitudeController.value.text != "" &&
|
||||
longitudeController.value.text != "") {
|
||||
double latitude = double.parse(latitudeController.text);
|
||||
double longitude = double.parse(longitudeController.text);
|
||||
addMarker(
|
||||
LatLng(latitude, longitude),
|
||||
ggmapController,
|
||||
deviceUpdateBloc,
|
||||
mapDialogLatitudeController,
|
||||
mapDialogLongitudeController,
|
||||
);
|
||||
}
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(appLocalization(context)
|
||||
.update_device_dialog_maps_dialog_title),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
String latitude = mapDialogLatitudeController.text;
|
||||
String longitude = mapDialogLongitudeController.text;
|
||||
log("Finish -- Latitude: $latitude, longitude: $longitude --");
|
||||
getDataFromApi(latitude, longitude, deviceUpdateBloc);
|
||||
latitudeController.text =
|
||||
mapDialogLatitudeController.text;
|
||||
longitudeController.text =
|
||||
mapDialogLongitudeController.text;
|
||||
bool isChange = true;
|
||||
deviceUpdateBloc.sinkIsChanged.add(isChange);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.check))
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
GoogleMap(
|
||||
onTap: (location) async {
|
||||
addMarker(
|
||||
location,
|
||||
ggmapController,
|
||||
deviceUpdateBloc,
|
||||
mapDialogLatitudeController,
|
||||
mapDialogLongitudeController,
|
||||
);
|
||||
},
|
||||
markers: markerSnapshot.data ?? {},
|
||||
onMapCreated: (GoogleMapController mapController) {
|
||||
ggmapController.complete(mapController);
|
||||
streamController.add(mapController);
|
||||
},
|
||||
initialCameraPosition: defaultPosition,
|
||||
),
|
||||
Container(
|
||||
// color: Colors.white,
|
||||
height: 80,
|
||||
alignment: Alignment.topCenter,
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: StreamBuilder<TextEditingController>(
|
||||
stream: deviceUpdateBloc.streamSearchLocation,
|
||||
builder: (context, searchLocation) {
|
||||
return NearBySearchSFM(
|
||||
textInputAction: TextInputAction.done,
|
||||
textEditingController:
|
||||
searchLocation.data ?? searchLocationController,
|
||||
googleAPIKey: ApplicationConstants.MAP_KEY,
|
||||
locationLatitude: 20.985424,
|
||||
locationLongitude: 105.738354,
|
||||
radius: 50000,
|
||||
inputDecoration: InputDecoration(
|
||||
hintText: appLocalization(context)
|
||||
.update_device_dialog_search_location_hint,
|
||||
border: InputBorder.none),
|
||||
debounceTime: 600,
|
||||
isLatLngRequired: true,
|
||||
getPlaceDetailWithLatLng: (Prediction prediction) {
|
||||
FocusScope.of(context).unfocus();
|
||||
addMarker(
|
||||
LatLng(double.parse(prediction.lat!),
|
||||
double.parse(prediction.lng!)),
|
||||
ggmapController,
|
||||
deviceUpdateBloc,
|
||||
mapDialogLatitudeController,
|
||||
mapDialogLongitudeController);
|
||||
},
|
||||
itemClick: (Prediction prediction) {
|
||||
searchLocationController.text =
|
||||
prediction.structuredFormatting!.mainText!;
|
||||
deviceUpdateBloc.sinkSearchLocation
|
||||
.add(searchLocationController);
|
||||
searchLocationController.selection =
|
||||
TextSelection.fromPosition(TextPosition(
|
||||
offset: prediction.structuredFormatting!
|
||||
.mainText!.length));
|
||||
},
|
||||
boxDecoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(
|
||||
color: Colors.grey,
|
||||
width: 0.5,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20))),
|
||||
);
|
||||
}),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
addMarker(
|
||||
LatLng position,
|
||||
Completer<GoogleMapController> mapController,
|
||||
DeviceUpdateBloc deviceUpdateBloc,
|
||||
TextEditingController mapDialogLatitudeController,
|
||||
TextEditingController mapDialogLongitudeController,
|
||||
) async {
|
||||
log("AddMarker -- Latitude: ${position.latitude}, longitude: ${position.longitude} --");
|
||||
|
||||
Set<Marker> marker = {};
|
||||
deviceUpdateBloc.sinkMarkers.add(marker);
|
||||
Marker newMarker = Marker(
|
||||
markerId: const MarkerId('value'),
|
||||
position: LatLng(position.latitude, position.longitude),
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
|
||||
draggable: true,
|
||||
onDragEnd: (position) {
|
||||
mapDialogLatitudeController.text = position.latitude.toString();
|
||||
mapDialogLongitudeController.text = position.longitude.toString();
|
||||
},
|
||||
);
|
||||
marker.add(newMarker);
|
||||
deviceUpdateBloc.sinkMarkers.add(marker);
|
||||
mapDialogLatitudeController.text = position.latitude.toString();
|
||||
mapDialogLongitudeController.text = position.longitude.toString();
|
||||
updateCameraPosition(position, 14, mapController);
|
||||
}
|
||||
|
||||
void getDataFromApi(String latitude, String longitude,
|
||||
DeviceUpdateBloc deviceUpdateBloc) async {
|
||||
String path =
|
||||
"maps/api/geocode/json?latlng=$latitude,$longitude&language=vi&result_type=political&key=${ApplicationConstants.MAP_KEY}";
|
||||
var url = Uri.parse('https://maps.googleapis.com/$path');
|
||||
|
||||
final response = await http.get(url);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
log("Loi: ${response.statusCode}");
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, dynamic> data = jsonDecode(response.body);
|
||||
if (!data.containsKey('results') || data['results'].isEmpty) {
|
||||
log("Khong co result");
|
||||
return;
|
||||
}
|
||||
|
||||
List<dynamic> results = data['results'];
|
||||
List<Geocode> geocodes =
|
||||
results.map((result) => Geocode.fromJson(result)).toList();
|
||||
|
||||
Map<String, String> locations =
|
||||
_extractLocationComponents(geocodes[0].addressComponents!);
|
||||
|
||||
// In ra thông tin của các location
|
||||
locations.forEach((key, value) {
|
||||
log("$key: $value");
|
||||
});
|
||||
|
||||
await _processLocations(locations, deviceUpdateBloc);
|
||||
}
|
||||
|
||||
Map<String, String> _extractLocationComponents(
|
||||
List<AddressComponent> addressComponents) {
|
||||
Map<String, String> locations = {};
|
||||
|
||||
for (var addressComponent in addressComponents) {
|
||||
String longName = addressComponent.longName ?? "";
|
||||
if (addressComponent.types!.contains('administrative_area_level_3') ||
|
||||
addressComponent.types!.contains('sublocality_level_1')) {
|
||||
locations['wardkey'] = longName;
|
||||
} else if (addressComponent.types!
|
||||
.contains('administrative_area_level_2') ||
|
||||
addressComponent.types!.contains('sublocality_level_2') ||
|
||||
addressComponent.types!.contains('locality')) {
|
||||
locations['districtkey'] = longName;
|
||||
} else if (addressComponent.types!
|
||||
.contains('administrative_area_level_1')) {
|
||||
locations['provincekey'] = longName;
|
||||
}
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
Future<void> _processLocations(
|
||||
Map<String, String> locations, DeviceUpdateBloc deviceUpdateBloc) async {
|
||||
String provinceNameFromAPI = locations['provincekey'] ?? "";
|
||||
String districtNameFromAPI = locations['districtkey'] ?? "";
|
||||
String wardNameFromAPI = locations['wardkey'] ?? "";
|
||||
|
||||
final province =
|
||||
await deviceUpdateBloc.getProvinceByName(provinceNameFromAPI);
|
||||
if (province.name != "null") {
|
||||
log("Province: ${province.fullName}, ProvinceCode: ${province.code}");
|
||||
deviceUpdateBloc.sinkProvinceData
|
||||
.add({"code": province.code!, "name": province.fullName!});
|
||||
deviceUpdateBloc.getAllProvinces();
|
||||
|
||||
final district = await deviceUpdateBloc.getDistrictByName(
|
||||
districtNameFromAPI, province.code!);
|
||||
log("Districtname: ${district.fullName}, districtCode: ${district.code}");
|
||||
deviceUpdateBloc.getAllDistricts(province.code!);
|
||||
if (district.name != "null") {
|
||||
deviceUpdateBloc.sinkDistrictData
|
||||
.add({"code": district.code!, "name": district.fullName!});
|
||||
final ward =
|
||||
await deviceUpdateBloc.getWardByName(wardNameFromAPI, district.code!);
|
||||
log("Wardname: ${ward.fullName}, WardCode: ${ward.code}");
|
||||
deviceUpdateBloc.getAllWards(district.code!);
|
||||
if (ward.name != "null") {
|
||||
log("Xac dinh duoc het thong tin tu toa do");
|
||||
deviceUpdateBloc.sinkWardData
|
||||
.add({"code": ward.code!, "name": ward.fullName!});
|
||||
} else {
|
||||
deviceUpdateBloc.sinkWardData.add({});
|
||||
}
|
||||
} else {
|
||||
deviceUpdateBloc.sinkDistrictData.add({});
|
||||
}
|
||||
} else {
|
||||
deviceUpdateBloc.sinkProvinceData.add({});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateCameraPosition(LatLng location, double zoom,
|
||||
Completer<GoogleMapController> mapController) async {
|
||||
final CameraPosition cameraPosition = CameraPosition(
|
||||
target: LatLng(
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
),
|
||||
zoom: zoom,
|
||||
);
|
||||
final GoogleMapController mapControllerNew = await mapController.future;
|
||||
mapControllerNew.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
cameraPosition,
|
||||
),
|
||||
);
|
||||
}
|
||||
35
lib/feature/devices/devices_manager_bloc.dart
Normal file
35
lib/feature/devices/devices_manager_bloc.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'device_model.dart';
|
||||
import '../../product/base/bloc/base_bloc.dart';
|
||||
import '../../product/services/api_services.dart';
|
||||
|
||||
import '../../product/utils/device_utils.dart';
|
||||
|
||||
class DevicesManagerBloc extends BlocBase {
|
||||
APIServices apiServices = APIServices();
|
||||
|
||||
final userRole = StreamController<String>.broadcast();
|
||||
StreamSink<String> get sinkUserRole => userRole.sink;
|
||||
Stream<String> get streamUserRole => userRole.stream;
|
||||
|
||||
final allDevices = StreamController<List<Device>>.broadcast();
|
||||
StreamSink<List<Device>> get sinkAllDevices => allDevices.sink;
|
||||
Stream<List<Device>> get streamAllDevices => allDevices.stream;
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
|
||||
void getDevice() async {
|
||||
String body = await apiServices.getOwnerDevices();
|
||||
if (body != "") {
|
||||
final data = jsonDecode(body);
|
||||
List<dynamic> items = data['items'];
|
||||
List<Device> originalDevices = Device.fromJsonDynamicList(items);
|
||||
List<Device> devices =
|
||||
DeviceUtils.instance.sortDeviceByState(originalDevices);
|
||||
sinkAllDevices.add(devices);
|
||||
}
|
||||
}
|
||||
}
|
||||
274
lib/feature/devices/devices_manager_screen.dart
Normal file
274
lib/feature/devices/devices_manager_screen.dart
Normal file
@@ -0,0 +1,274 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'add_new_device_widget.dart';
|
||||
import 'delete_device_widget.dart';
|
||||
import 'device_model.dart';
|
||||
import 'devices_manager_bloc.dart';
|
||||
import '../../product/base/bloc/base_bloc.dart';
|
||||
import '../../product/constant/enums/app_route_enums.dart';
|
||||
import '../../product/constant/enums/role_enums.dart';
|
||||
import '../../product/constant/icon/icon_constants.dart';
|
||||
import '../../product/extention/context_extention.dart';
|
||||
import '../../product/services/api_services.dart';
|
||||
import '../../product/services/language_services.dart';
|
||||
import '../../product/utils/device_utils.dart';
|
||||
|
||||
class DevicesManagerScreen extends StatefulWidget {
|
||||
const DevicesManagerScreen({super.key});
|
||||
|
||||
@override
|
||||
State<DevicesManagerScreen> createState() => _DevicesManagerScreenState();
|
||||
}
|
||||
|
||||
class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
|
||||
late DevicesManagerBloc devicesManagerBloc;
|
||||
String role = "Undefine";
|
||||
APIServices apiServices = APIServices();
|
||||
List<Device> devices = [];
|
||||
Timer? getAllDevicesTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
devicesManagerBloc = BlocProvider.of(context);
|
||||
getUserRole();
|
||||
// devicesManagerBloc.getDevice();
|
||||
// getAllOwnerDevices();
|
||||
// const duration = Duration(seconds: 10);
|
||||
// getAllDevicesTimer =
|
||||
// Timer.periodic(duration, (Timer t) => devicesManagerBloc.getDevice());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
getAllDevicesTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: StreamBuilder<List<Device>>(
|
||||
stream: devicesManagerBloc.streamAllDevices,
|
||||
initialData: devices,
|
||||
builder: (context, allDeviceSnapshot) {
|
||||
if (allDeviceSnapshot.data?.isEmpty ?? devices.isEmpty) {
|
||||
devicesManagerBloc.getDevice();
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
StreamBuilder<String>(
|
||||
stream: devicesManagerBloc.streamUserRole,
|
||||
initialData: role,
|
||||
builder: (context, roleSnapshot) {
|
||||
return PaginatedDataTable(
|
||||
header: Center(
|
||||
child: Text(
|
||||
appLocalization(context)
|
||||
.paginated_data_table_title,
|
||||
style: context.titleLargeTextStyle,
|
||||
),
|
||||
),
|
||||
columns: [
|
||||
if (roleSnapshot.data == RoleEnums.ADMIN.name ||
|
||||
roleSnapshot.data == RoleEnums.USER.name)
|
||||
DataColumn(
|
||||
label: Center(
|
||||
child: Text(appLocalization(context)
|
||||
.paginated_data_table_column_action))),
|
||||
DataColumn(
|
||||
label: Center(
|
||||
child: Text(appLocalization(context)
|
||||
.paginated_data_table_column_deviceName))),
|
||||
DataColumn(
|
||||
label: Center(
|
||||
child: Text(appLocalization(context)
|
||||
.paginated_data_table_column_deviceStatus))),
|
||||
DataColumn(
|
||||
label: Center(
|
||||
child: Text(appLocalization(context)
|
||||
.paginated_data_table_column_deviceBaterry))),
|
||||
DataColumn(
|
||||
label: Center(
|
||||
child: Text(appLocalization(context)
|
||||
.paginated_data_table_column_deviceSignal))),
|
||||
DataColumn(
|
||||
label: Center(
|
||||
child: Text(appLocalization(context)
|
||||
.paginated_data_table_column_deviceTemperature))),
|
||||
DataColumn(
|
||||
label: Center(
|
||||
child: Text(appLocalization(context)
|
||||
.paginated_data_table_column_deviceHump))),
|
||||
DataColumn(
|
||||
label: Center(
|
||||
child: Text(appLocalization(context)
|
||||
.paginated_data_table_column_devicePower))),
|
||||
],
|
||||
onPageChanged: (int pageIndex) {
|
||||
// log('Chuyen page: $pageIndex');
|
||||
},
|
||||
rowsPerPage: 5,
|
||||
actions: [
|
||||
if (roleSnapshot.data == RoleEnums.USER.name ||
|
||||
roleSnapshot.data == RoleEnums.ADMIN.name)
|
||||
IconButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.all<Color>(
|
||||
Colors.green),
|
||||
iconColor:
|
||||
MaterialStateProperty.all<Color>(
|
||||
Colors.white)),
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context)
|
||||
.clearSnackBars();
|
||||
addNewDevice(
|
||||
context, roleSnapshot.data ?? role);
|
||||
},
|
||||
icon: IconConstants.instance
|
||||
.getMaterialIcon(Icons.add))
|
||||
],
|
||||
source: DeviceSource(
|
||||
devices: allDeviceSnapshot.data ?? devices,
|
||||
context: context,
|
||||
devicesBloc: devicesManagerBloc,
|
||||
role: role));
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void getUserRole() async {
|
||||
role = await apiServices.getUserRole();
|
||||
devicesManagerBloc.sinkUserRole.add(role);
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceSource extends DataTableSource {
|
||||
String role;
|
||||
APIServices apiServices = APIServices();
|
||||
List<Device> devices;
|
||||
final DevicesManagerBloc devicesBloc;
|
||||
final BuildContext context;
|
||||
DeviceSource(
|
||||
{required this.devices,
|
||||
required this.context,
|
||||
required this.devicesBloc,
|
||||
required this.role});
|
||||
@override
|
||||
DataRow? getRow(int index) {
|
||||
if (index >= devices.length) {
|
||||
return null;
|
||||
}
|
||||
final device = devices[index];
|
||||
Map<String, dynamic> sensorMap = DeviceUtils.instance
|
||||
.getDeviceSensors(context, device.status?.sensors ?? []);
|
||||
String deviceState =
|
||||
DeviceUtils.instance.checkStateDevice(context, device.state!);
|
||||
return DataRow.byIndex(
|
||||
// color: getTableRowColor(device.state!),
|
||||
index: index,
|
||||
cells: [
|
||||
if (role == RoleEnums.USER.name || role == RoleEnums.ADMIN.name)
|
||||
DataCell(
|
||||
Center(
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
// style: ButtonStyle(),
|
||||
hoverColor: Colors.black,
|
||||
onPressed: () {
|
||||
context.pushNamed(AppRoutes.DEVICE_UPDATE.name,
|
||||
pathParameters: {'thingID': device.thingId!});
|
||||
},
|
||||
icon: const Icon(Icons.build, color: Colors.blue)),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
handleDeleteDevice(context, device.thingId!, role);
|
||||
},
|
||||
icon: const Icon(Icons.delete, color: Colors.red)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Text(device.name!,
|
||||
style: TextStyle(
|
||||
color: DeviceUtils.instance
|
||||
.getTableRowColor(device.state!))), onTap: () {
|
||||
// log(device.thingId.toString());
|
||||
context.pushNamed(AppRoutes.DEVICE_DETAIL.name,
|
||||
pathParameters: {'thingID': device.thingId!});
|
||||
}),
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text(deviceState,
|
||||
style: TextStyle(
|
||||
color: DeviceUtils.instance
|
||||
.getTableRowColor(device.state!)))), onTap: () {
|
||||
// log(device.thingId.toString());
|
||||
context.pushNamed(AppRoutes.DEVICE_DETAIL.name,
|
||||
pathParameters: {'thingID': device.thingId!});
|
||||
}),
|
||||
DataCell(
|
||||
Center(
|
||||
child: Center(
|
||||
child: Text(sensorMap['sensorBattery'] + "%",
|
||||
style: TextStyle(
|
||||
color: DeviceUtils.instance
|
||||
.getTableRowColor(device.state!))))),
|
||||
onTap: () => context.pushNamed(AppRoutes.DEVICE_DETAIL.name,
|
||||
pathParameters: {'thingID': device.thingId!}),
|
||||
),
|
||||
DataCell(
|
||||
Center(
|
||||
child: Center(
|
||||
child: Text(sensorMap['sensorCsq'],
|
||||
style: TextStyle(
|
||||
color: DeviceUtils.instance
|
||||
.getTableRowColor(device.state!))))),
|
||||
),
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text(sensorMap['sensorTemp'],
|
||||
style: TextStyle(
|
||||
color: DeviceUtils.instance
|
||||
.getTableRowColor(device.state!)))),
|
||||
),
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text(sensorMap['sensorHum'],
|
||||
style: TextStyle(
|
||||
color: DeviceUtils.instance
|
||||
.getTableRowColor(device.state!)))),
|
||||
),
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text(sensorMap['sensorVolt'],
|
||||
style: TextStyle(
|
||||
color: DeviceUtils.instance
|
||||
.getTableRowColor(device.state!)))),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get rowCount => devices.length;
|
||||
|
||||
@override
|
||||
bool get isRowCountApproximate => false;
|
||||
|
||||
@override
|
||||
int get selectedRowCount => 0;
|
||||
}
|
||||
Reference in New Issue
Block a user