Compare commits

..

19 Commits

Author SHA1 Message Date
d6ef06c1b3 Simplify widget structure in TotalEnergyConsumptionChart by removing unnecessary FittedBox wrapper around month title text. 2025-04-30 16:07:37 +03:00
c9aaf2580f Refactor TotalEnergyConsumptionChart to accept chartData as a parameter that it takes from TotalEnergyConsumptionBlocand update TotalEnergyConsumptionChartBox to use Bloc for state management. 2025-04-30 15:56:17 +03:00
d9cd5d0438 Injected TotalEnergyConsumptionBloc into AnalyticsPage. 2025-04-30 15:46:05 +03:00
3eb87dfde1 Created TotalEnergyConsumptionBloc and its implementation. 2025-04-30 15:45:07 +03:00
f29ff2551f Rename method getTotalEnergyConsumption to load in TotalEnergyConsumptionService and FakeTotalEnergyConsumptionService for consistency. 2025-04-30 15:34:10 +03:00
67dd59ee9c Add GetTotalEnergyConsumptionParam and FakeTotalEnergyConsumptionService implementations 2025-04-30 15:30:25 +03:00
bb3c3906d1 Add EnergyDataModel and update TotalEnergyConsumptionChart to use it 2025-04-30 15:15:05 +03:00
3873deca90 Created EnergyData model. 2025-04-30 15:09:30 +03:00
9431dd4500 extracted reusable helper methods into global extensions. 2025-04-30 15:02:53 +03:00
63718185e7 Refactor TotalEnergyConsumptionChart to TotalEnergyConsumptionChartBox for improved layout and encapsulation. 2025-04-30 14:59:01 +03:00
1f4e82d567 Enhance TotalEnergyConsumptionChart layout and tooltip functionality. 2025-04-30 14:56:01 +03:00
9f68d171ff progress towards making TotalEnergyConsumptionChart functional and look like the design. 2025-04-30 13:08:28 +03:00
6eba640037 bump-dependencies 2025-04-30 09:48:02 +03:00
7a088074e3 Prepared the layout of all charts. 2025-04-30 09:44:04 +03:00
d8f40badc0 animated tab buttons. 2025-04-30 09:20:02 +03:00
fdd5d0feed rem oved comments and removed copyWith fromAnalyticsTabState because the state object only has one property, so i replaced the state object with AnalyticsPageTab enum. 2025-04-30 09:17:52 +03:00
fb1f79c7bb Made AnalyticsPageTabsAndChildren responsive. 2025-04-30 09:14:30 +03:00
1923ac7014 Progress towards 1493. 2025-04-29 17:00:06 +03:00
c114161357 SP-1492. 2025-04-29 14:17:08 +03:00
39 changed files with 1029 additions and 116 deletions

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.25 2.1875H14.6875V1.875C14.6875 1.62636 14.5887 1.3879 14.4129 1.21209C14.2371 1.03627 13.9986 0.9375 13.75 0.9375C13.5014 0.9375 13.2629 1.03627 13.0871 1.21209C12.9113 1.3879 12.8125 1.62636 12.8125 1.875V2.1875H7.1875V1.875C7.1875 1.62636 7.08873 1.3879 6.91291 1.21209C6.7371 1.03627 6.49864 0.9375 6.25 0.9375C6.00136 0.9375 5.7629 1.03627 5.58709 1.21209C5.41127 1.3879 5.3125 1.62636 5.3125 1.875V2.1875H3.75C3.3356 2.1875 2.93817 2.35212 2.64515 2.64515C2.35212 2.93817 2.1875 3.3356 2.1875 3.75V16.25C2.1875 16.6644 2.35212 17.0618 2.64515 17.3549C2.93817 17.6479 3.3356 17.8125 3.75 17.8125H16.25C16.6644 17.8125 17.0618 17.6479 17.3549 17.3549C17.6479 17.0618 17.8125 16.6644 17.8125 16.25V3.75C17.8125 3.3356 17.6479 2.93817 17.3549 2.64515C17.0618 2.35212 16.6644 2.1875 16.25 2.1875ZM5.3125 4.0625C5.3125 4.31114 5.41127 4.5496 5.58709 4.72541C5.7629 4.90123 6.00136 5 6.25 5C6.49864 5 6.7371 4.90123 6.91291 4.72541C7.08873 4.5496 7.1875 4.31114 7.1875 4.0625H12.8125C12.8125 4.31114 12.9113 4.5496 13.0871 4.72541C13.2629 4.90123 13.5014 5 13.75 5C13.9986 5 14.2371 4.90123 14.4129 4.72541C14.5887 4.5496 14.6875 4.31114 14.6875 4.0625H15.9375V5.9375H4.0625V4.0625H5.3125ZM4.0625 15.9375V7.8125H15.9375V15.9375H4.0625Z" fill="#475569"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,6 @@
extension FormatNumberToKwh on num {
String get formatNumberToKwh {
final regExp = RegExp(r'(\d)(?=(\d{3})+$)');
return '${toStringAsFixed(0).replaceAllMapped(regExp, (match) => '${match[1]},')} kWh';
}
}

View File

@ -0,0 +1,19 @@
extension GetMonthNameFromNumber on num {
String get getMonthName {
return switch (this) {
1 => 'JAN',
2 => 'FEB',
3 => 'MAR',
4 => 'APR',
5 => 'MAY',
6 => 'JUN',
7 => 'JUL',
8 => 'AUG',
9 => 'SEP',
10 => 'OCT',
11 => 'NOV',
12 => 'DEC',
_ => 'N/A'
};
}
}

View File

@ -0,0 +1,14 @@
import 'package:equatable/equatable.dart';
class EnergyDataModel extends Equatable {
const EnergyDataModel({
required this.date,
required this.value,
});
final DateTime date;
final double value;
@override
List<Object?> get props => [date, value];
}

View File

@ -0,0 +1,18 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
part 'analytics_tab_event.dart';
class AnalyticsTabBloc extends Bloc<AnalyticsTabEvent, AnalyticsPageTab> {
AnalyticsTabBloc() : super(AnalyticsPageTab.energyManagement) {
on<UpdateAnalyticsTabEvent>(_onUpdateAnalyticsTabEvent);
}
void _onUpdateAnalyticsTabEvent(
UpdateAnalyticsTabEvent event,
Emitter<AnalyticsPageTab> emit,
) {
emit(event.analyticsTab);
}
}

View File

@ -0,0 +1,17 @@
part of 'analytics_tab_bloc.dart';
sealed class AnalyticsTabEvent extends Equatable {
const AnalyticsTabEvent();
@override
List<Object> get props => [];
}
class UpdateAnalyticsTabEvent extends AnalyticsTabEvent {
const UpdateAnalyticsTabEvent(this.analyticsTab);
final AnalyticsPageTab analyticsTab;
@override
List<Object> get props => [analyticsTab];
}

View File

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart';
import 'package:syncrow_web/pages/analytics/modules/overview/views/analytics_overview_view.dart';
enum AnalyticsPageTab {
overview(
title: 'Overview',
child: AnalyticsOverviewView(),
),
energyManagement(
title: 'Energy Management',
child: AnalyticsEnergyManagementView(),
),
occupancy(
title: 'Occupancy',
child: AnalyticsOccupancyView(),
);
const AnalyticsPageTab({
required this.title,
required this.child,
});
final Widget child;
final String title;
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/fake_total_energy_consumption_service.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AnalyticsPage extends StatelessWidget {
const AnalyticsPage({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AnalyticsTabBloc>(
create: (context) => AnalyticsTabBloc(),
),
BlocProvider(
create: (context) => TotalEnergyConsumptionBloc(
FakeTotalEnergyConsumptionService(),
),
),
],
child: const AnalyticsPageForm(),
);
}
}
class AnalyticsPageForm extends StatelessWidget {
const AnalyticsPageForm({super.key});
@override
Widget build(BuildContext context) {
return WebScaffold(
rightBody: const NavigateHomeGridView(),
appBarTitle: Text(
'Syncrow Analytics',
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
enableMenuSidebar: false,
scaffoldBody: const Row(
children: [
AnalyticsCommunitiesSidebar(),
Expanded(flex: 5, child: AnalyticsPageTabsAndChildren()),
],
),
);
}
}

View File

@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
class AnalyticsCommunitiesSidebar extends StatelessWidget {
const AnalyticsCommunitiesSidebar({super.key});
@override
Widget build(BuildContext context) {
return Expanded(
child: SpaceTreeView(
title: const Text('Communities'),
shouldDisableDeselectingChildrenOfSelectedParent: true,
onSelect: () {},
isSide: false,
),
);
}
}

View File

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class AnalyticsDateFilterButton extends StatelessWidget {
const AnalyticsDateFilterButton({super.key});
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
@override
Widget build(BuildContext context) {
return TextButton.icon(
style: TextButton.styleFrom(
foregroundColor: _color,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.greyColor,
width: 1,
),
),
backgroundColor: ColorsManager.transparentColor,
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
),
),
icon: SvgPicture.asset(
Assets.blankCalendar,
height: 20,
width: 20,
colorFilter: ColorFilter.mode(_color, BlendMode.srcIn),
),
label: Text(
_concatenateDate(DateTime(2024, 1), DateTime(2024, 12)),
style: const TextStyle(
fontWeight: FontWeight.w700,
),
),
onPressed: () {},
);
}
String _formatDate(DateTime date) {
final formatter = DateFormat('MMM yyyy');
final formattedDate = formatter.format(date);
return formattedDate;
}
String _concatenateDate(DateTime startDate, DateTime endDate) {
final formattedStartDate = _formatDate(startDate);
final formattedEndDate = _formatDate(endDate);
return '$formattedStartDate - $formattedEndDate';
}
}

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class AnalyticsPageTabButton extends StatelessWidget {
const AnalyticsPageTabButton({
super.key,
required this.tab,
required this.isSelected,
});
final AnalyticsPageTab tab;
final bool isSelected;
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: () => context.read<AnalyticsTabBloc>().add(
UpdateAnalyticsTabEvent(tab),
),
child: Text(
tab.title,
textAlign: TextAlign.center,
maxLines: 1,
softWrap: false,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400,
fontSize: 16,
color:
isSelected ? ColorsManager.slidingBlueColor : ColorsManager.textGray,
),
),
);
}
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart';
import 'package:syncrow_web/utils/style.dart';
class AnalyticsPageTabsAndChildren extends StatelessWidget {
const AnalyticsPageTabsAndChildren({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<AnalyticsTabBloc, AnalyticsPageTab>(
buildWhen: (previous, current) => previous != current,
builder: (context, selectedTab) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Container(
width: MediaQuery.sizeOf(context).width * 1,
decoration: subSectionContainerDecoration,
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 4,
child: FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: Row(
spacing: 32,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
...AnalyticsPageTab.values.map(
(tab) => AnimatedSwitcher(
switchInCurve: Curves.easeIn,
duration: const Duration(milliseconds: 200),
child: AnalyticsPageTabButton(
key: ValueKey(selectedTab),
tab: tab,
isSelected: tab == selectedTab,
),
),
),
],
),
),
),
const Spacer(),
const Expanded(
flex: 2,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerEnd,
child: AnalyticsDateFilterButton(),
),
),
],
),
),
),
Expanded(
flex: 8,
child: SizedBox(
width: MediaQuery.sizeOf(context).width,
child: AnimatedSwitcher(
switchInCurve: Curves.easeIn,
duration: const Duration(milliseconds: 200),
child: selectedTab.child,
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,42 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
part 'total_energy_consumption_event.dart';
part 'total_energy_consumption_state.dart';
class TotalEnergyConsumptionBloc
extends Bloc<TotalEnergyConsumptionEvent, TotalEnergyConsumptionState> {
TotalEnergyConsumptionBloc(
this._totalEnergyConsumptionService,
) : super(const TotalEnergyConsumptionState()) {
on<TotalEnergyConsumptionLoadEvent>(_onTotalEnergyConsumptionLoadEvent);
}
final TotalEnergyConsumptionService _totalEnergyConsumptionService;
Future<void> _onTotalEnergyConsumptionLoadEvent(
TotalEnergyConsumptionLoadEvent event,
Emitter<TotalEnergyConsumptionState> emit,
) async {
emit(state.copyWith(status: TotalEnergyConsumptionStatus.loading));
try {
final chartData = await _totalEnergyConsumptionService.load(event.param);
emit(
state.copyWith(
chartData: chartData,
status: TotalEnergyConsumptionStatus.loaded,
),
);
} catch (e) {
emit(
state.copyWith(
errorMessage: e.toString(),
status: TotalEnergyConsumptionStatus.failure,
),
);
}
}
}

View File

@ -0,0 +1,17 @@
part of 'total_energy_consumption_bloc.dart';
sealed class TotalEnergyConsumptionEvent extends Equatable {
const TotalEnergyConsumptionEvent();
@override
List<Object?> get props => [];
}
final class TotalEnergyConsumptionLoadEvent extends TotalEnergyConsumptionEvent {
const TotalEnergyConsumptionLoadEvent({required this.param});
final GetTotalEnergyConsumptionParam param ;
@override
List<Object?> get props => [param];
}

View File

@ -0,0 +1,35 @@
part of 'total_energy_consumption_bloc.dart';
enum TotalEnergyConsumptionStatus {
initial,
loading,
loaded,
failure,
}
final class TotalEnergyConsumptionState extends Equatable {
const TotalEnergyConsumptionState({
this.status = TotalEnergyConsumptionStatus.initial,
this.chartData = const <EnergyDataModel>[],
this.errorMessage,
});
final List<EnergyDataModel> chartData;
final TotalEnergyConsumptionStatus status;
final String? errorMessage;
TotalEnergyConsumptionState copyWith({
List<EnergyDataModel>? chartData,
TotalEnergyConsumptionStatus? status,
String? errorMessage,
}) {
return TotalEnergyConsumptionState(
chartData: chartData ?? this.chartData,
status: status ?? this.status,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object?> get props => [chartData, status, errorMessage];
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart';
import 'package:syncrow_web/utils/style.dart';
class AnalyticsEnergyManagementView extends StatelessWidget {
const AnalyticsEnergyManagementView({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(32),
child: Row(
spacing: 20,
children: [
const Expanded(
flex: 2,
child: Column(
spacing: 20,
children: [
Expanded(child: TotalEnergyConsumptionChartBox()),
Expanded(child: EnergyConsumptionPerDeviceChart()),
],
),
),
Expanded(
child: Container(
decoration: subSectionContainerDecoration.copyWith(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.all(30),
child: const Column(
spacing: 10,
children: [
Expanded(flex: 2, child: PowerClampEnergyDataWidget()),
Expanded(child: EnergyConsumptionByPhasesChart()),
],
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class EnergyConsumptionByPhasesChart extends StatelessWidget {
const EnergyConsumptionByPhasesChart({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

View File

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/style.dart';
class EnergyConsumptionPerDeviceChart extends StatelessWidget {
const EnergyConsumptionPerDeviceChart({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: subSectionContainerDecoration.copyWith(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.all(30),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Energy Consumption per Device',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 20),
Expanded(
child: Placeholder(),
),
],
),
);
}
}

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class PowerClampEnergyDataWidget extends StatelessWidget {
const PowerClampEnergyDataWidget({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

View File

@ -0,0 +1,175 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart';
import 'package:syncrow_web/pages/analytics/helpers/get_month_name_from_int.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
// energy_consumption_chart will return id, name and consumption
const phasesJson = {
"1": {
"phaseOne": 1000,
"phaseTwo": 2000,
"phaseThree": 3000,
}
};
class TotalEnergyConsumptionChart extends StatelessWidget {
const TotalEnergyConsumptionChart({required this.chartData, super.key});
final List<EnergyDataModel> chartData;
@override
Widget build(BuildContext context) {
return Expanded(
child: LineChart(
LineChartData(
titlesData: _titlesData(context),
lineBarsData: _lineBarsData,
gridData: FlGridData(
show: true,
drawVerticalLine: false,
drawHorizontalLine: true,
),
borderData: FlBorderData(
show: true,
border: const Border.symmetric(
horizontal: BorderSide(
color: ColorsManager.greyColor,
width: 1,
style: BorderStyle.solid,
),
),
),
lineTouchData: LineTouchData(
handleBuiltInTouches: true,
touchSpotThreshold: 2,
touchTooltipData: _lineTouchTooltipData(),
),
),
duration: Durations.extralong1,
curve: Curves.easeIn,
),
);
}
List<LineChartBarData> get _lineBarsData {
return [
LineChartBarData(
preventCurveOvershootingThreshold: 0.1,
curveSmoothness: 0.55,
preventCurveOverShooting: true,
spots: chartData
.asMap()
.entries
.map(
(entry) => FlSpot(
entry.key.toDouble(),
entry.value.value,
),
)
.toList(),
color: ColorsManager.blueColor.withValues(alpha: 0.6),
shadow: Shadow(color: Colors.black12),
show: true,
isCurved: true,
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
ColorsManager.vividBlue.withValues(alpha: 0.3),
ColorsManager.vividBlue.withValues(alpha: 0.2),
ColorsManager.vividBlue.withValues(alpha: 0.1),
Colors.transparent,
],
begin: Alignment.center,
end: Alignment.bottomCenter,
),
),
dotData: FlDotData(show: false),
isStrokeCapRound: true,
barWidth: 3,
),
];
}
FlTitlesData _titlesData(BuildContext context) {
return FlTitlesData(
show: true,
bottomTitles: AxisTitles(
drawBelowEverything: true,
sideTitles: SideTitles(
interval: 1,
reservedSize: 32,
showTitles: true,
maxIncluded: true,
getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Text(
chartData.elementAtOrNull(value.toInt())?.date.month.getMonthName ??
'',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 12,
color: ColorsManager.greyColor,
),
),
),
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
maxIncluded: true,
reservedSize: 110,
getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsetsDirectional.only(end: 12),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
value.formatNumberToKwh,
style: context.textTheme.bodySmall?.copyWith(
fontSize: 12,
color: ColorsManager.greyColor,
),
),
),
),
),
),
rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
);
}
LineTouchTooltipData _lineTouchTooltipData() {
return LineTouchTooltipData(
getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors,
tooltipBorder: const BorderSide(color: ColorsManager.semiTransparentBlack),
tooltipRoundedRadius: 16,
showOnTopOfTheChartBoxArea: false,
tooltipPadding: const EdgeInsets.all(8.0),
getTooltipItems: _getTooltipItems,
);
}
List<LineTooltipItem?> _getTooltipItems(List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
_getToolTipLabel(spot.x + 1, spot.y),
const TextStyle(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w600,
fontSize: 12,
),
);
}).toList();
}
String _getToolTipLabel(num month, double value) {
final monthLabel = month.getMonthName;
final valueLabel = value.formatNumberToKwh;
final labels = [monthLabel, valueLabel];
return labels.where((element) => element.isNotEmpty).join(', ');
}
}

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart';
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
import 'package:syncrow_web/utils/style.dart';
class TotalEnergyConsumptionChartBox extends StatefulWidget {
const TotalEnergyConsumptionChartBox({super.key});
@override
State<TotalEnergyConsumptionChartBox> createState() =>
_TotalEnergyConsumptionChartBoxState();
}
class _TotalEnergyConsumptionChartBoxState
extends State<TotalEnergyConsumptionChartBox> {
@override
void initState() {
final param = GetTotalEnergyConsumptionParam(
startDate: DateTime.now().subtract(const Duration(days: 30)),
endDate: DateTime.now(),
spaceId: '123',
);
context.read<TotalEnergyConsumptionBloc>().add(
TotalEnergyConsumptionLoadEvent(param: param),
);
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<TotalEnergyConsumptionBloc, TotalEnergyConsumptionState>(
builder: (context, state) => Container(
decoration: subSectionContainerDecoration.copyWith(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.all(30),
child: Column(
spacing: 20,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Total Energy Consumption',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w700,
),
),
TotalEnergyConsumptionChart(chartData: state.chartData),
],
),
),
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class AnalyticsOccupancyView extends StatelessWidget {
const AnalyticsOccupancyView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('AnalyticsOccupancyView is Working!'),
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class AnalyticsOverviewView extends StatelessWidget {
const AnalyticsOverviewView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Coming Soon!'),
);
}
}

View File

@ -0,0 +1,19 @@
class GetTotalEnergyConsumptionParam {
final DateTime? startDate;
final DateTime? endDate;
final String? spaceId;
GetTotalEnergyConsumptionParam({
this.startDate,
this.endDate,
this.spaceId,
});
Map<String, dynamic> toJson() {
return {
'startDate': startDate?.toIso8601String(),
'endDate': endDate?.toIso8601String(),
'spaceId': spaceId,
};
}
}

View File

@ -0,0 +1,25 @@
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
class FakeTotalEnergyConsumptionService implements TotalEnergyConsumptionService {
@override
Future<List<EnergyDataModel>> load(
GetTotalEnergyConsumptionParam param,
) {
return Future.value([
EnergyDataModel(date: DateTime(2025, 1), value: 18000),
EnergyDataModel(date: DateTime(2025, 2), value: 25000),
EnergyDataModel(date: DateTime(2025, 3), value: 22000),
EnergyDataModel(date: DateTime(2025, 4), value: 21000),
EnergyDataModel(date: DateTime(2025, 5), value: 30000),
EnergyDataModel(date: DateTime(2025, 6), value: 23000),
EnergyDataModel(date: DateTime(2025, 7), value: 21000),
EnergyDataModel(date: DateTime(2025, 8), value: 25000),
EnergyDataModel(date: DateTime(2025, 9), value: 21100),
EnergyDataModel(date: DateTime(2025, 10), value: 22000),
EnergyDataModel(date: DateTime(2025, 11), value: 21000),
EnergyDataModel(date: DateTime(2025, 12), value: 27500),
]);
}
}

View File

@ -0,0 +1,8 @@
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
abstract interface class TotalEnergyConsumptionService {
Future<List<EnergyDataModel>> load(
GetTotalEnergyConsumptionParam param,
);
}

View File

@ -142,6 +142,19 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
},
color: ColorsManager.primaryColor,
),
HomeItemModel(
title: 'Syncrow Analytics',
icon: Assets.devicesIcon,
active: true,
onPress: (context) {
context.read<SpaceTreeBloc>().add(ClearCachedData());
BlocProvider.of<RoutineBloc>(context)
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
context.go(RoutesConst.analytics);
},
color: ColorsManager.primaryColor,
),
// HomeItemModel(
// title: 'Move in',

View File

@ -50,8 +50,9 @@ class HomeMobilePage extends StatelessWidget {
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 3,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
itemCount: homeItems.length,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
@ -60,10 +61,11 @@ class HomeMobilePage extends StatelessWidget {
itemBuilder: (context, index) {
return HomeCard(
index: index,
active: homeItems[index]['active'],
name: homeItems[index]['title'],
img: homeItems[index]['icon'],
onTap: () => homeBloc.homeItems[index].onPress(context),
active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!,
onTap: () =>
homeBloc.homeItems[index].onPress(context),
);
},
),
@ -94,6 +96,11 @@ class HomeMobilePage extends StatelessWidget {
'icon': Assets.devicesIcon,
'active': true,
},
{
'title': 'Syncrow Analytics',
'icon': Assets.iconEdit,
'active': true,
},
// {
// 'title': 'Move in',
// 'icon': Assets.moveinIcon,

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.dart';
import 'package:syncrow_web/pages/home/view/home_card.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
@ -24,7 +24,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
void initState() {
super.initState();
final homeBloc = BlocProvider.of<HomeBloc>(context);
homeBloc.add(FetchUserInfo());
homeBloc.add(const FetchUserInfo());
}
@override
@ -97,7 +97,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 3, // Change this count if needed.
itemCount: homeBloc.homeItems.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // Adjust as needed.
crossAxisSpacing: 20.0,

View File

@ -17,7 +17,15 @@ import 'package:syncrow_web/utils/style.dart';
class SpaceTreeView extends StatefulWidget {
final bool? isSide;
final Function onSelect;
const SpaceTreeView({required this.onSelect, this.isSide, super.key});
final bool shouldDisableDeselectingChildrenOfSelectedParent;
final Widget? title;
const SpaceTreeView({
required this.onSelect,
this.isSide,
super.key,
this.shouldDisableDeselectingChildrenOfSelectedParent = false,
this.title,
});
@override
State<SpaceTreeView> createState() => _SpaceTreeViewState();
@ -41,17 +49,31 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
final communities =
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
final communities = state.searchQuery.isNotEmpty
? state.filteredCommunity
: state.communityList;
return Container(
height: MediaQuery.sizeOf(context).height,
decoration: widget.isSide == true
? subSectionContainerDecoration.copyWith(color: ColorsManager.whiteColors)
? subSectionContainerDecoration.copyWith(
color: ColorsManager.whiteColors)
: const BoxDecoration(color: ColorsManager.whiteColors),
child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
children: [
if (widget.title != null)
Container(
alignment: AlignmentDirectional.centerStart,
padding: const EdgeInsets.all(24),
child: DefaultTextStyle(
style: context.textTheme.titleMedium!.copyWith(
color: ColorsManager.blackColor,
fontSize: 20,
),
child: widget.title!,
),
),
if (widget.isSide == true)
Container(
decoration: const BoxDecoration(
@ -79,10 +101,12 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.blackColor,
),
onChanged: (value) => context.read<SpaceTreeBloc>().add(
SearchQueryEvent(value),
),
decoration: textBoxDecoration(radios: 20)?.copyWith(
onChanged: (value) =>
context.read<SpaceTreeBloc>().add(
SearchQueryEvent(value),
),
decoration:
textBoxDecoration(radios: 20)?.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding: const EdgeInsets.only(right: 16),
@ -92,7 +116,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium?.copyWith(
hintStyle:
context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray,
@ -131,15 +156,16 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
itemBuilder: (context, index) {
return CustomExpansionTileSpaceTree(
title: communities[index].name,
isSelected:
state.selectedCommunities.contains(communities[index].uuid),
isSoldCheck:
state.selectedCommunities.contains(communities[index].uuid),
onExpansionChanged: () => context.read<SpaceTreeBloc>().add(
OnCommunityExpanded(
communities[index].uuid,
),
),
isSelected: state.selectedCommunities
.contains(communities[index].uuid),
isSoldCheck: state.selectedCommunities
.contains(communities[index].uuid),
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnCommunityExpanded(
communities[index].uuid,
),
),
isExpanded: state.expandedCommunities.contains(
communities[index].uuid,
),
@ -156,8 +182,19 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
(space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded: state.expandedSpaces.contains(space.uuid),
isExpanded:
state.expandedSpaces.contains(space.uuid),
onItemSelected: () {
final isParentSelected = _isParentSelected(
state,
communities[index],
space,
);
if (widget
.shouldDisableDeselectingChildrenOfSelectedParent &&
isParentSelected) {
return;
}
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
communities[index],
@ -167,15 +204,18 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
);
widget.onSelect();
},
onExpansionChanged: () => context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
communities[index].uuid,
space.uuid ?? '',
),
),
isSelected: state.selectedSpaces.contains(space.uuid) ||
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
communities[index].uuid,
space.uuid ?? '',
),
),
isSelected: state.selectedSpaces
.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck:
state.soldCheck.contains(space.uuid),
isSoldCheck: state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces(
context,
state,
@ -196,6 +236,13 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
});
}
bool _isParentSelected(
SpaceTreeState state, CommunityModel community, SpaceModel space) {
return state.selectedCommunities.contains(community.uuid) ||
(space.spaceModel?.uuid != null &&
state.selectedSpaces.contains(space.spaceModel?.uuid));
}
List<Widget> _buildNestedSpaces(
BuildContext context,
SpaceTreeState state,
@ -204,8 +251,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
) {
return space.children.map((child) {
return CustomExpansionTileSpaceTree(
isSelected:
state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid),
isSelected: state.selectedSpaces.contains(child.uuid) ||
state.soldCheck.contains(child.uuid),
isSoldCheck: state.soldCheck.contains(child.uuid),
title: child.name,
isExpanded: state.expandedSpaces.contains(child.uuid),

View File

@ -457,17 +457,16 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
emit(SpaceManagementLoading());
try {
final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
final updatedSpaces =
await saveSpacesHierarchically(event.context, event.spaces, event.communityUuid);
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
emit(SpaceCreationSuccess(spaces: updatedSpaces));
if (previousState is SpaceManagementLoaded) {
await _updateLoadedState(
spaceTreeState,
event.context,
previousState,
allSpaces,
event.communityUuid,
@ -484,39 +483,35 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
}
Future<void> _updateLoadedState(
SpaceTreeState spaceTreeState,
BuildContext context,
SpaceManagementLoaded previousState,
List<SpaceModel> allSpaces,
String communityUuid,
Emitter<SpaceManagementState> emit,
) async {
try {
var prevSpaceModels = await fetchSpaceModels();
var prevSpaceModels = await fetchSpaceModels();
await fetchTags();
final spaceTreeState = context.read<SpaceTreeBloc>().state;
final communities = spaceTreeState.searchQuery.isNotEmpty
? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
await fetchTags();
for (var community in communities) {
if (community.uuid == communityUuid) {
community.spaces = allSpaces;
_spaceTreeBloc.add(InitialEvent());
final communities = spaceTreeState.searchQuery.isNotEmpty
? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
for (var community in communities) {
if (community.uuid == communityUuid) {
community.spaces = allSpaces;
_spaceTreeBloc.add(InitialEvent());
emit(SpaceManagementLoaded(
emit(SpaceManagementLoaded(
communities: communities,
products: _cachedProducts ?? [],
selectedCommunity: community,
selectedSpace: null,
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? [],
));
return;
}
allTags: _cachedTags ?? []));
return;
} else {
print("Community not found");
}
} catch (e, stackTrace) {
rethrow;
}
}

View File

@ -53,9 +53,6 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
builder: (context, state) {
if (state is SpaceManagementLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state is SpaceManagementInitial) {
return const Center(child: CircularProgressIndicator());
} else if (state is BlankState) {
return LoadedSpaceView(
communities: state.communities,

View File

@ -526,8 +526,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = true;
});
return;
} else if (isNameFieldExist) {
return;
} else {
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
if (newName.isNotEmpty) {

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';

View File

@ -2,10 +2,10 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
@ -114,48 +114,41 @@ class _SidebarWidgetState extends State<SidebarWidget> {
return Container(
width: _width,
decoration: subSectionContainerDecoration,
child: spaceTreeState is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SidebarHeader(onAddCommunity: _onAddCommunity),
CustomSearchBar(
onSearchChanged: _onSearchChanged,
),
const SizedBox(height: 16),
Expanded(
child: Builder(
builder: (_) {
return SidebarCommunitiesList(
scrollController: _scrollController,
onScrollToEnd: () {},
communities: filteredCommunities,
itemBuilder: (context, index) {
if (index == filteredCommunities.length) {
final spaceTreeState = context.read<SpaceTreeBloc>().state;
if (spaceTreeState.paginationIsLoading) {
return const Padding(
padding: EdgeInsets.all(8.0),
child: Center(child: CircularProgressIndicator()),
);
} else {
return const SizedBox.shrink();
}
}
return _buildCommunityTile(context, filteredCommunities[index]);
},
);
},
),
),
if (spaceTreeState.paginationIsLoading || spaceTreeState.isSearching)
Center(
child: CircularProgressIndicator(),
)
],
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SidebarHeader(onAddCommunity: _onAddCommunity),
CustomSearchBar(
onSearchChanged: _onSearchChanged,
),
const SizedBox(height: 16),
Expanded(
child: Visibility(
visible: filteredCommunities.isNotEmpty,
replacement: const EmptySearchResultWidget(),
child: SidebarCommunitiesList(
scrollController: _scrollController,
onScrollToEnd: () {},
communities: filteredCommunities,
itemBuilder: (context, index) {
if (index == filteredCommunities.length) {
final spaceTreeState = context.read<SpaceTreeBloc>().state;
if (spaceTreeState.paginationIsLoading) {
return const Padding(
padding: EdgeInsets.all(8.0),
child: Center(child: CircularProgressIndicator()),
);
} else {
return const SizedBox.shrink();
}
}
return _buildCommunityTile(context, filteredCommunities[index]);
}),
),
),
],
),
);
}

View File

@ -1,5 +1,6 @@
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/access_management/view/access_management.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/views/analytics_page.dart';
import 'package:syncrow_web/pages/auth/view/login_page.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart';
@ -37,6 +38,11 @@ class AppRoutes {
GoRoute(
path: RoutesConst.rolesAndPermissions,
builder: (context, state) => const RolesAndPermissionPage()),
GoRoute(
path: RoutesConst.analytics,
name: 'analytics',
builder: (context, state) => const AnalyticsPage(),
),
];
}
}

View File

@ -480,4 +480,5 @@ class Assets {
static const String DisappeDelayIcon = 'assets/icons/disappe_delay_icon.svg';
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
static const String blankCalendar = 'assets/icons/blank_calendar.svg';
}

View File

@ -6,4 +6,5 @@ class RoutesConst {
static const String deviceManagementPage = '/device-management-page';
static const String spacesManagementPage = '/spaces_management-page';
static const String rolesAndPermissions = '/roles_and_Permissions-page';
static const String analytics = '/syncrow_analytics';
}

View File

@ -35,21 +35,21 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6
flutter_bloc: ^8.1.5
flutter_bloc: ^9.1.0
equatable: ^2.0.5
graphview: ^1.2.0
flutter_svg: ^2.0.10+1
dio: ^5.5.0+1
get_it: ^7.6.7
get_it: ^8.0.3
flutter_secure_storage: ^9.2.2
shared_preferences: ^2.3.0
dropdown_button2: ^2.3.9
data_table_2: ^2.5.15
go_router:
intl: ^0.19.0
dropdown_search: ^5.0.6
intl: ^0.20.2
dropdown_search: ^6.0.2
flutter_dotenv: ^5.1.0
fl_chart: ^0.69.0
fl_chart: ^0.71.0
uuid: ^4.4.2
time_picker_spinner: ^1.0.0
intl_phone_field: ^3.2.0
@ -60,7 +60,7 @@ dependencies:
firebase_core: ^3.11.0
firebase_crashlytics: ^4.3.2
firebase_database: ^11.3.2
bloc: ^8.1.4
bloc: ^9.0.0
dev_dependencies:
@ -72,7 +72,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.0
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec