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

View File

@@ -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,28 +112,37 @@ 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, // minRadius: 20,
maxRadius: 20, // maxRadius: 20,
backgroundImage: AssetImage( // backgroundImage: AssetImage(
stateImgAssets( // stateImgAssets(
deviceSnapshot.data!.state!, // 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( Row(
children: [ children: [
Card( Card(

View File

@@ -49,8 +49,9 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return Scaffold(
child: StreamBuilder<List<Device>>( body: SafeArea(
child: StreamBuilder<List<Device>>(
stream: devicesManagerBloc.streamAllDevices, stream: devicesManagerBloc.streamAllDevices,
initialData: devices, initialData: devices,
builder: (context, allDeviceSnapshot) { builder: (context, allDeviceSnapshot) {
@@ -63,88 +64,93 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
StreamBuilder<String>( StreamBuilder<String>(
stream: devicesManagerBloc.streamUserRole, stream: devicesManagerBloc.streamUserRole,
initialData: role, initialData: role,
builder: (context, roleSnapshot) { builder: (context, roleSnapshot) {
return PaginatedDataTable( return PaginatedDataTable(
header: Center( header: Center(
child: Text( child: Text(
appLocalization(context) appLocalization(context)
.paginated_data_table_title, .paginated_data_table_title,
style: context.titleLargeTextStyle, style: context.titleLargeTextStyle,
), ),
), ),
columns: [ columns: [
if (roleSnapshot.data == RoleEnums.ADMIN.name || if (roleSnapshot.data == RoleEnums.ADMIN.name ||
roleSnapshot.data == RoleEnums.USER.name) roleSnapshot.data == RoleEnums.USER.name)
DataColumn( DataColumn(
label: Center( label: Center(
child: Text(appLocalization(context) child: Text(appLocalization(context)
.paginated_data_table_column_action))), .paginated_data_table_column_action))),
DataColumn( DataColumn(
label: Center( label: Center(
child: Text(appLocalization(context) child: Text(appLocalization(context)
.paginated_data_table_column_deviceName))), .paginated_data_table_column_deviceName))),
DataColumn( DataColumn(
label: Center( label: Center(
child: Text(appLocalization(context) child: Text(appLocalization(context)
.paginated_data_table_column_deviceStatus))), .paginated_data_table_column_deviceStatus))),
DataColumn( DataColumn(
label: Center( label: Center(
child: Text(appLocalization(context) child: Text(appLocalization(context)
.paginated_data_table_column_deviceBaterry))), .paginated_data_table_column_deviceBaterry))),
DataColumn( DataColumn(
label: Center( label: Center(
child: Text(appLocalization(context) child: Text(appLocalization(context)
.paginated_data_table_column_deviceSignal))), .paginated_data_table_column_deviceSignal))),
DataColumn( DataColumn(
label: Center( label: Center(
child: Text(appLocalization(context) child: Text(appLocalization(context)
.paginated_data_table_column_deviceTemperature))), .paginated_data_table_column_deviceTemperature))),
DataColumn( DataColumn(
label: Center( label: Center(
child: Text(appLocalization(context) child: Text(appLocalization(context)
.paginated_data_table_column_deviceHump))), .paginated_data_table_column_deviceHump))),
DataColumn( DataColumn(
label: Center( label: Center(
child: Text(appLocalization(context) child: Text(appLocalization(context)
.paginated_data_table_column_devicePower))), .paginated_data_table_column_devicePower))),
], ],
onPageChanged: (int pageIndex) { onPageChanged: (int pageIndex) {
// log('Chuyen page: $pageIndex'); // log('Chuyen page: $pageIndex');
}, },
rowsPerPage: 5, rowsPerPage: 5,
actions: [ actions: [
if (roleSnapshot.data == RoleEnums.USER.name || if (roleSnapshot.data == RoleEnums.USER.name ||
roleSnapshot.data == RoleEnums.ADMIN.name) roleSnapshot.data == RoleEnums.ADMIN.name)
IconButton( IconButton(
style: ButtonStyle( style: ButtonStyle(
backgroundColor: backgroundColor:
MaterialStateProperty.all<Color>( MaterialStateProperty.all<Color>(
Colors.green), Colors.green),
iconColor: iconColor:
MaterialStateProperty.all<Color>( MaterialStateProperty.all<Color>(
Colors.white)), Colors.white)),
onPressed: () { onPressed: () {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
.clearSnackBars(); .clearSnackBars();
addNewDevice( addNewDevice(
context, roleSnapshot.data ?? role); context, roleSnapshot.data ?? role);
}, },
icon: IconConstants.instance icon: IconConstants.instance
.getMaterialIcon(Icons.add)) .getMaterialIcon(Icons.add))
], ],
source: DeviceSource( source: DeviceSource(
devices: allDeviceSnapshot.data ?? devices, devices: allDeviceSnapshot.data ?? devices,
context: context, context: context,
devicesBloc: devicesManagerBloc, devicesBloc: devicesManagerBloc,
role: role)); role: role,
}) ),
);
},
)
], ],
), ),
); );
} }
}), },
),
),
); );
} }

View File

@@ -61,203 +61,207 @@ class _HomeScreenState extends State<HomeScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Scaffold(
padding: context.paddingLow, body: Padding(
child: SingleChildScrollView( padding: context.paddingLow,
child: Column( child: SingleChildScrollView(
children: <Widget>[ child: Column(
Row( children: <Widget>[
children: [ Row(
Text( children: [
appLocalization(context).notification, Text(
style: context.titleMediumTextStyle, appLocalization(context).notification,
), style: context.titleMediumTextStyle,
SizedBox(width: context.lowValue), ),
StreamBuilder<int>( SizedBox(width: context.lowValue),
stream: homeBloc.streamCountNotitication, StreamBuilder<int>(
builder: (context, countSnapshot) { stream: homeBloc.streamCountNotitication,
return Text( builder: (context, countSnapshot) {
"(${countSnapshot.data ?? 0})", return Text(
style: context.titleMediumTextStyle, "(${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,
),
],
),
),
); );
} },
}, )
],
),
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>>(
StreamBuilder<List<DeviceWithAlias>>( stream: homeBloc.streamAllDevicesAlias,
stream: homeBloc.streamAllDevicesAlias, initialData: allDevicesAlias,
initialData: allDevicesAlias, builder: (context, allDeviceSnapshot) {
builder: (context, allDeviceSnapshot) { return StreamBuilder<List<DeviceWithAlias>>(
return StreamBuilder<List<DeviceWithAlias>>( stream: homeBloc.streamOnlineDevicesAlias,
stream: homeBloc.streamOnlineDevicesAlias, initialData: onlineDevicesAlias,
initialData: onlineDevicesAlias, builder: (context, onlineDeviceSnapshot) {
builder: (context, onlineDeviceSnapshot) { return StreamBuilder<List<DeviceWithAlias>>(
return StreamBuilder<List<DeviceWithAlias>>( stream: homeBloc.streamOfflineDevicesAlias,
stream: homeBloc.streamOfflineDevicesAlias, initialData: offlineDevicesAlias,
initialData: offlineDevicesAlias, builder: (context, deviceDashboardSnapshot) {
builder: (context, deviceDashboardSnapshot) { return StreamBuilder<List<DeviceWithAlias>>(
return StreamBuilder<List<DeviceWithAlias>>( stream: homeBloc.streamWarningDevicesAlias,
stream: homeBloc.streamWarningDevicesAlias, initialData: warningDevicesAlias,
initialData: warningDevicesAlias, builder: (context, warningDeviceSnapshot) {
builder: (context, warningDeviceSnapshot) { return StreamBuilder<List<DeviceWithAlias>>(
return StreamBuilder<List<DeviceWithAlias>>( stream: homeBloc.streamNotUseDevicesAlias,
stream: homeBloc.streamNotUseDevicesAlias, initialData: notUseDevicesAlias,
initialData: notUseDevicesAlias, builder: (context, notUseSnapshot) {
builder: (context, notUseSnapshot) { return OverviewCard(
return OverviewCard( isOwner: true,
isOwner: true, total: allDeviceSnapshot.data!.length,
total: allDeviceSnapshot.data!.length, active: onlineDeviceSnapshot.data!.length,
active: onlineDeviceSnapshot.data!.length, inactive:
inactive: deviceDashboardSnapshot.data!.length,
deviceDashboardSnapshot.data!.length, warning: warningDeviceSnapshot.data!.length,
warning: warningDeviceSnapshot.data!.length, unused: notUseSnapshot.data!.length,
unused: notUseSnapshot.data!.length, );
); },
}, );
); },
}, );
); },
}, );
); },
}, );
); },
}, ),
), SizedBox(height: context.lowValue),
SizedBox(height: context.lowValue), StreamBuilder<List<DeviceWithAlias>>(
StreamBuilder<List<DeviceWithAlias>>( stream: homeBloc.streamAllDevicesAliasJoined,
stream: homeBloc.streamAllDevicesAliasJoined, initialData: allDevicesAliasJoined,
initialData: allDevicesAliasJoined, builder: (context, allDeviceSnapshot) {
builder: (context, allDeviceSnapshot) { if (allDeviceSnapshot.data?.isEmpty ?? true) {
if (allDeviceSnapshot.data?.isEmpty ?? true) { return const SizedBox.shrink();
return const SizedBox.shrink(); }
} return StreamBuilder<List<DeviceWithAlias>>(
return StreamBuilder<List<DeviceWithAlias>>( stream: homeBloc.streamOnlineDevicesAliasJoined,
stream: homeBloc.streamOnlineDevicesAliasJoined, initialData: onlineDevicesAliasJoined,
initialData: onlineDevicesAliasJoined, builder: (context, onlineDeviceSnapshot) {
builder: (context, onlineDeviceSnapshot) { return StreamBuilder<List<DeviceWithAlias>>(
return StreamBuilder<List<DeviceWithAlias>>( stream: homeBloc.streamOfflineDevicesAliasJoined,
stream: homeBloc.streamOfflineDevicesAliasJoined, initialData: offlineDevicesAliasJoined,
initialData: offlineDevicesAliasJoined, builder: (context, deviceDashboardSnapshot) {
builder: (context, deviceDashboardSnapshot) { return StreamBuilder<List<DeviceWithAlias>>(
return StreamBuilder<List<DeviceWithAlias>>( stream: homeBloc.streamWarningDevicesAliasJoined,
stream: homeBloc.streamWarningDevicesAliasJoined, initialData: warningDevicesAliasJoined,
initialData: warningDevicesAliasJoined, builder: (context, warningDeviceSnapshot) {
builder: (context, warningDeviceSnapshot) { return StreamBuilder<List<DeviceWithAlias>>(
return StreamBuilder<List<DeviceWithAlias>>( stream: homeBloc.streamNotUseDevicesAliasJoined,
stream: homeBloc.streamNotUseDevicesAliasJoined, initialData: notUseDevicesAliasJoined,
initialData: notUseDevicesAliasJoined, builder: (context, notUseSnapshot) {
builder: (context, notUseSnapshot) { return OverviewCard(
return OverviewCard( isOwner: false,
isOwner: false, total: allDeviceSnapshot.data!.length,
total: allDeviceSnapshot.data!.length, active: onlineDeviceSnapshot.data!.length,
active: onlineDeviceSnapshot.data!.length, inactive:
inactive: deviceDashboardSnapshot.data!.length,
deviceDashboardSnapshot.data!.length, warning: warningDeviceSnapshot.data!.length,
warning: warningDeviceSnapshot.data!.length, unused: notUseSnapshot.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/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,
body: IndexedStack( itemAnimationProperties: const ItemAnimationProperties(
index: indexSnapshot.data ?? currentPageIndex, duration: Duration(milliseconds: 200),
children: roleSnapshot.data == RoleEnums.USER.name curve: Curves.bounceInOut,
? userBody ),
: modBody, 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/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';

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

View File

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

View File

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