Update Bottom Navigator in MainScreen

This commit is contained in:
anhtunz
2024-12-24 09:26:43 +07:00
parent 9046b21831
commit e047fe1e27
14 changed files with 944 additions and 633 deletions

View 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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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))),
),
),
);
}

View File

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