From e047fe1e271281825bd6097b78327822afd36943 Mon Sep 17 00:00:00 2001 From: anhtunz Date: Tue, 24 Dec 2024 09:26:43 +0700 Subject: [PATCH] Update Bottom Navigator in MainScreen --- .../{log => device_log}/device_logs_bloc.dart | 0 .../device_logs_model.dart | 0 .../device_log/device_logs_screen.dart | 302 ++++++++++++++ .../device_detail/device_detail_bloc.dart | 37 ++ .../device_detail/device_detail_screen.dart | 82 +++- .../devices/devices_manager_screen.dart | 166 ++++---- lib/feature/home/home_screen.dart | 392 +++++++++--------- lib/feature/log/device_logs_screen.dart | 295 ------------- lib/feature/main/main_screen.dart | 170 ++++++-- .../navigation/navigation_router.dart | 4 +- lib/product/shared/shared_line_chart.dart | 101 +++++ lib/product/utils/device_utils.dart | 2 +- pubspec.lock | 24 ++ pubspec.yaml | 2 + 14 files changed, 944 insertions(+), 633 deletions(-) rename lib/feature/{log => device_log}/device_logs_bloc.dart (100%) rename lib/feature/{log => device_log}/device_logs_model.dart (100%) create mode 100644 lib/feature/device_log/device_logs_screen.dart delete mode 100644 lib/feature/log/device_logs_screen.dart create mode 100644 lib/product/shared/shared_line_chart.dart diff --git a/lib/feature/log/device_logs_bloc.dart b/lib/feature/device_log/device_logs_bloc.dart similarity index 100% rename from lib/feature/log/device_logs_bloc.dart rename to lib/feature/device_log/device_logs_bloc.dart diff --git a/lib/feature/log/device_logs_model.dart b/lib/feature/device_log/device_logs_model.dart similarity index 100% rename from lib/feature/log/device_logs_model.dart rename to lib/feature/device_log/device_logs_model.dart diff --git a/lib/feature/device_log/device_logs_screen.dart b/lib/feature/device_log/device_logs_screen.dart new file mode 100644 index 0000000..c0eca4f --- /dev/null +++ b/lib/feature/device_log/device_logs_screen.dart @@ -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 createState() => _DeviceLogsScreenState(); +} + +class _DeviceLogsScreenState extends State { + TextEditingController fromDate = TextEditingController(); + String fromDateApi = ''; + DateTime? dateTime; + String thingID = ""; + late DeviceLogsBloc deviceLogsBloc; + List allDevices = []; + List 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>( + stream: deviceLogsBloc.streamAllDevices, + builder: (context, allDevicesSnapshot) { + if (allDevicesSnapshot.data?[0].thingId == null) { + deviceLogsBloc.getAllDevices(); + return const Center( + child: CircularProgressIndicator(), + ); + } else { + return StreamBuilder>( + stream: deviceLogsBloc.streamSensors, + initialData: sensors, + builder: (context, sensorsSnapshot) { + return Padding( + padding: context.paddingLow, + child: Scaffold( + body: SafeArea( + child: Column( + children: [ + DropdownButtonFormField2( + 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( + 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( + 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 refresh() async { + offset = 0; + sensors.clear(); + deviceLogsBloc.sensors.add(sensors); + hasMore = true; + deviceLogsBloc.sinkHasMore.add(hasMore); + deviceLogsBloc.getDeviceLogByThingID(offset, thingID, dateTime!, sensors); + } +} diff --git a/lib/feature/devices/device_detail/device_detail_bloc.dart b/lib/feature/devices/device_detail/device_detail_bloc.dart index 474dd83..dd5bec4 100644 --- a/lib/feature/devices/device_detail/device_detail_bloc.dart +++ b/lib/feature/devices/device_detail/device_detail_bloc.dart @@ -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 get sinkDeviceLocation => deviceLocation.sink; Stream get streamDeviceLocation => deviceLocation.stream; + final sensorTemps = StreamController>.broadcast(); + StreamSink> get sinkSensorTemps => sensorTemps.sink; + Stream> 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 sensorTemps = []; + DateTime twoDaysAgo = DateTime.now().subtract(const Duration(days: 2)); + String from = DateTimeUtils.instance.formatDateTimeToString(twoDaysAgo); + String now = DateTimeUtils.instance.formatDateTimeToString(DateTime.now()); + Map 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); + } + } + } } diff --git a/lib/feature/devices/device_detail/device_detail_screen.dart b/lib/feature/devices/device_detail/device_detail_screen.dart index 57a9ae5..d320509 100644 --- a/lib/feature/devices/device_detail/device_detail_screen.dart +++ b/lib/feature/devices/device_detail/device_detail_screen.dart @@ -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 { 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 { ), ], ), + StreamBuilder>( + 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( diff --git a/lib/feature/devices/devices_manager_screen.dart b/lib/feature/devices/devices_manager_screen.dart index bc76489..0155cec 100644 --- a/lib/feature/devices/devices_manager_screen.dart +++ b/lib/feature/devices/devices_manager_screen.dart @@ -49,8 +49,9 @@ class _DevicesManagerScreenState extends State { @override Widget build(BuildContext context) { - return SafeArea( - child: StreamBuilder>( + return Scaffold( + body: SafeArea( + child: StreamBuilder>( stream: devicesManagerBloc.streamAllDevices, initialData: devices, builder: (context, allDeviceSnapshot) { @@ -63,88 +64,93 @@ class _DevicesManagerScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ StreamBuilder( - 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( - Colors.green), - iconColor: - MaterialStateProperty.all( - 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( + Colors.green), + iconColor: + MaterialStateProperty.all( + 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, + ), + ); + }, + ) ], ), ); } - }), + }, + ), + ), ); } diff --git a/lib/feature/home/home_screen.dart b/lib/feature/home/home_screen.dart index 38d8aae..5900d15 100644 --- a/lib/feature/home/home_screen.dart +++ b/lib/feature/home/home_screen.dart @@ -61,203 +61,207 @@ class _HomeScreenState extends State { @override Widget build(BuildContext context) { - return Padding( - padding: context.paddingLow, - child: SingleChildScrollView( - child: Column( - children: [ - Row( - children: [ - Text( - appLocalization(context).notification, - style: context.titleMediumTextStyle, - ), - SizedBox(width: context.lowValue), - StreamBuilder( - stream: homeBloc.streamCountNotitication, - builder: (context, countSnapshot) { - return Text( - "(${countSnapshot.data ?? 0})", - style: context.titleMediumTextStyle, - ); - }, - ) - ], - ), - SizedBox( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: StreamBuilder>>( - 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( - 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( - 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: [ + Row( + children: [ + Text( + appLocalization(context).notification, + style: context.titleMediumTextStyle, + ), + SizedBox(width: context.lowValue), + StreamBuilder( + stream: homeBloc.streamCountNotitication, + builder: (context, countSnapshot) { + return Text( + "(${countSnapshot.data ?? 0})", + style: context.titleMediumTextStyle, ); - } - }, + }, + ) + ], + ), + SizedBox( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: StreamBuilder>>( + 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( + 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( + 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>( - stream: homeBloc.streamAllDevicesAlias, - initialData: allDevicesAlias, - builder: (context, allDeviceSnapshot) { - return StreamBuilder>( - stream: homeBloc.streamOnlineDevicesAlias, - initialData: onlineDevicesAlias, - builder: (context, onlineDeviceSnapshot) { - return StreamBuilder>( - stream: homeBloc.streamOfflineDevicesAlias, - initialData: offlineDevicesAlias, - builder: (context, deviceDashboardSnapshot) { - return StreamBuilder>( - stream: homeBloc.streamWarningDevicesAlias, - initialData: warningDevicesAlias, - builder: (context, warningDeviceSnapshot) { - return StreamBuilder>( - 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>( - stream: homeBloc.streamAllDevicesAliasJoined, - initialData: allDevicesAliasJoined, - builder: (context, allDeviceSnapshot) { - if (allDeviceSnapshot.data?.isEmpty ?? true) { - return const SizedBox.shrink(); - } - return StreamBuilder>( - stream: homeBloc.streamOnlineDevicesAliasJoined, - initialData: onlineDevicesAliasJoined, - builder: (context, onlineDeviceSnapshot) { - return StreamBuilder>( - stream: homeBloc.streamOfflineDevicesAliasJoined, - initialData: offlineDevicesAliasJoined, - builder: (context, deviceDashboardSnapshot) { - return StreamBuilder>( - stream: homeBloc.streamWarningDevicesAliasJoined, - initialData: warningDevicesAliasJoined, - builder: (context, warningDeviceSnapshot) { - return StreamBuilder>( - 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>( + stream: homeBloc.streamAllDevicesAlias, + initialData: allDevicesAlias, + builder: (context, allDeviceSnapshot) { + return StreamBuilder>( + stream: homeBloc.streamOnlineDevicesAlias, + initialData: onlineDevicesAlias, + builder: (context, onlineDeviceSnapshot) { + return StreamBuilder>( + stream: homeBloc.streamOfflineDevicesAlias, + initialData: offlineDevicesAlias, + builder: (context, deviceDashboardSnapshot) { + return StreamBuilder>( + stream: homeBloc.streamWarningDevicesAlias, + initialData: warningDevicesAlias, + builder: (context, warningDeviceSnapshot) { + return StreamBuilder>( + 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>( + stream: homeBloc.streamAllDevicesAliasJoined, + initialData: allDevicesAliasJoined, + builder: (context, allDeviceSnapshot) { + if (allDeviceSnapshot.data?.isEmpty ?? true) { + return const SizedBox.shrink(); + } + return StreamBuilder>( + stream: homeBloc.streamOnlineDevicesAliasJoined, + initialData: onlineDevicesAliasJoined, + builder: (context, onlineDeviceSnapshot) { + return StreamBuilder>( + stream: homeBloc.streamOfflineDevicesAliasJoined, + initialData: offlineDevicesAliasJoined, + builder: (context, deviceDashboardSnapshot) { + return StreamBuilder>( + stream: homeBloc.streamWarningDevicesAliasJoined, + initialData: warningDevicesAliasJoined, + builder: (context, warningDeviceSnapshot) { + return StreamBuilder>( + 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, + ); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ), + ], + ), ), ), ); diff --git a/lib/feature/log/device_logs_screen.dart b/lib/feature/log/device_logs_screen.dart deleted file mode 100644 index 9eae191..0000000 --- a/lib/feature/log/device_logs_screen.dart +++ /dev/null @@ -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 createState() => _DeviceLogsScreenState(); -} - -class _DeviceLogsScreenState extends State { - TextEditingController fromDate = TextEditingController(); - String fromDateApi = ''; - DateTime? dateTime; - String thingID = ""; - late DeviceLogsBloc deviceLogsBloc; - List allDevices = []; - List 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>( - stream: deviceLogsBloc.streamAllDevices, - builder: (context, allDevicesSnapshot) { - if (allDevicesSnapshot.data?[0].thingId == null) { - deviceLogsBloc.getAllDevices(); - return const Center( - child: CircularProgressIndicator(), - ); - } else { - return StreamBuilder>( - stream: deviceLogsBloc.streamSensors, - initialData: sensors, - builder: (context, sensorsSnapshot) { - return Padding( - padding: context.paddingLow, - child: Scaffold( - body: SafeArea( - child: Column( - children: [ - DropdownButtonFormField2( - 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( - 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( - 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 refresh() async { - offset = 0; - sensors.clear(); - deviceLogsBloc.sensors.add(sensors); - hasMore = true; - deviceLogsBloc.sinkHasMore.add(hasMore); - deviceLogsBloc.getDeviceLogByThingID(offset, thingID, dateTime!, sensors); - } -} diff --git a/lib/feature/main/main_screen.dart b/lib/feature/main/main_screen.dart index f460c48..dd56f92 100644 --- a/lib/feature/main/main_screen.dart +++ b/lib/feature/main/main_screen.dart @@ -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 with WidgetsBindingObserver { WidgetsBinding.instance.removeObserver(this); } + PersistentTabController controller = PersistentTabController(initialIndex: 0); + List _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 _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(); @@ -204,16 +275,16 @@ class _MainScreenState extends State with WidgetsBindingObserver { return Scaffold( appBar: AppBar( backgroundColor: Colors.transparent, - centerTitle: true, - title: StreamBuilder( - stream: mainBloc.streamTitle, - initialData: titlePage, - builder: (context, titleSnapshot) { - return Text( - titleSnapshot.data ?? ApplicationConstants.APP_NAME, - ); - }, - ), + // centerTitle: true, + // title: StreamBuilder( + // stream: mainBloc.streamTitle, + // initialData: titlePage, + // builder: (context, titleSnapshot) { + // return Text( + // titleSnapshot.data ?? ApplicationConstants.APP_NAME, + // ); + // }, + // ), actions: [ StreamBuilder( stream: mainBloc.streamThemeMode, @@ -306,13 +377,8 @@ class _MainScreenState extends State 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 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, ), ); }, diff --git a/lib/product/constant/navigation/navigation_router.dart b/lib/product/constant/navigation/navigation_router.dart index 0053824..f026d4d 100644 --- a/lib/product/constant/navigation/navigation_router.dart +++ b/lib/product/constant/navigation/navigation_router.dart @@ -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'; diff --git a/lib/product/shared/shared_line_chart.dart b/lib/product/shared/shared_line_chart.dart new file mode 100644 index 0000000..865d272 --- /dev/null +++ b/lib/product/shared/shared_line_chart.dart @@ -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 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 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))), + ), + ), + ); +} diff --git a/lib/product/utils/device_utils.dart b/lib/product/utils/device_utils.dart index 8ae0f2a..9343304 100644 --- a/lib/product/utils/device_utils.dart +++ b/lib/product/utils/device_utils.dart @@ -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'; diff --git a/pubspec.lock b/pubspec.lock index f7f0efc..d7b26b1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -129,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.9" + equatable: + dependency: transitive + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" fake_async: dependency: transitive description: @@ -201,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.5.18" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "6b9eb2b3017241d05c482c01f668dd05cc909ec9a0114fdd49acd958ff2432fa" + url: "https://pub.dev" + source: hosted + version: "0.64.0" flex_color_scheme: dependency: "direct main" description: @@ -581,6 +597,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3" + persistent_bottom_nav_bar_v2: + dependency: "direct main" + description: + name: persistent_bottom_nav_bar_v2 + sha256: "2fbaf1e8b18108d8a303304a306d68bcfb78abfd553295f489a8a315dff479e2" + url: "https://pub.dev" + source: hosted + version: "4.2.8" petitparser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c88cdf1..87e81a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,6 +60,8 @@ dependencies: qr_flutter: ^4.1.0 flutter_polyline_points: ^2.0.0 simple_ripple_animation: ^0.1.0 + fl_chart: ^0.64.0 + persistent_bottom_nav_bar_v2: ^4.2.8 dev_dependencies: flutter_test: