Complete refactoring SFM App Source Code
This commit is contained in:
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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user