Update Bottom Navigator in MainScreen
This commit is contained in:
302
lib/feature/device_log/device_logs_screen.dart
Normal file
302
lib/feature/device_log/device_logs_screen.dart
Normal file
@@ -0,0 +1,302 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:sfm_app/feature/devices/device_model.dart';
|
||||
import 'package:sfm_app/feature/device_log/device_logs_bloc.dart';
|
||||
import 'package:sfm_app/product/constant/icon/icon_constants.dart';
|
||||
import 'package:sfm_app/product/extention/context_extention.dart';
|
||||
import 'package:sfm_app/product/services/language_services.dart';
|
||||
import 'package:sfm_app/product/shared/shared_snack_bar.dart';
|
||||
import 'package:sfm_app/product/utils/date_time_utils.dart';
|
||||
import 'package:sfm_app/product/utils/device_utils.dart';
|
||||
|
||||
import '../../product/base/bloc/base_bloc.dart';
|
||||
import 'device_logs_model.dart';
|
||||
|
||||
class DeviceLogsScreen extends StatefulWidget {
|
||||
const DeviceLogsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<DeviceLogsScreen> createState() => _DeviceLogsScreenState();
|
||||
}
|
||||
|
||||
class _DeviceLogsScreenState extends State<DeviceLogsScreen> {
|
||||
TextEditingController fromDate = TextEditingController();
|
||||
String fromDateApi = '';
|
||||
DateTime? dateTime;
|
||||
String thingID = "";
|
||||
late DeviceLogsBloc deviceLogsBloc;
|
||||
List<Device> allDevices = [];
|
||||
List<SensorLogs> sensors = [];
|
||||
int offset = 0;
|
||||
final controller = ScrollController();
|
||||
bool hasMore = true;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
deviceLogsBloc = BlocProvider.of(context);
|
||||
controller.addListener(
|
||||
() {
|
||||
if (controller.position.maxScrollExtent == controller.offset) {
|
||||
offset += 30;
|
||||
deviceLogsBloc.getDeviceLogByThingID(
|
||||
offset, thingID, dateTime!, sensors);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
sensors.clear();
|
||||
deviceLogsBloc.sinkSensors.add(sensors);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: StreamBuilder<List<Device>>(
|
||||
stream: deviceLogsBloc.streamAllDevices,
|
||||
builder: (context, allDevicesSnapshot) {
|
||||
if (allDevicesSnapshot.data?[0].thingId == null) {
|
||||
deviceLogsBloc.getAllDevices();
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
} else {
|
||||
return StreamBuilder<List<SensorLogs>>(
|
||||
stream: deviceLogsBloc.streamSensors,
|
||||
initialData: sensors,
|
||||
builder: (context, sensorsSnapshot) {
|
||||
return Padding(
|
||||
padding: context.paddingLow,
|
||||
child: Scaffold(
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
DropdownButtonFormField2<String>(
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: context.paddingLowVertical,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
hint: Text(
|
||||
appLocalization(context)
|
||||
.choose_device_dropdownButton,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
items: allDevicesSnapshot.data?.isNotEmpty ?? false
|
||||
? allDevicesSnapshot.data!
|
||||
.map(
|
||||
(device) => DropdownMenuItem<String>(
|
||||
value: device.thingId,
|
||||
child: Text(
|
||||
device.name!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
: [],
|
||||
buttonStyleData: const ButtonStyleData(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
),
|
||||
onChanged: (value) {
|
||||
thingID = value!;
|
||||
setState(() {});
|
||||
},
|
||||
onSaved: (value) {
|
||||
log("On Saved");
|
||||
},
|
||||
iconStyleData: const IconStyleData(
|
||||
icon: Icon(
|
||||
Icons.arrow_drop_down,
|
||||
),
|
||||
iconSize: 24,
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: context.paddingLowVertical,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: context.dynamicWidth(0.6),
|
||||
child: TextField(
|
||||
controller: fromDate,
|
||||
decoration: InputDecoration(
|
||||
icon: IconConstants.instance
|
||||
.getMaterialIcon(
|
||||
Icons.calendar_month_outlined,
|
||||
),
|
||||
labelText: appLocalization(context)
|
||||
.choose_date_start_datePicker,
|
||||
),
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
DateTime? datePicker =
|
||||
await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(2023),
|
||||
lastDate: DateTime(2050),
|
||||
);
|
||||
dateTime = datePicker;
|
||||
if (datePicker != null) {
|
||||
fromDateApi = DateTimeUtils.instance
|
||||
.formatDateTimeToString(datePicker);
|
||||
setState(
|
||||
() {
|
||||
fromDate.text =
|
||||
DateFormat('yyyy-MM-dd')
|
||||
.format(datePicker);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: TextButton.icon(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Colors.green),
|
||||
),
|
||||
onPressed: () {
|
||||
if (fromDateApi.isEmpty) {
|
||||
showNoIconTopSnackBar(
|
||||
context,
|
||||
appLocalization(context)
|
||||
.notification_enter_all_inf,
|
||||
Colors.red,
|
||||
Colors.white,
|
||||
);
|
||||
} else {
|
||||
deviceLogsBloc.getDeviceLogByThingID(
|
||||
offset,
|
||||
thingID,
|
||||
dateTime!,
|
||||
sensors);
|
||||
}
|
||||
log("ThingID: $thingID");
|
||||
log("From Date: ${DateTimeUtils.instance.formatDateTimeToString(dateTime!)}");
|
||||
},
|
||||
icon: IconConstants.instance
|
||||
.getMaterialIcon(Icons.search),
|
||||
label: Text(
|
||||
appLocalization(context)
|
||||
.find_button_content,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: context.lowValue,
|
||||
),
|
||||
Expanded(
|
||||
child: sensorsSnapshot.data?.isEmpty ?? false
|
||||
? Center(
|
||||
child: Text(appLocalization(context)
|
||||
.no_data_message),
|
||||
)
|
||||
: RefreshIndicator(
|
||||
onRefresh: refresh,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
itemCount:
|
||||
sensorsSnapshot.data!.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index <
|
||||
sensorsSnapshot.data!.length) {
|
||||
return logDetail(
|
||||
sensorsSnapshot.data![index],
|
||||
index,
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: context.paddingLow,
|
||||
child: StreamBuilder<bool>(
|
||||
stream:
|
||||
deviceLogsBloc.streamHasMore,
|
||||
builder:
|
||||
(context, hasMoreSnapshot) {
|
||||
return Center(
|
||||
child: hasMoreSnapshot.data ??
|
||||
hasMore
|
||||
? const CircularProgressIndicator()
|
||||
: Text(appLocalization(
|
||||
context)
|
||||
.main_no_data),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget logDetail(SensorLogs sensor, int index) {
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
subtitle:
|
||||
Text(DeviceUtils.instance.getDeviceSensorsLog(context, sensor)),
|
||||
// leading: const Icon(Icons.sensors_outlined),
|
||||
leading: Text(index.toString()),
|
||||
title: Text(
|
||||
DateTimeUtils.instance
|
||||
.convertCurrentMillisToDateTimeString(sensor.time ?? 0),
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
thickness: 0.5,
|
||||
indent: 50,
|
||||
endIndent: 100,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
offset = 0;
|
||||
sensors.clear();
|
||||
deviceLogsBloc.sensors.add(sensors);
|
||||
hasMore = true;
|
||||
deviceLogsBloc.sinkHasMore.add(hasMore);
|
||||
deviceLogsBloc.getDeviceLogByThingID(offset, thingID, dateTime!, sensors);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ 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 '../../../product/utils/date_time_utils.dart';
|
||||
import '../../device_log/device_logs_model.dart';
|
||||
import '../device_model.dart';
|
||||
|
||||
import '../../../product/base/bloc/base_bloc.dart';
|
||||
@@ -28,6 +30,10 @@ class DetailDeviceBloc extends BlocBase {
|
||||
StreamSink<String> get sinkDeviceLocation => deviceLocation.sink;
|
||||
Stream<String> get streamDeviceLocation => deviceLocation.stream;
|
||||
|
||||
final sensorTemps = StreamController<List<SensorLogs>>.broadcast();
|
||||
StreamSink<List<SensorLogs>> get sinkSensorTemps => sensorTemps.sink;
|
||||
Stream<List<SensorLogs>> get streamSensorTemps => sensorTemps.stream;
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
|
||||
@@ -76,4 +82,35 @@ class DetailDeviceBloc extends BlocBase {
|
||||
await DeviceUtils.instance.getFullDeviceLocation(context, areaPath);
|
||||
sinkDeviceLocation.add(fullLocation);
|
||||
}
|
||||
|
||||
void getNearerSensorValue(String thingID) async {
|
||||
List<SensorLogs> sensorTemps = [];
|
||||
DateTime twoDaysAgo = DateTime.now().subtract(const Duration(days: 2));
|
||||
String from = DateTimeUtils.instance.formatDateTimeToString(twoDaysAgo);
|
||||
String now = DateTimeUtils.instance.formatDateTimeToString(DateTime.now());
|
||||
Map<String, dynamic> params = {
|
||||
'thing_id': thingID,
|
||||
'from': from,
|
||||
'to': now,
|
||||
'limit': '500',
|
||||
};
|
||||
final body = await apiServices.getLogsOfDevice(thingID, params);
|
||||
if (body != "") {
|
||||
final data = jsonDecode(body);
|
||||
DeviceLog devicesListLog = DeviceLog.fromJson(data);
|
||||
if (devicesListLog.sensors!.isNotEmpty) {
|
||||
for (var sensor in devicesListLog.sensors!) {
|
||||
if (sensor.name == "8") {
|
||||
if (sensorTemps.length < 100) {
|
||||
sensorTemps.add(sensor);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sensorTemps = sensorTemps.reversed.toList();
|
||||
sinkSensorTemps.add(sensorTemps);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:sfm_app/feature/device_log/device_logs_model.dart';
|
||||
import 'package:sfm_app/product/shared/shared_line_chart.dart';
|
||||
import 'package:simple_ripple_animation/simple_ripple_animation.dart';
|
||||
import 'dart:math' as math;
|
||||
import '../device_model.dart';
|
||||
@@ -110,28 +112,37 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
|
||||
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(
|
||||
// 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!,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
CircleAvatar(
|
||||
minRadius: 20,
|
||||
maxRadius: 20,
|
||||
backgroundImage: AssetImage(
|
||||
stateImgAssets(
|
||||
deviceSnapshot.data!.state!,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -182,6 +193,33 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
StreamBuilder<List<SensorLogs>>(
|
||||
stream: detailDeviceBloc.streamSensorTemps,
|
||||
builder: (context, sensorTempsSnapshot) {
|
||||
if (sensorTempsSnapshot.data == null) {
|
||||
detailDeviceBloc
|
||||
.getNearerSensorValue(widget.thingID);
|
||||
return const AspectRatio(
|
||||
aspectRatio: 1.5,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.5,
|
||||
child: Container(
|
||||
margin: context.paddingLow,
|
||||
child: sharedLineChart(
|
||||
"Nhiệt độ đo được (°C)",
|
||||
sensorTempsSnapshot.data ?? [],
|
||||
60,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Card(
|
||||
|
||||
@@ -49,8 +49,9 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: StreamBuilder<List<Device>>(
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: StreamBuilder<List<Device>>(
|
||||
stream: devicesManagerBloc.streamAllDevices,
|
||||
initialData: devices,
|
||||
builder: (context, allDeviceSnapshot) {
|
||||
@@ -63,88 +64,93 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
|
||||
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));
|
||||
})
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,203 +61,207 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: context.paddingLow,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
appLocalization(context).notification,
|
||||
style: context.titleMediumTextStyle,
|
||||
),
|
||||
SizedBox(width: context.lowValue),
|
||||
StreamBuilder<int>(
|
||||
stream: homeBloc.streamCountNotitication,
|
||||
builder: (context, countSnapshot) {
|
||||
return Text(
|
||||
"(${countSnapshot.data ?? 0})",
|
||||
style: context.titleMediumTextStyle,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: StreamBuilder<Map<String, List<DeviceWithAlias>>>(
|
||||
stream: homeBloc.streamOwnerDevicesStatus,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data?['state'] != null ||
|
||||
snapshot.data?['battery'] != null) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (snapshot.data?['state'] != null)
|
||||
...snapshot.data!['state']!
|
||||
.map(
|
||||
(item) => FutureBuilder<Widget>(
|
||||
future: warningCard(
|
||||
context, apiServices, item),
|
||||
builder: (context, warningCardSnapshot) {
|
||||
if (warningCardSnapshot.hasData) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400,
|
||||
maxHeight: 260,
|
||||
),
|
||||
child: warningCardSnapshot.data!,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
if (snapshot.data?['battery'] != null)
|
||||
...snapshot.data!['battery']!
|
||||
.map(
|
||||
(batteryItem) => FutureBuilder<Widget>(
|
||||
future: notificationCard(
|
||||
context,
|
||||
"lowBattery",
|
||||
"Cảnh báo pin yếu",
|
||||
batteryItem.name!,
|
||||
batteryItem.areaPath!,
|
||||
),
|
||||
builder: (context, warningCardSnapshot) {
|
||||
if (warningCardSnapshot.hasData) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400,
|
||||
maxHeight: 260,
|
||||
),
|
||||
child: warningCardSnapshot.data!,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
]);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: context.paddingMedium,
|
||||
child: Center(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.check_circle_outline_rounded,
|
||||
size: 40,
|
||||
color: Colors.green,
|
||||
),
|
||||
SizedBox(width: context.lowValue),
|
||||
Text(
|
||||
appLocalization(context)
|
||||
.notification_description,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
padding: context.paddingLow,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
appLocalization(context).notification,
|
||||
style: context.titleMediumTextStyle,
|
||||
),
|
||||
SizedBox(width: context.lowValue),
|
||||
StreamBuilder<int>(
|
||||
stream: homeBloc.streamCountNotitication,
|
||||
builder: (context, countSnapshot) {
|
||||
return Text(
|
||||
"(${countSnapshot.data ?? 0})",
|
||||
style: context.titleMediumTextStyle,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: StreamBuilder<Map<String, List<DeviceWithAlias>>>(
|
||||
stream: homeBloc.streamOwnerDevicesStatus,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data?['state'] != null ||
|
||||
snapshot.data?['battery'] != null) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (snapshot.data?['state'] != null)
|
||||
...snapshot.data!['state']!
|
||||
.map(
|
||||
(item) => FutureBuilder<Widget>(
|
||||
future: warningCard(
|
||||
context, apiServices, item),
|
||||
builder:
|
||||
(context, warningCardSnapshot) {
|
||||
if (warningCardSnapshot.hasData) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400,
|
||||
maxHeight: 260,
|
||||
),
|
||||
child: warningCardSnapshot.data!,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
if (snapshot.data?['battery'] != null)
|
||||
...snapshot.data!['battery']!
|
||||
.map(
|
||||
(batteryItem) => FutureBuilder<Widget>(
|
||||
future: notificationCard(
|
||||
context,
|
||||
"lowBattery",
|
||||
"Cảnh báo pin yếu",
|
||||
batteryItem.name!,
|
||||
batteryItem.areaPath!,
|
||||
),
|
||||
builder:
|
||||
(context, warningCardSnapshot) {
|
||||
if (warningCardSnapshot.hasData) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400,
|
||||
maxHeight: 260,
|
||||
),
|
||||
child: warningCardSnapshot.data!,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
]);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: context.paddingMedium,
|
||||
child: Center(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.check_circle_outline_rounded,
|
||||
size: 40,
|
||||
color: Colors.green,
|
||||
),
|
||||
SizedBox(width: context.lowValue),
|
||||
Text(
|
||||
appLocalization(context)
|
||||
.notification_description,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamAllDevicesAlias,
|
||||
initialData: allDevicesAlias,
|
||||
builder: (context, allDeviceSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamOnlineDevicesAlias,
|
||||
initialData: onlineDevicesAlias,
|
||||
builder: (context, onlineDeviceSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamOfflineDevicesAlias,
|
||||
initialData: offlineDevicesAlias,
|
||||
builder: (context, deviceDashboardSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamWarningDevicesAlias,
|
||||
initialData: warningDevicesAlias,
|
||||
builder: (context, warningDeviceSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamNotUseDevicesAlias,
|
||||
initialData: notUseDevicesAlias,
|
||||
builder: (context, notUseSnapshot) {
|
||||
return OverviewCard(
|
||||
isOwner: true,
|
||||
total: allDeviceSnapshot.data!.length,
|
||||
active: onlineDeviceSnapshot.data!.length,
|
||||
inactive:
|
||||
deviceDashboardSnapshot.data!.length,
|
||||
warning: warningDeviceSnapshot.data!.length,
|
||||
unused: notUseSnapshot.data!.length,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(height: context.lowValue),
|
||||
StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamAllDevicesAliasJoined,
|
||||
initialData: allDevicesAliasJoined,
|
||||
builder: (context, allDeviceSnapshot) {
|
||||
if (allDeviceSnapshot.data?.isEmpty ?? true) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamOnlineDevicesAliasJoined,
|
||||
initialData: onlineDevicesAliasJoined,
|
||||
builder: (context, onlineDeviceSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamOfflineDevicesAliasJoined,
|
||||
initialData: offlineDevicesAliasJoined,
|
||||
builder: (context, deviceDashboardSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamWarningDevicesAliasJoined,
|
||||
initialData: warningDevicesAliasJoined,
|
||||
builder: (context, warningDeviceSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamNotUseDevicesAliasJoined,
|
||||
initialData: notUseDevicesAliasJoined,
|
||||
builder: (context, notUseSnapshot) {
|
||||
return OverviewCard(
|
||||
isOwner: false,
|
||||
total: allDeviceSnapshot.data!.length,
|
||||
active: onlineDeviceSnapshot.data!.length,
|
||||
inactive:
|
||||
deviceDashboardSnapshot.data!.length,
|
||||
warning: warningDeviceSnapshot.data!.length,
|
||||
unused: notUseSnapshot.data!.length,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamAllDevicesAlias,
|
||||
initialData: allDevicesAlias,
|
||||
builder: (context, allDeviceSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamOnlineDevicesAlias,
|
||||
initialData: onlineDevicesAlias,
|
||||
builder: (context, onlineDeviceSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamOfflineDevicesAlias,
|
||||
initialData: offlineDevicesAlias,
|
||||
builder: (context, deviceDashboardSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamWarningDevicesAlias,
|
||||
initialData: warningDevicesAlias,
|
||||
builder: (context, warningDeviceSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamNotUseDevicesAlias,
|
||||
initialData: notUseDevicesAlias,
|
||||
builder: (context, notUseSnapshot) {
|
||||
return OverviewCard(
|
||||
isOwner: true,
|
||||
total: allDeviceSnapshot.data!.length,
|
||||
active: onlineDeviceSnapshot.data!.length,
|
||||
inactive:
|
||||
deviceDashboardSnapshot.data!.length,
|
||||
warning: warningDeviceSnapshot.data!.length,
|
||||
unused: notUseSnapshot.data!.length,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(height: context.lowValue),
|
||||
StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamAllDevicesAliasJoined,
|
||||
initialData: allDevicesAliasJoined,
|
||||
builder: (context, allDeviceSnapshot) {
|
||||
if (allDeviceSnapshot.data?.isEmpty ?? true) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamOnlineDevicesAliasJoined,
|
||||
initialData: onlineDevicesAliasJoined,
|
||||
builder: (context, onlineDeviceSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamOfflineDevicesAliasJoined,
|
||||
initialData: offlineDevicesAliasJoined,
|
||||
builder: (context, deviceDashboardSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamWarningDevicesAliasJoined,
|
||||
initialData: warningDevicesAliasJoined,
|
||||
builder: (context, warningDeviceSnapshot) {
|
||||
return StreamBuilder<List<DeviceWithAlias>>(
|
||||
stream: homeBloc.streamNotUseDevicesAliasJoined,
|
||||
initialData: notUseDevicesAliasJoined,
|
||||
builder: (context, notUseSnapshot) {
|
||||
return OverviewCard(
|
||||
isOwner: false,
|
||||
total: allDeviceSnapshot.data!.length,
|
||||
active: onlineDeviceSnapshot.data!.length,
|
||||
inactive:
|
||||
deviceDashboardSnapshot.data!.length,
|
||||
warning: warningDeviceSnapshot.data!.length,
|
||||
unused: notUseSnapshot.data!.length,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:sfm_app/feature/devices/device_model.dart';
|
||||
import 'package:sfm_app/feature/log/device_logs_bloc.dart';
|
||||
import 'package:sfm_app/product/constant/icon/icon_constants.dart';
|
||||
import 'package:sfm_app/product/extention/context_extention.dart';
|
||||
import 'package:sfm_app/product/services/language_services.dart';
|
||||
import 'package:sfm_app/product/shared/shared_snack_bar.dart';
|
||||
import 'package:sfm_app/product/utils/date_time_utils.dart';
|
||||
import 'package:sfm_app/product/utils/device_utils.dart';
|
||||
|
||||
import '../../product/base/bloc/base_bloc.dart';
|
||||
import 'device_logs_model.dart';
|
||||
|
||||
class DeviceLogsScreen extends StatefulWidget {
|
||||
const DeviceLogsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<DeviceLogsScreen> createState() => _DeviceLogsScreenState();
|
||||
}
|
||||
|
||||
class _DeviceLogsScreenState extends State<DeviceLogsScreen> {
|
||||
TextEditingController fromDate = TextEditingController();
|
||||
String fromDateApi = '';
|
||||
DateTime? dateTime;
|
||||
String thingID = "";
|
||||
late DeviceLogsBloc deviceLogsBloc;
|
||||
List<Device> allDevices = [];
|
||||
List<SensorLogs> sensors = [];
|
||||
int offset = 0;
|
||||
final controller = ScrollController();
|
||||
bool hasMore = true;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
deviceLogsBloc = BlocProvider.of(context);
|
||||
controller.addListener(
|
||||
() {
|
||||
if (controller.position.maxScrollExtent == controller.offset) {
|
||||
offset += 30;
|
||||
deviceLogsBloc.getDeviceLogByThingID(
|
||||
offset, thingID, dateTime!, sensors);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
sensors.clear();
|
||||
deviceLogsBloc.sinkSensors.add(sensors);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<List<Device>>(
|
||||
stream: deviceLogsBloc.streamAllDevices,
|
||||
builder: (context, allDevicesSnapshot) {
|
||||
if (allDevicesSnapshot.data?[0].thingId == null) {
|
||||
deviceLogsBloc.getAllDevices();
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
} else {
|
||||
return StreamBuilder<List<SensorLogs>>(
|
||||
stream: deviceLogsBloc.streamSensors,
|
||||
initialData: sensors,
|
||||
builder: (context, sensorsSnapshot) {
|
||||
return Padding(
|
||||
padding: context.paddingLow,
|
||||
child: Scaffold(
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
DropdownButtonFormField2<String>(
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: context.paddingLowVertical,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
hint: Text(
|
||||
appLocalization(context)
|
||||
.choose_device_dropdownButton,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
items: allDevicesSnapshot.data?.isNotEmpty ?? false
|
||||
? allDevicesSnapshot.data!
|
||||
.map(
|
||||
(device) => DropdownMenuItem<String>(
|
||||
value: device.thingId,
|
||||
child: Text(
|
||||
device.name!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
: [],
|
||||
buttonStyleData: const ButtonStyleData(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
),
|
||||
onChanged: (value) {
|
||||
thingID = value!;
|
||||
setState(() {});
|
||||
},
|
||||
onSaved: (value) {
|
||||
log("On Saved");
|
||||
},
|
||||
iconStyleData: const IconStyleData(
|
||||
icon: Icon(
|
||||
Icons.arrow_drop_down,
|
||||
),
|
||||
iconSize: 24,
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: context.paddingLowVertical,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: context.dynamicWidth(0.6),
|
||||
child: TextField(
|
||||
controller: fromDate,
|
||||
decoration: InputDecoration(
|
||||
icon:
|
||||
IconConstants.instance.getMaterialIcon(
|
||||
Icons.calendar_month_outlined,
|
||||
),
|
||||
labelText: appLocalization(context)
|
||||
.choose_date_start_datePicker,
|
||||
),
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
DateTime? datePicker = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(2023),
|
||||
lastDate: DateTime(2050),
|
||||
);
|
||||
dateTime = datePicker;
|
||||
if (datePicker != null) {
|
||||
fromDateApi = DateTimeUtils.instance
|
||||
.formatDateTimeToString(datePicker);
|
||||
setState(
|
||||
() {
|
||||
fromDate.text =
|
||||
DateFormat('yyyy-MM-dd')
|
||||
.format(datePicker);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: TextButton.icon(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStatePropertyAll(Colors.green),
|
||||
),
|
||||
onPressed: () {
|
||||
if (fromDateApi.isEmpty) {
|
||||
showNoIconTopSnackBar(
|
||||
context,
|
||||
appLocalization(context)
|
||||
.notification_enter_all_inf,
|
||||
Colors.red,
|
||||
Colors.white,
|
||||
);
|
||||
} else {
|
||||
deviceLogsBloc.getDeviceLogByThingID(
|
||||
offset, thingID, dateTime!, sensors);
|
||||
}
|
||||
log("ThingID: $thingID");
|
||||
log("From Date: ${DateTimeUtils.instance.formatDateTimeToString(dateTime!)}");
|
||||
},
|
||||
icon: IconConstants.instance
|
||||
.getMaterialIcon(Icons.search),
|
||||
label: Text(
|
||||
appLocalization(context)
|
||||
.find_button_content,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: context.lowValue,
|
||||
),
|
||||
Expanded(
|
||||
child: sensorsSnapshot.data?.isEmpty ?? false
|
||||
? Center(
|
||||
child: Text(
|
||||
appLocalization(context).no_data_message),
|
||||
)
|
||||
: RefreshIndicator(
|
||||
onRefresh: refresh,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: sensorsSnapshot.data!.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index <
|
||||
sensorsSnapshot.data!.length) {
|
||||
return logDetail(
|
||||
sensorsSnapshot.data![index],
|
||||
index,
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: context.paddingLow,
|
||||
child: StreamBuilder<bool>(
|
||||
stream:
|
||||
deviceLogsBloc.streamHasMore,
|
||||
builder:
|
||||
(context, hasMoreSnapshot) {
|
||||
return Center(
|
||||
child: hasMoreSnapshot.data ??
|
||||
hasMore
|
||||
? const CircularProgressIndicator()
|
||||
: Text(
|
||||
appLocalization(context)
|
||||
.main_no_data),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget logDetail(SensorLogs sensor, int index) {
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
subtitle:
|
||||
Text(DeviceUtils.instance.getDeviceSensorsLog(context, sensor)),
|
||||
// leading: const Icon(Icons.sensors_outlined),
|
||||
leading: Text(index.toString()),
|
||||
title: Text(
|
||||
DateTimeUtils.instance
|
||||
.convertCurrentMillisToDateTimeString(sensor.time ?? 0),
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
thickness: 0.5,
|
||||
indent: 50,
|
||||
endIndent: 100,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
offset = 0;
|
||||
sensors.clear();
|
||||
deviceLogsBloc.sensors.add(sensors);
|
||||
hasMore = true;
|
||||
deviceLogsBloc.sinkHasMore.add(hasMore);
|
||||
deviceLogsBloc.getDeviceLogByThingID(offset, thingID, dateTime!, sensors);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'dart:developer';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:persistent_bottom_nav_bar_v2/persistent-tab-view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:badges/badges.dart' as badges;
|
||||
import 'package:sfm_app/feature/home/home_bloc.dart';
|
||||
@@ -17,8 +18,8 @@ import '../devices/devices_manager_screen.dart';
|
||||
import '../home/home_screen.dart';
|
||||
import '../inter_family/inter_family_bloc.dart';
|
||||
import '../inter_family/inter_family_screen.dart';
|
||||
import '../log/device_logs_bloc.dart';
|
||||
import '../log/device_logs_screen.dart';
|
||||
import '../device_log/device_logs_bloc.dart';
|
||||
import '../device_log/device_logs_screen.dart';
|
||||
import 'main_bloc.dart';
|
||||
import '../map/map_bloc.dart';
|
||||
import '../map/map_screen.dart';
|
||||
@@ -102,6 +103,76 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
}
|
||||
|
||||
PersistentTabController controller = PersistentTabController(initialIndex: 0);
|
||||
List<PersistentBottomNavBarItem> _navBarsItems() {
|
||||
return [
|
||||
PersistentBottomNavBarItem(
|
||||
icon: IconConstants.instance.getMaterialIcon(Icons.home),
|
||||
title: appLocalization(context).home_page_destination,
|
||||
activeColorPrimary: Colors.blue,
|
||||
inactiveColorPrimary: Colors.grey,
|
||||
inactiveIcon:
|
||||
IconConstants.instance.getMaterialIcon(Icons.home_outlined),
|
||||
),
|
||||
PersistentBottomNavBarItem(
|
||||
icon: IconConstants.instance.getMaterialIcon(Icons.settings),
|
||||
title: appLocalization(context).manager_page_destination,
|
||||
activeColorPrimary: Colors.blue,
|
||||
inactiveColorPrimary: Colors.grey,
|
||||
inactiveIcon:
|
||||
IconConstants.instance.getMaterialIcon(Icons.settings_outlined),
|
||||
),
|
||||
PersistentBottomNavBarItem(
|
||||
icon: IconConstants.instance.getMaterialIcon(Icons.location_on),
|
||||
title: appLocalization(context).map_page_destination,
|
||||
activeColorPrimary: Colors.blue,
|
||||
inactiveColorPrimary: Colors.grey,
|
||||
inactiveIcon:
|
||||
IconConstants.instance.getMaterialIcon(Icons.location_on_outlined),
|
||||
),
|
||||
PersistentBottomNavBarItem(
|
||||
icon: IconConstants.instance.getMaterialIcon(Icons.history),
|
||||
title: appLocalization(context).history_page_destination,
|
||||
activeColorPrimary: Colors.blue,
|
||||
inactiveColorPrimary: Colors.grey,
|
||||
inactiveIcon:
|
||||
IconConstants.instance.getMaterialIcon(Icons.history_outlined),
|
||||
),
|
||||
PersistentBottomNavBarItem(
|
||||
icon: IconConstants.instance.getMaterialIcon(Icons.group),
|
||||
title: appLocalization(context).group_page_destination,
|
||||
activeColorPrimary: Colors.blue,
|
||||
inactiveColorPrimary: Colors.grey,
|
||||
inactiveIcon:
|
||||
IconConstants.instance.getMaterialIcon(Icons.group_outlined),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildScreens() {
|
||||
return [
|
||||
BlocProvider(
|
||||
child: const HomeScreen(),
|
||||
blocBuilder: () => HomeBloc(),
|
||||
),
|
||||
BlocProvider(
|
||||
child: const DevicesManagerScreen(),
|
||||
blocBuilder: () => DevicesManagerBloc()),
|
||||
BlocProvider(
|
||||
child: const MapScreen(),
|
||||
blocBuilder: () => MapBloc(),
|
||||
),
|
||||
BlocProvider(
|
||||
child: const DeviceLogsScreen(),
|
||||
blocBuilder: () => DeviceLogsBloc(),
|
||||
),
|
||||
BlocProvider(
|
||||
child: const InterFamilyScreen(),
|
||||
blocBuilder: () => InterFamilyBloc(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ThemeNotifier themeNotifier = context.watch<ThemeNotifier>();
|
||||
@@ -204,16 +275,16 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
centerTitle: true,
|
||||
title: StreamBuilder<String>(
|
||||
stream: mainBloc.streamTitle,
|
||||
initialData: titlePage,
|
||||
builder: (context, titleSnapshot) {
|
||||
return Text(
|
||||
titleSnapshot.data ?? ApplicationConstants.APP_NAME,
|
||||
);
|
||||
},
|
||||
),
|
||||
// centerTitle: true,
|
||||
// title: StreamBuilder<String>(
|
||||
// stream: mainBloc.streamTitle,
|
||||
// initialData: titlePage,
|
||||
// builder: (context, titleSnapshot) {
|
||||
// return Text(
|
||||
// titleSnapshot.data ?? ApplicationConstants.APP_NAME,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
actions: [
|
||||
StreamBuilder<bool>(
|
||||
stream: mainBloc.streamThemeMode,
|
||||
@@ -306,13 +377,8 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
},
|
||||
),
|
||||
PopupMenuButton(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(8.0),
|
||||
bottomRight: Radius.circular(8.0),
|
||||
topLeft: Radius.circular(8.0),
|
||||
topRight: Radius.circular(8.0),
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (context) {
|
||||
@@ -355,27 +421,53 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
)
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: Container(
|
||||
decoration:
|
||||
BoxDecoration(borderRadius: BorderRadius.circular(50)),
|
||||
padding: context.paddingLow,
|
||||
child: NavigationBar(
|
||||
onDestinationSelected: (index) {
|
||||
currentPageIndex = index;
|
||||
mainBloc.sinkCurrentPageIndex.add(currentPageIndex);
|
||||
checkSelectedIndex(currentPageIndex);
|
||||
},
|
||||
selectedIndex: indexSnapshot.data ?? currentPageIndex,
|
||||
destinations: roleSnapshot.data == RoleEnums.USER.name
|
||||
? userDestinations
|
||||
: modDestinations,
|
||||
// bottomNavigationBar: Container(
|
||||
// decoration:
|
||||
// BoxDecoration(borderRadius: BorderRadius.circular(50)),
|
||||
// padding: context.paddingLow,
|
||||
// child: NavigationBar(
|
||||
// onDestinationSelected: (index) {
|
||||
// currentPageIndex = index;
|
||||
// mainBloc.sinkCurrentPageIndex.add(currentPageIndex);
|
||||
// checkSelectedIndex(currentPageIndex);
|
||||
// },
|
||||
// selectedIndex: indexSnapshot.data ?? currentPageIndex,
|
||||
// destinations: roleSnapshot.data == RoleEnums.USER.name
|
||||
// ? userDestinations
|
||||
// : modDestinations,
|
||||
// ),
|
||||
// ),
|
||||
// body: IndexedStack(
|
||||
// index: indexSnapshot.data ?? currentPageIndex,
|
||||
// children: roleSnapshot.data == RoleEnums.USER.name
|
||||
// ? userBody
|
||||
// : modBody,
|
||||
// ),
|
||||
body: PersistentTabView(
|
||||
context,
|
||||
controller: controller,
|
||||
screens: _buildScreens(),
|
||||
items: _navBarsItems(),
|
||||
confineInSafeArea: true,
|
||||
handleAndroidBackButtonPress: true,
|
||||
resizeToAvoidBottomInset: true,
|
||||
stateManagement: true,
|
||||
hideNavigationBarWhenKeyboardShows: true,
|
||||
// backgroundColor: Colors.transparent,
|
||||
decoration: NavBarDecoration(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
),
|
||||
body: IndexedStack(
|
||||
index: indexSnapshot.data ?? currentPageIndex,
|
||||
children: roleSnapshot.data == RoleEnums.USER.name
|
||||
? userBody
|
||||
: modBody,
|
||||
popAllScreensOnTapOfSelectedTab: true,
|
||||
itemAnimationProperties: const ItemAnimationProperties(
|
||||
duration: Duration(milliseconds: 200),
|
||||
curve: Curves.bounceInOut,
|
||||
),
|
||||
screenTransitionAnimation: const ScreenTransitionAnimation(
|
||||
animateTabTransition: true,
|
||||
curve: Curves.linear,
|
||||
duration: Duration(milliseconds: 200),
|
||||
),
|
||||
navBarStyle: NavBarStyle.style4,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -17,8 +17,8 @@ import '../../../feature/inter_family/group_detail/group_detail_bloc.dart';
|
||||
import '../../../feature/inter_family/group_detail/group_detail_screen.dart';
|
||||
import '../../../feature/inter_family/inter_family_bloc.dart';
|
||||
import '../../../feature/inter_family/inter_family_screen.dart';
|
||||
import '../../../feature/log/device_logs_bloc.dart';
|
||||
import '../../../feature/log/device_logs_screen.dart';
|
||||
import '../../../feature/device_log/device_logs_bloc.dart';
|
||||
import '../../../feature/device_log/device_logs_screen.dart';
|
||||
import '../../../feature/main/main_bloc.dart';
|
||||
import '../../../feature/main/main_screen.dart';
|
||||
import '../../../feature/map/map_bloc.dart';
|
||||
|
||||
101
lib/product/shared/shared_line_chart.dart
Normal file
101
lib/product/shared/shared_line_chart.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sfm_app/feature/device_log/device_logs_model.dart';
|
||||
import 'package:sfm_app/product/utils/date_time_utils.dart';
|
||||
|
||||
Widget sharedLineChart(
|
||||
String chartName, List<SensorLogs> sensors, double maxValue) {
|
||||
double max = sensors
|
||||
.map((sensor) => sensor.value!) // Lấy giá trị của từng sensor
|
||||
.reduce((a, b) => a > b ? a : b)
|
||||
.toDouble();
|
||||
double averageValue = (0 + maxValue) / 2;
|
||||
return LineChart(
|
||||
LineChartData(
|
||||
minX: 0,
|
||||
minY: 0,
|
||||
maxY: max + 20,
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
axisNameSize: 20,
|
||||
axisNameWidget: Text(chartName),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 40,
|
||||
getTitlesWidget: (value, meta) {
|
||||
if (value == 0) {
|
||||
return const Text("0");
|
||||
} else if (value == averageValue) {
|
||||
return Text(averageValue.toInt().toString());
|
||||
} else if (value == maxValue) {
|
||||
return Text(maxValue.toInt().toString());
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
))),
|
||||
lineTouchData: LineTouchData(
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
tooltipBgColor: Colors.grey.withOpacity(0.3),
|
||||
getTooltipItems: (List<LineBarSpot> touchedSpots) {
|
||||
return touchedSpots.map((spot) {
|
||||
final index = spot.x.toInt();
|
||||
final sensorData = sensors[index];
|
||||
return LineTooltipItem(
|
||||
'Time: ${DateTimeUtils.instance.convertCurrentMillisToDateTimeString(sensorData.time!)}\nValue: ${sensorData.value}',
|
||||
const TextStyle(),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
handleBuiltInTouches: true,
|
||||
),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
color: Colors.green.withOpacity(0.8),
|
||||
barWidth: 5,
|
||||
curveSmoothness: 0.35,
|
||||
spots: sensors
|
||||
.asMap()
|
||||
.entries
|
||||
.map(
|
||||
(entry) => FlSpot(
|
||||
entry.key.toDouble(),
|
||||
entry.value.value!.toDouble(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
isCurved: true,
|
||||
dotData: const FlDotData(
|
||||
show: false,
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
color: Colors.green.withOpacity(0.2),
|
||||
),
|
||||
)
|
||||
],
|
||||
gridData: const FlGridData(show: false),
|
||||
borderData: FlBorderData(
|
||||
border: Border(
|
||||
top: BorderSide.none,
|
||||
right: BorderSide.none,
|
||||
left: BorderSide(color: Colors.black.withOpacity(0.7)),
|
||||
bottom: BorderSide(color: Colors.black.withOpacity(0.7))),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sfm_app/feature/log/device_logs_model.dart';
|
||||
import 'package:sfm_app/feature/device_log/device_logs_model.dart';
|
||||
import 'package:sfm_app/product/services/api_services.dart';
|
||||
import 'package:sfm_app/product/services/language_services.dart';
|
||||
import 'package:sfm_app/product/shared/model/district_model.dart';
|
||||
|
||||
24
pubspec.lock
24
pubspec.lock
@@ -129,6 +129,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.9"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -201,6 +209,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.5.18"
|
||||
fl_chart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fl_chart
|
||||
sha256: "6b9eb2b3017241d05c482c01f668dd05cc909ec9a0114fdd49acd958ff2432fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.64.0"
|
||||
flex_color_scheme:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -581,6 +597,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
persistent_bottom_nav_bar_v2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: persistent_bottom_nav_bar_v2
|
||||
sha256: "2fbaf1e8b18108d8a303304a306d68bcfb78abfd553295f489a8a315dff479e2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.8"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -60,6 +60,8 @@ dependencies:
|
||||
qr_flutter: ^4.1.0
|
||||
flutter_polyline_points: ^2.0.0
|
||||
simple_ripple_animation: ^0.1.0
|
||||
fl_chart: ^0.64.0
|
||||
persistent_bottom_nav_bar_v2: ^4.2.8
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user