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/services/api_services.dart';
|
||||||
import 'package:sfm_app/product/utils/device_utils.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 '../device_model.dart';
|
||||||
|
|
||||||
import '../../../product/base/bloc/base_bloc.dart';
|
import '../../../product/base/bloc/base_bloc.dart';
|
||||||
@@ -28,6 +30,10 @@ class DetailDeviceBloc extends BlocBase {
|
|||||||
StreamSink<String> get sinkDeviceLocation => deviceLocation.sink;
|
StreamSink<String> get sinkDeviceLocation => deviceLocation.sink;
|
||||||
Stream<String> get streamDeviceLocation => deviceLocation.stream;
|
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
|
@override
|
||||||
void dispose() {}
|
void dispose() {}
|
||||||
|
|
||||||
@@ -76,4 +82,35 @@ class DetailDeviceBloc extends BlocBase {
|
|||||||
await DeviceUtils.instance.getFullDeviceLocation(context, areaPath);
|
await DeviceUtils.instance.getFullDeviceLocation(context, areaPath);
|
||||||
sinkDeviceLocation.add(fullLocation);
|
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:flutter/material.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.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 'package:simple_ripple_animation/simple_ripple_animation.dart';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import '../device_model.dart';
|
import '../device_model.dart';
|
||||||
@@ -110,21 +112,32 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
|
|||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
// SizedBox(
|
||||||
height: 25,
|
// height: 25,
|
||||||
width: 25,
|
// width: 25,
|
||||||
child: RippleAnimation(
|
// child: RippleAnimation(
|
||||||
color: DeviceUtils.instance
|
// color: DeviceUtils.instance
|
||||||
.getColorRiple(
|
// .getColorRiple(
|
||||||
deviceSnapshot.data!.state!),
|
// deviceSnapshot.data!.state!),
|
||||||
delay:
|
// delay:
|
||||||
const Duration(milliseconds: 800),
|
// const Duration(milliseconds: 800),
|
||||||
repeat: true,
|
// repeat: true,
|
||||||
minRadius: 40,
|
// minRadius: 40,
|
||||||
ripplesCount: 6,
|
// ripplesCount: 6,
|
||||||
duration: const Duration(
|
// duration: const Duration(
|
||||||
milliseconds: 6 * 300),
|
// milliseconds: 6 * 300),
|
||||||
child: CircleAvatar(
|
// child: CircleAvatar(
|
||||||
|
// minRadius: 20,
|
||||||
|
// maxRadius: 20,
|
||||||
|
// backgroundImage: AssetImage(
|
||||||
|
// stateImgAssets(
|
||||||
|
// deviceSnapshot.data!.state!,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
CircleAvatar(
|
||||||
minRadius: 20,
|
minRadius: 20,
|
||||||
maxRadius: 20,
|
maxRadius: 20,
|
||||||
backgroundImage: AssetImage(
|
backgroundImage: AssetImage(
|
||||||
@@ -133,8 +146,6 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: context.lowValue,
|
width: context.lowValue,
|
||||||
),
|
),
|
||||||
@@ -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(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Card(
|
Card(
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return Scaffold(
|
||||||
|
body: SafeArea(
|
||||||
child: StreamBuilder<List<Device>>(
|
child: StreamBuilder<List<Device>>(
|
||||||
stream: devicesManagerBloc.streamAllDevices,
|
stream: devicesManagerBloc.streamAllDevices,
|
||||||
initialData: devices,
|
initialData: devices,
|
||||||
@@ -138,13 +139,18 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
|
|||||||
devices: allDeviceSnapshot.data ?? devices,
|
devices: allDeviceSnapshot.data ?? devices,
|
||||||
context: context,
|
context: context,
|
||||||
devicesBloc: devicesManagerBloc,
|
devicesBloc: devicesManagerBloc,
|
||||||
role: role));
|
role: role,
|
||||||
})
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Scaffold(
|
||||||
|
body: Padding(
|
||||||
padding: context.paddingLow,
|
padding: context.paddingLow,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -102,7 +103,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
(item) => FutureBuilder<Widget>(
|
(item) => FutureBuilder<Widget>(
|
||||||
future: warningCard(
|
future: warningCard(
|
||||||
context, apiServices, item),
|
context, apiServices, item),
|
||||||
builder: (context, warningCardSnapshot) {
|
builder:
|
||||||
|
(context, warningCardSnapshot) {
|
||||||
if (warningCardSnapshot.hasData) {
|
if (warningCardSnapshot.hasData) {
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
@@ -129,7 +131,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
batteryItem.name!,
|
batteryItem.name!,
|
||||||
batteryItem.areaPath!,
|
batteryItem.areaPath!,
|
||||||
),
|
),
|
||||||
builder: (context, warningCardSnapshot) {
|
builder:
|
||||||
|
(context, warningCardSnapshot) {
|
||||||
if (warningCardSnapshot.hasData) {
|
if (warningCardSnapshot.hasData) {
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
@@ -260,6 +263,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.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:provider/provider.dart';
|
||||||
import 'package:badges/badges.dart' as badges;
|
import 'package:badges/badges.dart' as badges;
|
||||||
import 'package:sfm_app/feature/home/home_bloc.dart';
|
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 '../home/home_screen.dart';
|
||||||
import '../inter_family/inter_family_bloc.dart';
|
import '../inter_family/inter_family_bloc.dart';
|
||||||
import '../inter_family/inter_family_screen.dart';
|
import '../inter_family/inter_family_screen.dart';
|
||||||
import '../log/device_logs_bloc.dart';
|
import '../device_log/device_logs_bloc.dart';
|
||||||
import '../log/device_logs_screen.dart';
|
import '../device_log/device_logs_screen.dart';
|
||||||
import 'main_bloc.dart';
|
import 'main_bloc.dart';
|
||||||
import '../map/map_bloc.dart';
|
import '../map/map_bloc.dart';
|
||||||
import '../map/map_screen.dart';
|
import '../map/map_screen.dart';
|
||||||
@@ -102,6 +103,76 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
WidgetsBinding.instance.removeObserver(this);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ThemeNotifier themeNotifier = context.watch<ThemeNotifier>();
|
ThemeNotifier themeNotifier = context.watch<ThemeNotifier>();
|
||||||
@@ -204,16 +275,16 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
centerTitle: true,
|
// centerTitle: true,
|
||||||
title: StreamBuilder<String>(
|
// title: StreamBuilder<String>(
|
||||||
stream: mainBloc.streamTitle,
|
// stream: mainBloc.streamTitle,
|
||||||
initialData: titlePage,
|
// initialData: titlePage,
|
||||||
builder: (context, titleSnapshot) {
|
// builder: (context, titleSnapshot) {
|
||||||
return Text(
|
// return Text(
|
||||||
titleSnapshot.data ?? ApplicationConstants.APP_NAME,
|
// titleSnapshot.data ?? ApplicationConstants.APP_NAME,
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
actions: [
|
actions: [
|
||||||
StreamBuilder<bool>(
|
StreamBuilder<bool>(
|
||||||
stream: mainBloc.streamThemeMode,
|
stream: mainBloc.streamThemeMode,
|
||||||
@@ -306,13 +377,8 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
shape: const RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
bottomLeft: Radius.circular(8.0),
|
|
||||||
bottomRight: Radius.circular(8.0),
|
|
||||||
topLeft: Radius.circular(8.0),
|
|
||||||
topRight: Radius.circular(8.0),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.more_vert),
|
icon: const Icon(Icons.more_vert),
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
@@ -355,27 +421,53 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: Container(
|
// bottomNavigationBar: Container(
|
||||||
decoration:
|
// decoration:
|
||||||
BoxDecoration(borderRadius: BorderRadius.circular(50)),
|
// BoxDecoration(borderRadius: BorderRadius.circular(50)),
|
||||||
padding: context.paddingLow,
|
// padding: context.paddingLow,
|
||||||
child: NavigationBar(
|
// child: NavigationBar(
|
||||||
onDestinationSelected: (index) {
|
// onDestinationSelected: (index) {
|
||||||
currentPageIndex = index;
|
// currentPageIndex = index;
|
||||||
mainBloc.sinkCurrentPageIndex.add(currentPageIndex);
|
// mainBloc.sinkCurrentPageIndex.add(currentPageIndex);
|
||||||
checkSelectedIndex(currentPageIndex);
|
// checkSelectedIndex(currentPageIndex);
|
||||||
},
|
// },
|
||||||
selectedIndex: indexSnapshot.data ?? currentPageIndex,
|
// selectedIndex: indexSnapshot.data ?? currentPageIndex,
|
||||||
destinations: roleSnapshot.data == RoleEnums.USER.name
|
// destinations: roleSnapshot.data == RoleEnums.USER.name
|
||||||
? userDestinations
|
// ? userDestinations
|
||||||
: modDestinations,
|
// : 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),
|
||||||
),
|
),
|
||||||
|
popAllScreensOnTapOfSelectedTab: true,
|
||||||
|
itemAnimationProperties: const ItemAnimationProperties(
|
||||||
|
duration: Duration(milliseconds: 200),
|
||||||
|
curve: Curves.bounceInOut,
|
||||||
),
|
),
|
||||||
body: IndexedStack(
|
screenTransitionAnimation: const ScreenTransitionAnimation(
|
||||||
index: indexSnapshot.data ?? currentPageIndex,
|
animateTabTransition: true,
|
||||||
children: roleSnapshot.data == RoleEnums.USER.name
|
curve: Curves.linear,
|
||||||
? userBody
|
duration: Duration(milliseconds: 200),
|
||||||
: modBody,
|
),
|
||||||
|
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/group_detail/group_detail_screen.dart';
|
||||||
import '../../../feature/inter_family/inter_family_bloc.dart';
|
import '../../../feature/inter_family/inter_family_bloc.dart';
|
||||||
import '../../../feature/inter_family/inter_family_screen.dart';
|
import '../../../feature/inter_family/inter_family_screen.dart';
|
||||||
import '../../../feature/log/device_logs_bloc.dart';
|
import '../../../feature/device_log/device_logs_bloc.dart';
|
||||||
import '../../../feature/log/device_logs_screen.dart';
|
import '../../../feature/device_log/device_logs_screen.dart';
|
||||||
import '../../../feature/main/main_bloc.dart';
|
import '../../../feature/main/main_bloc.dart';
|
||||||
import '../../../feature/main/main_screen.dart';
|
import '../../../feature/main/main_screen.dart';
|
||||||
import '../../../feature/map/map_bloc.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 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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/api_services.dart';
|
||||||
import 'package:sfm_app/product/services/language_services.dart';
|
import 'package:sfm_app/product/services/language_services.dart';
|
||||||
import 'package:sfm_app/product/shared/model/district_model.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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.9"
|
version: "2.3.9"
|
||||||
|
equatable:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: equatable
|
||||||
|
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.7"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -201,6 +209,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.5.18"
|
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:
|
flex_color_scheme:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -581,6 +597,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
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:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ dependencies:
|
|||||||
qr_flutter: ^4.1.0
|
qr_flutter: ^4.1.0
|
||||||
flutter_polyline_points: ^2.0.0
|
flutter_polyline_points: ^2.0.0
|
||||||
simple_ripple_animation: ^0.1.0
|
simple_ripple_animation: ^0.1.0
|
||||||
|
fl_chart: ^0.64.0
|
||||||
|
persistent_bottom_nav_bar_v2: ^4.2.8
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user