mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 01:35:25 +00:00
Compare commits
115 Commits
SP-1493-FE
...
ci/cd-enha
Author | SHA1 | Date | |
---|---|---|---|
f19120c754 | |||
6b3eca23af | |||
4f4f11c330 | |||
8a25fa798c | |||
6612e91430 | |||
56c613fb0c | |||
53222bee81 | |||
bfb9158652 | |||
7f03222c12 | |||
5e6c14efeb | |||
9bbf3e75fa | |||
303b0236f1 | |||
4e3e63723e | |||
38ff20f86a | |||
d539e6266e | |||
7467f8d0ea | |||
a11e20147e | |||
55a6974bdc | |||
f8f58a24b8 | |||
682e69e65f | |||
59a59231ec | |||
ad41a2a87e | |||
974aa8f2a4 | |||
428cd34492 | |||
1a6121c452 | |||
e8f9ae944c | |||
7e37aed026 | |||
d89e227599 | |||
5a68b22f0c | |||
38184ca8b2 | |||
4d5de7bc05 | |||
1a3006fa43 | |||
490ca2057e | |||
06637a16bb | |||
696978a78d | |||
818e4e4d51 | |||
af877d7839 | |||
a33b1e3f49 | |||
c3cce334ab | |||
947e9e404c | |||
cd8264b6ce | |||
7467be6980 | |||
0353c73dac | |||
a050792f32 | |||
464f7b7347 | |||
cd54574279 | |||
18acae3e85 | |||
f081a7fc2d | |||
5996ff3928 | |||
a0d1cb988a | |||
c3ec9000d4 | |||
3d6a60b406 | |||
69c9240641 | |||
098013e5c8 | |||
11fb9e4894 | |||
390da9213d | |||
cae8b029fe | |||
6b883c8bb3 | |||
08c99bcbcb | |||
f6448d3eff | |||
a657a9a25e | |||
f55fa25bdf | |||
7242218b2f | |||
e43de3f64c | |||
9c250986b2 | |||
d8faafd1c0 | |||
24c30ddcb5 | |||
bafd2b4d13 | |||
56f9b1fc9a | |||
a9cc92ff86 | |||
3c7edae88a | |||
56c2d11535 | |||
3aa5bff758 | |||
28d1e5a5a7 | |||
fe036a8190 | |||
82e145de9d | |||
ebeb514a5b | |||
6b7e02ee53 | |||
b01136b6e9 | |||
97f8c6c8c9 | |||
6e527503c1 | |||
d6ef06c1b3 | |||
c9aaf2580f | |||
d9cd5d0438 | |||
3eb87dfde1 | |||
f29ff2551f | |||
67dd59ee9c | |||
bb3c3906d1 | |||
3873deca90 | |||
9431dd4500 | |||
63718185e7 | |||
1f4e82d567 | |||
9f68d171ff | |||
6eba640037 | |||
7a088074e3 | |||
8d2d9dd0bb | |||
cfc68f1568 | |||
02e08ad92f | |||
d7899a24f5 | |||
800c0ba47f | |||
fe4e775902 | |||
5247856cb4 | |||
4a8b8a32ba | |||
2abce77eb5 | |||
7efd1c3c87 | |||
7a0d9aefb7 | |||
21cc25cfc4 | |||
e2ec4bbf31 | |||
51b46ae197 | |||
36ee22603a | |||
b0abd42b0c | |||
ba4da78846 | |||
dc20d69f20 | |||
cf6ec231dc | |||
d0530f7fc3 |
26
.github/pull_request_template.md
vendored
Normal file
26
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<!--
|
||||
Thanks for contributing!
|
||||
|
||||
Provide a description of your changes below and a general summary in the title
|
||||
|
||||
Please look at the following checklist to ensure that your PR can be accepted quickly:
|
||||
-->
|
||||
|
||||
## Jira Ticket
|
||||
[SP-0000](https://syncrow.atlassian.net/browse/SP-0000)
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Type of Change
|
||||
|
||||
<!--- Put an `x` in all the boxes that apply: -->
|
||||
|
||||
- [ ] ✨ New feature (non-breaking change which adds functionality)
|
||||
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)
|
||||
- [ ] 🧹 Code refactor
|
||||
- [ ] ✅ Build configuration change
|
||||
- [ ] 📝 Documentation
|
||||
- [ ] 🗑️ Chore
|
@ -4,10 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build_and_deploy_job:
|
||||
|
@ -4,18 +4,12 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build_and_deploy_job:
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
|
||||
runs-on: ubuntu-latest
|
||||
name: Build and Deploy Job
|
||||
steps:
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
|
29
.github/workflows/pr-check.yml
vendored
Normal file
29
.github/workflows/pr-check.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
name: Pull Request Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
- main
|
||||
|
||||
jobs:
|
||||
setup_flutter:
|
||||
runs-on: ubuntu-latest
|
||||
name: Setup Flutter and Dependencies
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
lfs: false
|
||||
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.27.3'
|
||||
|
||||
- name: Install dependencies
|
||||
run: flutter pub get
|
||||
|
||||
- name: Run Flutter Build
|
||||
run: flutter build web --web-renderer canvaskit -t lib/main_dev.dart
|
@ -26,6 +26,7 @@ linter:
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
prefer_const_constructors: true
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
6
lib/pages/analytics/helpers/format_number_to_kwh.dart
Normal file
6
lib/pages/analytics/helpers/format_number_to_kwh.dart
Normal 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';
|
||||
}
|
||||
}
|
19
lib/pages/analytics/helpers/get_month_name_from_int.dart
Normal file
19
lib/pages/analytics/helpers/get_month_name_from_int.dart
Normal 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'
|
||||
};
|
||||
}
|
||||
}
|
32
lib/pages/analytics/models/device_energy_data_model.dart
Normal file
32
lib/pages/analytics/models/device_energy_data_model.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||
|
||||
class DeviceEnergyDataModel extends Equatable {
|
||||
const DeviceEnergyDataModel({
|
||||
required this.energy,
|
||||
required this.deviceName,
|
||||
required this.deviceId,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
final List<EnergyDataModel> energy;
|
||||
final String deviceName;
|
||||
final String deviceId;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [energy, deviceName, deviceId];
|
||||
|
||||
factory DeviceEnergyDataModel.fromJson(Map<String, dynamic> json) {
|
||||
final energy = (json['energy'] as List<dynamic>? ?? [])
|
||||
.map((e) => EnergyDataModel.fromJson(e))
|
||||
.toList();
|
||||
return DeviceEnergyDataModel(
|
||||
energy: energy,
|
||||
deviceName: json['device_name'] as String? ?? '',
|
||||
deviceId: json['device_id'] as String? ?? '',
|
||||
color: Color(int.parse(json['color'] as String? ?? '0xFF000000')),
|
||||
);
|
||||
}
|
||||
}
|
21
lib/pages/analytics/models/energy_data_model.dart
Normal file
21
lib/pages/analytics/models/energy_data_model.dart
Normal file
@ -0,0 +1,21 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class EnergyDataModel extends Equatable {
|
||||
const EnergyDataModel({
|
||||
required this.date,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
final DateTime date;
|
||||
final double value;
|
||||
|
||||
factory EnergyDataModel.fromJson(Map<String, dynamic> json) {
|
||||
return EnergyDataModel(
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
value: (json['value'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [date, value];
|
||||
}
|
27
lib/pages/analytics/models/phases_energy_consumption.dart
Normal file
27
lib/pages/analytics/models/phases_energy_consumption.dart
Normal file
@ -0,0 +1,27 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class PhasesEnergyConsumption extends Equatable {
|
||||
final int month;
|
||||
final double phaseA;
|
||||
final double phaseB;
|
||||
final double phaseC;
|
||||
|
||||
const PhasesEnergyConsumption({
|
||||
required this.month,
|
||||
required this.phaseA,
|
||||
required this.phaseB,
|
||||
required this.phaseC,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [month, phaseA, phaseB, phaseC];
|
||||
|
||||
factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) {
|
||||
return PhasesEnergyConsumption(
|
||||
month: json['month'] as int,
|
||||
phaseA: (json['phaseA'] as num).toDouble(),
|
||||
phaseB: (json['phaseB'] as num).toDouble(),
|
||||
phaseC: (json['phaseC'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
}
|
13
lib/pages/analytics/models/power_clamp_energy_status.dart
Normal file
13
lib/pages/analytics/models/power_clamp_energy_status.dart
Normal file
@ -0,0 +1,13 @@
|
||||
class PowerClampEnergyStatus {
|
||||
final String iconPath;
|
||||
final String title;
|
||||
final String value;
|
||||
final String unit;
|
||||
|
||||
const PowerClampEnergyStatus({
|
||||
required this.iconPath,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.unit,
|
||||
});
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'analytics_date_picker_event.dart';
|
||||
|
||||
class AnalyticsDatePickerBloc extends Bloc<AnalyticsDatePickerEvent, DateTime> {
|
||||
AnalyticsDatePickerBloc() : super(DateTime.now()) {
|
||||
on<UpdateAnalyticsDatePickerEvent>(_onUpdateAnalyticsDatePickerEvent);
|
||||
}
|
||||
|
||||
void _onUpdateAnalyticsDatePickerEvent(
|
||||
UpdateAnalyticsDatePickerEvent event,
|
||||
Emitter<DateTime> emit,
|
||||
) {
|
||||
emit(event.date);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
part of 'analytics_date_picker_bloc.dart';
|
||||
|
||||
sealed class AnalyticsDatePickerEvent extends Equatable {
|
||||
const AnalyticsDatePickerEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
final class UpdateAnalyticsDatePickerEvent extends AnalyticsDatePickerEvent {
|
||||
const UpdateAnalyticsDatePickerEvent(this.date);
|
||||
|
||||
final DateTime date;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [date];
|
||||
}
|
@ -1,13 +1,8 @@
|
||||
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(),
|
||||
|
@ -1,9 +1,20 @@
|
||||
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/blocs/analytics_tab/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/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_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/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.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/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
|
||||
@ -12,8 +23,37 @@ class AnalyticsPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<AnalyticsTabBloc>(
|
||||
create: (context) => AnalyticsTabBloc(),
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<AnalyticsTabBloc>(
|
||||
create: (context) => AnalyticsTabBloc(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => TotalEnergyConsumptionBloc(
|
||||
FakeTotalEnergyConsumptionService(),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => EnergyConsumptionByPhasesBloc(
|
||||
FakeEnergyConsumptionByPhasesService(),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => EnergyConsumptionPerDeviceBloc(
|
||||
FakeEnergyConsumptionPerDeviceService(),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => PowerClampInfoBloc(
|
||||
RemotePowerClampInfoService(HTTPService()),
|
||||
),
|
||||
),
|
||||
BlocProvider<RealtimeDeviceChangesBloc>(
|
||||
create: (context) => RealtimeDeviceChangesBloc(
|
||||
FirebaseRealtimeDeviceService(),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: const AnalyticsPageForm(),
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
|
||||
|
||||
class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
||||
@ -6,13 +9,47 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: SpaceTreeView(
|
||||
title: const Text('Communities'),
|
||||
shouldDisableDeselectingChildrenOfSelectedParent: true,
|
||||
onSelect: () {},
|
||||
isSide: false,
|
||||
),
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return Expanded(
|
||||
child: SpaceTreeView(
|
||||
title: const Text('Communities'),
|
||||
shouldDisableDeselectingChildrenOfSelectedParent: true,
|
||||
onSelect: () {
|
||||
/// Necessary to wait for the state to update before fethcing the data.
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
() {
|
||||
if (context.mounted) {
|
||||
FetchEnergyManagementDataHelper.fetchEnergyManagementData(
|
||||
context,
|
||||
);
|
||||
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
|
||||
context,
|
||||
);
|
||||
context.read<PowerClampInfoBloc>().add(
|
||||
const ClearPowerClampInfoEvent(),
|
||||
);
|
||||
final (selectedCommunities, selectedSpaces) =
|
||||
FetchEnergyManagementDataHelper
|
||||
.getSelectedCommunitiesAndSpaces(context);
|
||||
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
|
||||
context.read<PowerClampInfoBloc>().add(
|
||||
const ClearPowerClampInfoEvent(),
|
||||
);
|
||||
} else {
|
||||
FetchEnergyManagementDataHelper.loadPowerClampInfo(
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
isSide: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,57 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/month_picker_widget.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class AnalyticsDateFilterButton extends StatelessWidget {
|
||||
class AnalyticsDateFilterButton extends StatefulWidget {
|
||||
const AnalyticsDateFilterButton({super.key});
|
||||
|
||||
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
||||
|
||||
@override
|
||||
State<AnalyticsDateFilterButton> createState() =>
|
||||
_AnalyticsDateFilterButtonState();
|
||||
}
|
||||
|
||||
class _AnalyticsDateFilterButtonState extends State<AnalyticsDateFilterButton> {
|
||||
late final AnalyticsDatePickerBloc _analyticsDatePickerBloc;
|
||||
@override
|
||||
void initState() {
|
||||
_analyticsDatePickerBloc = AnalyticsDatePickerBloc();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_analyticsDatePickerBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@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,
|
||||
return BlocProvider.value(
|
||||
value: _analyticsDatePickerBloc,
|
||||
child: Builder(builder: (context) {
|
||||
final selectedDate = context.watch<AnalyticsDatePickerBloc>().state;
|
||||
return TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AnalyticsDateFilterButton._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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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: () {},
|
||||
icon: SvgPicture.asset(
|
||||
Assets.blankCalendar,
|
||||
height: 20,
|
||||
width: 20,
|
||||
colorFilter:
|
||||
ColorFilter.mode(AnalyticsDateFilterButton._color, BlendMode.srcIn),
|
||||
),
|
||||
label: Text(
|
||||
_formatDate(selectedDate),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => MonthPickerWidget(
|
||||
selectedDate: selectedDate,
|
||||
onDateSelected: (value) {
|
||||
_analyticsDatePickerBloc.add(
|
||||
UpdateAnalyticsDatePickerEvent(value),
|
||||
);
|
||||
FetchEnergyManagementDataHelper.fetchEnergyManagementData(
|
||||
context,
|
||||
selectedDate: value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
final formatter = DateFormat('MMM yyyy');
|
||||
final formattedDate = formatter.format(date);
|
||||
String _formatDate(DateTime? date) {
|
||||
final formatter = DateFormat('MMMM yyyy');
|
||||
final formattedDate = formatter.format(date ?? DateTime.now());
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
String _concatenateDate(DateTime startDate, DateTime endDate) {
|
||||
final formattedStartDate = _formatDate(startDate);
|
||||
final formattedEndDate = _formatDate(endDate);
|
||||
return '$formattedStartDate - $formattedEndDate';
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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/blocs/analytics_tab/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';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
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/blocs/analytics_tab/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';
|
||||
@ -19,7 +19,6 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: MediaQuery.sizeOf(context).width * 1,
|
||||
decoration: subSectionContainerDecoration,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Row(
|
||||
@ -68,13 +67,10 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
||||
),
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: AnimatedSwitcher(
|
||||
switchInCurve: Curves.easeIn,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: selectedTab.child,
|
||||
),
|
||||
child: AnimatedSwitcher(
|
||||
switchInCurve: Curves.easeIn,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: selectedTab.child,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -0,0 +1,194 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class MonthPickerWidget extends StatefulWidget {
|
||||
const MonthPickerWidget({
|
||||
super.key,
|
||||
required this.selectedDate,
|
||||
required this.onDateSelected,
|
||||
});
|
||||
|
||||
final DateTime selectedDate;
|
||||
final ValueChanged<DateTime>? onDateSelected;
|
||||
|
||||
@override
|
||||
State<MonthPickerWidget> createState() => _MonthPickerWidgetState();
|
||||
}
|
||||
|
||||
class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
||||
late int _currentYear;
|
||||
int? _selectedMonth;
|
||||
|
||||
static const _monthNames = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentYear = widget.selectedDate.year;
|
||||
_selectedMonth = widget.selectedDate.month - 1;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
child: Container(
|
||||
padding: const EdgeInsetsDirectional.all(20),
|
||||
width: 320,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildYearSelector(),
|
||||
_buildMonthsGrid(),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: FilledButton.styleFrom(
|
||||
fixedSize: const Size(106, 40),
|
||||
backgroundColor: const Color(0xFFEDF2F7),
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 16,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorsManager.grey700,
|
||||
),
|
||||
),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
final date = DateTime(
|
||||
_currentYear,
|
||||
_selectedMonth! + 1,
|
||||
);
|
||||
widget.onDateSelected?.call(date);
|
||||
},
|
||||
style: FilledButton.styleFrom(
|
||||
fixedSize: const Size(106, 40),
|
||||
backgroundColor: ColorsManager.vividBlue.withValues(
|
||||
alpha: 0.7,
|
||||
),
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 16,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Done',
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorsManager.whiteColors,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Row _buildYearSelector() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'$_currentYear',
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorsManager.grey700,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () => setState(() => _currentYear = _currentYear - 1),
|
||||
icon: const Icon(
|
||||
Icons.chevron_left,
|
||||
color: ColorsManager.grey700,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => setState(() => _currentYear = _currentYear + 1),
|
||||
icon: const Icon(
|
||||
Icons.chevron_right,
|
||||
color: ColorsManager.grey700,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMonthsGrid() {
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: 12,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(8),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
childAspectRatio: 2.5,
|
||||
mainAxisSpacing: 8,
|
||||
mainAxisExtent: 30,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final isSelected = _selectedMonth == index;
|
||||
return InkWell(
|
||||
onTap: () => setState(() => _selectedMonth = index),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
||||
: const Color(0xFFEDF2F7),
|
||||
borderRadius:
|
||||
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
|
||||
),
|
||||
child: Text(
|
||||
_monthNames[index],
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontSize: 12,
|
||||
color: isSelected
|
||||
? ColorsManager.whiteColors
|
||||
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
|
||||
|
||||
part 'energy_consumption_by_phases_event.dart';
|
||||
part 'energy_consumption_by_phases_state.dart';
|
||||
|
||||
class EnergyConsumptionByPhasesBloc
|
||||
extends Bloc<EnergyConsumptionByPhasesEvent, EnergyConsumptionByPhasesState> {
|
||||
EnergyConsumptionByPhasesBloc(
|
||||
this._energyConsumptionByPhasesService,
|
||||
) : super(const EnergyConsumptionByPhasesState()) {
|
||||
on<LoadEnergyConsumptionByPhasesEvent>(_onLoadEnergyConsumptionByPhasesEvent);
|
||||
on<ClearEnergyConsumptionByPhasesEvent>(_onClearEnergyConsumptionByPhasesEvent);
|
||||
}
|
||||
|
||||
final EnergyConsumptionByPhasesService _energyConsumptionByPhasesService;
|
||||
|
||||
Future<void> _onLoadEnergyConsumptionByPhasesEvent(
|
||||
LoadEnergyConsumptionByPhasesEvent event,
|
||||
Emitter<EnergyConsumptionByPhasesState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: EnergyConsumptionByPhasesStatus.loading));
|
||||
try {
|
||||
final chartData = await _energyConsumptionByPhasesService.load(event.param);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: EnergyConsumptionByPhasesStatus.loaded,
|
||||
chartData: chartData,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: EnergyConsumptionByPhasesStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onClearEnergyConsumptionByPhasesEvent(
|
||||
ClearEnergyConsumptionByPhasesEvent event,
|
||||
Emitter<EnergyConsumptionByPhasesState> emit,
|
||||
) async {
|
||||
emit(const EnergyConsumptionByPhasesState());
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
part of 'energy_consumption_by_phases_bloc.dart';
|
||||
|
||||
sealed class EnergyConsumptionByPhasesEvent extends Equatable {
|
||||
const EnergyConsumptionByPhasesEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadEnergyConsumptionByPhasesEvent extends EnergyConsumptionByPhasesEvent {
|
||||
const LoadEnergyConsumptionByPhasesEvent({
|
||||
required this.param,
|
||||
});
|
||||
|
||||
final GetEnergyConsumptionByPhasesParam param;
|
||||
|
||||
@override
|
||||
List<Object> get props => [param];
|
||||
}
|
||||
|
||||
final class ClearEnergyConsumptionByPhasesEvent extends EnergyConsumptionByPhasesEvent {
|
||||
const ClearEnergyConsumptionByPhasesEvent();
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
part of 'energy_consumption_by_phases_bloc.dart';
|
||||
|
||||
enum EnergyConsumptionByPhasesStatus {
|
||||
initial,
|
||||
loading,
|
||||
loaded,
|
||||
failure,
|
||||
}
|
||||
|
||||
final class EnergyConsumptionByPhasesState extends Equatable {
|
||||
const EnergyConsumptionByPhasesState({
|
||||
this.status = EnergyConsumptionByPhasesStatus.initial,
|
||||
this.chartData = const <PhasesEnergyConsumption>[],
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
final List<PhasesEnergyConsumption> chartData;
|
||||
final EnergyConsumptionByPhasesStatus status;
|
||||
final String? errorMessage;
|
||||
|
||||
EnergyConsumptionByPhasesState copyWith({
|
||||
List<PhasesEnergyConsumption>? chartData,
|
||||
EnergyConsumptionByPhasesStatus? status,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return EnergyConsumptionByPhasesState(
|
||||
chartData: chartData ?? this.chartData,
|
||||
status: status ?? this.status,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [chartData, status, errorMessage];
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
|
||||
|
||||
part 'energy_consumption_per_device_event.dart';
|
||||
part 'energy_consumption_per_device_state.dart';
|
||||
|
||||
class EnergyConsumptionPerDeviceBloc
|
||||
extends Bloc<EnergyConsumptionPerDeviceEvent, EnergyConsumptionPerDeviceState> {
|
||||
EnergyConsumptionPerDeviceBloc(
|
||||
this._energyConsumptionPerDeviceService,
|
||||
) : super(const EnergyConsumptionPerDeviceState()) {
|
||||
on<LoadEnergyConsumptionPerDeviceEvent>(_onLoadEnergyConsumptionPerDeviceEvent);
|
||||
on<ClearEnergyConsumptionPerDeviceEvent>(_onClearEnergyConsumptionPerDeviceEvent);
|
||||
}
|
||||
|
||||
final EnergyConsumptionPerDeviceService _energyConsumptionPerDeviceService;
|
||||
|
||||
Future<void> _onLoadEnergyConsumptionPerDeviceEvent(
|
||||
LoadEnergyConsumptionPerDeviceEvent event,
|
||||
Emitter<EnergyConsumptionPerDeviceState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: EnergyConsumptionPerDeviceStatus.loading));
|
||||
try {
|
||||
final chartData = await _energyConsumptionPerDeviceService.load(event.param);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: EnergyConsumptionPerDeviceStatus.loaded,
|
||||
chartData: chartData,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: EnergyConsumptionPerDeviceStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onClearEnergyConsumptionPerDeviceEvent(
|
||||
ClearEnergyConsumptionPerDeviceEvent event,
|
||||
Emitter<EnergyConsumptionPerDeviceState> emit,
|
||||
) async {
|
||||
emit(const EnergyConsumptionPerDeviceState());
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
part of 'energy_consumption_per_device_bloc.dart';
|
||||
|
||||
sealed class EnergyConsumptionPerDeviceEvent extends Equatable {
|
||||
const EnergyConsumptionPerDeviceEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class LoadEnergyConsumptionPerDeviceEvent
|
||||
extends EnergyConsumptionPerDeviceEvent {
|
||||
const LoadEnergyConsumptionPerDeviceEvent(this.param);
|
||||
|
||||
final GetEnergyConsumptionPerDeviceParam param;
|
||||
|
||||
@override
|
||||
List<Object> get props => [param];
|
||||
}
|
||||
|
||||
final class ClearEnergyConsumptionPerDeviceEvent
|
||||
extends EnergyConsumptionPerDeviceEvent {
|
||||
const ClearEnergyConsumptionPerDeviceEvent();
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
part of 'energy_consumption_per_device_bloc.dart';
|
||||
|
||||
enum EnergyConsumptionPerDeviceStatus { initial, loading, loaded, failure }
|
||||
|
||||
final class EnergyConsumptionPerDeviceState extends Equatable {
|
||||
const EnergyConsumptionPerDeviceState({
|
||||
this.status = EnergyConsumptionPerDeviceStatus.initial,
|
||||
this.chartData = const <DeviceEnergyDataModel>[],
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
final List<DeviceEnergyDataModel> chartData;
|
||||
final EnergyConsumptionPerDeviceStatus status;
|
||||
final String? errorMessage;
|
||||
|
||||
EnergyConsumptionPerDeviceState copyWith({
|
||||
List<DeviceEnergyDataModel>? chartData,
|
||||
EnergyConsumptionPerDeviceStatus? status,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return EnergyConsumptionPerDeviceState(
|
||||
chartData: chartData ?? this.chartData,
|
||||
status: status ?? this.status,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [chartData, status, errorMessage];
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||
|
||||
part 'power_clamp_info_event.dart';
|
||||
part 'power_clamp_info_state.dart';
|
||||
|
||||
class PowerClampInfoBloc extends Bloc<PowerClampInfoEvent, PowerClampInfoState> {
|
||||
PowerClampInfoBloc(
|
||||
this._powerClampInfoService,
|
||||
) : super(const PowerClampInfoState()) {
|
||||
on<LoadPowerClampInfoEvent>(_onLoadPowerClampInfoEvent);
|
||||
on<UpdatePowerClampStatusEvent>(_onUpdatePowerClampStatusEvent);
|
||||
on<ClearPowerClampInfoEvent>(_onClearPowerClampInfoEvent);
|
||||
}
|
||||
|
||||
final PowerClampInfoService _powerClampInfoService;
|
||||
|
||||
Future<void> _onLoadPowerClampInfoEvent(
|
||||
LoadPowerClampInfoEvent event,
|
||||
Emitter<PowerClampInfoState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: PowerClampInfoStatus.loading));
|
||||
try {
|
||||
final powerClampModel = await _powerClampInfoService.getInfo(event.deviceId);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: PowerClampInfoStatus.loaded,
|
||||
powerClampModel: powerClampModel,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: PowerClampInfoStatus.error,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onUpdatePowerClampStatusEvent(
|
||||
UpdatePowerClampStatusEvent event,
|
||||
Emitter<PowerClampInfoState> emit,
|
||||
) async {
|
||||
final currentModel = state.powerClampModel;
|
||||
if (currentModel == null) return;
|
||||
|
||||
final updatedStatus = PowerStatus.fromStatusList(event.statusList);
|
||||
final updatedModel = currentModel.copyWith(statusPower: updatedStatus);
|
||||
|
||||
emit(state.copyWith(powerClampModel: updatedModel));
|
||||
}
|
||||
|
||||
void _onClearPowerClampInfoEvent(
|
||||
ClearPowerClampInfoEvent event,
|
||||
Emitter<PowerClampInfoState> emit,
|
||||
) {
|
||||
emit(const PowerClampInfoState());
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
part of 'power_clamp_info_bloc.dart';
|
||||
|
||||
sealed class PowerClampInfoEvent extends Equatable {
|
||||
const PowerClampInfoEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class LoadPowerClampInfoEvent extends PowerClampInfoEvent {
|
||||
const LoadPowerClampInfoEvent(this.deviceId);
|
||||
|
||||
final String deviceId;
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
|
||||
final class UpdatePowerClampStatusEvent extends PowerClampInfoEvent {
|
||||
const UpdatePowerClampStatusEvent(this.statusList);
|
||||
|
||||
final List<Status> statusList;
|
||||
|
||||
@override
|
||||
List<Object> get props => [statusList];
|
||||
}
|
||||
|
||||
final class ClearPowerClampInfoEvent extends PowerClampInfoEvent {
|
||||
const ClearPowerClampInfoEvent();
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
part of 'power_clamp_info_bloc.dart';
|
||||
|
||||
enum PowerClampInfoStatus { initial, loading, loaded, error }
|
||||
|
||||
final class PowerClampInfoState extends Equatable {
|
||||
const PowerClampInfoState({
|
||||
this.status = PowerClampInfoStatus.initial,
|
||||
this.powerClampModel,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
final PowerClampInfoStatus status;
|
||||
final PowerClampModel? powerClampModel;
|
||||
final String? errorMessage;
|
||||
|
||||
PowerClampInfoState copyWith({
|
||||
PowerClampInfoStatus? status,
|
||||
PowerClampModel? powerClampModel,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return PowerClampInfoState(
|
||||
status: status ?? this.status,
|
||||
powerClampModel: powerClampModel ?? this.powerClampModel,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, powerClampModel, errorMessage];
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/realtime_device_service.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
part 'realtime_device_changes_event.dart';
|
||||
part 'realtime_device_changes_state.dart';
|
||||
|
||||
class RealtimeDeviceChangesBloc
|
||||
extends Bloc<RealtimeDeviceChangesEvent, RealtimeDeviceChangesState> {
|
||||
RealtimeDeviceChangesBloc(
|
||||
this._realtimeDeviceService,
|
||||
) : super(const RealtimeDeviceChangesState()) {
|
||||
on<RealtimeDeviceChangesStarted>(_onRealtimeDeviceChangesStarted);
|
||||
on<RealtimeDeviceChangesClosed>(_onRealtimeDeviceChangesClosed);
|
||||
on<_RealtimeDeviceChangesUpdated>(_onRealtimeDeviceChangesUpdated);
|
||||
}
|
||||
|
||||
final RealtimeDeviceService _realtimeDeviceService;
|
||||
StreamSubscription<List<Status>>? _subscription;
|
||||
|
||||
Future<void> _onRealtimeDeviceChangesStarted(
|
||||
RealtimeDeviceChangesStarted event,
|
||||
Emitter<RealtimeDeviceChangesState> emit,
|
||||
) async {
|
||||
await _subscription?.cancel();
|
||||
_subscription = _realtimeDeviceService.subscribe(event.deviceId).listen(
|
||||
(data) {
|
||||
add(_RealtimeDeviceChangesUpdated(data));
|
||||
},
|
||||
onError: (error) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: RealtimeDeviceChangesStatus.failure,
|
||||
errorMessage: '$error',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onRealtimeDeviceChangesClosed(
|
||||
RealtimeDeviceChangesClosed event,
|
||||
Emitter<RealtimeDeviceChangesState> emit,
|
||||
) async {
|
||||
add(const _RealtimeDeviceChangesUpdated([]));
|
||||
await _subscription?.cancel();
|
||||
_subscription = null;
|
||||
emit(const RealtimeDeviceChangesState());
|
||||
}
|
||||
|
||||
void _onRealtimeDeviceChangesUpdated(
|
||||
_RealtimeDeviceChangesUpdated event,
|
||||
Emitter<RealtimeDeviceChangesState> emit,
|
||||
) {
|
||||
final currentState = state;
|
||||
final updatedList = [
|
||||
...currentState.deviceStatusList.where(
|
||||
(device) => !event.deviceStatusList
|
||||
.any((newDevice) => newDevice.code == device.code),
|
||||
),
|
||||
...event.deviceStatusList,
|
||||
];
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: RealtimeDeviceChangesStatus.loaded,
|
||||
deviceStatusList: updatedList,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _subscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
part of 'realtime_device_changes_bloc.dart';
|
||||
|
||||
sealed class RealtimeDeviceChangesEvent extends Equatable {
|
||||
const RealtimeDeviceChangesEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class RealtimeDeviceChangesStarted extends RealtimeDeviceChangesEvent {
|
||||
const RealtimeDeviceChangesStarted(this.deviceId);
|
||||
|
||||
final String deviceId;
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
final class RealtimeDeviceChangesClosed extends RealtimeDeviceChangesEvent {
|
||||
const RealtimeDeviceChangesClosed();
|
||||
}
|
||||
|
||||
class _RealtimeDeviceChangesUpdated extends RealtimeDeviceChangesEvent {
|
||||
final List<Status> deviceStatusList;
|
||||
|
||||
const _RealtimeDeviceChangesUpdated(this.deviceStatusList);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
part of 'realtime_device_changes_bloc.dart';
|
||||
|
||||
enum RealtimeDeviceChangesStatus { initial, loaded, failure }
|
||||
|
||||
final class RealtimeDeviceChangesState extends Equatable {
|
||||
const RealtimeDeviceChangesState({
|
||||
this.status = RealtimeDeviceChangesStatus.initial,
|
||||
this.deviceStatusList = const <Status>[],
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
final RealtimeDeviceChangesStatus status;
|
||||
final List<Status> deviceStatusList;
|
||||
final String? errorMessage;
|
||||
|
||||
RealtimeDeviceChangesState copyWith({
|
||||
RealtimeDeviceChangesStatus? status,
|
||||
List<Status>? deviceStatusList,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return RealtimeDeviceChangesState(
|
||||
status: status ?? this.status,
|
||||
deviceStatusList: deviceStatusList ?? this.deviceStatusList,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, deviceStatusList, errorMessage];
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
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);
|
||||
on<ClearTotalEnergyConsumptionEvent>(_onClearTotalEnergyConsumptionEvent);
|
||||
}
|
||||
|
||||
final TotalEnergyConsumptionService _totalEnergyConsumptionService;
|
||||
|
||||
Future<void> _onTotalEnergyConsumptionLoadEvent(
|
||||
TotalEnergyConsumptionLoadEvent event,
|
||||
Emitter<TotalEnergyConsumptionState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(state.copyWith(status: TotalEnergyConsumptionStatus.loading));
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onClearTotalEnergyConsumptionEvent(
|
||||
ClearTotalEnergyConsumptionEvent event,
|
||||
Emitter<TotalEnergyConsumptionState> emit,
|
||||
) async {
|
||||
emit(const TotalEnergyConsumptionState());
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
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];
|
||||
}
|
||||
|
||||
final class ClearTotalEnergyConsumptionEvent extends TotalEnergyConsumptionEvent {
|
||||
const ClearTotalEnergyConsumptionEvent();
|
||||
}
|
@ -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];
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||
|
||||
abstract final class EnergyConsumptionByPhasesChartHelper {
|
||||
const EnergyConsumptionByPhasesChartHelper._();
|
||||
|
||||
static const fakeData = <PhasesEnergyConsumption>[
|
||||
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
|
||||
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
|
||||
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
|
||||
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
|
||||
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
|
||||
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
|
||||
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
|
||||
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
|
||||
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 80, phaseC: 100),
|
||||
];
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
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/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
abstract final class EnergyManagementChartsHelper {
|
||||
const EnergyManagementChartsHelper._();
|
||||
|
||||
static FlTitlesData titlesData(
|
||||
BuildContext context, {
|
||||
double? leftTitlesInterval,
|
||||
}) {
|
||||
const emptyTitle = AxisTitles(sideTitles: SideTitles(showTitles: false));
|
||||
return FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
drawBelowEverything: true,
|
||||
sideTitles: SideTitles(
|
||||
interval: 1,
|
||||
reservedSize: 32,
|
||||
showTitles: true,
|
||||
maxIncluded: true,
|
||||
getTitlesWidget: (value, meta) => Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
||||
child: Text(
|
||||
value.toString(),
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.greyColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
maxIncluded: true,
|
||||
interval: leftTitlesInterval,
|
||||
reservedSize: 110,
|
||||
getTitlesWidget: (value, meta) => Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||
child: FittedBox(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
value.formatNumberToKwh,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
fontSize: 12,
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
rightTitles: emptyTitle,
|
||||
topTitles: emptyTitle,
|
||||
);
|
||||
}
|
||||
|
||||
static String getToolTipLabel(num month, double value) {
|
||||
final monthLabel = month.toString();
|
||||
final valueLabel = value.formatNumberToKwh;
|
||||
final labels = [monthLabel, valueLabel];
|
||||
return labels.where((element) => element.isNotEmpty).join(', ');
|
||||
}
|
||||
|
||||
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
|
||||
return touchedSpots.map((spot) {
|
||||
return LineTooltipItem(
|
||||
getToolTipLabel(spot.x, spot.y),
|
||||
const TextStyle(
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
static LineTouchTooltipData lineTouchTooltipData() {
|
||||
return LineTouchTooltipData(
|
||||
getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors,
|
||||
tooltipBorder: const BorderSide(color: ColorsManager.semiTransparentBlack),
|
||||
tooltipRoundedRadius: 16,
|
||||
showOnTopOfTheChartBoxArea: false,
|
||||
tooltipPadding: const EdgeInsets.all(8),
|
||||
getTooltipItems: getTooltipItems,
|
||||
);
|
||||
}
|
||||
|
||||
static FlBorderData borderData() {
|
||||
return FlBorderData(
|
||||
show: true,
|
||||
border: const Border.symmetric(
|
||||
horizontal: BorderSide(
|
||||
color: ColorsManager.greyColor,
|
||||
style: BorderStyle.solid,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static FlGridData gridData() {
|
||||
return const FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
drawHorizontalLine: true,
|
||||
);
|
||||
}
|
||||
|
||||
static LineTouchData lineTouchData() {
|
||||
return LineTouchData(
|
||||
handleBuiltInTouches: true,
|
||||
touchSpotThreshold: 2,
|
||||
touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_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/params/get_energy_consumption_by_phases_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
|
||||
abstract final class FetchEnergyManagementDataHelper {
|
||||
const FetchEnergyManagementDataHelper._();
|
||||
|
||||
static void fetchEnergyManagementData(
|
||||
BuildContext context, {
|
||||
DateTime? selectedDate,
|
||||
}) {
|
||||
final (selectedCommunities, selectedSpaces) =
|
||||
getSelectedCommunitiesAndSpaces(context);
|
||||
|
||||
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
|
||||
clearAllData(context);
|
||||
return;
|
||||
}
|
||||
|
||||
loadTotalEnergyConsumption(context);
|
||||
loadEnergyConsumptionByPhases(context);
|
||||
loadEnergyConsumptionPerDevice(context);
|
||||
return;
|
||||
}
|
||||
|
||||
static (List<String> selectedCommunities, List<String> selectedSpaces)
|
||||
getSelectedCommunitiesAndSpaces(BuildContext context) {
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
final selectedCommunities = spaceTreeState.selectedCommunities;
|
||||
final selectedSpaces = spaceTreeState.selectedSpaces;
|
||||
|
||||
return (selectedCommunities, selectedSpaces);
|
||||
}
|
||||
|
||||
static void loadEnergyConsumptionByPhases(
|
||||
BuildContext context, {
|
||||
DateTime? selectedDate,
|
||||
}) {
|
||||
final param = GetEnergyConsumptionByPhasesParam(
|
||||
startDate: selectedDate,
|
||||
spaceId: '',
|
||||
);
|
||||
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||
LoadEnergyConsumptionByPhasesEvent(param: param),
|
||||
);
|
||||
}
|
||||
|
||||
static void loadTotalEnergyConsumption(
|
||||
BuildContext context, {
|
||||
DateTime? selectedDate,
|
||||
}) {
|
||||
final (selectedCommunities, selectedSpaces) =
|
||||
getSelectedCommunitiesAndSpaces(context);
|
||||
|
||||
final param = GetTotalEnergyConsumptionParam(
|
||||
spaceId: selectedCommunities.firstOrNull,
|
||||
startDate: selectedDate,
|
||||
);
|
||||
context.read<TotalEnergyConsumptionBloc>().add(
|
||||
TotalEnergyConsumptionLoadEvent(param: param),
|
||||
);
|
||||
}
|
||||
|
||||
static void loadEnergyConsumptionPerDevice(BuildContext context) {
|
||||
const param = GetEnergyConsumptionPerDeviceParam();
|
||||
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||
const LoadEnergyConsumptionPerDeviceEvent(param),
|
||||
);
|
||||
}
|
||||
|
||||
static void loadPowerClampInfo(BuildContext context) {
|
||||
context.read<PowerClampInfoBloc>().add(
|
||||
const LoadPowerClampInfoEvent('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
|
||||
);
|
||||
}
|
||||
|
||||
static void loadRealtimeDeviceChanges(BuildContext context) {
|
||||
context.read<RealtimeDeviceChangesBloc>().add(
|
||||
const RealtimeDeviceChangesStarted('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
|
||||
);
|
||||
}
|
||||
|
||||
static void clearAllData(BuildContext context) {
|
||||
context.read<RealtimeDeviceChangesBloc>().add(
|
||||
const RealtimeDeviceChangesClosed(),
|
||||
);
|
||||
|
||||
context.read<PowerClampInfoBloc>().add(
|
||||
const ClearPowerClampInfoEvent(),
|
||||
);
|
||||
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||
const ClearEnergyConsumptionPerDeviceEvent(),
|
||||
);
|
||||
|
||||
context.read<TotalEnergyConsumptionBloc>().add(
|
||||
const ClearTotalEnergyConsumptionEvent(),
|
||||
);
|
||||
|
||||
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||
const ClearEnergyConsumptionByPhasesEvent(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,12 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.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';
|
||||
|
||||
class AnalyticsEnergyManagementView extends StatelessWidget {
|
||||
const AnalyticsEnergyManagementView({super.key});
|
||||
|
||||
static const _padding = EdgeInsetsDirectional.all(32);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Center(
|
||||
child: Text('EnergyManagementView is Working!'),
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isMediumOrLess = constraints.maxWidth <= 900;
|
||||
if (isMediumOrLess) {
|
||||
return SingleChildScrollView(
|
||||
padding: _padding,
|
||||
child: Column(
|
||||
spacing: 32,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.sizeOf(context).height * 1.2,
|
||||
child: const PowerClampEnergyDataWidget(),
|
||||
),
|
||||
SizedBox(
|
||||
height: MediaQuery.sizeOf(context).height * 0.5,
|
||||
child: const TotalEnergyConsumptionChartBox(),
|
||||
),
|
||||
SizedBox(
|
||||
height: MediaQuery.sizeOf(context).height * 0.5,
|
||||
child: const EnergyConsumptionPerDeviceChartBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Container(
|
||||
padding: _padding,
|
||||
height: MediaQuery.sizeOf(context).height * 1,
|
||||
child: const Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
spacing: 32,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Column(
|
||||
spacing: 20,
|
||||
children: [
|
||||
Expanded(child: TotalEnergyConsumptionChartBox()),
|
||||
Expanded(child: EnergyConsumptionPerDeviceChartBox()),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(child: PowerClampEnergyDataWidget()),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class ChartTitle extends StatelessWidget {
|
||||
const ChartTitle({super.key, required this.title});
|
||||
|
||||
final Widget title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTextStyle(
|
||||
style: context.textTheme.titleLarge!.copyWith(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
child: title,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/helpers/get_month_name_from_int.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
||||
const EnergyConsumptionByPhasesChart({
|
||||
super.key,
|
||||
required this.energyData,
|
||||
});
|
||||
|
||||
final List<PhasesEnergyConsumption> energyData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
gridData: EnergyManagementChartsHelper.gridData(),
|
||||
borderData: EnergyManagementChartsHelper.borderData(),
|
||||
barTouchData: _barTouchData(context),
|
||||
titlesData: _titlesData(context),
|
||||
barGroups: energyData.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final data = entry.value;
|
||||
|
||||
return BarChartGroupData(
|
||||
x: index,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
color: ColorsManager.vividBlue.withValues(alpha: 0.1),
|
||||
toY: data.phaseA + data.phaseB + data.phaseC,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(
|
||||
0,
|
||||
data.phaseA,
|
||||
ColorsManager.vividBlue.withValues(alpha: 0.8),
|
||||
),
|
||||
BarChartRodStackItem(
|
||||
data.phaseA,
|
||||
data.phaseA + data.phaseB,
|
||||
ColorsManager.vividBlue.withValues(alpha: 0.4),
|
||||
),
|
||||
BarChartRodStackItem(
|
||||
data.phaseA + data.phaseB,
|
||||
data.phaseA + data.phaseB + data.phaseC,
|
||||
ColorsManager.vividBlue.withValues(alpha: 0.15),
|
||||
),
|
||||
],
|
||||
width: 16,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BarTouchData _barTouchData(BuildContext context) {
|
||||
return BarTouchData(
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors,
|
||||
tooltipBorder: const BorderSide(
|
||||
color: ColorsManager.semiTransparentBlack,
|
||||
),
|
||||
tooltipRoundedRadius: 16,
|
||||
tooltipPadding: const EdgeInsets.all(8),
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) => getTooltipItem(
|
||||
context: context,
|
||||
group: group,
|
||||
groupIndex: groupIndex,
|
||||
rod: rod,
|
||||
rodIndex: rodIndex,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BarTooltipItem? getTooltipItem({
|
||||
required BuildContext context,
|
||||
required BarChartGroupData group,
|
||||
required int groupIndex,
|
||||
required BarChartRodData rod,
|
||||
required int rodIndex,
|
||||
}) {
|
||||
final data = energyData;
|
||||
|
||||
final month = data[group.x.toInt()].month.getMonthName;
|
||||
final phaseA = data[group.x.toInt()].phaseA;
|
||||
final phaseB = data[group.x.toInt()].phaseB;
|
||||
final phaseC = data[group.x.toInt()].phaseC;
|
||||
|
||||
return BarTooltipItem(
|
||||
'$month\n',
|
||||
context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Phase A: $phaseA\n',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: 'Phase B: $phaseB\n',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: 'Phase C: $phaseC',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
FlTitlesData _titlesData(BuildContext context) {
|
||||
final titlesData = EnergyManagementChartsHelper.titlesData(
|
||||
context,
|
||||
leftTitlesInterval: 250,
|
||||
);
|
||||
|
||||
final leftTitles = titlesData.leftTitles.copyWith(
|
||||
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||
reservedSize: 70,
|
||||
),
|
||||
);
|
||||
|
||||
final bottomTitles = AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: (value, _) {
|
||||
final month = energyData[value.toInt()].month.getMonthName;
|
||||
return FittedBox(
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: RotatedBox(
|
||||
quarterTurns: 3,
|
||||
child: Text(
|
||||
month,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.greyColor,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
reservedSize: 36,
|
||||
),
|
||||
);
|
||||
|
||||
return titlesData.copyWith(
|
||||
leftTitles: leftTitles,
|
||||
bottomTitles: bottomTitles,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.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_by_phases_title.dart';
|
||||
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class EnergyConsumptionByPhasesChartBox extends StatelessWidget {
|
||||
const EnergyConsumptionByPhasesChartBox({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EnergyConsumptionByPhasesBloc,
|
||||
EnergyConsumptionByPhasesState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
padding: const EdgeInsetsDirectional.all(20),
|
||||
decoration: secondarySection,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 20,
|
||||
children: [
|
||||
AnalyticsErrorWidget(state.errorMessage),
|
||||
EnergyConsumptionByPhasesTitle(isLoading: state.status == EnergyConsumptionByPhasesStatus.loading,),
|
||||
Expanded(
|
||||
child: EnergyConsumptionByPhasesChart(
|
||||
energyData: state.chartData,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class EnergyConsumptionByPhasesTitle extends StatelessWidget {
|
||||
const EnergyConsumptionByPhasesTitle({super.key, required this.isLoading});
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ChartsLoadingWidget(isLoading: isLoading),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: FittedBox(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: ChartTitle(
|
||||
title: Text(
|
||||
'Energy Consumption by Phases',
|
||||
style: context.textTheme.titleLarge?.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
...<(String title, double opacity)>[
|
||||
('A', 0.8),
|
||||
('B', 0.4),
|
||||
('C', 0.15),
|
||||
].map((phase) => _buildPhaseCell(context, phase)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPhaseCell(
|
||||
BuildContext context,
|
||||
(String title, double colorOpacity) phase,
|
||||
) {
|
||||
final (title, colorOpacity) = phase;
|
||||
|
||||
return Expanded(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 4,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: ColorsManager.vividBlue.withValues(
|
||||
alpha: colorOpacity,
|
||||
),
|
||||
radius: 4,
|
||||
),
|
||||
Text(
|
||||
'Phase $title',
|
||||
style: context.textTheme.labelSmall?.copyWith(
|
||||
fontSize: 8,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.lightGreyColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||
|
||||
class EnergyConsumptionPerDeviceChart extends StatelessWidget {
|
||||
const EnergyConsumptionPerDeviceChart({super.key, required this.chartData});
|
||||
|
||||
final List<DeviceEnergyDataModel> chartData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LineChart(
|
||||
LineChartData(
|
||||
titlesData: EnergyManagementChartsHelper.titlesData(
|
||||
context,
|
||||
leftTitlesInterval: 250,
|
||||
),
|
||||
gridData: EnergyManagementChartsHelper.gridData(),
|
||||
borderData: EnergyManagementChartsHelper.borderData(),
|
||||
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||
lineBarsData: chartData.map((e) {
|
||||
return _buildChartBar(
|
||||
color: e.color,
|
||||
spots: e.energy
|
||||
.map(
|
||||
(energy) => FlSpot(
|
||||
energy.date.day.toDouble(),
|
||||
energy.value,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
duration: Durations.extralong1,
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
}
|
||||
|
||||
LineChartBarData _buildChartBar({
|
||||
required Color color,
|
||||
required List<FlSpot> spots,
|
||||
}) {
|
||||
return LineChartBarData(
|
||||
spots: spots,
|
||||
dashArray: [12, 18],
|
||||
isCurved: true,
|
||||
color: color,
|
||||
barWidth: 3,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(show: false),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.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/energy_consumption_per_device_devices_list.dart';
|
||||
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
||||
const EnergyConsumptionPerDeviceChartBox({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EnergyConsumptionPerDeviceBloc,
|
||||
EnergyConsumptionPerDeviceState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
decoration: subSectionContainerDecoration.copyWith(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
padding: const EdgeInsets.all(30),
|
||||
child: Column(
|
||||
spacing: 20,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AnalyticsErrorWidget(state.errorMessage),
|
||||
Row(
|
||||
spacing: 32,
|
||||
children: [
|
||||
if (state.status == EnergyConsumptionPerDeviceStatus.loading)
|
||||
const ChartsLoadingWidget(isLoading: true),
|
||||
const Expanded(
|
||||
flex: 3,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: ChartTitle(
|
||||
title: Text('Energy Consumption per Device'),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: EnergyConsumptionPerDeviceDevicesList(
|
||||
chartData: state.chartData,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(height: 0),
|
||||
Expanded(
|
||||
child: EnergyConsumptionPerDeviceChart(chartData: state.chartData),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
||||
const EnergyConsumptionPerDeviceDevicesList({required this.chartData, super.key});
|
||||
|
||||
final List<DeviceEnergyDataModel> chartData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
spacing: 16,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: chartData.map((e) => _buildDeviceCell(context, e)).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDeviceCell(BuildContext context, DeviceEnergyDataModel device) {
|
||||
return Container(
|
||||
height: MediaQuery.sizeOf(context).height * 0.0365,
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadiusDirectional.circular(8),
|
||||
border: Border.all(
|
||||
color: ColorsManager.greyColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 4,
|
||||
backgroundColor: device.color,
|
||||
),
|
||||
Text(
|
||||
device.deviceName,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class PowerClampEnergyDataDeviceDropdown extends StatelessWidget {
|
||||
const PowerClampEnergyDataDeviceDropdown({super.key});
|
||||
|
||||
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
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,
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Device 1',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
onPressed: () {},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart';
|
||||
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class PowerClampEnergyDataWidget extends StatelessWidget {
|
||||
const PowerClampEnergyDataWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<RealtimeDeviceChangesBloc, RealtimeDeviceChangesState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.deviceStatusList != current.deviceStatusList ||
|
||||
previous.status != current.status,
|
||||
listener: (context, state) => context.read<PowerClampInfoBloc>().add(
|
||||
UpdatePowerClampStatusEvent(state.deviceStatusList),
|
||||
),
|
||||
child: BlocBuilder<PowerClampInfoBloc, PowerClampInfoState>(
|
||||
builder: (context, state) {
|
||||
final generalDataPoints =
|
||||
state.powerClampModel?.status.general.dataPoints ?? [];
|
||||
return Container(
|
||||
decoration: subSectionContainerDecoration.copyWith(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
padding: const EdgeInsetsDirectional.all(32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AnalyticsErrorWidget(state.errorMessage),
|
||||
_buildHeader(context),
|
||||
Text(
|
||||
'Device ID:',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
SelectableText(
|
||||
state.powerClampModel?.productUuid ?? 'N/A',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: PowerClampEnergyStatusWidget(
|
||||
status: [
|
||||
PowerClampEnergyStatus(
|
||||
iconPath: Assets.powerActiveIcon,
|
||||
title: 'Active',
|
||||
value: _valueFromCode('EnergyConsumed', generalDataPoints),
|
||||
unit: 'W',
|
||||
),
|
||||
PowerClampEnergyStatus(
|
||||
iconPath: Assets.voltMeterIcon,
|
||||
title: 'Current',
|
||||
value: _valueFromCode('Current', generalDataPoints),
|
||||
unit: 'A',
|
||||
),
|
||||
PowerClampEnergyStatus(
|
||||
iconPath: Assets.frequencyIcon,
|
||||
title: 'Frequency',
|
||||
value: _valueFromCode('Frequency', generalDataPoints),
|
||||
unit: 'Hz',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: PowerClampPhasesDataWidget(
|
||||
phaseA: state.powerClampModel?.status.phaseA,
|
||||
phaseB: state.powerClampModel?.status.phaseB,
|
||||
phaseC: state.powerClampModel?.status.phaseC,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const Expanded(flex: 3, child: EnergyConsumptionByPhasesChartBox()),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: SelectableText(
|
||||
'Smart Power Clamp',
|
||||
style: context.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.vividBlue.withValues(alpha: 0.6),
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const Expanded(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
child: PowerClampEnergyDataDeviceDropdown(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _valueFromCode(String code, List<DataPoint> points) {
|
||||
return points
|
||||
.firstWhere((e) => e.code == code, orElse: () => DataPoint(value: '--'))
|
||||
.value
|
||||
.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class PowerClampEnergyStatusWidget extends StatelessWidget {
|
||||
const PowerClampEnergyStatusWidget({
|
||||
super.key,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
final List<PowerClampEnergyStatus> status;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: secondarySection.copyWith(boxShadow: const []),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: List.generate(
|
||||
status.length * 2 - 1,
|
||||
(index) => index.isEven
|
||||
? Expanded(child: _buildItem(context, status[index ~/ 2]))
|
||||
: _buildDivider(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(BuildContext context, PowerClampEnergyStatus item) {
|
||||
return Center(
|
||||
child: ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: SvgPicture.asset(
|
||||
item.iconPath,
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
height: 18,
|
||||
width: 18,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
title: Text(
|
||||
item.title,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.textPrimaryColor.withValues(alpha: 0.83),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
trailing: Text.rich(
|
||||
TextSpan(
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.textPrimaryColor.withValues(alpha: 0.83),
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 16,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: '${item.value} '),
|
||||
TextSpan(
|
||||
text: item.unit,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.textPrimaryColor.withValues(alpha: 0.83),
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDivider() {
|
||||
return Container(
|
||||
height: 1,
|
||||
decoration: const BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Color.fromARGB(20, 0, 0, 0),
|
||||
offset: Offset(0, 1),
|
||||
blurRadius: 1,
|
||||
),
|
||||
BoxShadow(
|
||||
color: Color.fromARGB(30, 0, 0, 0),
|
||||
offset: Offset(0, -2),
|
||||
blurRadius: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class PowerClampPhase extends StatelessWidget {
|
||||
const PowerClampPhase({
|
||||
super.key,
|
||||
required this.iconPath,
|
||||
required this.title,
|
||||
required this.value,
|
||||
this.unit,
|
||||
});
|
||||
|
||||
final String iconPath;
|
||||
final String title;
|
||||
final String value;
|
||||
final String? unit;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 4),
|
||||
decoration: containerWhiteDecoration.copyWith(boxShadow: const []),
|
||||
padding: const EdgeInsetsDirectional.all(8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 10,
|
||||
children: [
|
||||
_buildIcon(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 4,
|
||||
children: [
|
||||
_buildTitle(context),
|
||||
_buildValue(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildValue(BuildContext context) {
|
||||
final textStyle = context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.textPrimaryColor.withValues(
|
||||
alpha: 0.83,
|
||||
),
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 15,
|
||||
);
|
||||
|
||||
return Expanded(
|
||||
flex: 2,
|
||||
child: FittedBox(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
style: textStyle,
|
||||
children: [
|
||||
TextSpan(text: '$value '),
|
||||
if (unit != null)
|
||||
TextSpan(
|
||||
text: unit,
|
||||
style: textStyle?.copyWith(
|
||||
color: ColorsManager.textPrimaryColor.withValues(
|
||||
alpha: 0.83,
|
||||
),
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTitle(BuildContext context) {
|
||||
return Expanded(
|
||||
flex: 2,
|
||||
child: FittedBox(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
title,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: ColorsManager.lightGreyColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIcon() {
|
||||
return Expanded(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: SvgPicture.asset(iconPath),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phase.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class PowerClampPhasesDataWidget extends StatelessWidget {
|
||||
const PowerClampPhasesDataWidget({
|
||||
required this.phaseA,
|
||||
required this.phaseB,
|
||||
required this.phaseC,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Phase? phaseA;
|
||||
final Phase? phaseB;
|
||||
final Phase? phaseC;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final phases = [phaseA, phaseB, phaseC];
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: secondarySection.copyWith(boxShadow: const []),
|
||||
child: Row(
|
||||
children: List.generate(5, (index) {
|
||||
if (index.isOdd) return _buildSeparator();
|
||||
final phaseIndex = index ~/ 2;
|
||||
final phase = phases[phaseIndex];
|
||||
final phaseSuffix = ['A', 'B', 'C'][phaseIndex];
|
||||
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 14),
|
||||
child: Column(
|
||||
spacing: 4,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: AlignmentDirectional.center,
|
||||
child: Text(
|
||||
'Phase ${phaseIndex + 1}',
|
||||
style: context.textTheme.titleLarge?.copyWith(
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
PowerClampPhase(
|
||||
iconPath: Assets.powerActiveIcon,
|
||||
title: 'Active Power',
|
||||
value: _valueFromCode(
|
||||
code: 'ReactivePower$phaseSuffix',
|
||||
points: phase?.dataPoints,
|
||||
),
|
||||
unit: 'W',
|
||||
),
|
||||
PowerClampPhase(
|
||||
iconPath: Assets.voltageIcon,
|
||||
title: 'Voltage',
|
||||
value: _valueFromCode(
|
||||
code: 'Voltage$phaseSuffix',
|
||||
points: phase?.dataPoints,
|
||||
),
|
||||
unit: 'V',
|
||||
),
|
||||
PowerClampPhase(
|
||||
iconPath: Assets.voltMeterIcon,
|
||||
title: 'Current',
|
||||
value: _valueFromCode(
|
||||
code: 'Current$phaseSuffix',
|
||||
points: phase?.dataPoints,
|
||||
),
|
||||
unit: 'A',
|
||||
),
|
||||
PowerClampPhase(
|
||||
iconPath: Assets.speedoMeter,
|
||||
title: 'Power Factor',
|
||||
value: _valueFromCode(
|
||||
code: 'PowerFactor$phaseSuffix',
|
||||
points: phase?.dataPoints,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSeparator() {
|
||||
return Container(
|
||||
height: double.infinity,
|
||||
width: 1,
|
||||
decoration: const BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Color.fromARGB(20, 0, 0, 0),
|
||||
offset: Offset(1, 0),
|
||||
blurRadius: 1,
|
||||
),
|
||||
BoxShadow(
|
||||
color: Color.fromARGB(30, 0, 0, 0),
|
||||
offset: Offset(-2, 0),
|
||||
blurRadius: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _valueFromCode({
|
||||
required String code,
|
||||
required List<DataPoint>? points,
|
||||
}) {
|
||||
final element = points?.firstWhere(
|
||||
(e) => e.code == code,
|
||||
orElse: () => DataPoint(value: '--'),
|
||||
);
|
||||
|
||||
return element?.value.toString() ?? '--';
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.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: EnergyManagementChartsHelper.titlesData(
|
||||
context,
|
||||
leftTitlesInterval: 5000,
|
||||
),
|
||||
gridData: EnergyManagementChartsHelper.gridData(),
|
||||
borderData: EnergyManagementChartsHelper.borderData(),
|
||||
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||
lineBarsData: _lineBarsData,
|
||||
),
|
||||
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: const 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: const FlDotData(show: false),
|
||||
isStrokeCapRound: true,
|
||||
barWidth: 3,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
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/chart_title.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart';
|
||||
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class TotalEnergyConsumptionChartBox extends StatelessWidget {
|
||||
const TotalEnergyConsumptionChartBox({super.key});
|
||||
|
||||
@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: [
|
||||
AnalyticsErrorWidget(state.errorMessage),
|
||||
Row(
|
||||
children: [
|
||||
ChartsLoadingWidget(
|
||||
isLoading: state.status == TotalEnergyConsumptionStatus.loading,
|
||||
),
|
||||
const Expanded(
|
||||
flex: 3,
|
||||
child: FittedBox(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: ChartTitle(title: Text('Total Energy Consumption')),
|
||||
),
|
||||
),
|
||||
const Spacer(flex: 4),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
TotalEnergyConsumptionChart(chartData: state.chartData),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
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!'),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class GetEnergyConsumptionByPhasesParam extends Equatable {
|
||||
final DateTime? startDate;
|
||||
final DateTime? endDate;
|
||||
final String? spaceId;
|
||||
|
||||
const GetEnergyConsumptionByPhasesParam({
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
this.spaceId,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'startDate': startDate?.toIso8601String(),
|
||||
'endDate': endDate?.toIso8601String(),
|
||||
'spaceId': spaceId,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [startDate, endDate, spaceId];
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
class GetEnergyConsumptionPerDeviceParam {
|
||||
const GetEnergyConsumptionPerDeviceParam();
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
class GetTotalEnergyConsumptionParam {
|
||||
final DateTime? startDate;
|
||||
final DateTime? endDate;
|
||||
final String? spaceId;
|
||||
|
||||
const GetTotalEnergyConsumptionParam({
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
this.spaceId,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'startDate': startDate?.toIso8601String(),
|
||||
'endDate': endDate?.toIso8601String(),
|
||||
'spaceId': spaceId,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
||||
|
||||
abstract interface class EnergyConsumptionByPhasesService {
|
||||
Future<List<PhasesEnergyConsumption>> load(
|
||||
GetEnergyConsumptionByPhasesParam param,
|
||||
);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
|
||||
|
||||
class FakeEnergyConsumptionByPhasesService
|
||||
implements EnergyConsumptionByPhasesService {
|
||||
@override
|
||||
Future<List<PhasesEnergyConsumption>> load(
|
||||
GetEnergyConsumptionByPhasesParam param,
|
||||
) {
|
||||
return Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() => const [
|
||||
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
|
||||
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
|
||||
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
|
||||
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
|
||||
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
|
||||
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
|
||||
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
|
||||
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
|
||||
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 100, phaseC: 100),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
final class RemoteEnergyConsumptionByPhasesService
|
||||
implements EnergyConsumptionByPhasesService {
|
||||
const RemoteEnergyConsumptionByPhasesService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
Future<List<PhasesEnergyConsumption>> load(
|
||||
GetEnergyConsumptionByPhasesParam param,
|
||||
) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: 'endpoint',
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (data) {
|
||||
final json = data as Map<String, dynamic>? ?? {};
|
||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||
return mappedData.map((e) {
|
||||
final jsonData = e as Map<String, dynamic>;
|
||||
return PhasesEnergyConsumption.fromJson(jsonData);
|
||||
}).toList();
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
throw Exception('Failed to load energy consumption per device: $e');
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||
|
||||
abstract interface class EnergyConsumptionPerDeviceService {
|
||||
Future<List<DeviceEnergyDataModel>> load(
|
||||
GetEnergyConsumptionPerDeviceParam param,
|
||||
);
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import 'dart:math' as math show Random;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
|
||||
|
||||
class FakeEnergyConsumptionPerDeviceService
|
||||
implements EnergyConsumptionPerDeviceService {
|
||||
@override
|
||||
Future<List<DeviceEnergyDataModel>> load(
|
||||
GetEnergyConsumptionPerDeviceParam param,
|
||||
) {
|
||||
final random = math.Random();
|
||||
return Future.delayed(const Duration(milliseconds: 500), () {
|
||||
return [
|
||||
(Colors.redAccent, 1),
|
||||
(Colors.lightBlueAccent, 2),
|
||||
(Colors.purpleAccent, 3),
|
||||
].map((e) {
|
||||
final (color, index) = e;
|
||||
return DeviceEnergyDataModel(
|
||||
color: color,
|
||||
energy: List.generate(30, (i) => i)
|
||||
.map(
|
||||
(index) => EnergyDataModel(
|
||||
date: DateTime(2025, 1, index + 1),
|
||||
value: random.nextInt(100) + (index * 100),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
deviceName: 'Device $index',
|
||||
deviceId: 'device_$index',
|
||||
);
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
class RemoteEnergyConsumptionPerDeviceService
|
||||
implements EnergyConsumptionPerDeviceService {
|
||||
const RemoteEnergyConsumptionPerDeviceService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
Future<List<DeviceEnergyDataModel>> load(
|
||||
GetEnergyConsumptionPerDeviceParam param,
|
||||
) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: 'endpoint',
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (data) {
|
||||
final json = data as Map<String, dynamic>? ?? {};
|
||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||
return mappedData.map((e) {
|
||||
final jsonData = e as Map<String, dynamic>;
|
||||
return DeviceEnergyDataModel.fromJson(jsonData);
|
||||
}).toList();
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
throw Exception('Failed to load energy consumption per device: $e');
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||
|
||||
abstract interface class PowerClampInfoService {
|
||||
Future<PowerClampModel> getInfo(String deviceId);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
final class RemotePowerClampInfoService implements PowerClampInfoService {
|
||||
const RemotePowerClampInfoService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
Future<PowerClampModel> getInfo(String deviceId) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: '/devices/$deviceId/functions/status',
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (data) {
|
||||
final json = data as Map<String, Object?>? ?? {};
|
||||
final mappedData = json['data'] as Map<String, Object?>? ?? {};
|
||||
return PowerClampModel.fromJson(mappedData);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
throw Exception('Failed to fetch power clamp info: $e');
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/realtime_device_service.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class FirebaseRealtimeDeviceService implements RealtimeDeviceService {
|
||||
@override
|
||||
Stream<List<Status>> subscribe(String deviceId) {
|
||||
try {
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
|
||||
return ref.onValue.asyncMap((event) {
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
|
||||
if (data == null || data['status'] == null) {
|
||||
throw Exception('Invalid data received from Firebase');
|
||||
}
|
||||
|
||||
final statusMap = data['status'] as List<dynamic>;
|
||||
return statusMap.map((status) {
|
||||
if (status is! Map<dynamic, dynamic>) {
|
||||
throw Exception('Invalid status format');
|
||||
}
|
||||
|
||||
return Status(
|
||||
code: status['code']?.toString() ?? '',
|
||||
value: num.tryParse(status['value']?.toString() ?? '0'),
|
||||
);
|
||||
}).toList();
|
||||
});
|
||||
} catch (e) {
|
||||
throw Exception('Error subscribing to device status: $e');
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
abstract interface class RealtimeDeviceService {
|
||||
Stream<List<Status>> subscribe(String deviceId);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
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(
|
||||
List.generate(30, (index) {
|
||||
return EnergyDataModel(
|
||||
date: DateTime(2025, 1, index + 1),
|
||||
value: 20000 + (index * 1000) % 5000,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
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';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
class RemoteTotalEnergyConsumptionService implements TotalEnergyConsumptionService {
|
||||
const RemoteTotalEnergyConsumptionService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
Future<List<EnergyDataModel>> load(
|
||||
GetTotalEnergyConsumptionParam param,
|
||||
) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: 'endpoint',
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (data) {
|
||||
final json = data as Map<String, dynamic>? ?? {};
|
||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||
return mappedData.map((e) {
|
||||
final jsonData = e as Map<String, dynamic>;
|
||||
return EnergyDataModel.fromJson(jsonData);
|
||||
}).toList();
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
throw Exception('Failed to load total energy consumption: $e');
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
26
lib/pages/analytics/widgets/analytics_error_widget.dart
Normal file
26
lib/pages/analytics/widgets/analytics_error_widget.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class AnalyticsErrorWidget extends StatelessWidget {
|
||||
const AnalyticsErrorWidget(this.errorMessage, {super.key});
|
||||
|
||||
final String? errorMessage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Visibility(
|
||||
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
|
||||
child: Text(
|
||||
'$errorMessage ?? "Something went wrong"',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.red,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
25
lib/pages/analytics/widgets/charts_loading_widget.dart
Normal file
25
lib/pages/analytics/widgets/charts_loading_widget.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChartsLoadingWidget extends StatelessWidget {
|
||||
const ChartsLoadingWidget({
|
||||
required this.isLoading,
|
||||
super.key,
|
||||
});
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Visibility(
|
||||
visible: isLoading,
|
||||
child: const SizedBox.square(
|
||||
dimension: 16,
|
||||
child: FittedBox(
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.only(end: 8),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
// PowerClampModel class to represent the response
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class PowerClampModel {
|
||||
String productUuid;
|
||||
String productType;
|
||||
@ -46,11 +48,27 @@ class PowerStatus {
|
||||
|
||||
factory PowerStatus.fromJson(Map<String, dynamic> json) {
|
||||
return PowerStatus(
|
||||
phaseA: Phase.fromJson(json['phaseA']as List<dynamic>? ?? []),
|
||||
phaseB: Phase.fromJson(json['phaseB']as List<dynamic>? ?? []),
|
||||
phaseC: Phase.fromJson(json['phaseC']as List<dynamic>? ?? []),
|
||||
general: Phase.fromJson(json['general']as List<dynamic>? ?? []
|
||||
));
|
||||
phaseA: Phase.fromJson(json['phaseA'] as List<dynamic>? ?? []),
|
||||
phaseB: Phase.fromJson(json['phaseB'] as List<dynamic>? ?? []),
|
||||
phaseC: Phase.fromJson(json['phaseC'] as List<dynamic>? ?? []),
|
||||
general: Phase.fromJson(json['general'] as List<dynamic>? ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
factory PowerStatus.fromStatusList(List<Status> statuses) {
|
||||
List<DataPoint> extractPhase(String prefix) {
|
||||
return statuses
|
||||
.where((s) => s.code.endsWith(prefix))
|
||||
.map((s) => DataPoint(code: s.code, value: s.value))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return PowerStatus(
|
||||
phaseA: Phase(dataPoints: extractPhase('A')),
|
||||
phaseB: Phase(dataPoints: extractPhase('B')),
|
||||
phaseC: Phase(dataPoints: extractPhase('C')),
|
||||
general: Phase(dataPoints: extractPhase('')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,19 +143,6 @@ 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',
|
||||
// icon: Assets.moveinIcon,
|
||||
|
@ -19,7 +19,6 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class UsersPage extends StatelessWidget {
|
||||
UsersPage({super.key});
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
||||
@ -90,8 +89,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
|
||||
|
||||
// Find the index of the item in teh current itemsList
|
||||
int index =
|
||||
updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
||||
int index = updatedIfItems
|
||||
.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
||||
// Replace the map if the index is valid
|
||||
if (index != -1) {
|
||||
updatedIfItems[index] = event.item;
|
||||
@ -100,9 +99,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
|
||||
if (event.isTabToRun) {
|
||||
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
||||
emit(state.copyWith(
|
||||
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
||||
} else {
|
||||
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
||||
emit(state.copyWith(
|
||||
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,8 +111,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
|
||||
|
||||
// Find the index of the item in teh current itemsList
|
||||
int index =
|
||||
currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
||||
int index = currentItems
|
||||
.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
||||
// Replace the map if the index is valid
|
||||
if (index != -1) {
|
||||
currentItems[index] = event.item;
|
||||
@ -122,7 +123,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
emit(state.copyWith(thenItems: currentItems));
|
||||
}
|
||||
|
||||
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
||||
void _onAddFunctionsToRoutine(
|
||||
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
||||
try {
|
||||
if (event.functions.isEmpty) return;
|
||||
|
||||
@ -172,17 +174,20 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
||||
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||
createRoutineBloc.selectedCommunityId == '') {
|
||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||
List<String> spacesList =
|
||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||
for (var spaceId in spacesList) {
|
||||
scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
||||
scenes
|
||||
.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scenes.addAll(await SceneApi.getScenes(
|
||||
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectUuid));
|
||||
scenes.addAll(await SceneApi.getScenes(createRoutineBloc.selectedSpaceId,
|
||||
createRoutineBloc.selectedCommunityId, projectUuid));
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
@ -199,7 +204,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
|
||||
Future<void> _onLoadAutomation(
|
||||
LoadAutomation event, Emitter<RoutineState> emit) async {
|
||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||
List<ScenesModel> automations = [];
|
||||
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
||||
@ -207,17 +213,22 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||
try {
|
||||
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
||||
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||
createRoutineBloc.selectedCommunityId == '') {
|
||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||
List<String> spacesList =
|
||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||
for (var spaceId in spacesList) {
|
||||
automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId));
|
||||
automations.addAll(
|
||||
await SceneApi.getAutomation(spaceId, communityId, projectId));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
automations.addAll(await SceneApi.getAutomation(
|
||||
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectId));
|
||||
createRoutineBloc.selectedSpaceId,
|
||||
createRoutineBloc.selectedCommunityId,
|
||||
projectId));
|
||||
}
|
||||
emit(state.copyWith(
|
||||
automations: automations,
|
||||
@ -233,14 +244,16 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
|
||||
FutureOr<void> _onSearchRoutines(
|
||||
SearchRoutines event, Emitter<RoutineState> emit) async {
|
||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
emit(state.copyWith(isLoading: false, errorMessage: null));
|
||||
emit(state.copyWith(searchText: event.query));
|
||||
}
|
||||
|
||||
FutureOr<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
|
||||
FutureOr<void> _onAddSelectedIcon(
|
||||
AddSelectedIcon event, Emitter<RoutineState> emit) {
|
||||
emit(state.copyWith(selectedIcon: event.icon));
|
||||
}
|
||||
|
||||
@ -254,7 +267,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
return actions.last['deviceId'] == 'delay';
|
||||
}
|
||||
|
||||
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
||||
Future<void> _onCreateScene(
|
||||
CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
||||
try {
|
||||
// Check if first action is delay
|
||||
// if (_isFirstActionDelay(state.thenItems)) {
|
||||
@ -343,7 +357,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
|
||||
Future<void> _onCreateAutomation(
|
||||
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
|
||||
try {
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
if (state.routineName == null || state.routineName!.isEmpty) {
|
||||
@ -456,7 +471,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
actions: actions,
|
||||
);
|
||||
|
||||
final result = await SceneApi.createAutomation(createAutomationModel, projectUuid);
|
||||
final result =
|
||||
await SceneApi.createAutomation(createAutomationModel, projectUuid);
|
||||
if (result['success']) {
|
||||
add(ResetRoutineState());
|
||||
add(const LoadAutomation());
|
||||
@ -477,17 +493,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
|
||||
FutureOr<void> _onRemoveDragCard(
|
||||
RemoveDragCard event, Emitter<RoutineState> emit) {
|
||||
if (event.isFromThen) {
|
||||
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
||||
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||
final selectedFunctions =
|
||||
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||
|
||||
thenItems.removeAt(event.index);
|
||||
selectedFunctions.remove(event.key);
|
||||
emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
|
||||
emit(state.copyWith(
|
||||
thenItems: thenItems, selectedFunctions: selectedFunctions));
|
||||
} else {
|
||||
final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
|
||||
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||
final selectedFunctions =
|
||||
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||
|
||||
ifItems.removeAt(event.index);
|
||||
selectedFunctions.remove(event.key);
|
||||
@ -510,11 +530,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
));
|
||||
}
|
||||
|
||||
FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
||||
FutureOr<void> _onEffectiveTimeEvent(
|
||||
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
||||
emit(state.copyWith(effectiveTime: event.effectiveTime));
|
||||
}
|
||||
|
||||
FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
|
||||
FutureOr<void> _onSetRoutineName(
|
||||
SetRoutineName event, Emitter<RoutineState> emit) {
|
||||
emit(state.copyWith(
|
||||
routineName: event.name,
|
||||
));
|
||||
@ -641,7 +663,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
// return (thenItems, ifItems, currentFunctions);
|
||||
// }
|
||||
|
||||
Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
|
||||
Future<void> _onGetSceneDetails(
|
||||
GetSceneDetails event, Emitter<RoutineState> emit) async {
|
||||
try {
|
||||
emit(state.copyWith(
|
||||
isLoading: true,
|
||||
@ -690,9 +713,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
deviceCards[deviceId] = {
|
||||
'entityId': action.entityId,
|
||||
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
||||
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
|
||||
? action.entityId
|
||||
: const Uuid().v4(),
|
||||
'uniqueCustomId':
|
||||
action.type == 'automation' || action.actionExecutor == 'delay'
|
||||
? action.entityId
|
||||
: const Uuid().v4(),
|
||||
'title': action.actionExecutor == 'delay'
|
||||
? 'Delay'
|
||||
: action.type == 'automation'
|
||||
@ -732,7 +756,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
),
|
||||
);
|
||||
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
|
||||
} else if (action.executorProperty != null && action.actionExecutor != 'delay') {
|
||||
} else if (action.executorProperty != null &&
|
||||
action.actionExecutor != 'delay') {
|
||||
final functions = matchingDevice?.functions ?? [];
|
||||
final functionCode = action.executorProperty?.functionCode;
|
||||
for (DeviceFunction function in functions) {
|
||||
@ -798,7 +823,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
|
||||
FutureOr<void> _onResetRoutineState(
|
||||
ResetRoutineState event, Emitter<RoutineState> emit) {
|
||||
emit(state.copyWith(
|
||||
ifItems: [],
|
||||
thenItems: [],
|
||||
@ -831,7 +857,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||
if (state.isTabToRun) {
|
||||
await SceneApi.deleteScene(
|
||||
unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? '');
|
||||
unitUuid: spaceBloc.state.selectedSpaces[0],
|
||||
sceneId: state.sceneId ?? '');
|
||||
} else {
|
||||
await SceneApi.deleteAutomation(
|
||||
unitUuid: spaceBloc.state.selectedSpaces[0],
|
||||
@ -876,7 +903,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
// }
|
||||
// }
|
||||
|
||||
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
|
||||
FutureOr<void> _fetchDevices(
|
||||
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
try {
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
@ -885,17 +913,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||
|
||||
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
||||
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||
createRoutineBloc.selectedCommunityId == '') {
|
||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||
List<String> spacesList =
|
||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||
for (var spaceId in spacesList) {
|
||||
devices.addAll(
|
||||
await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid));
|
||||
devices.addAll(await DevicesManagementApi()
|
||||
.fetchDevices(communityId, spaceId, projectUuid));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||
createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedSpaceId, projectUuid));
|
||||
createRoutineBloc.selectedCommunityId,
|
||||
createRoutineBloc.selectedSpaceId,
|
||||
projectUuid));
|
||||
}
|
||||
|
||||
emit(state.copyWith(isLoading: false, devices: devices));
|
||||
@ -904,7 +936,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
|
||||
FutureOr<void> _onUpdateScene(
|
||||
UpdateScene event, Emitter<RoutineState> emit) async {
|
||||
try {
|
||||
// Check if first action is delay
|
||||
// if (_isFirstActionDelay(state.thenItems)) {
|
||||
@ -971,7 +1004,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
actions: actions,
|
||||
);
|
||||
|
||||
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
|
||||
final result =
|
||||
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
|
||||
if (result['success']) {
|
||||
add(ResetRoutineState());
|
||||
add(const LoadScenes());
|
||||
@ -990,7 +1024,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
|
||||
FutureOr<void> _onUpdateAutomation(
|
||||
UpdateAutomation event, Emitter<RoutineState> emit) async {
|
||||
try {
|
||||
if (state.routineName == null || state.routineName!.isEmpty) {
|
||||
emit(state.copyWith(
|
||||
@ -1106,8 +1141,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
|
||||
if (result['success']) {
|
||||
add(ResetRoutineState());
|
||||
add(LoadAutomation());
|
||||
add(LoadScenes());
|
||||
add(const LoadAutomation());
|
||||
add(const LoadScenes());
|
||||
} else {
|
||||
emit(state.copyWith(
|
||||
isLoading: false,
|
||||
@ -1291,10 +1326,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
}
|
||||
|
||||
final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
|
||||
final ifItems =
|
||||
deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
|
||||
final thenItems = deviceThenCards.values
|
||||
.where((card) =>
|
||||
card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
|
||||
card['type'] == 'action' ||
|
||||
card['type'] == 'automation' ||
|
||||
card['type'] == 'scene')
|
||||
.toList();
|
||||
|
||||
emit(state.copyWith(
|
||||
@ -1316,7 +1354,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSceneTrigger(SceneTrigger event, Emitter<RoutineState> emit) async {
|
||||
Future<void> _onSceneTrigger(
|
||||
SceneTrigger event, Emitter<RoutineState> emit) async {
|
||||
emit(state.copyWith(loadingSceneId: event.sceneId));
|
||||
|
||||
try {
|
||||
@ -1361,21 +1400,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
|
||||
|
||||
// Remove from loading set safely
|
||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
||||
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||
..remove(event.automationId);
|
||||
|
||||
emit(state.copyWith(
|
||||
automations: updatedAutomations,
|
||||
loadingAutomationIds: updatedLoadingIds,
|
||||
));
|
||||
} else {
|
||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
||||
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||
..remove(event.automationId);
|
||||
emit(state.copyWith(
|
||||
loadingAutomationIds: updatedLoadingIds,
|
||||
errorMessage: 'Update failed',
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
||||
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||
..remove(event.automationId);
|
||||
emit(state.copyWith(
|
||||
loadingAutomationIds: updatedLoadingIds,
|
||||
errorMessage: 'Update error: ${e.toString()}',
|
||||
|
@ -23,6 +23,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
on<OnCommunityUpdated>(_onCommunityUpdate);
|
||||
on<PaginationEvent>(_fetchPaginationSpaces);
|
||||
on<DebouncedSearchEvent>(_onDebouncedSearch);
|
||||
on<SpaceTreeClearSelectionEvent>(_onSpaceTreeClearSelectionEvent);
|
||||
}
|
||||
Timer _timer = Timer(const Duration(microseconds: 0), () {});
|
||||
|
||||
@ -36,8 +37,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
final updatedCommunity = event.updatedCommunity;
|
||||
final updatedCommunities = List<CommunityModel>.from(state.communityList);
|
||||
|
||||
final index =
|
||||
updatedCommunities.indexWhere((community) => community.uuid == updatedCommunity.uuid);
|
||||
final index = updatedCommunities
|
||||
.indexWhere((community) => community.uuid == updatedCommunity.uuid);
|
||||
|
||||
if (index != -1) {
|
||||
updatedCommunities[index] = updatedCommunity;
|
||||
@ -93,8 +94,11 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
if (paginationModel.hasNext && state.searchQuery.isEmpty) {
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
|
||||
paginationModel = await CommunitySpaceManagementApi().fetchCommunitiesAndSpaces(
|
||||
projectId: projectUuid, page: paginationModel.pageNum, search: state.searchQuery);
|
||||
paginationModel = await CommunitySpaceManagementApi()
|
||||
.fetchCommunitiesAndSpaces(
|
||||
projectId: projectUuid,
|
||||
page: paginationModel.pageNum,
|
||||
search: state.searchQuery);
|
||||
|
||||
communities.addAll(paginationModel.communities);
|
||||
}
|
||||
@ -107,16 +111,19 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
paginationIsLoading: false));
|
||||
}
|
||||
|
||||
void _onCommunityAdded(OnCommunityAdded event, Emitter<SpaceTreeState> emit) async {
|
||||
void _onCommunityAdded(
|
||||
OnCommunityAdded event, Emitter<SpaceTreeState> emit) async {
|
||||
final updatedCommunities = List<CommunityModel>.from(state.communityList);
|
||||
updatedCommunities.add(event.newCommunity);
|
||||
|
||||
emit(state.copyWith(communitiesList: updatedCommunities));
|
||||
}
|
||||
|
||||
_onCommunityExpanded(OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
|
||||
_onCommunityExpanded(
|
||||
OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
|
||||
try {
|
||||
List<String> updatedExpandedCommunityList = List.from(state.expandedCommunities);
|
||||
List<String> updatedExpandedCommunityList =
|
||||
List.from(state.expandedCommunities);
|
||||
|
||||
if (updatedExpandedCommunityList.contains(event.communityId)) {
|
||||
updatedExpandedCommunityList.remove(event.communityId);
|
||||
@ -148,14 +155,18 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
}
|
||||
}
|
||||
|
||||
_onCommunitySelected(OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
|
||||
_onCommunitySelected(
|
||||
OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
|
||||
try {
|
||||
List<String> updatedSelectedCommunities =
|
||||
List.from(state.selectedCommunities.toSet().toList());
|
||||
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
|
||||
List<String> updatedSelectedSpaces =
|
||||
List.from(state.selectedSpaces.toSet().toList());
|
||||
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
|
||||
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
|
||||
List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityId] ?? [];
|
||||
Map<String, List<String>> communityAndSpaces =
|
||||
Map.from(state.selectedCommunityAndSpaces);
|
||||
List<String> selectedSpacesInCommunity =
|
||||
communityAndSpaces[event.communityId] ?? [];
|
||||
|
||||
List<String> childrenIds = _getAllChildIds(event.children);
|
||||
|
||||
@ -188,11 +199,14 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
try {
|
||||
List<String> updatedSelectedCommunities =
|
||||
List.from(state.selectedCommunities.toSet().toList());
|
||||
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
|
||||
List<String> updatedSelectedSpaces =
|
||||
List.from(state.selectedSpaces.toSet().toList());
|
||||
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
|
||||
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
|
||||
Map<String, List<String>> communityAndSpaces =
|
||||
Map.from(state.selectedCommunityAndSpaces);
|
||||
|
||||
List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityModel.uuid] ?? [];
|
||||
List<String> selectedSpacesInCommunity =
|
||||
communityAndSpaces[event.communityModel.uuid] ?? [];
|
||||
|
||||
List<String> childrenIds = _getAllChildIds(event.children);
|
||||
bool isChildSelected = false;
|
||||
@ -215,9 +229,11 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
selectedSpacesInCommunity.addAll(childrenIds);
|
||||
}
|
||||
|
||||
List<String> spaces = _getThePathToChild(event.communityModel.uuid, event.spaceId);
|
||||
List<String> spaces =
|
||||
_getThePathToChild(event.communityModel.uuid, event.spaceId);
|
||||
for (String space in spaces) {
|
||||
if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) {
|
||||
if (!updatedSelectedSpaces.contains(space) &&
|
||||
!updatedSoldChecks.contains(space)) {
|
||||
updatedSoldChecks.add(space);
|
||||
}
|
||||
}
|
||||
@ -240,7 +256,9 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
updatedSoldChecks.remove(event.spaceId);
|
||||
|
||||
List<String> parents =
|
||||
_getThePathToChild(event.communityModel.uuid, event.spaceId).toSet().toList();
|
||||
_getThePathToChild(event.communityModel.uuid, event.spaceId)
|
||||
.toSet()
|
||||
.toList();
|
||||
|
||||
if (updatedSelectedSpaces.isEmpty) {
|
||||
updatedSoldChecks.removeWhere(parents.contains);
|
||||
@ -248,7 +266,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
} else {
|
||||
// Check if any parent has selected children
|
||||
for (String space in parents) {
|
||||
if (!_noChildrenSelected(event.communityModel, space, updatedSelectedSpaces, parents)) {
|
||||
if (!_noChildrenSelected(
|
||||
event.communityModel, space, updatedSelectedSpaces, parents)) {
|
||||
updatedSoldChecks.remove(space);
|
||||
}
|
||||
}
|
||||
@ -273,8 +292,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
}
|
||||
}
|
||||
|
||||
_noChildrenSelected(
|
||||
CommunityModel community, String spaceId, List<String> selectedSpaces, List<String> parents) {
|
||||
_noChildrenSelected(CommunityModel community, String spaceId,
|
||||
List<String> selectedSpaces, List<String> parents) {
|
||||
if (selectedSpaces.contains(spaceId)) {
|
||||
return true;
|
||||
}
|
||||
@ -300,7 +319,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
if (_timer.isActive) {
|
||||
_timer.cancel(); // clear timer
|
||||
}
|
||||
_timer = Timer(duration, () async => add(DebouncedSearchEvent(event.searchQuery)));
|
||||
_timer =
|
||||
Timer(duration, () async => add(DebouncedSearchEvent(event.searchQuery)));
|
||||
|
||||
// List<CommunityModel> communities = List.from(state.communityList);
|
||||
// List<CommunityModel> filteredCommunity = [];
|
||||
@ -324,7 +344,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
}
|
||||
}
|
||||
|
||||
_onDebouncedSearch(DebouncedSearchEvent event, Emitter<SpaceTreeState> emit) async {
|
||||
_onDebouncedSearch(
|
||||
DebouncedSearchEvent event, Emitter<SpaceTreeState> emit) async {
|
||||
emit(state.copyWith(
|
||||
isSearching: true,
|
||||
));
|
||||
@ -333,7 +354,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
|
||||
paginationModel = await CommunitySpaceManagementApi()
|
||||
.fetchCommunitiesAndSpaces(projectId: projectUuid, page: 1, search: event.searchQuery);
|
||||
.fetchCommunitiesAndSpaces(
|
||||
projectId: projectUuid, page: 1, search: event.searchQuery);
|
||||
} catch (_) {}
|
||||
|
||||
emit(state.copyWith(
|
||||
@ -405,8 +427,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
return children;
|
||||
}
|
||||
|
||||
bool _anySpacesSelectedInCommunity(
|
||||
CommunityModel community, List<String> selectedSpaces, List<String> partialCheckedList) {
|
||||
bool _anySpacesSelectedInCommunity(CommunityModel community,
|
||||
List<String> selectedSpaces, List<String> partialCheckedList) {
|
||||
bool result = false;
|
||||
List<String> ids = _getAllChildIds(community.spaces);
|
||||
for (var id in ids) {
|
||||
@ -435,7 +457,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
return ids;
|
||||
}
|
||||
|
||||
List<String> _getAllParentsIds(SpaceModel child, String spaceId, List<String> listIds) {
|
||||
List<String> _getAllParentsIds(
|
||||
SpaceModel child, String spaceId, List<String> listIds) {
|
||||
List<String> ids = listIds;
|
||||
|
||||
ids.add(child.uuid ?? '');
|
||||
@ -457,6 +480,19 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
||||
return [];
|
||||
}
|
||||
|
||||
void _onSpaceTreeClearSelectionEvent(
|
||||
SpaceTreeClearSelectionEvent event,
|
||||
Emitter<SpaceTreeState> emit,
|
||||
) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectedCommunities: [],
|
||||
selectedCommunityAndSpaces: {},
|
||||
selectedSpaces: [],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
_timer.cancel();
|
||||
|
@ -108,3 +108,7 @@ class OnCommunityUpdated extends SpaceTreeEvent {
|
||||
class ClearAllData extends SpaceTreeEvent {}
|
||||
|
||||
class ClearCachedData extends SpaceTreeEvent {}
|
||||
|
||||
class SpaceTreeClearSelectionEvent extends SpaceTreeEvent {
|
||||
const SpaceTreeClearSelectionEvent();
|
||||
}
|
||||
|
@ -170,13 +170,13 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
communities[index].uuid,
|
||||
),
|
||||
onItemSelected: () {
|
||||
widget.onSelect();
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnCommunitySelected(
|
||||
communities[index].uuid,
|
||||
communities[index].spaces,
|
||||
),
|
||||
);
|
||||
widget.onSelect();
|
||||
},
|
||||
children: communities[index].spaces.map(
|
||||
(space) {
|
||||
@ -195,6 +195,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
isParentSelected) {
|
||||
return;
|
||||
}
|
||||
widget.onSelect();
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnSpaceSelected(
|
||||
communities[index],
|
||||
@ -202,7 +203,6 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
space.children,
|
||||
),
|
||||
);
|
||||
widget.onSelect();
|
||||
},
|
||||
onExpansionChanged: () =>
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
|
@ -457,16 +457,17 @@ 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(
|
||||
event.context,
|
||||
spaceTreeState,
|
||||
previousState,
|
||||
allSpaces,
|
||||
event.communityUuid,
|
||||
@ -483,35 +484,39 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
}
|
||||
|
||||
Future<void> _updateLoadedState(
|
||||
BuildContext context,
|
||||
SpaceTreeState spaceTreeState,
|
||||
SpaceManagementLoaded previousState,
|
||||
List<SpaceModel> allSpaces,
|
||||
String communityUuid,
|
||||
Emitter<SpaceManagementState> emit,
|
||||
) async {
|
||||
var prevSpaceModels = await fetchSpaceModels();
|
||||
await fetchTags();
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
final communities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
try {
|
||||
var prevSpaceModels = await fetchSpaceModels();
|
||||
|
||||
for (var community in communities) {
|
||||
if (community.uuid == communityUuid) {
|
||||
community.spaces = allSpaces;
|
||||
_spaceTreeBloc.add(InitialEvent());
|
||||
await fetchTags();
|
||||
|
||||
emit(SpaceManagementLoaded(
|
||||
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(
|
||||
communities: communities,
|
||||
products: _cachedProducts ?? [],
|
||||
selectedCommunity: community,
|
||||
selectedSpace: null,
|
||||
spaceModels: prevSpaceModels,
|
||||
allTags: _cachedTags ?? []));
|
||||
return;
|
||||
} else {
|
||||
print("Community not found");
|
||||
allTags: _cachedTags ?? [],
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,9 @@ 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,
|
||||
|
@ -526,6 +526,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
isNameFieldInvalid = true;
|
||||
});
|
||||
return;
|
||||
} else if (isNameFieldExist) {
|
||||
return;
|
||||
} else {
|
||||
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
|
||||
if (newName.isNotEmpty) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
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';
|
||||
|
@ -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,41 +114,48 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
return Container(
|
||||
width: _width,
|
||||
decoration: subSectionContainerDecoration,
|
||||
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]);
|
||||
}),
|
||||
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(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -34,9 +34,8 @@ class UserPermissionApi {
|
||||
path: ApiEndpoints.roleTypes,
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
final List<RoleTypeModel> fetchedRoles = (json['data'] as List)
|
||||
.map((item) => RoleTypeModel.fromJson(item))
|
||||
.toList();
|
||||
final List<RoleTypeModel> fetchedRoles =
|
||||
(json['data'] as List).map((item) => RoleTypeModel.fromJson(item)).toList();
|
||||
return fetchedRoles;
|
||||
},
|
||||
);
|
||||
@ -48,9 +47,7 @@ class UserPermissionApi {
|
||||
path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid),
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
return (json as List)
|
||||
.map((data) => PermissionOption.fromJson(data))
|
||||
.toList();
|
||||
return (json as List).map((data) => PermissionOption.fromJson(data)).toList();
|
||||
},
|
||||
);
|
||||
return response ?? [];
|
||||
@ -195,14 +192,10 @@ class UserPermissionApi {
|
||||
|
||||
Future<bool> changeUserStatusById(userUuid, status, String projectUuid) async {
|
||||
try {
|
||||
Map<String, dynamic> bodya = {
|
||||
"disable": status,
|
||||
"projectUuid": projectUuid
|
||||
};
|
||||
Map<String, dynamic> bodya = {"disable": status, "projectUuid": projectUuid};
|
||||
|
||||
final response = await _httpService.put(
|
||||
path: ApiEndpoints.changeUserStatus
|
||||
.replaceAll("{invitedUserUuid}", userUuid),
|
||||
path: ApiEndpoints.changeUserStatus.replaceAll("{invitedUserUuid}", userUuid),
|
||||
body: bodya,
|
||||
expectedResponseModel: (json) {
|
||||
return json['success'];
|
||||
|
@ -72,4 +72,5 @@ abstract class ColorsManager {
|
||||
//background: #F8F8F8;
|
||||
static const Color vividBlue = Color(0xFF023DFE);
|
||||
static const Color semiTransparentRed = Color(0x99FF0000);
|
||||
static const Color grey700 = Color(0xFF2D3748);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
abstract class ApiEndpoints {
|
||||
static const String projectUuid = "0e62577c-06fa-41b9-8a92-99a21fbaf51c";
|
||||
static const String projectUuid = "bcda711e-9fc2-4168-a05e-171b4026d1ff";
|
||||
static String baseUrl = dotenv.env['BASE_URL'] ?? '';
|
||||
static const String signUp = '/authentication/user/signup';
|
||||
static const String login = '/authentication/user/login';
|
||||
|
@ -1,3 +1,3 @@
|
||||
class TempConst {
|
||||
static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c';
|
||||
static const projectId = 'bcda711e-9fc2-4168-a05e-171b4026d1ff';
|
||||
}
|
||||
|
@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'color_manager.dart';
|
||||
|
||||
InputDecoration? textBoxDecoration(
|
||||
{bool suffixIcon = false, double radios = 8}) =>
|
||||
InputDecoration? textBoxDecoration({bool suffixIcon = false, double radios = 8}) =>
|
||||
InputDecoration(
|
||||
focusColor: ColorsManager.grayColor,
|
||||
suffixIcon: suffixIcon ? const Icon(Icons.search) : null,
|
||||
@ -68,10 +67,24 @@ BoxDecoration subSectionContainerDecoration = BoxDecoration(
|
||||
],
|
||||
);
|
||||
|
||||
final secondarySection = BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 7,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
color: ColorsManager.circleRolesBackground,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(15),
|
||||
),
|
||||
);
|
||||
|
||||
InputDecoration inputTextFormDeco({hintText}) => InputDecoration(
|
||||
hintText: hintText,
|
||||
border: const OutlineInputBorder(
|
||||
|
||||
borderSide: BorderSide(
|
||||
width: 1,
|
||||
color: ColorsManager.textGray, // Border color for unfocused state
|
||||
|
14
pubspec.yaml
14
pubspec.yaml
@ -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
|
||||
|
Reference in New Issue
Block a user