Compare commits

..

49 Commits

Author SHA1 Message Date
6606491458 made active dynamic 2025-05-25 10:59:41 +03:00
92abcdc4f9 SP-1492-landing_page_analytics_button_design. 2025-05-25 10:57:23 +03:00
7aa9e7e5dc fixed typos. 2025-05-22 16:44:32 +03:00
e9abac7933 added analytics icon. 2025-05-22 16:44:22 +03:00
9eaa367d32 fix horizontal scroll bar 2025-05-22 05:48:49 -05:00
24372a0618 Merge pull request #198 from SyncrowIOT/SP-1580-FE-Watermark-Does-Not-Match-Design-Specification
SP-1580-FE-Watermark-Does-Not-Match-Design-Specification
2025-05-22 11:25:49 +03:00
8988947694 Merge pull request #191 from SyncrowIOT/syncrow_analytics_sidebar_selection_behavior
Syncrow analytics sidebar selection behavior
2025-05-22 11:25:22 +03:00
ef875ef7dc Merge pull request #197 from SyncrowIOT/SP-1510-occupancy_chart_api_integration
Sp 1510 occupancy chart api integration
2025-05-22 11:24:55 +03:00
94e4fbd5db Apply correct business logic in OccupancyDataLoadingStrategy. 2025-05-21 16:08:48 +03:00
302ef36b17 Merge branch 'dev' of https://github.com/SyncrowIOT/web into syncrow_analytics_sidebar_selection_behavior 2025-05-21 15:56:29 +03:00
c508d016c2 SP-1580-FE-Watermark-Does-Not-Match-Design-Specification 2025-05-21 11:08:00 +03:00
e0ad7855d3 converted GetOccupancyParam.toJson to an expression method. 2025-05-21 10:59:04 +03:00
ecf588cfcb reverted to dynamic endpoint. 2025-05-21 10:58:21 +03:00
c9d15d102b fixes in OccupancyChart for a more pleasant UI. 2025-05-21 10:57:53 +03:00
64a29681de Merge pull request #196 from SyncrowIOT/SP-1475-FE-Only-the-arrow-button-is-clickable-make-the-whole-name-clickable-with-the-arrow
Sp 1475 fe only the arrow button is clickable make the whole name clickable with the arrow
2025-05-21 10:31:25 +03:00
02b07cfdb6 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1510-occupancy_chart_api_integration 2025-05-21 10:23:56 +03:00
0a94557eee SP-1510-Occupancy Chart API Integration. 2025-05-21 10:23:31 +03:00
4f8d1c4ffd Merge pull request #195 from SyncrowIOT/charts-reworks
Charts reworks
2025-05-21 10:22:55 +03:00
06b320a75d move icon to the center and change subspace title name 2025-05-21 10:16:12 +03:00
000fe70663 format. 2025-05-21 09:59:50 +03:00
4257f7f0f3 Corrected color of titles in charts. 2025-05-21 09:55:17 +03:00
b2bf3866a9 Deleted pubspec.lock, and added it to .gitignore. 2025-05-21 09:09:32 +03:00
a15b5439f0 Refactor user dropdown menu to display user's full name and arrow icon in a row for better layout consistency 2025-05-20 16:39:10 +03:00
fd2a09cada Deleted unused FakeEnergyConsumptionPerDeviceService. 2025-05-20 14:22:23 +03:00
4c2802acfc date picker decorations matched with design. 2025-05-20 14:20:16 +03:00
15343be258 show space uuid in analytics devices dropdown. 2025-05-20 14:11:25 +03:00
c21842cc6d removed overflow and fixed sizing and text drawing of PowerClampEnergyStatusWidget. 2025-05-20 13:56:00 +03:00
4326559e14 shows OccupancyHeatMapBox instead of a Placeholder in vertical srcollable AnalyticsOccupancyView. 2025-05-20 13:51:04 +03:00
4ded7d5202 Merge pull request #194 from SyncrowIOT/SP-1448-FE-Use-SliderValueSelector-widget-for-all-slider-widgets-in-Web-Routine
add step parameter in onTapFunction.
2025-05-19 11:37:56 +03:00
0d45a155e3 add step parameter in onTapFunction.
Add dialogType parameter in WaterHeaterPresenceSensor and CeilingSensorDialog.
Update step parameter in FlushValueSelectorWidget.
Update step parameter in FunctionBloc and WaterHeaterFunctions.
Update step, unit, min, and max parameters in ACFunction subclasses.
2025-05-19 11:22:15 +03:00
625f737791 SP-1506 rework
Remove extra line.

The colors of the data on X axis and Y axis are not identical to design.

Display days only on the X axis.

When the bar chart loads, we see it coming from the top (check the attached video).
2025-05-19 11:08:26 +03:00
494ae1c941 SP-1495 reworks.
1. Overlapping line not removed.
2. The colors of the data on X axis and Y axis are not identical to design.
3. Day 1 and 2 are missing on the X axis.
4. When the chart loads, we see it coming from the top right corner (check the attached video).
5. Display all available devices even if they have no data and make the chart empty state.
2025-05-19 10:52:44 +03:00
f67d0e2912 SP-1494 reworks.
1. When the chart loads, we see it coming from the top right corner (check the attached video).
2. Day 1 is missing on the X axis.
3. Overlapping line not removed.
2025-05-19 10:17:48 +03:00
17aad13b2a Merge pull request #193 from SyncrowIOT/feature/make_analytics_date_picker_not_show_future_dates
Feature/make_analytics_date_picker_not_show_future_dates
2025-05-15 16:58:25 +03:00
a849c1dafb removed unused import. 2025-05-15 16:31:11 +03:00
3e3e17019a format. 2025-05-15 16:22:54 +03:00
b1bae3cb15 fixed overflow bug on charts. 2025-05-15 15:59:02 +03:00
051bf657ed Changed background color of analytics date pickers to match the design language of the platform. 2025-05-15 15:29:09 +03:00
5191c1e456 Performed selection validation, and made future dates disabled. 2025-05-15 15:28:36 +03:00
7a073f10aa Merge pull request #189 from SyncrowIOT/1495-calendar-bugfixes
1495 calendar bugfixes
2025-05-15 14:31:11 +03:00
900d47faae Merge pull request #190 from SyncrowIOT/SP-1506-FE-implement-chart-per-phase
SP-1506-FE-chart per phase api integration.
2025-05-15 14:30:58 +03:00
e35a7fdc70 Merge pull request #192 from SyncrowIOT/bugfix/charts-horizontal-lines
bugfix/charts-horizontal-lines
2025-05-15 14:30:37 +03:00
d80f5e1f3a Refactor energy consumption charts to enhance grid data configuration
Updated the grid data for EnergyConsumptionByPhasesChart, EnergyConsumptionPerDeviceChart, and TotalEnergyConsumptionChart to include horizontal line visibility and set a horizontal interval of 250. Removed unused phasesJson constant from TotalEnergyConsumptionChart for cleaner code.
2025-05-15 14:25:13 +03:00
baaf5111b1 Applied correct business logic in EnergyManagementDataLoadingStrategy. 2025-05-15 12:48:18 +03:00
745205063e added correct behavior to OccupancyDataLoadingStrategy. 2025-05-15 12:46:12 +03:00
39d125ac7e loads energy management data on date changed. 2025-05-15 10:11:55 +03:00
ad15d0e138 loads occupancy chart on date changed. 2025-05-15 10:08:41 +03:00
e6d272a60d loads heatmap data on calendar change. 2025-05-15 10:06:13 +03:00
8dfe8d10d4 removed requestType from query parameters of RemoteOccupancyAnalyticsDevicesService._makeRequest. 2025-05-15 10:01:43 +03:00
66 changed files with 1541 additions and 2221 deletions

1
.gitignore vendored
View File

@ -30,6 +30,7 @@ migrate_working_dir/
.pub-cache/
.pub/
/build/
pubspec.lock
# Symbolication related
app.*.symbols

View File

@ -10,6 +10,7 @@
analyzer:
errors:
constant_identifier_names: ignore
overridden_fields: ignore
include: package:flutter_lints/flutter.yaml
linter:

View File

@ -0,0 +1,19 @@
<svg width="101" height="101" viewBox="0 0 101 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_9554_2115)">
<path d="M93.8334 86.1048C97.5937 86.1048 100.653 83.0454 100.653 79.2849V9.83487C100.653 4.4501 96.2722 0.0692444 90.8875 0.0692444H10.4187C5.03394 0.0692444 0.653076 4.4501 0.653076 9.83487V79.2849C0.653076 83.0454 3.71245 86.1048 7.4728 86.1048H9.63745V96.163H2.6062C1.52749 96.163 0.653076 97.0376 0.653076 98.1161C0.653076 99.1946 1.52749 100.069 2.6062 100.069H98.7C99.7787 100.069 100.653 99.1946 100.653 98.1161C100.653 97.0376 99.7787 96.163 98.7 96.163H91.6687V86.1048H93.8334ZM4.55933 9.83487C4.55933 6.60401 7.18784 3.97549 10.4187 3.97549H90.8875C94.1183 3.97549 96.7468 6.60401 96.7468 9.83487V14.1317H4.75542C4.68921 14.1317 4.62378 14.1353 4.55933 14.1417V9.83487ZM22.1375 96.163H13.5437V78.5849H22.1375V96.163ZM44.0125 96.163H35.4187V56.9896H44.0125V96.163ZM65.8875 96.163H57.2937V73.5067H65.8875V96.163ZM89.7156 50.1669H77.2156C76.1369 50.1669 75.2625 51.0415 75.2625 52.12V61.9831C75.2625 63.0616 76.1369 63.9362 77.2156 63.9362C78.2943 63.9362 79.1687 63.0616 79.1687 61.9831V54.0732H87.7625V96.163H79.1687V75.1181C79.1687 74.0396 78.2943 73.1649 77.2156 73.1649C76.1369 73.1649 75.2625 74.0396 75.2625 75.1181V96.163H69.7937V71.5536C69.7937 70.4751 68.9193 69.6005 67.8406 69.6005H55.3406C54.2619 69.6005 53.3875 70.4751 53.3875 71.5536V96.163H47.9187V55.0364C47.9187 53.9579 47.0443 53.0833 45.9656 53.0833H33.4656C32.3869 53.0833 31.5125 53.9579 31.5125 55.0364V96.163H26.0437V76.6317C26.0437 75.5532 25.1693 74.6786 24.0906 74.6786H11.5906C10.5119 74.6786 9.63745 75.5532 9.63745 76.6317V82.1985H7.4728C5.86616 82.1985 4.55933 80.8915 4.55933 79.2849V18.028C4.62378 18.0345 4.68921 18.038 4.75542 18.038H96.7468V79.2849C96.7468 80.8915 95.4398 82.1985 93.8334 82.1985H91.6687V52.12C91.6687 51.0415 90.7943 50.1669 89.7156 50.1669Z" fill="white"/>
<path d="M88.1531 7.10049H44.0251C42.9464 7.10049 42.072 7.9751 42.072 9.05362C42.072 10.1321 42.9464 11.0067 44.0251 11.0067H88.1531C89.2318 11.0067 90.1062 10.1321 90.1062 9.05362C90.1062 7.9751 89.2318 7.10049 88.1531 7.10049Z" fill="white"/>
<path d="M22.3464 7.67276C21.9832 7.30948 21.4792 7.10049 20.9656 7.10049C20.4519 7.10049 19.948 7.30928 19.5847 7.67276C19.2214 8.03604 19.0125 8.53995 19.0125 9.05362C19.0125 9.56729 19.2214 10.0712 19.5847 10.4343C19.948 10.7978 20.4519 11.0067 20.9656 11.0067C21.4792 11.0067 21.9832 10.7978 22.3464 10.4343C22.7097 10.0712 22.9187 9.56729 22.9187 9.05362C22.9187 8.53995 22.7097 8.03604 22.3464 7.67276Z" fill="white"/>
<path d="M14.5339 7.67276C14.1707 7.30948 13.6667 7.10049 13.1531 7.10049C12.6394 7.10049 12.1355 7.30928 11.7722 7.67276C11.4089 8.03604 11.2 8.53995 11.2 9.05362C11.2 9.56729 11.4089 10.0712 11.7722 10.4343C12.1355 10.7978 12.6394 11.0067 13.1531 11.0067C13.6667 11.0067 14.1707 10.7978 14.5339 10.4343C14.8972 10.0712 15.1062 9.56729 15.1062 9.05362C15.1062 8.53995 14.8972 8.03604 14.5339 7.67276Z" fill="white"/>
<path d="M30.1589 7.67276C29.7957 7.30948 29.2937 7.10049 28.7781 7.10049C28.2644 7.10049 27.7605 7.30928 27.3972 7.67276C27.0339 8.03604 26.825 8.53995 26.825 9.05362C26.825 9.56729 27.0339 10.0712 27.3972 10.4343C27.7605 10.7978 28.2644 11.0067 28.7781 11.0067C29.2917 11.0067 29.7957 10.7978 30.1589 10.4343C30.5222 10.0712 30.7312 9.56729 30.7312 9.05362C30.7312 8.53995 30.5222 8.03604 30.1589 7.67276Z" fill="white"/>
<path d="M78.5964 67.5634C78.2332 67.1981 77.7292 66.9911 77.2156 66.9911C76.7019 66.9911 76.198 67.1983 75.8347 67.5634C75.4714 67.9267 75.2625 68.4306 75.2625 68.9442C75.2625 69.4579 75.4714 69.9618 75.8347 70.3251C76.198 70.6882 76.7019 70.8974 77.2156 70.8974C77.7292 70.8974 78.2332 70.6884 78.5964 70.3251C78.9597 69.9618 79.1687 69.4579 79.1687 68.9442C79.1687 68.4286 78.9597 67.9267 78.5964 67.5634Z" fill="white"/>
<path d="M83.4656 26.6198C79.5885 26.6198 76.4344 29.7739 76.4344 33.6511C76.4344 35.1911 76.9334 36.6161 77.7764 37.7757L64.8027 50.7493C63.7959 50.1921 62.6393 49.8737 61.4092 49.8737C60.3008 49.8737 59.252 50.1325 58.3186 50.5913L44.7719 37.0446C45.3291 36.0378 45.6475 34.8812 45.6475 33.6511C45.6475 29.7739 42.4934 26.6198 38.6162 26.6198C34.7391 26.6198 31.585 29.7739 31.585 33.6511C31.585 35.0062 31.9713 36.2722 32.6381 37.3468L21.1043 48.8804C20.0975 48.3232 18.9408 48.0048 17.7109 48.0048C13.8338 48.0048 10.6797 51.1589 10.6797 55.0361C10.6797 58.9132 13.8338 62.0673 17.7109 62.0673C21.5881 62.0673 24.7422 58.9132 24.7422 55.0361C24.7422 53.806 24.4238 52.6493 23.8666 51.6425L35.5381 39.971C36.4684 40.4259 37.5129 40.6821 38.6162 40.6821C39.8461 40.6821 41.0027 40.3638 42.0096 39.8066L55.4236 53.2204C54.7613 54.2927 54.3779 55.5544 54.3779 56.9046C54.3779 60.7818 57.532 63.9359 61.4092 63.9359C65.2863 63.9359 68.4404 60.7818 68.4404 56.9046C68.4404 55.6745 68.1221 54.5179 67.5648 53.5111L80.8861 40.1898C81.6855 40.5066 82.5553 40.6823 83.4656 40.6823C87.3428 40.6823 90.4969 37.5282 90.4969 33.6511C90.4969 29.7739 87.3428 26.6198 83.4656 26.6198ZM17.7111 58.1614C15.9881 58.1614 14.5861 56.7595 14.5861 55.0364C14.5861 53.3134 15.9881 51.9114 17.7111 51.9114C19.4342 51.9114 20.8361 53.3134 20.8361 55.0364C20.8361 56.7595 19.4342 58.1614 17.7111 58.1614ZM38.6164 36.7761C36.8934 36.7761 35.4914 35.3741 35.4914 33.6511C35.4914 31.928 36.8934 30.5261 38.6164 30.5261C40.3395 30.5261 41.7414 31.928 41.7414 33.6511C41.7414 35.3741 40.3395 36.7761 38.6164 36.7761ZM61.4094 60.03C59.6863 60.03 58.2844 58.628 58.2844 56.905C58.2844 55.182 59.6863 53.78 61.4094 53.78C63.1324 53.78 64.5344 55.182 64.5344 56.905C64.5344 58.628 63.1324 60.03 61.4094 60.03ZM83.4656 36.7761C81.7426 36.7761 80.3406 35.3741 80.3406 33.6511C80.3406 31.928 81.7426 30.5261 83.4656 30.5261C85.1887 30.5261 86.5906 31.928 86.5906 33.6511C86.5906 35.3741 85.1887 36.7761 83.4656 36.7761Z" fill="white"/>
<path d="M29.6238 25.255C29.2585 24.8917 28.7566 24.6825 28.2429 24.6825C27.7273 24.6825 27.2234 24.8915 26.8601 25.255C26.4968 25.6181 26.2898 26.122 26.2898 26.6357C26.2898 27.1493 26.4968 27.6532 26.8601 28.0165C27.2253 28.3798 27.7273 28.5888 28.2429 28.5888C28.7566 28.5888 29.2585 28.38 29.6238 28.0165C29.9871 27.6532 30.196 27.1493 30.196 26.6357C30.196 26.122 29.9871 25.6181 29.6238 25.255Z" fill="white"/>
<path d="M20.9656 24.6827H11.5906C10.5119 24.6827 9.63745 25.5573 9.63745 26.6358C9.63745 27.7143 10.5119 28.589 11.5906 28.589H20.9656C22.0443 28.589 22.9187 27.7143 22.9187 26.6358C22.9187 25.5573 22.0443 24.6827 20.9656 24.6827Z" fill="white"/>
<path d="M20.9656 34.0577H11.5906C10.5119 34.0577 9.63745 34.9323 9.63745 36.0108C9.63745 37.0893 10.5119 37.964 11.5906 37.964H20.9656C22.0443 37.964 22.9187 37.0893 22.9187 36.0108C22.9187 34.9323 22.0443 34.0577 20.9656 34.0577Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_9554_2115">
<rect width="100" height="100" fill="white" transform="translate(0.653076 0.0692444)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -1,13 +1,71 @@
class AnalyticsDevice {
const AnalyticsDevice({required this.name, required this.uuid});
const AnalyticsDevice({
required this.uuid,
required this.name,
this.createdAt,
this.updatedAt,
this.deviceTuyaUuid,
this.isActive,
this.productDevice,
this.spaceUuid,
});
final String uuid;
final String name;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? deviceTuyaUuid;
final bool? isActive;
final ProductDevice? productDevice;
final String? spaceUuid;
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
return AnalyticsDevice(
uuid: json['uuid'] as String? ?? '',
name: json['name'] as String? ?? '',
uuid: json['uuid'] as String,
name: json['name'] as String,
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
deviceTuyaUuid: json['deviceTuyaUuid'] as String?,
isActive: json['isActive'] as bool?,
productDevice: json['productDevice'] != null
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
: null,
spaceUuid: (json['spaces'] as List<dynamic>?)
?.map((e) => e['uuid'])
.firstOrNull
?.toString(),
);
}
}
class ProductDevice {
const ProductDevice({
this.uuid,
this.createdAt,
this.updatedAt,
this.catName,
this.prodId,
this.name,
this.prodType,
});
final String? uuid;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? catName;
final String? prodId;
final String? name;
final String? prodType;
factory ProductDevice.fromJson(Map<String, dynamic> json) {
return ProductDevice(
uuid: json['uuid'] as String?,
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
catName: json['catName'] as String?,
prodId: json['prodId'] as String?,
name: json['name'] as String?,
prodType: json['prodType'] as String?,
);
}
}

View File

@ -1,18 +1,32 @@
import 'package:equatable/equatable.dart';
class Occupacy extends Equatable {
final String date;
final DateTime date;
final String occupancy;
final String spaceUuid;
final int occupiedSeconds;
const Occupacy({required this.date, required this.occupancy});
const Occupacy({
required this.date,
required this.occupancy,
required this.spaceUuid,
required this.occupiedSeconds,
});
factory Occupacy.fromJson(Map<String, dynamic> json) {
return Occupacy(
date: json['date'] as String,
occupancy: json['occupancy'] as String,
date: DateTime.parse(json['event_date'] as String? ?? '${DateTime.now()}'),
occupancy: (json['occupancy_percentage'] ?? 0).toString(),
spaceUuid: json['space_uuid'] as String? ?? '',
occupiedSeconds: json['occupied_seconds'] as int? ?? 0,
);
}
@override
List<Object?> get props => [date, occupancy];
List<Object?> get props => [
date,
occupancy,
spaceUuid,
occupiedSeconds,
];
}

View File

@ -14,7 +14,6 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community,
List<SpaceModel> spaces,
) {
// Add to space tree bloc first
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
community.uuid,
@ -69,7 +68,9 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community,
SpaceModel child,
) {
// Do nothing else as per original implementation
if (child.children.isNotEmpty) {
return onSpaceSelected(context, community, child);
}
}
@override

View File

@ -14,23 +14,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
CommunityModel community,
List<SpaceModel> spaces,
) {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
community.uuid,
spaces.isNotEmpty ? [spaces.first] : [],
),
);
final spaceTreeState = context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedCommunities.contains(community.uuid)) {
clearData(context);
return;
}
FetchOccupancyDataHelper.loadOccupancyData(
context,
communityId: community.uuid,
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
);
// Do Nothing
}
@override
@ -40,26 +24,17 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
SpaceModel space,
) {
final spaceTreeBloc = context.read<SpaceTreeBloc>();
final selectedSpacesIds = spaceTreeBloc.state.selectedSpaces;
final isSpaceSelected = selectedSpacesIds.contains(space.uuid);
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
if (selectedSpacesIds.isEmpty) {
spaceTreeBloc.add(OnCommunitySelected(community.uuid, [space]));
} else if (isSpaceSelected) {
spaceTreeBloc.add(const SpaceTreeClearSelectionEvent());
} else {
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
..add(OnSpaceSelected(community, space.uuid ?? '', []));
}
final spaceTreeState = context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
spaceTreeState.selectedSpaces.contains(space.uuid)) {
if (isSpaceSelected) {
clearData(context);
return;
}
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
..add(OnSpaceSelected(community, space.uuid ?? '', []));
FetchOccupancyDataHelper.loadOccupancyData(
context,
communityId: community.uuid,
@ -73,7 +48,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
CommunityModel community,
SpaceModel child,
) {
// Do nothing
onSpaceSelected(context, community, child);
}
@override

View File

@ -17,7 +17,7 @@ import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_en
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/remote_occupancy_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_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';
@ -75,7 +75,11 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
FirebaseRealtimeDeviceService(),
),
),
BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())),
BlocProvider(
create: (context) => OccupancyBloc(
RemoteOccupancyService(_httpService),
),
),
BlocProvider(
create: (context) => OccupancyHeatMapBloc(
RemoteOccupancyHeatMapService(_httpService),

View File

@ -14,7 +14,6 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
@override
Widget build(BuildContext context) {
final spaceTreeState = context.read<SpaceTreeBloc>().state;
return BlocBuilder<AnalyticsTabBloc, AnalyticsPageTab>(
buildWhen: (previous, current) => previous != current,
builder: (context, selectedTab) => Column(
@ -68,15 +67,22 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(montlyDate: value),
);
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
selectedDate: value,
communityId:
spaceTreeState.selectedCommunities.firstOrNull ??
'',
spaceId:
spaceTreeState.selectedSpaces.firstOrNull ?? '',
);
final spaceTreeState =
context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchEnergyManagementDataHelper
.loadEnergyManagementData(
context,
shouldFetchAnalyticsDevices: false,
selectedDate: value,
communityId:
spaceTreeState.selectedCommunities.firstOrNull ??
'',
spaceId:
spaceTreeState.selectedSpaces.firstOrNull ?? '',
);
}
},
selectedDate: context
.watch<AnalyticsDatePickerBloc>()

View File

@ -45,7 +45,7 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Theme.of(context).colorScheme.surface,
backgroundColor: ColorsManager.whiteColors,
child: Container(
padding: const EdgeInsetsDirectional.all(20),
width: 320,
@ -121,6 +121,7 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
}
Row _buildYearSelector() {
final currentYear = DateTime.now().year;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -134,17 +135,35 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
),
const Spacer(),
IconButton(
onPressed: () => setState(() => _currentYear = _currentYear - 1),
onPressed: () {
setState(() {
_currentYear = _currentYear - 1;
});
},
icon: const Icon(
Icons.chevron_left,
color: ColorsManager.grey700,
),
),
IconButton(
onPressed: () => setState(() => _currentYear = _currentYear + 1),
icon: const Icon(
onPressed: _currentYear < currentYear
? () {
setState(() {
_currentYear = _currentYear + 1;
// Clear selected month if it becomes invalid in the new year
if (_currentYear == currentYear &&
_selectedMonth != null &&
_selectedMonth! > DateTime.now().month - 1) {
_selectedMonth = null;
}
});
}
: null,
icon: Icon(
Icons.chevron_right,
color: ColorsManager.grey700,
color: _currentYear < currentYear
? ColorsManager.grey700
: ColorsManager.grey700.withValues(alpha: 0.3),
),
),
],
@ -152,11 +171,13 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
}
Widget _buildMonthsGrid() {
final currentDate = DateTime.now();
final isCurrentYear = _currentYear == currentDate.year;
return GridView.builder(
shrinkWrap: true,
itemCount: 12,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 2.5,
@ -165,25 +186,43 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
),
itemBuilder: (context, index) {
final isSelected = _selectedMonth == index;
final isFutureMonth = isCurrentYear && index > currentDate.month - 1;
return InkWell(
onTap: () => setState(() => _selectedMonth = index),
child: Container(
alignment: Alignment.center,
onTap: isFutureMonth ? null : () => setState(() => _selectedMonth = index),
child: DecoratedBox(
decoration: BoxDecoration(
color: isSelected
? ColorsManager.vividBlue.withValues(alpha: 0.7)
: const Color(0xFFEDF2F7),
borderRadius:
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
color: const Color(0xFFEDF2F7),
borderRadius: BorderRadius.only(
topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
bottomRight:
index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
),
),
child: Text(
_monthNames[index],
style: context.textTheme.titleSmall?.copyWith(
fontSize: 12,
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: isSelected
? ColorsManager.whiteColors
: ColorsManager.blackColor.withValues(alpha: 0.8),
fontWeight: FontWeight.w500,
? ColorsManager.vividBlue.withValues(alpha: 0.7)
: isFutureMonth
? ColorsManager.grey700.withValues(alpha: 0.1)
: 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
: isFutureMonth
? ColorsManager.blackColor.withValues(alpha: 0.3)
: ColorsManager.blackColor.withValues(alpha: 0.8),
fontWeight: FontWeight.w500,
),
),
),
),

View File

@ -20,9 +20,9 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
late int _currentYear;
static final years = List.generate(
DateTime.now().year - 2020 + 1,
DateTime.now().year - (DateTime.now().year - 5) + 1,
(index) => (2020 + index),
);
).where((year) => year <= DateTime.now().year).toList();
@override
void initState() {
@ -33,7 +33,7 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Theme.of(context).colorScheme.surface,
backgroundColor: ColorsManager.whiteColors,
child: Container(
padding: const EdgeInsetsDirectional.all(20),
width: 320,
@ -109,7 +109,6 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
shrinkWrap: true,
itemCount: years.length,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 2.5,
@ -120,23 +119,35 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
final isSelected = _currentYear == years[index];
return InkWell(
onTap: () => setState(() => _currentYear = years[index]),
child: Container(
alignment: Alignment.center,
child: DecoratedBox(
decoration: BoxDecoration(
color: isSelected
? ColorsManager.vividBlue.withValues(alpha: 0.7)
: const Color(0xFFEDF2F7),
borderRadius:
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
color: const Color(0xFFEDF2F7),
borderRadius: BorderRadius.only(
topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
bottomRight:
index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
),
),
child: Text(
years[index].toString(),
style: context.textTheme.titleSmall?.copyWith(
fontSize: 12,
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: isSelected
? ColorsManager.whiteColors
: ColorsManager.blackColor.withValues(alpha: 0.8),
fontWeight: FontWeight.w500,
? ColorsManager.vividBlue.withValues(alpha: 0.7)
: const Color(0xFFEDF2F7),
borderRadius:
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
),
child: Text(
years[index].toString(),
style: context.textTheme.titleSmall?.copyWith(
fontSize: 12,
color: isSelected
? ColorsManager.whiteColors
: ColorsManager.blackColor.withValues(alpha: 0.8),
fontWeight: FontWeight.w500,
),
),
),
),

View File

@ -21,12 +21,13 @@ abstract final class EnergyManagementChartsHelper {
reservedSize: 32,
showTitles: true,
maxIncluded: true,
minIncluded: true,
getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsetsDirectional.only(top: 20.0),
child: Text(
(value + 1).toString(),
value.toString(),
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.greyColor,
color: ColorsManager.lightGreyColor,
fontSize: 12,
),
),
@ -36,7 +37,8 @@ abstract final class EnergyManagementChartsHelper {
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
maxIncluded: true,
maxIncluded: false,
minIncluded: true,
interval: leftTitlesInterval,
reservedSize: 110,
getTitlesWidget: (value, meta) => Padding(
@ -70,7 +72,7 @@ abstract final class EnergyManagementChartsHelper {
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
getToolTipLabel(spot.x + 1, spot.y),
getToolTipLabel(spot.x, spot.y),
const TextStyle(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w600,
@ -91,31 +93,38 @@ abstract final class EnergyManagementChartsHelper {
);
}
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(
return FlGridData(
show: true,
drawVerticalLine: false,
drawHorizontalLine: true,
horizontalInterval: 250,
getDrawingHorizontalLine: (value) {
return FlLine(
color: ColorsManager.greyColor,
strokeWidth: 1,
dashArray: value == 0 ? null : [5, 5],
);
},
);
}
static FlBorderData borderData() {
return FlBorderData(
border: const Border(
bottom: BorderSide(
color: ColorsManager.greyColor,
style: BorderStyle.solid,
),
),
show: true,
);
}
static LineTouchData lineTouchData() {
return LineTouchData(
handleBuiltInTouches: true,
touchSpotThreshold: 2,
touchSpotThreshold: 16,
touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(),
);
}

View File

@ -26,6 +26,7 @@ abstract final class FetchEnergyManagementDataHelper {
required String communityId,
required String spaceId,
DateTime? selectedDate,
bool shouldFetchAnalyticsDevices = true,
}) {
if (communityId.isEmpty && spaceId.isEmpty) {
clearAllData(context);
@ -34,12 +35,16 @@ abstract final class FetchEnergyManagementDataHelper {
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
final selectedDate0 = selectedDate ?? datePickerState.monthlyDate;
loadAnalyticsDevices(
context,
communityUuid: communityId,
spaceUuid: spaceId,
selectedDate: selectedDate0,
);
if (shouldFetchAnalyticsDevices) {
loadAnalyticsDevices(
context,
communityUuid: communityId,
spaceUuid: spaceId,
selectedDate: selectedDate0,
);
loadRealtimeDeviceChanges(context);
loadPowerClampInfo(context);
}
loadTotalEnergyConsumption(
context,
selectedDate: selectedDate0,
@ -60,8 +65,6 @@ abstract final class FetchEnergyManagementDataHelper {
spaceId: spaceId,
selectedDate: selectedDate0,
);
loadRealtimeDeviceChanges(context);
loadPowerClampInfo(context);
}
static void loadEnergyConsumptionByPhases(

View File

@ -48,6 +48,7 @@ class AnalyticsDeviceDropdown extends StatelessWidget {
}
Widget _buildDevicesDropdown(BuildContext context, AnalyticsDevicesState state) {
final spaceUuid = state.selectedDevice?.spaceUuid;
return DropdownButton<AnalyticsDevice?>(
value: state.selectedDevice,
isDense: true,
@ -60,10 +61,30 @@ class AnalyticsDeviceDropdown extends StatelessWidget {
),
style: _getTextStyle(context),
padding: _defaultPadding,
selectedItemBuilder: (context) {
return state.devices.map((e) => Text(e.name)).toList();
},
items: state.devices.map((e) {
return DropdownMenuItem(
value: e,
child: Text(e.name),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(e.name),
if (spaceUuid != null)
FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart,
child: Text(
spaceUuid,
style: _getTextStyle(context)?.copyWith(
fontSize: 10,
),
),
),
],
),
);
}).toList(),
onChanged: (value) {

View File

@ -18,7 +18,10 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
Widget build(BuildContext context) {
return BarChart(
BarChartData(
gridData: EnergyManagementChartsHelper.gridData(),
gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250,
),
borderData: EnergyManagementChartsHelper.borderData(),
barTouchData: _barTouchData(context),
titlesData: _titlesData(context),
@ -53,7 +56,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
ColorsManager.vividBlue.withValues(alpha: 0.15),
),
],
width: 16,
width: 8,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
@ -63,6 +66,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
);
}).toList(),
),
duration: Duration.zero,
);
}
@ -157,9 +161,9 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, _) {
final month = DateFormat('dd/MM').format(energyData[value.toInt()].date);
final month = DateFormat('d').format(energyData[value.toInt()].date);
return FittedBox(
alignment: AlignmentDirectional.bottomCenter,
alignment: AlignmentDirectional.center,
fit: BoxFit.scaleDown,
child: RotatedBox(
quarterTurns: 3,
@ -173,7 +177,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
),
);
},
reservedSize: 36,
reservedSize: 18,
),
);

View File

@ -16,7 +16,11 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
context,
leftTitlesInterval: 250,
),
gridData: EnergyManagementChartsHelper.gridData(),
gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250,
),
borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
lineBarsData: chartData.map((e) {
@ -33,7 +37,7 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
);
}).toList(),
),
duration: Durations.extralong1,
duration: Duration.zero,
curve: Curves.easeIn,
);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_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';
@ -46,6 +47,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
flex: 2,
child: EnergyConsumptionPerDeviceDevicesList(
chartData: state.chartData,
devices: context.watch<AnalyticsDevicesBloc>().state.devices,
),
),
],

View File

@ -1,10 +1,16 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.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});
const EnergyConsumptionPerDeviceDevicesList({
required this.chartData,
required this.devices,
super.key,
});
final List<AnalyticsDevice> devices;
final List<DeviceEnergyDataModel> chartData;
@override
@ -16,45 +22,60 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: chartData.map((e) => _buildDeviceCell(context, e)).toList(),
children: devices.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,
Widget _buildDeviceCell(BuildContext context, AnalyticsDevice device) {
final deviceColor = chartData
.firstWhere(
(element) => element.deviceId == device.uuid,
orElse: () => const DeviceEnergyDataModel(
energy: [],
deviceName: '',
deviceId: '',
color: Colors.red,
),
)
.color;
return Tooltip(
message: '${device.name}\n${device.productDevice?.uuid ?? ''}',
child: Container(
height: MediaQuery.sizeOf(context).height * 0.0365,
padding: const EdgeInsetsDirectional.symmetric(
vertical: 8,
horizontal: 12,
),
),
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,
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: deviceColor,
),
),
],
Text(
device.name,
textAlign: TextAlign.center,
style: const TextStyle(
color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
],
),
),
),
);

View File

@ -12,7 +12,6 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/po
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/pages/space_tree/bloc/space_tree_bloc.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';

View File

@ -48,6 +48,9 @@ class PowerClampEnergyStatusWidget extends StatelessWidget {
fontWeight: FontWeight.w400,
fontSize: 16,
),
softWrap: true,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: Text.rich(
TextSpan(

View File

@ -4,15 +4,6 @@ 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});
@ -23,13 +14,19 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
return Expanded(
child: LineChart(
LineChartData(
titlesData: EnergyManagementChartsHelper.titlesData(context),
gridData: EnergyManagementChartsHelper.gridData(),
titlesData: EnergyManagementChartsHelper.titlesData(
context,
leftTitlesInterval: 250,
),
gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250,
),
borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
lineBarsData: _lineBarsData,
),
duration: Durations.extralong1,
duration: Duration.zero,
curve: Curves.easeIn,
),
);
@ -46,7 +43,7 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
.entries
.map(
(entry) => FlSpot(
entry.key.toDouble(),
entry.value.date.day.toDouble(),
entry.value.value,
),
)

View File

@ -28,25 +28,12 @@ abstract final class FetchOccupancyDataHelper {
loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId);
final selectedDevice = context.read<AnalyticsDevicesBloc>().state.selectedDevice;
context.read<OccupancyBloc>().add(
LoadOccupancyEvent(
GetOccupancyParam(
monthDate:
'${datePickerState.monthlyDate.year}-${datePickerState.monthlyDate.month}',
spaceUuid: spaceId,
communityUuid: communityId,
),
),
);
context.read<OccupancyHeatMapBloc>().add(
LoadOccupancyHeatMapEvent(
GetOccupancyHeatMapParam(
spaceUuid: spaceId,
year: datePickerState.yearlyDate,
),
),
);
loadOccupancyChartData(
context,
spaceUuid: spaceId,
date: datePickerState.monthlyDate,
);
loadHeatMapData(context, spaceUuid: spaceId, year: datePickerState.yearlyDate);
if (selectedDevice case final AnalyticsDevice device) {
context.read<RealtimeDeviceChangesBloc>()
@ -57,6 +44,33 @@ abstract final class FetchOccupancyDataHelper {
}
}
static void loadHeatMapData(
BuildContext context, {
required String spaceUuid,
required DateTime year,
}) {
context.read<OccupancyHeatMapBloc>().add(
LoadOccupancyHeatMapEvent(
GetOccupancyHeatMapParam(spaceUuid: spaceUuid, year: year),
),
);
}
static void loadOccupancyChartData(
BuildContext context, {
required String spaceUuid,
required DateTime date,
}) {
context.read<OccupancyBloc>().add(
LoadOccupancyEvent(
GetOccupancyParam(
monthDate: '${date.year}-${date.month.toString().padLeft(2, '0')}',
spaceUuid: spaceUuid,
),
),
);
}
static void loadAnalyticsDevices(
BuildContext context, {
required String communityUuid,

View File

@ -20,9 +20,9 @@ class AnalyticsOccupancyView extends StatelessWidget {
child: Column(
spacing: 32,
children: [
SizedBox(height: height * 0.45, child: const OccupancyEndSideBar()),
SizedBox(height: height * 0.46, child: const OccupancyEndSideBar()),
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
SizedBox(height: height * 0.5, child: const Placeholder()),
SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()),
],
),
);

View File

@ -16,30 +16,38 @@ class OccupancyChart extends StatelessWidget {
Widget build(BuildContext context) {
return BarChart(
BarChartData(
maxY: 1.0,
maxY: 100.0,
gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 0.25,
horizontalInterval: 20,
),
borderData: EnergyManagementChartsHelper.borderData(),
barTouchData: _barTouchData(context),
titlesData: _titlesData(context),
titlesData: _titlesData(context).copyWith(
leftTitles: _titlesData(context).leftTitles.copyWith(
sideTitles: _titlesData(context).leftTitles.sideTitles.copyWith(
maxIncluded: true,
minIncluded: true,
),
),
),
barGroups: List.generate(chartData.length, (index) {
final actual = chartData[index];
final occupancyValue = double.parse(actual.occupancy);
return BarChartGroupData(
x: index,
barsSpace: 0,
groupVertically: true,
barRods: [
BarChartRodData(
toY: 1.0,
fromY: double.parse(actual.occupancy) + 0.025,
toY: 100.0,
fromY: occupancyValue == 0 ? occupancyValue : occupancyValue + 2.5,
color: ColorsManager.graysColor,
width: _chartWidth,
borderRadius: BorderRadius.circular(10),
),
BarChartRodData(
toY: double.parse(actual.occupancy),
toY: occupancyValue,
color: ColorsManager.vividBlue.withValues(alpha: 0.8),
width: _chartWidth,
borderRadius: BorderRadius.circular(10),
@ -81,7 +89,7 @@ class OccupancyChart extends StatelessWidget {
final data = chartData;
final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
final percentage = '${(occupancyValue * 100).toStringAsFixed(0)}%';
final percentage = '${(occupancyValue).toStringAsFixed(0)}%';
return BarTooltipItem(
percentage,
@ -101,14 +109,14 @@ class OccupancyChart extends StatelessWidget {
final leftTitles = titlesData.leftTitles.copyWith(
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
reservedSize: 70,
interval: 0.25,
interval: 20,
getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsetsDirectional.only(end: 12),
child: FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: Text(
'${(value * 100).toStringAsFixed(0)}%',
'${(value).toStringAsFixed(0)}%',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 12,
color: ColorsManager.greyColor,

View File

@ -47,11 +47,14 @@ class OccupancyChartBox extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(montlyDate: value),
);
FetchOccupancyDataHelper.loadOccupancyData(
context,
communityId: spaceTreeState.selectedCommunities.firstOrNull ?? '',
spaceId: spaceTreeState.selectedSpaces.firstOrNull ?? '',
);
if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchOccupancyDataHelper.loadOccupancyChartData(
context,
spaceUuid:
spaceTreeState.selectedSpaces.firstOrNull ?? '',
date: value,
);
}
},
selectedDate: context
.watch<AnalyticsDatePickerBloc>()

View File

@ -47,12 +47,14 @@ class OccupancyHeatMapBox extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(yearlyDate: value),
);
FetchOccupancyDataHelper.loadOccupancyData(
context,
communityId:
spaceTreeState.selectedCommunities.firstOrNull ?? '',
spaceId: spaceTreeState.selectedSpaces.firstOrNull ?? '',
);
if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchOccupancyDataHelper.loadHeatMapData(
context,
spaceUuid:
spaceTreeState.selectedSpaces.firstOrNull ?? '',
year: value,
);
}
},
datePickerType: DatePickerType.year,
selectedDate: context

View File

@ -1,19 +1,11 @@
class GetOccupancyParam {
final String monthDate;
final String? spaceUuid;
final String communityUuid;
GetOccupancyParam({
required this.monthDate,
required this.spaceUuid,
required this.communityUuid,
});
Map<String, dynamic> toJson() {
return {
'monthDate': monthDate,
'spaceUuid': spaceUuid,
'communityUuid': communityUuid,
};
}
Map<String, dynamic> toJson() => {'monthDate': monthDate};
}

View File

@ -39,7 +39,6 @@ class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService
path:
'/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}/devices',
queryParameters: {
'requestType': param.requestType.name,
'communityUuid': param.communityUuid,
'spaceUuid': param.spaceUuid,
'productType': param.deviceTypes.first,

View File

@ -1,39 +0,0 @@
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();
});
}
}

View File

@ -34,10 +34,7 @@ abstract final class _EnergyConsumptionPerDeviceMapper {
static List<DeviceEnergyDataModel> map(dynamic data) {
final json = data as Map<String, dynamic>? ?? {};
final mappedData = json['data'] as List<dynamic>? ?? [];
return mappedData.where((e) {
final deviceData = (e as Map<String, dynamic>)['data'] as List<dynamic>? ?? [];
return deviceData.isNotEmpty;
}).map((e) {
return mappedData.map((e) {
final deviceData = e as Map<String, dynamic>;
final energyData = deviceData['data'] as List<dynamic>;

View File

@ -1,19 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart';
class FakeOccupacyService implements OccupacyService {
@override
Future<List<Occupacy>> load(GetOccupancyParam param) async {
return await Future.delayed(
const Duration(seconds: 1),
() => List.generate(
30,
(index) => Occupacy(
date: DateTime.now().subtract(Duration(days: index)).toString(),
occupancy: ((index / 100)).toString(),
),
),
);
}
}

View File

@ -0,0 +1,32 @@
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteOccupancyService implements OccupacyService {
const RemoteOccupancyService(this._httpService);
final HTTPService _httpService;
@override
Future<List<Occupacy>> load(GetOccupancyParam param) async {
try {
final response = await _httpService.get(
path: '/occupancy/duration/space/${param.spaceUuid}',
showServerMessage: true,
queryParameters: param.toJson(),
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 Occupacy.fromJson(jsonData);
}).toList();
},
);
return response;
} catch (e) {
throw Exception('Failed to load energy consumption per phase: $e');
}
}
}

View File

@ -63,7 +63,8 @@ class _DynamicTableState extends State<DynamicTable> {
}
}
bool _compareListOfLists(List<List<dynamic>> oldList, List<List<dynamic>> newList) {
bool _compareListOfLists(
List<List<dynamic>> oldList, List<List<dynamic>> newList) {
// Check if the old and new lists are the same
if (oldList.length != newList.length) return false;
@ -111,8 +112,8 @@ class _DynamicTableState extends State<DynamicTable> {
trackVisibility: true,
child: Scrollbar(
controller: _horizontalScrollController,
thumbVisibility: false,
trackVisibility: false,
thumbVisibility: true,
trackVisibility: true,
notificationPredicate: (notif) => notif.depth == 1,
child: SingleChildScrollView(
controller: _verticalScrollController,
@ -132,46 +133,60 @@ class _DynamicTableState extends State<DynamicTable> {
children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell(widget.headers[index], index);
return _buildTableHeaderCell(
widget.headers[index], index);
})
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
],
),
),
widget.isEmpty
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
),
Text(
widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: ColorsManager.grayColor),
)
],
),
],
),
],
? SizedBox(
height: widget.size.height * 0.5,
width: widget.size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
),
Text(
widget.tableName == 'AccessManagement'
? 'No Password '
: 'No Devices',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color:
ColorsManager.grayColor),
)
],
),
],
),
],
),
)
: Column(
children: List.generate(widget.data.length, (index) {
children:
List.generate(widget.data.length, (index) {
final row = widget.data[index];
return Row(
children: [
if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.08),
...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.08)),
if (widget.withCheckBox)
_buildRowCheckbox(
index, widget.size.height * 0.08),
...row.map((cell) => _buildTableCell(
cell.toString(),
widget.size.height * 0.08)),
],
);
}),
@ -196,7 +211,9 @@ class _DynamicTableState extends State<DynamicTable> {
),
child: Checkbox(
value: _selectAll,
onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null,
onChanged: widget.withSelectAll && widget.data.isNotEmpty
? _toggleSelectAll
: null,
),
);
}
@ -238,7 +255,9 @@ class _DynamicTableState extends State<DynamicTable> {
constraints: const BoxConstraints.expand(height: 40),
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4),
padding: EdgeInsets.symmetric(
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
vertical: 4),
child: Text(
title,
style: context.textTheme.titleSmall!.copyWith(

View File

@ -97,7 +97,8 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
children: [
_buildInfoRow('Space Name:',
device.spaces?.firstOrNull?.spaceName ?? 'N/A'),
_buildInfoRow('Room:', device.subspace?.subspaceName ?? 'N/A'),
_buildInfoRow(
'Sub space:', device.subspace?.subspaceName ?? 'N/A'),
],
),
TableRow(

View File

@ -11,16 +11,11 @@ import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_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/services/home_api.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
class HomeBloc extends Bloc<HomeEvent, HomeState> {
// final Graph graph = Graph()..isTree = true;
// final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();
// List<Node> sourcesList = [];
// List<Node> destinationsList = [];
UserModel? user;
String terms = '';
String policy = '';
@ -33,22 +28,6 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
on<ConfirmUserAgreementEvent>(_confirmUserAgreement);
}
// void _createNode(CreateNewNode event, Emitter<HomeState> emit) async {
// emit(HomeInitial());
// sourcesList.add(event.sourceNode);
// destinationsList.add(event.destinationNode);
// for (int i = 0; i < sourcesList.length; i++) {
// graph.addEdge(sourcesList[i], destinationsList[i]);
// }
// builder
// ..siblingSeparation = (100)
// ..levelSeparation = (150)
// ..subtreeSeparation = (150)
// ..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM);
// emit(HomeUpdateTree(graph: graph, builder: builder));
// }
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try {
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
@ -99,16 +78,6 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
}
}
// static Future fetchUserInfo() async {
// try {
// var uuid =
// await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
// user = await HomeApi().fetchUserInfo(uuid);
// } catch (e) {
// return;
// }
// }
List<HomeItemModel> homeItems = [
HomeItemModel(
title: 'Access Management',
@ -118,7 +87,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
context.read<SpaceTreeBloc>().add(ClearCachedData());
context.go(RoutesConst.accessManagementPage);
},
color: null,
color: const Color(0xFF0036E6),
),
HomeItemModel(
title: 'Space Management',
@ -128,7 +97,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
context.read<SpaceTreeBloc>().add(ClearCachedData());
context.go(RoutesConst.spacesManagementPage);
},
color: ColorsManager.primaryColor,
color: const Color(0xFF0026A2),
),
HomeItemModel(
title: 'Devices Management',
@ -140,12 +109,11 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
context.go(RoutesConst.deviceManagementPage);
},
color: ColorsManager.primaryColor,
color: const Color(0xFF00165E),
),
HomeItemModel(
title: 'Syncrow Analytics',
icon: Assets.devicesIcon,
icon: Assets.analyticsIcon,
active: true,
onPress: (context) {
context.read<SpaceTreeBloc>().add(ClearCachedData());
@ -153,43 +121,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
context.go(RoutesConst.analytics);
},
color: ColorsManager.primaryColor,
color: const Color(0xFF023DFE),
),
// HomeItemModel(
// title: 'Move in',
// icon: Assets.moveinIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.primaryColor,
// ),
// HomeItemModel(
// title: 'Construction',
// icon: Assets.constructionIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.primaryColor,
// ),
// HomeItemModel(
// title: 'Energy',
// icon: Assets.energyIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
// ),
// HomeItemModel(
// title: 'Integrations',
// icon: Assets.integrationsIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
// ),
// HomeItemModel(
// title: 'Asset',
// icon: Assets.assetIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
// ),
];
}

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class HomeCard extends StatelessWidget {
final bool active;
@ -8,6 +7,7 @@ class HomeCard extends StatelessWidget {
final int index;
final String name;
final Function()? onTap;
final Color? color;
const HomeCard({
super.key,
required this.name,
@ -15,28 +15,16 @@ class HomeCard extends StatelessWidget {
this.active = false,
required this.img,
required this.onTap,
required this.color,
});
@override
Widget build(BuildContext context) {
// bool evenNumbers = index % 2 == 0;
return InkWell(
onTap: active ? onTap : null,
child: Container(
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10),
decoration: BoxDecoration(
color: index == 0 && active
? ColorsManager.blue1.withOpacity(0.9)
: index == 1 && active
? ColorsManager.blue2.withOpacity(0.9)
: index == 2 && active
? ColorsManager.blue3
: index == 4 && active == false
? ColorsManager.blue4.withOpacity(0.2)
: index == 7 && active == false
? ColorsManager.blue4.withOpacity(0.2)
: ColorsManager.blueColor.withOpacity(0.2),
// (active ?ColorsManager.blueColor
// : ColorsManager.blueColor.withOpacity(0.2)),
color: color,
borderRadius: BorderRadius.circular(30),
),
child: Column(
@ -64,15 +52,9 @@ class HomeCard extends StatelessWidget {
),
const SizedBox(height: 10),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
child: SvgPicture.asset(
img,
),
),
],
child: Align(
alignment: AlignmentDirectional.bottomEnd,
child: SvgPicture.asset(img),
),
),
],

View File

@ -50,7 +50,7 @@ class HomeMobilePage extends StatelessWidget {
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: homeItems.length,
itemCount: homeBloc.homeItems.length,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
@ -61,7 +61,8 @@ class HomeMobilePage extends StatelessWidget {
itemBuilder: (context, index) {
return HomeCard(
index: index,
active: homeBloc.homeItems[index].active!,
active: true,
color: homeBloc.homeItems[index].color,
name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!,
onTap: () =>
@ -78,56 +79,4 @@ class HomeMobilePage extends StatelessWidget {
),
);
}
final dynamic homeItems = [
{
'title': 'Access',
'icon': Assets.accessIcon,
'active': true,
},
{
'title': 'Space\nManagement',
'icon': Assets.spaseManagementIcon,
'color': ColorsManager.primaryColor,
'active': true,
},
{
'title': 'Devices',
'icon': Assets.devicesIcon,
'active': true,
},
{
'title': 'Syncrow Analytics',
'icon': Assets.iconEdit,
'active': true,
},
// {
// 'title': 'Move in',
// 'icon': Assets.moveinIcon,
// 'active': false,
// },
// {
// 'title': 'Construction',
// 'icon': Assets.constructionIcon,
// 'active': false,
// },
// {
// 'title': 'Energy',
// 'icon': Assets.energyIcon,
// 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
// 'active': false,
// },
// {
// 'title': 'Integrations',
// 'icon': Assets.integrationsIcon,
// 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
// 'active': false,
// },
// {
// 'title': 'Asset',
// 'icon': Assets.assetIcon,
// 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
// 'active': false,
// },
];
}

View File

@ -106,6 +106,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
),
itemBuilder: (context, index) {
return HomeCard(
color: homeBloc.homeItems[index].color,
index: index,
active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!,

View File

@ -26,8 +26,10 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
functionCode: event.functionData.functionCode,
operationName: event.functionData.operationName,
value: event.functionData.value ?? existingData.value,
valueDescription: event.functionData.valueDescription ?? existingData.valueDescription,
valueDescription: event.functionData.valueDescription ??
existingData.valueDescription,
condition: event.functionData.condition ?? existingData.condition,
step: event.functionData.step ?? existingData.step,
);
} else {
functions.clear();
@ -59,8 +61,10 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
);
}
FutureOr<void> _onSelectFunction(SelectFunction event, Emitter<FunctionBlocState> emit) {
FutureOr<void> _onSelectFunction(
SelectFunction event, Emitter<FunctionBlocState> emit) {
emit(state.copyWith(
selectedFunction: event.functionCode, selectedOperationName: event.operationName));
selectedFunction: event.functionCode,
selectedOperationName: event.operationName));
}
}

View File

@ -14,6 +14,10 @@ abstract class ACFunction extends DeviceFunction<AcStatusModel> {
required super.operationName,
required super.icon,
required this.type,
super.step,
super.unit,
super.max,
super.min,
});
List<ACOperationalValue> getOperationalValues();
@ -75,26 +79,24 @@ class ModeFunction extends ACFunction {
}
class TempSetFunction extends ACFunction {
final int min;
final int max;
final int step;
TempSetFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 160,
max = 300,
step = 1,
super(
TempSetFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'temp_set',
operationName: 'Set Temperature',
icon: Assets.assetsTempreture,
type: type,
min: 200,
max: 300,
step: 1,
unit: "°C",
);
@override
List<ACOperationalValue> getOperationalValues() {
List<ACOperationalValue> values = [];
for (int temp = min; temp <= max; temp += step) {
for (int temp = min!.toInt(); temp <= max!; temp += step!.toInt()) {
values.add(ACOperationalValue(
icon: Assets.assetsTempreture,
description: "${temp / 10}°C",
@ -104,7 +106,6 @@ class TempSetFunction extends ACFunction {
return values;
}
}
class LevelFunction extends ACFunction {
LevelFunction(
{required super.deviceId, required super.deviceName, required type})
@ -166,9 +167,10 @@ class ChildLockFunction extends ACFunction {
}
class CurrentTempFunction extends ACFunction {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
final String unit = "°C";
CurrentTempFunction(
{required super.deviceId, required super.deviceName, required type})
@ -185,7 +187,7 @@ class CurrentTempFunction extends ACFunction {
@override
List<ACOperationalValue> getOperationalValues() {
List<ACOperationalValue> values = [];
for (int temp = min; temp <= max; temp += step) {
for (int temp = min.toInt(); temp <= max; temp += step.toInt()) {
values.add(ACOperationalValue(
icon: Assets.currentTemp,
description: "${temp / 10}°C",

View File

@ -6,10 +6,12 @@ class CpsOperationalValue {
final String description;
final dynamic value;
CpsOperationalValue({
required this.icon,
required this.description,
required this.value,
});
}
@ -94,9 +96,9 @@ final class CpsSensitivityFunction extends CpsFunctions {
icon: Assets.sensitivity,
);
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
static const _images = <String>[
Assets.sensitivityFeature1,
@ -115,10 +117,10 @@ final class CpsSensitivityFunction extends CpsFunctions {
@override
List<CpsOperationalValue> getOperationalValues() {
final values = <CpsOperationalValue>[];
for (var value = min; value <= max; value += step) {
for (var value = min; value <= max; value += step.toInt()) {
values.add(
CpsOperationalValue(
icon: _images[value],
icon: _images[value.toInt()],
description: '$value',
value: value,
),
@ -142,9 +144,9 @@ final class CpsMovingSpeedFunction extends CpsFunctions {
icon: Assets.speedoMeter,
);
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
@ -173,9 +175,9 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions {
icon: Assets.spatialStaticValue,
);
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
@ -204,9 +206,9 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions {
icon: Assets.spatialMotionValue,
);
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
@ -375,9 +377,9 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions {
icon: Assets.presenceJudgementThrshold,
);
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
@ -406,9 +408,9 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions {
icon: Assets.presenceJudgementThrshold,
);
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {

View File

@ -4,6 +4,11 @@ abstract class DeviceFunction<T> {
final String code;
final String operationName;
final String icon;
final double? step;
final String? unit;
final double? max;
final double? min;
DeviceFunction({
required this.deviceId,
@ -11,6 +16,10 @@ abstract class DeviceFunction<T> {
required this.code,
required this.operationName,
required this.icon,
this.step,
this.unit,
this.max,
this.min,
});
}
@ -22,6 +31,10 @@ class DeviceFunctionData {
final dynamic value;
final String? condition;
final String? valueDescription;
final double? step;
final String? unit;
final double? max;
final double? min;
DeviceFunctionData({
required this.entityId,
@ -31,6 +44,10 @@ class DeviceFunctionData {
required this.value,
this.condition,
this.valueDescription,
this.step,
this.unit,
this.max,
this.min,
});
Map<String, dynamic> toJson() {
@ -42,6 +59,10 @@ class DeviceFunctionData {
'value': value,
if (condition != null) 'condition': condition,
if (valueDescription != null) 'valueDescription': valueDescription,
if (step != null) 'step': step,
if (unit != null) 'unit': unit,
if (max != null) 'max': max,
if (min != null) 'min': min,
};
}
@ -54,6 +75,10 @@ class DeviceFunctionData {
value: json['value'],
condition: json['condition'],
valueDescription: json['valueDescription'],
step: json['step']?.toDouble(),
unit: json['unit'],
max: json['max']?.toDouble(),
min: json['min']?.toDouble(),
);
}
@ -68,7 +93,11 @@ class DeviceFunctionData {
other.operationName == operationName &&
other.value == value &&
other.condition == condition &&
other.valueDescription == valueDescription;
other.valueDescription == valueDescription &&
other.step == step &&
other.unit == unit &&
other.max == max &&
other.min == min;
}
@override
@ -79,6 +108,10 @@ class DeviceFunctionData {
operationName.hashCode ^
value.hashCode ^
condition.hashCode ^
valueDescription.hashCode;
valueDescription.hashCode ^
step.hashCode ^
unit.hashCode ^
max.hashCode ^
min.hashCode;
}
}

View File

@ -20,12 +20,11 @@ abstract class FlushFunctions
}
class FlushPresenceDelayFunction extends FlushFunctions {
final int min;
FlushPresenceDelayFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
}) :
super(
code: FlushMountedPresenceSensorModel.codePresenceState,
operationName: 'Presence State',
@ -50,9 +49,9 @@ class FlushPresenceDelayFunction extends FlushFunctions {
}
class FlushSensiReduceFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
FlushSensiReduceFunction({
required super.deviceId,
@ -80,8 +79,8 @@ class FlushSensiReduceFunction extends FlushFunctions {
}
class FlushNoneDelayFunction extends FlushFunctions {
final int min;
final int max;
final double min;
final double max;
final String unit;
FlushNoneDelayFunction({
@ -110,9 +109,9 @@ class FlushNoneDelayFunction extends FlushFunctions {
}
class FlushIlluminanceFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
FlushIlluminanceFunction({
required super.deviceId,
@ -130,7 +129,7 @@ class FlushIlluminanceFunction extends FlushFunctions {
@override
List<FlushOperationalValue> getOperationalValues() {
List<FlushOperationalValue> values = [];
for (int lux = min; lux <= max; lux += step) {
for (int lux = min.toInt(); lux <= max; lux += step.toInt()) {
values.add(FlushOperationalValue(
icon: Assets.IlluminanceIcon,
description: "$lux Lux",
@ -142,9 +141,9 @@ class FlushIlluminanceFunction extends FlushFunctions {
}
class FlushOccurDistReduceFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
FlushOccurDistReduceFunction({
required super.deviceId,
@ -173,9 +172,9 @@ class FlushOccurDistReduceFunction extends FlushFunctions {
// ==== then functions ====
class FlushSensitivityFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
FlushSensitivityFunction({
required super.deviceId,
@ -203,9 +202,9 @@ class FlushSensitivityFunction extends FlushFunctions {
}
class FlushNearDetectionFunction extends FlushFunctions {
final int min;
final double min;
final double max;
final int step;
final double step;
final String unit;
FlushNearDetectionFunction({
@ -225,7 +224,7 @@ class FlushNearDetectionFunction extends FlushFunctions {
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
for (var value = min.toDouble(); value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
@ -237,9 +236,9 @@ class FlushNearDetectionFunction extends FlushFunctions {
}
class FlushMaxDetectDistFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
final String unit;
FlushMaxDetectDistFunction({
@ -259,7 +258,7 @@ class FlushMaxDetectDistFunction extends FlushFunctions {
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
for (var value = min; value <= max; value += step.toInt()) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
@ -271,9 +270,9 @@ class FlushMaxDetectDistFunction extends FlushFunctions {
}
class FlushTargetConfirmTimeFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
final String unit;
FlushTargetConfirmTimeFunction({
@ -293,7 +292,7 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions {
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
for (var value = min.toDouble(); value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
@ -305,9 +304,9 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions {
}
class FlushDisappeDelayFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
final String unit;
FlushDisappeDelayFunction({
@ -327,7 +326,7 @@ class FlushDisappeDelayFunction extends FlushFunctions {
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
for (var value = min.toDouble(); value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
@ -339,9 +338,9 @@ class FlushDisappeDelayFunction extends FlushFunctions {
}
class FlushIndentLevelFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
final String unit;
FlushIndentLevelFunction({
@ -361,7 +360,7 @@ class FlushIndentLevelFunction extends FlushFunctions {
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
for (var value = min.toDouble(); value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
@ -373,9 +372,9 @@ class FlushIndentLevelFunction extends FlushFunctions {
}
class FlushTriggerLevelFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
final String unit;
FlushTriggerLevelFunction({
@ -395,7 +394,7 @@ class FlushTriggerLevelFunction extends FlushFunctions {
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
for (var value = min.toDouble(); value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',

View File

@ -20,17 +20,16 @@ abstract class WaterHeaterFunctions
}
class WHRestartStatusFunction extends WaterHeaterFunctions {
final int min;
WHRestartStatusFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
super(
}) : super(
code: 'relay_status',
operationName: 'Restart Status',
icon: Assets.refreshStatusIcon,
);
@override
List<WaterHeaterOperationalValue> getOperationalValues() {
@ -55,13 +54,11 @@ class WHRestartStatusFunction extends WaterHeaterFunctions {
}
class WHSwitchFunction extends WaterHeaterFunctions {
final int min;
WHSwitchFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
super(
}) : super(
code: 'switch_1',
operationName: 'Switch',
icon: Assets.assetsAcPower,
@ -104,12 +101,11 @@ class TimerConfirmTimeFunction extends WaterHeaterFunctions {
}
class BacklightFunction extends WaterHeaterFunctions {
final int min;
BacklightFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
}) :
super(
code: 'switch_backlight',
operationName: 'Backlight',

View File

@ -4,7 +4,7 @@ import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart
import 'package:syncrow_web/utils/constants/assets.dart';
abstract class WpsFunctions extends DeviceFunction<WallSensorModel> {
final String type;
final String type;
WpsFunctions({
required super.deviceId,
@ -13,6 +13,10 @@ abstract class WpsFunctions extends DeviceFunction<WallSensorModel> {
required super.operationName,
required super.icon,
required this.type,
super.step,
super.unit,
super.max,
super.min,
});
List<WpsOperationalValue> getOperationalValues();
@ -20,9 +24,13 @@ abstract class WpsFunctions extends DeviceFunction<WallSensorModel> {
// For far_detection (75-600cm in 75cm steps)
class FarDetectionFunction extends WpsFunctions {
final int min;
final int max;
final int step;
final double min;
@override
final double max;
@override
final double step;
@override
final String unit;
FarDetectionFunction(
@ -41,7 +49,7 @@ class FarDetectionFunction extends WpsFunctions {
@override
List<WpsOperationalValue> getOperationalValues() {
final values = <WpsOperationalValue>[];
for (var value = min; value <= max; value += step) {
for (var value = min; value <= max; value += step.toInt()) {
values.add(WpsOperationalValue(
icon: Assets.currentDistanceIcon,
description: '$value $unit',
@ -54,9 +62,9 @@ class FarDetectionFunction extends WpsFunctions {
// For presence_time (0-65535 minutes)
class PresenceTimeFunction extends WpsFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
final String unit;
PresenceTimeFunction(
@ -86,9 +94,9 @@ class PresenceTimeFunction extends WpsFunctions {
// For motion_sensitivity_value (1-5 levels)
class MotionSensitivityFunction extends WpsFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
MotionSensitivityFunction(
{required super.deviceId, required super.deviceName, required type})
@ -116,9 +124,9 @@ class MotionSensitivityFunction extends WpsFunctions {
}
class MotionLessSensitivityFunction extends WpsFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
MotionLessSensitivityFunction(
{required super.deviceId, required super.deviceName, required type})
@ -171,8 +179,8 @@ class IndicatorFunction extends WpsFunctions {
}
class NoOneTimeFunction extends WpsFunctions {
final int min;
final int max;
final double min;
final double max;
final String unit;
NoOneTimeFunction(
@ -225,9 +233,9 @@ class PresenceStateFunction extends WpsFunctions {
}
class CurrentDistanceFunction extends WpsFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
CurrentDistanceFunction(
{required super.deviceId, required super.deviceName, required type})
@ -244,11 +252,10 @@ class CurrentDistanceFunction extends WpsFunctions {
@override
List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = [];
for (int cm = min; cm <= max; cm += step) {
for (int cm = min.toInt(); cm <= max; cm += step.toInt()) {
values.add(WpsOperationalValue(
icon: Assets.assetsTempreture,
description: "${cm}CM",
value: cm,
));
}
@ -257,9 +264,9 @@ class CurrentDistanceFunction extends WpsFunctions {
}
class IlluminanceValueFunction extends WpsFunctions {
final int min;
final int max;
final int step;
final double min;
final double max;
final double step;
IlluminanceValueFunction({
required super.deviceId,
@ -277,7 +284,7 @@ class IlluminanceValueFunction extends WpsFunctions {
@override
List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = [];
for (int lux = min; lux <= max; lux += step) {
for (int lux = min.toInt(); lux <= max; lux += step.toInt()) {
values.add(WpsOperationalValue(
icon: Assets.IlluminanceIcon,
description: "$lux Lux",

View File

@ -0,0 +1,297 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncrow_web/pages/routines/widgets/condition_toggle.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class CustomRoutinesTextbox extends StatefulWidget {
final String? currentCondition;
final String dialogType;
final (double, double) sliderRange;
final dynamic displayedValue;
final dynamic initialValue;
final void Function(String condition) onConditionChanged;
final void Function(double value) onTextChanged;
final String unit;
final double dividendOfRange;
final double stepIncreaseAmount;
final bool withSpecialChar;
const CustomRoutinesTextbox({
required this.dialogType,
required this.sliderRange,
required this.displayedValue,
required this.initialValue,
required this.onConditionChanged,
required this.onTextChanged,
required this.currentCondition,
required this.unit,
required this.dividendOfRange,
required this.stepIncreaseAmount,
required this.withSpecialChar,
super.key,
});
@override
State<CustomRoutinesTextbox> createState() => _CustomRoutinesTextboxState();
}
class _CustomRoutinesTextboxState extends State<CustomRoutinesTextbox> {
late final TextEditingController _controller;
bool hasError = false;
String? errorMessage;
int getDecimalPlaces(double step) {
String stepStr = step.toString();
if (stepStr.contains('.')) {
List<String> parts = stepStr.split('.');
String decimalPart = parts[1];
decimalPart = decimalPart.replaceAll(RegExp(r'0+$'), '');
return decimalPart.isEmpty ? 0 : decimalPart.length;
} else {
return 0;
}
}
@override
void initState() {
super.initState();
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
double initialValue;
if (widget.initialValue != null &&
widget.initialValue is num &&
(widget.initialValue as num) == 0) {
initialValue = 0.0;
} else {
initialValue = double.tryParse(widget.displayedValue) ?? 0.0;
}
_controller = TextEditingController(
text: initialValue.toStringAsFixed(decimalPlaces),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _validateInput(String value) {
final doubleValue = double.tryParse(value);
if (doubleValue == null) {
setState(() {
errorMessage = "Invalid number";
hasError = true;
});
return;
}
final min = widget.sliderRange.$1;
final max = widget.sliderRange.$2;
if (doubleValue < min) {
setState(() {
errorMessage = "Value must be at least $min";
hasError = true;
});
} else if (doubleValue > max) {
setState(() {
errorMessage = "Value must be at most $max";
hasError = true;
});
} else {
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
int factor = pow(10, decimalPlaces).toInt();
int scaledStep = (widget.stepIncreaseAmount * factor).round();
int scaledValue = (doubleValue * factor).round();
if (scaledValue % scaledStep != 0) {
setState(() {
errorMessage = "must be a multiple of ${widget.stepIncreaseAmount}";
hasError = true;
});
} else {
setState(() {
errorMessage = null;
hasError = false;
});
}
}
}
@override
void didUpdateWidget(CustomRoutinesTextbox oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.initialValue != oldWidget.initialValue) {
if (widget.initialValue != null &&
widget.initialValue is num &&
(widget.initialValue as num) == 0) {
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
_controller.text = 0.0.toStringAsFixed(decimalPlaces);
}
}
}
void _correctAndUpdateValue(String value) {
final doubleValue = double.tryParse(value) ?? 0.0;
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
double rounded = (doubleValue / widget.stepIncreaseAmount).round() *
widget.stepIncreaseAmount;
rounded = rounded.clamp(widget.sliderRange.$1, widget.sliderRange.$2);
rounded = double.parse(rounded.toStringAsFixed(decimalPlaces));
setState(() {
hasError = false;
errorMessage = null;
});
_controller.text = rounded.toStringAsFixed(decimalPlaces);
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
widget.onTextChanged(rounded);
}
@override
Widget build(BuildContext context) {
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
List<TextInputFormatter> formatters = [];
if (decimalPlaces == 0) {
formatters.add(FilteringTextInputFormatter.digitsOnly);
} else {
formatters.add(FilteringTextInputFormatter.allow(
RegExp(r'^\d*\.?\d{0,' + decimalPlaces.toString() + r'}$'),
));
}
formatters.add(RangeInputFormatter(
min: widget.sliderRange.$1,
max: widget.sliderRange.$2,
));
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (widget.dialogType == 'IF')
ConditionToggle(
currentCondition: widget.currentCondition,
onChanged: widget.onConditionChanged,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 2),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
'Step: ${widget.stepIncreaseAmount}',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor,
fontSize: 10,
fontWeight: FontWeight.w400,
),
),
],
),
),
Center(
child: Container(
width: 170,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFFF8F8F8),
borderRadius: BorderRadius.circular(20),
border: hasError
? Border.all(color: Colors.red, width: 1)
: Border.all(
color: ColorsManager.lightGrayBorderColor, width: 1),
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
style: context.textTheme.bodyLarge?.copyWith(
fontSize: 20,
fontWeight: FontWeight.bold,
color: ColorsManager.blackColor,
),
keyboardType: TextInputType.number,
inputFormatters: widget.withSpecialChar == true
? [FilteringTextInputFormatter.digitsOnly]
: null,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
),
onChanged: _validateInput,
onFieldSubmitted: _correctAndUpdateValue,
onTapOutside: (_) =>
_correctAndUpdateValue(_controller.text),
),
),
const SizedBox(width: 12),
Text(
widget.unit,
style: context.textTheme.bodyMedium?.copyWith(
fontSize: 20,
fontWeight: FontWeight.bold,
color: ColorsManager.vividBlue,
),
),
],
),
),
),
if (errorMessage != null)
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Text(
errorMessage!,
style: context.textTheme.bodySmall?.copyWith(
color: Colors.red,
fontSize: 10,
),
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Min. ${widget.sliderRange.$1.toInt()}${widget.unit}',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor,
fontSize: 10,
fontWeight: FontWeight.w400,
),
),
Text(
'Max. ${widget.sliderRange.$2.toInt()}${widget.unit}',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor,
fontSize: 10,
fontWeight: FontWeight.w400,
),
),
],
),
),
const SizedBox(height: 16),
],
);
}
}

View File

@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -76,24 +77,20 @@ class ACHelper {
context: context,
acFunctions: acFunctions,
device: device,
onFunctionSelected: (functionCode, operationName) {
onFunctionSelected:
(functionCode, operationName) {
RoutineTapFunctionHelper.onTapFunction(
context,
functionCode: functionCode,
functionOperationName: operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'temp_set',
'temp_current',
],
defaultValue: functionCode == 'temp_set'
? 200
: functionCode == 'temp_current'
? -100
: 0,
);
context,
functionCode: functionCode,
functionOperationName: operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'temp_set',
'temp_current',
],
defaultValue: 0);
},
),
),
@ -206,27 +203,61 @@ class ACHelper {
required String operationName,
bool? removeComparators,
}) {
final initialVal = selectedFunction == 'temp_set' ? 200 : -100;
final selectedFn =
acFunctions.firstWhere((f) => f.code == selectedFunction);
if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') {
final initialValue = selectedFunctionData?.value ?? initialVal;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
// Convert stored integer value to display value
final displayValue =
(selectedFunctionData?.value ?? selectedFn.min ?? 0) / 10;
final minValue = selectedFn.min! / 10;
final maxValue = selectedFn.max! / 10;
return CustomRoutinesTextbox(
withSpecialChar: true,
dividendOfRange: maxValue,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
removeComparators: removeComparators,
dialogType: selectedFn.type,
sliderRange: (minValue, maxValue),
displayedValue: displayValue.toStringAsFixed(1),
initialValue: displayValue.toDouble(),
unit: selectedFn.unit!,
onConditionChanged: (condition) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: selectedFn.operationName,
condition: condition,
value: 0,
step: selectedFn.step,
unit: selectedFn.unit,
max: selectedFn.max,
min: selectedFn.min,
),
),
),
onTextChanged: (value) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: selectedFn.operationName,
value: (value * 10).round(), // Store as integer
condition: selectedFunctionData?.condition,
step: selectedFn.step,
unit: selectedFn.unit,
max: selectedFn.max,
min: selectedFn.min,
),
),
),
stepIncreaseAmount: selectedFn.step! / 10, // Convert step for display
);
}
final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
context: context,
values: values,
values: selectedFn.getOperationalValues(),
selectedValue: selectedFunctionData?.value,
device: device,
operationName: operationName,
@ -235,150 +266,151 @@ class ACHelper {
);
}
/// Build temperature selector for AC functions dialog
static Widget _buildTemperatureSelector({
required BuildContext context,
required dynamic initialValue,
required String? currentCondition,
required String selectCode,
AllDevicesModel? device,
required String operationName,
DeviceFunctionData? selectedFunctionData,
bool? removeComparators,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (removeComparators != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildTemperatureDisplay(
context,
initialValue,
device,
operationName,
selectedFunctionData,
selectCode,
),
const SizedBox(height: 20),
_buildTemperatureSlider(
context,
initialValue,
device,
operationName,
selectedFunctionData,
selectCode,
),
],
);
}
// /// Build temperature selector for AC functions dialog
// static Widget _buildTemperatureSelector({
// required BuildContext context,
// required dynamic initialValue,
// required String? currentCondition,
// required String selectCode,
// AllDevicesModel? device,
// required String operationName,
// DeviceFunctionData? selectedFunctionData,
// bool? removeComparators,
// }) {
// return Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// if (removeComparators != true)
// _buildConditionToggle(
// context,
// currentCondition,
// selectCode,
// device,
// operationName,
// selectedFunctionData,
// ),
// const SizedBox(height: 20),
// _buildTemperatureDisplay(
// context,
// initialValue,
// device,
// operationName,
// selectedFunctionData,
// selectCode,
// ),
// const SizedBox(height: 20),
// _buildTemperatureSlider(
// context,
// initialValue,
// device,
// operationName,
// selectedFunctionData,
// selectCode,
// ),
// ],
// );
// }
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// /// Build condition toggle for AC functions dialog
// static Widget _buildConditionToggle(
// BuildContext context,
// String? currentCondition,
// String selectCode,
// AllDevicesModel? device,
// String operationName,
// DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
// // Function(String) onConditionChanged,
// ) {
// final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value ?? selectCode == 'temp_set'
? 200
: -100,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
// return ToggleButtons(
// onPressed: (int index) {
// context.read<FunctionBloc>().add(
// AddFunction(
// functionData: DeviceFunctionData(
// entityId: device?.uuid ?? '',
// functionCode: selectCode,
// operationName: operationName,
// condition: conditions[index],
// value: selectedFunctionData?.value ?? selectCode == 'temp_set'
// ? 200
// : -100,
// valueDescription: selectedFunctionData?.valueDescription,
// ),
// ),
// );
// },
// borderRadius: const BorderRadius.all(Radius.circular(8)),
// selectedBorderColor: ColorsManager.primaryColorWithOpacity,
// selectedColor: Colors.white,
// fillColor: ColorsManager.primaryColorWithOpacity,
// color: ColorsManager.primaryColorWithOpacity,
// constraints: const BoxConstraints(
// minHeight: 40.0,
// minWidth: 40.0,
// ),
// isSelected:
// conditions.map((c) => c == (currentCondition ?? "==")).toList(),
// children: conditions.map((c) => Text(c)).toList(),
// );
// }
/// Build temperature display for AC functions dialog
static Widget _buildTemperatureDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
final initialVal = selectCode == 'temp_set' ? 200 : -100;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
'${(initialValue ?? initialVal) / 10}°C',
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
// /// Build temperature display for AC functions dialog
// static Widget _buildTemperatureDisplay(
// BuildContext context,
// dynamic initialValue,
// AllDevicesModel? device,
// String operationName,
// DeviceFunctionData? selectedFunctionData,
// String selectCode,
// ) {
// final initialVal = selectCode == 'temp_set' ? 200 : -100;
// return Container(
// padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
// decoration: BoxDecoration(
// color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
// borderRadius: BorderRadius.circular(10),
// ),
// child: Text(
// '${(initialValue ?? initialVal) / 10}°C',
// style: context.textTheme.headlineMedium!.copyWith(
// color: ColorsManager.primaryColorWithOpacity,
// ),
// ),
// );
// }
static Widget _buildTemperatureSlider(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
return Slider(
value: initialValue is int ? initialValue.toDouble() : 200.0,
min: selectCode == 'temp_current' ? -100 : 200,
max: selectCode == 'temp_current' ? 900 : 300,
divisions: 10,
label: '${((initialValue ?? 160) / 10).toInt()}°C',
onChanged: (value) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
);
}
// static Widget _buildTemperatureSlider(
// BuildContext context,
// dynamic initialValue,
// AllDevicesModel? device,
// String operationName,
// DeviceFunctionData? selectedFunctionData,
// String selectCode,
// ) {
// return Slider(
// value: initialValue is int ? initialValue.toDouble() : 200.0,
// min: selectCode == 'temp_current' ? -100 : 200,
// max: selectCode == 'temp_current' ? 900 : 300,
// divisions: 10,
// label: '${((initialValue ?? 160) / 10).toInt()}°C',
// onChanged: (value) {
// context.read<FunctionBloc>().add(
// AddFunction(
// functionData: DeviceFunctionData(
// entityId: device?.uuid ?? '',
// functionCode: selectCode,
// operationName: operationName,
// value: value,
// condition: selectedFunctionData?.condition,
// valueDescription: selectedFunctionData?.valueDescription,
// ),
// ),
// );
// },
// );
// }
static Widget _buildOperationalValuesList({
required BuildContext context,
@ -414,7 +446,9 @@ class ACHelper {
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24,
color: isSelected
? ColorsManager.primaryColorWithOpacity
@ -430,7 +464,8 @@ class ACHelper {
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);

View File

@ -41,7 +41,8 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
void initState() {
super.initState();
_cpsFunctions = widget.functions.whereType<CpsFunctions>().where((function) {
_cpsFunctions =
widget.functions.whereType<CpsFunctions>().where((function) {
if (widget.dialogType == 'THEN') {
return function.type == 'THEN' || function.type == 'BOTH';
}
@ -149,6 +150,7 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
device: widget.device,
)
: CpsDialogSliderSelector(
step: selectedCpsFunctions.step!,
operations: operations,
selectedFunction: selectedFunction ?? '',
selectedFunctionData: selectedFunctionData,

View File

@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
@ -16,6 +17,7 @@ class CpsDialogSliderSelector extends StatelessWidget {
required this.device,
required this.operationName,
required this.dialogType,
required this.step,
super.key,
});
@ -26,13 +28,16 @@ class CpsDialogSliderSelector extends StatelessWidget {
final AllDevicesModel? device;
final String operationName;
final String dialogType;
final double step;
@override
Widget build(BuildContext context) {
return SliderValueSelector(
return CustomRoutinesTextbox(
withSpecialChar: false,
currentCondition: selectedFunctionData.condition,
dialogType: dialogType,
sliderRange: CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode),
sliderRange:
CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode),
displayedValue: CpsSliderHelpers.displayText(
value: selectedFunctionData.value,
functionCode: selectedFunctionData.functionCode,
@ -50,7 +55,7 @@ class CpsDialogSliderSelector extends StatelessWidget {
),
),
),
onSliderChanged: (value) => context.read<FunctionBloc>().add(
onTextChanged: (value) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
@ -64,6 +69,7 @@ class CpsDialogSliderSelector extends StatelessWidget {
dividendOfRange: CpsSliderHelpers.dividendOfRange(
selectedFunctionData.functionCode,
),
stepIncreaseAmount: step,
);
}
}

View File

@ -34,30 +34,33 @@ class CpsFunctionsList extends StatelessWidget {
itemBuilder: (context, index) {
final function = cpsFunctions[index];
return RoutineDialogFunctionListTile(
iconPath: function.icon,
operationName: function.operationName,
onTap: () => RoutineTapFunctionHelper.onTapFunction(
context,
functionCode: function.code,
functionOperationName: function.operationName,
functionValueDescription: selectedFunctionData?.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'static_max_dis',
'presence_reference',
'moving_reference',
'perceptual_boundary',
'moving_boundary',
'moving_rigger_time',
'moving_static_time',
'none_body_time',
'moving_max_dis',
'moving_range',
'presence_range',
if (dialogType == "IF") 'sensitivity',
],
),
);
iconPath: function.icon,
operationName: function.operationName,
onTap: () {
RoutineTapFunctionHelper.onTapFunction(
context,
step: function.step,
functionCode: function.code,
functionOperationName: function.operationName,
functionValueDescription:
selectedFunctionData?.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'static_max_dis',
'presence_reference',
'moving_reference',
'perceptual_boundary',
'moving_boundary',
'moving_rigger_time',
'moving_static_time',
'none_body_time',
'moving_max_dis',
'moving_range',
'presence_range',
if (dialogType == "IF") 'sensitivity',
],
);
});
},
),
);

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart';
@ -21,22 +22,20 @@ class FlushOperationalValuesList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(20),
itemCount: values.length,
itemBuilder: (context, index) =>
_buildValueItem(context, values[index]),
);
return ListView.builder(
padding: const EdgeInsets.all(20),
itemCount: values.length,
itemBuilder: (context, index) => _buildValueItem(context, values[index]),
);
}
Widget _buildValueItem(BuildContext context, FlushOperationalValue value) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SvgPicture.asset(value.icon, width: 25, height: 25),
Expanded(child: _buildValueDescription(value)),
_buildValueRadio(context, value),
],
@ -44,9 +43,6 @@ class FlushOperationalValuesList extends StatelessWidget {
);
}
Widget _buildValueDescription(FlushOperationalValue value) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
@ -60,6 +56,4 @@ class FlushOperationalValuesList extends StatelessWidget {
groupValue: selectedValue,
onChanged: (_) => onSelect(value));
}
}

View File

@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
@ -66,7 +67,8 @@ class FlushValueSelectorWidget extends StatelessWidget {
if (isDistanceDetection) {
initialValue = initialValue / 100;
}
return SliderValueSelector(
return CustomRoutinesTextbox(
withSpecialChar: true,
currentCondition: functionData.condition,
dialogType: dialogType,
sliderRange: sliderRange,
@ -83,7 +85,7 @@ class FlushValueSelectorWidget extends StatelessWidget {
),
),
),
onSliderChanged: (value) {
onTextChanged: (value) {
final roundedValue = _roundToStep(value, stepSize);
final finalValue =
isDistanceDetection ? (roundedValue * 100).toInt() : roundedValue;
@ -102,6 +104,7 @@ class FlushValueSelectorWidget extends StatelessWidget {
},
unit: _unit,
dividendOfRange: stepSize,
stepIncreaseAmount: stepSize,
);
}

View File

@ -8,6 +8,7 @@ abstract final class RoutineTapFunctionHelper {
static void onTapFunction(
BuildContext context, {
double? step,
required String functionCode,
required String functionOperationName,
required String? functionValueDescription,

View File

@ -4,11 +4,11 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -87,14 +87,15 @@ class OneGangSwitchHelper {
size: 16,
color: ColorsManager.textGray,
),
onTap: () =>
RoutineTapFunctionHelper.onTapFunction(
onTap: () => RoutineTapFunctionHelper
.onTapFunction(
context,
functionCode: function.code,
functionOperationName:
function.operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
selectedFunctionData
.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
@ -108,14 +109,16 @@ class OneGangSwitchHelper {
if (selectedFunction != null)
Expanded(
child: _buildValueSelector(
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
acFunctions: oneGangFunctions,
device: device,
operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors,
),
context: context,
selectedFunction: selectedFunction,
selectedFunctionData:
selectedFunctionData,
acFunctions: oneGangFunctions,
device: device,
operationName:
selectedOperationName ?? '',
removeComparetors: removeComparetors,
dialogType: dialogType),
),
],
),
@ -172,6 +175,7 @@ class OneGangSwitchHelper {
AllDevicesModel? device,
required String operationName,
required bool removeComparetors,
required String dialogType,
}) {
if (selectedFunction == 'countdown_1') {
final initialValue = selectedFunctionData?.value ?? 0;
@ -184,6 +188,7 @@ class OneGangSwitchHelper {
operationName: operationName,
selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors,
dialogType: dialogType,
);
}
final selectedFn = acFunctions.firstWhere(
@ -216,93 +221,18 @@ class OneGangSwitchHelper {
required String operationName,
DeviceFunctionData? selectedFunctionData,
required bool removeComparetors,
String? dialogType,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
selectedFunctionData, selectCode, dialogType!),
],
);
}
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
/// Build temperature display for AC functions dialog
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider(
BuildContext context,
dynamic initialValue,
@ -310,38 +240,47 @@ class OneGangSwitchHelper {
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
String dialogType,
) {
const twelveHoursInSeconds = 43200.0;
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: twelveHoursInSeconds,
stepValue: 1,
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
return CustomRoutinesTextbox(
withSpecialChar: false,
currentCondition: selectedFunctionData?.condition,
dialogType: dialogType,
sliderRange: (0, 43200),
displayedValue: (initialValue ?? 0).toString(),
initialValue: (initialValue ?? 0).toString(),
onConditionChanged: (condition) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
condition: condition,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue = value.round();
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
unit: 'sec',
dividendOfRange: 1,
stepIncreaseAmount: 1,
);
}
@ -377,7 +316,9 @@ class OneGangSwitchHelper {
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24,
color: isSelected
? ColorsManager.primaryColorWithOpacity
@ -393,7 +334,8 @@ class OneGangSwitchHelper {
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);

View File

@ -4,10 +4,10 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -86,20 +86,21 @@ class ThreeGangSwitchHelper {
size: 16,
color: ColorsManager.textGray,
),
onTap: () =>
RoutineTapFunctionHelper.onTapFunction(
onTap: () => RoutineTapFunctionHelper
.onTapFunction(
context,
functionCode: function.code,
functionOperationName:
function.operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
selectedFunctionData
.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
'countdown_2',
'countdown_3',
],
codesToAddIntoFunctionsWithDefaultValue:
function.code
.startsWith('countdown')
? [function.code]
: [],
),
);
},
@ -109,14 +110,16 @@ class ThreeGangSwitchHelper {
if (selectedFunction != null)
Expanded(
child: _buildValueSelector(
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
switchFunctions: switchFunctions,
device: device,
operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors,
),
context: context,
selectedFunction: selectedFunction,
selectedFunctionData:
selectedFunctionData,
switchFunctions: switchFunctions,
device: device,
operationName:
selectedOperationName ?? '',
removeComparetors: removeComparetors,
dialogType: dialogType),
),
],
),
@ -133,14 +136,6 @@ class ThreeGangSwitchHelper {
onConfirm: state.addedFunctions.isNotEmpty
? () {
/// add the functions to the routine bloc
// for (var function in state.addedFunctions) {
// context.read<RoutineBloc>().add(
// AddFunctionToRoutine(
// function,
// uniqueCustomId,
// ),
// );
// }
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
@ -173,24 +168,26 @@ class ThreeGangSwitchHelper {
AllDevicesModel? device,
required String operationName,
required bool removeComparetors,
required String dialogType,
}) {
if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2' ||
selectedFunction == 'countdown_3') {
final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors,
);
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors,
dialogType: dialogType);
}
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
final selectedFn =
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
@ -213,93 +210,18 @@ class ThreeGangSwitchHelper {
required String operationName,
DeviceFunctionData? selectedFunctionData,
bool? removeComparetors,
required String dialogType,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
selectedFunctionData, selectCode, dialogType),
],
);
}
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
/// Build temperature display for AC functions dialog
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider(
BuildContext context,
dynamic initialValue,
@ -307,38 +229,47 @@ class ThreeGangSwitchHelper {
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
String dialogType,
) {
const twelveHoursInSeconds = 43200.0;
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: twelveHoursInSeconds,
stepValue: 1,
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
return CustomRoutinesTextbox(
withSpecialChar: true,
currentCondition: selectedFunctionData?.condition,
dialogType: dialogType,
sliderRange: (0, 43200),
displayedValue: (initialValue ?? 0).toString(),
initialValue: (initialValue ?? 0).toString(),
onConditionChanged: (condition) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
condition: condition,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue = value.round();
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
unit: 'sec',
dividendOfRange: 1,
stepIncreaseAmount: 1,
);
}
@ -374,7 +305,9 @@ class ThreeGangSwitchHelper {
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24,
color: isSelected
? ColorsManager.primaryColorWithOpacity
@ -390,7 +323,8 @@ class ThreeGangSwitchHelper {
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);

View File

@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -86,14 +87,15 @@ class TwoGangSwitchHelper {
size: 16,
color: ColorsManager.textGray,
),
onTap: () =>
RoutineTapFunctionHelper.onTapFunction(
onTap: () => RoutineTapFunctionHelper
.onTapFunction(
context,
functionCode: function.code,
functionOperationName:
function.operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
selectedFunctionData
.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
@ -115,6 +117,7 @@ class TwoGangSwitchHelper {
device: device,
operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors,
dialogType: dialogType,
),
),
],
@ -172,22 +175,25 @@ class TwoGangSwitchHelper {
AllDevicesModel? device,
required String operationName,
required bool removeComparetors,
required String dialogType,
}) {
if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors,
);
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors,
dialogType: dialogType);
}
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
final selectedFn =
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
@ -210,25 +216,13 @@ class TwoGangSwitchHelper {
required String operationName,
DeviceFunctionData? selectedFunctionData,
bool? removeComparetors,
String? dialogType,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
selectedFunctionData, selectCode, dialogType!),
],
);
}
@ -269,7 +263,8 @@ class TwoGangSwitchHelper {
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
isSelected:
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
@ -304,38 +299,48 @@ class TwoGangSwitchHelper {
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
String dialogType,
) {
const twelveHoursInSeconds = 43200.0;
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: twelveHoursInSeconds,
stepValue: 1,
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
return CustomRoutinesTextbox(
withSpecialChar: true,
currentCondition: selectedFunctionData?.condition,
dialogType: dialogType,
sliderRange: (0, 43200),
displayedValue: (initialValue ?? 0).toString(),
initialValue: (initialValue ?? 0).toString(),
onConditionChanged: (condition) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
condition: condition,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue =
value.round(); // Round to nearest integer (stepSize 1)
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
unit: 'sec',
dividendOfRange: 1,
stepIncreaseAmount: 1,
);
}
@ -371,7 +376,9 @@ class TwoGangSwitchHelper {
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24,
color: isSelected
? ColorsManager.primaryColorWithOpacity
@ -387,7 +394,8 @@ class TwoGangSwitchHelper {
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);

View File

@ -4,8 +4,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
class WpsValueSelectorWidget extends StatelessWidget {
final String selectedFunction;
@ -27,11 +27,13 @@ class WpsValueSelectorWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final selectedFn = wpsFunctions.firstWhere((f) => f.code == selectedFunction);
final selectedFn =
wpsFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
if (_isSliderFunction(selectedFunction)) {
return SliderValueSelector(
return CustomRoutinesTextbox(
withSpecialChar: false,
currentCondition: functionData.condition,
dialogType: dialogType,
sliderRange: sliderRange,
@ -48,7 +50,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
),
),
),
onSliderChanged: (value) => context.read<FunctionBloc>().add(
onTextChanged: (value) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
@ -61,6 +63,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
),
unit: _unit,
dividendOfRange: 1,
stepIncreaseAmount: _steps,
);
}
@ -99,4 +102,10 @@ class WpsValueSelectorWidget extends StatelessWidget {
'illuminance_value' => 'Lux',
_ => '',
};
double get _steps => switch (functionData.functionCode) {
'presence_time' => 1,
'dis_current' => 1,
'illuminance_value' => 1,
_ => 1,
};
}

View File

@ -176,6 +176,7 @@ class _WaterHeaterDialogRoutinesState extends State<WaterHeaterDialogRoutines> {
functionData: functionData,
whFunctions: _waterHeaterFunctions,
device: widget.device,
dialogType: widget.dialogType,
),
);
}

View File

@ -2,25 +2,24 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/water_heater/water_heater_operational_values_list.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class WaterHeaterValueSelectorWidget extends StatelessWidget {
final String selectedFunction;
final DeviceFunctionData functionData;
final List<WaterHeaterFunctions> whFunctions;
final AllDevicesModel? device;
final String dialogType;
const WaterHeaterValueSelectorWidget({
required this.selectedFunction,
required this.functionData,
required this.whFunctions,
required this.device,
required this.dialogType,
super.key,
});
@ -39,22 +38,6 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildConditionToggle(
context,
functionData.condition,
selectedFunction,
device,
selectedFn.operationName,
functionData,
),
_buildCountDownDisplay(
context,
functionData.value,
device,
selectedFn.operationName,
functionData,
selectedFunction,
),
_buildCountDownSlider(
context,
functionData.value,
@ -62,6 +45,7 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget {
selectedFn.operationName,
functionData,
selectedFunction,
dialogType
),
const SizedBox(height: 10),
],
@ -90,28 +74,6 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget {
);
}
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider(
BuildContext context,
dynamic initialValue,
@ -119,78 +81,47 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget {
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
String dialogType,
) {
const twelveHoursInSeconds = 43200.0;
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: twelveHoursInSeconds,
stepValue: 1,
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) -
(operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
return CustomRoutinesTextbox(
withSpecialChar: false,
currentCondition: selectedFunctionData?.condition,
dialogType: dialogType,
sliderRange: (0, 43200),
displayedValue: (initialValue ?? 0).toString(),
initialValue: (initialValue ?? 0).toString(),
onConditionChanged: (condition) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
value: condition,
condition: condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue = value.round();
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
);
}
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected:
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
unit: 'sec',
dividendOfRange: 1,
stepIncreaseAmount: 1,
);
}
}

View File

@ -32,7 +32,7 @@ class VisitorPasswordDialog extends StatelessWidget {
.stateDialog(
context: context,
message: 'Password Created Successfully',
title: 'Send Success',
title: 'Sent Successfully',
widgeta: Column(
children: [
if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty)
@ -95,7 +95,7 @@ class VisitorPasswordDialog extends StatelessWidget {
visitorBloc.stateDialog(
context: context,
message: state.message,
title: 'Something Wrong',
title: 'Something went wrong',
);
}
},

View File

@ -2,6 +2,7 @@ class Assets {
Assets._();
static const String background = "assets/images/Background.png";
static const String webBackground = "assets/images/web_Background.svg";
static const String webBackgroundPng = "assets/images/web_Background.png";
static const String blackLogo = "assets/images/black-logo.png";
static const String logo = "assets/images/Logo.svg";
static const String logoHorizontal = "assets/images/logo_horizontal.png";
@ -13,13 +14,14 @@ class Assets {
static const String rightLine = "assets/images/right_line.png";
static const String google = "assets/images/google.svg";
static const String facebook = "assets/images/facebook.svg";
static const String invisiblePassword =
"assets/images/Password_invisible.svg";
static const String invisiblePassword = "assets/images/Password_invisible.svg";
static const String visiblePassword = "assets/images/password_visible.svg";
static const String accessIcon = "assets/images/access_icon.svg";
static const String spaseManagementIcon =
"assets/images/spase_management_icon.svg";
static const String devicesIcon = "assets/images/devices_icon.svg";
static const String analyticsIcon = "assets/icons/landing_analytics.svg";
static const String moveinIcon = "assets/images/movein_icon.svg";
static const String constructionIcon = "assets/images/construction_icon.svg";
static const String energyIcon = "assets/images/energy_icon.svg";
@ -31,8 +33,7 @@ class Assets {
static const String emptyTable = "assets/images/empty_table.svg";
// General assets
static const String motionlessDetection =
"assets/icons/motionless_detection.svg";
static const String motionlessDetection = "assets/icons/motionless_detection.svg";
static const String acHeating = "assets/icons/ac_heating.svg";
static const String acPowerOff = "assets/icons/ac_power_off.svg";
static const String acFanMiddle = "assets/icons/ac_fan_middle.svg";
@ -69,22 +70,19 @@ class Assets {
"assets/icons/automation_functions/temp_password_unlock.svg";
static const String doorlockNormalOpen =
"assets/icons/automation_functions/doorlock_normal_open.svg";
static const String doorbell =
"assets/icons/automation_functions/doorbell.svg";
static const String doorbell = "assets/icons/automation_functions/doorbell.svg";
static const String remoteUnlockViaApp =
"assets/icons/automation_functions/remote_unlock_via_app.svg";
static const String doubleLock =
"assets/icons/automation_functions/double_lock.svg";
static const String selfTestResult =
"assets/icons/automation_functions/self_test_result.svg";
static const String lockAlarm =
"assets/icons/automation_functions/lock_alarm.svg";
static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg";
static const String presenceState =
"assets/icons/automation_functions/presence_state.svg";
static const String currentTemp =
"assets/icons/automation_functions/current_temp.svg";
static const String presence =
"assets/icons/automation_functions/presence.svg";
static const String presence = "assets/icons/automation_functions/presence.svg";
static const String residualElectricity =
"assets/icons/automation_functions/residual_electricity.svg";
static const String hijackAlarm =
@ -101,15 +99,12 @@ class Assets {
// Presence Sensor Assets
static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg";
static const String sensorPresenceIcon =
"assets/icons/sensor_presence_ic.svg";
static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg";
static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg";
static const String illuminanceRecordIcon =
"assets/icons/illuminance_record_ic.svg";
static const String presenceRecordIcon =
"assets/icons/presence_record_ic.svg";
static const String helpDescriptionIcon =
"assets/icons/help_description_ic.svg";
static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg";
static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg";
static const String lightPulp = "assets/icons/light_pulb.svg";
static const String acDevice = "assets/icons/ac_device.svg";
@ -159,12 +154,10 @@ class Assets {
static const String unit = 'assets/icons/unit_icon.svg';
static const String villa = 'assets/icons/villa_icon.svg';
static const String iconEdit = 'assets/icons/icon_edit_icon.svg';
static const String textFieldSearch =
'assets/icons/textfield_search_icon.svg';
static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg';
static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg';
static const String addIcon = 'assets/icons/add_icon.svg';
static const String smartThermostatIcon =
'assets/icons/smart_thermostat_icon.svg';
static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg';
static const String smartLightIcon = 'assets/icons/smart_light_icon.svg';
static const String presenceSensor = 'assets/icons/presence_sensor.svg';
static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg';
@ -212,8 +205,7 @@ class Assets {
//assets/icons/water_leak_normal.svg
static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg';
//assets/icons/water_leak_detected.svg
static const String waterLeakDetected =
'assets/icons/water_leak_detected.svg';
static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg';
//assets/icons/automation_records.svg
static const String automationRecords = 'assets/icons/automation_records.svg';
@ -284,16 +276,13 @@ class Assets {
"assets/icons/functions_icons/sensitivity.svg";
static const String assetsSensitivityOperationIcon =
"assets/icons/functions_icons/sesitivity_operation_icon.svg";
static const String assetsAcPower =
"assets/icons/functions_icons/ac_power.svg";
static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg";
static const String assetsAcPowerOFF =
"assets/icons/functions_icons/ac_power_off.svg";
static const String assetsChildLock =
"assets/icons/functions_icons/child_lock.svg";
static const String assetsFreezing =
"assets/icons/functions_icons/freezing.svg";
static const String assetsFanSpeed =
"assets/icons/functions_icons/fan_speed.svg";
static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg";
static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg";
static const String assetsAcCooling =
"assets/icons/functions_icons/ac_cooling.svg";
static const String assetsAcHeating =
@ -302,8 +291,7 @@ class Assets {
"assets/icons/functions_icons/celsius_degrees.svg";
static const String assetsTempreture =
"assets/icons/functions_icons/tempreture.svg";
static const String assetsAcFanLow =
"assets/icons/functions_icons/ac_fan_low.svg";
static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg";
static const String assetsAcFanMiddle =
"assets/icons/functions_icons/ac_fan_middle.svg";
static const String assetsAcFanHigh =
@ -322,8 +310,7 @@ class Assets {
"assets/icons/functions_icons/far_detection.svg";
static const String assetsFarDetectionFunction =
"assets/icons/functions_icons/far_detection_function.svg";
static const String assetsIndicator =
"assets/icons/functions_icons/indicator.svg";
static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg";
static const String assetsMotionDetection =
"assets/icons/functions_icons/motion_detection.svg";
static const String assetsMotionlessDetection =
@ -336,8 +323,7 @@ class Assets {
"assets/icons/functions_icons/master_state.svg";
static const String assetsSwitchAlarmSound =
"assets/icons/functions_icons/switch_alarm_sound.svg";
static const String assetsResetOff =
"assets/icons/functions_icons/reset_off.svg";
static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg";
// Assets for automation_functions
static const String assetsCardUnlock =
@ -381,14 +367,12 @@ class Assets {
static const String activeUser = 'assets/icons/active_user.svg';
static const String deActiveUser = 'assets/icons/deactive_user.svg';
static const String invitedIcon = 'assets/icons/invited_icon.svg';
static const String rectangleCheckBox =
'assets/icons/rectangle_check_box.png';
static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png';
static const String CheckBoxChecked = 'assets/icons/box_checked.png';
static const String emptyBox = 'assets/icons/empty_box.png';
static const String completeProcessIcon =
'assets/icons/compleate_process_icon.svg';
static const String currentProcessIcon =
'assets/icons/current_process_icon.svg';
static const String currentProcessIcon = 'assets/icons/current_process_icon.svg';
static const String uncomplete_ProcessIcon =
'assets/icons/uncompleate_process_icon.svg';
static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg';
@ -409,11 +393,9 @@ class Assets {
static const String successIcon = 'assets/icons/success_icon.svg';
static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png';
static const String scenesPlayIconCheck =
'assets/icons/scenesPlayIconCheck.png';
static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png';
static const String presenceStateIcon = 'assets/icons/presence_state.svg';
static const String currentDistanceIcon =
'assets/icons/current_distance_icon.svg';
static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg';
static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg';
static const String motionDetectionSensitivityIcon =
@ -436,44 +418,29 @@ class Assets {
static const String cpsMode4 = 'assets/icons/cps_mode4.svg';
static const String closeToMotion = 'assets/icons/close_to_motion.svg';
static const String farAwayMotion = 'assets/icons/far_away_motion.svg';
static const String communicationFault =
'assets/icons/communication_fault.svg';
static const String communicationFault = 'assets/icons/communication_fault.svg';
static const String radarFault = 'assets/icons/radar_fault.svg';
static const String selfTestingSuccess =
'assets/icons/self_testing_success.svg';
static const String selfTestingFailure =
'assets/icons/self_testing_failure.svg';
static const String selfTestingTimeout =
'assets/icons/self_testing_timeout.svg';
static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg';
static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg';
static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg';
static const String movingSpeed = 'assets/icons/moving_speed.svg';
static const String boundary = 'assets/icons/boundary.svg';
static const String motionMeter = 'assets/icons/motion_meter.svg';
static const String spatialStaticValue =
'assets/icons/spatial_static_value.svg';
static const String spatialMotionValue =
'assets/icons/spatial_motion_value.svg';
static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg';
static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg';
static const String presenceJudgementThrshold =
'assets/icons/presence_judgement_threshold.svg';
static const String spaceType = 'assets/icons/space_type.svg';
static const String sportsPara = 'assets/icons/sports_para.svg';
static const String sensitivityFeature1 =
'assets/icons/sensitivity_feature_1.svg';
static const String sensitivityFeature2 =
'assets/icons/sensitivity_feature_2.svg';
static const String sensitivityFeature3 =
'assets/icons/sensitivity_feature_3.svg';
static const String sensitivityFeature4 =
'assets/icons/sensitivity_feature_4.svg';
static const String sensitivityFeature5 =
'assets/icons/sensitivity_feature_5.svg';
static const String sensitivityFeature6 =
'assets/icons/sensitivity_feature_6.svg';
static const String sensitivityFeature7 =
'assets/icons/sensitivity_feature_7.svg';
static const String sensitivityFeature8 =
'assets/icons/sensitivity_feature_8.svg';
static const String sensitivityFeature9 =
'assets/icons/sensitivity_feature_9.svg';
static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg';
static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg';
static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg';
static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg';
static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg';
static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg';
static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg';
static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg';
static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg';
static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg';
static const String targetConfirmTimeIcon =
'assets/icons/target_confirm_time_icon.svg';
@ -481,5 +448,5 @@ class Assets {
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
static const String blankCalendar = 'assets/icons/blank_calendar.svg';
static const String refreshStatusIcon = 'assets/icons/refresh_status_icon.svg';
static const String refreshStatusIcon = 'assets/icons/refresh_status_icon.svg';
}

View File

@ -41,13 +41,24 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
_isDropdownOpen = false;
});
},
child: Transform.rotate(
angle: _isDropdownOpen ? -1.5708 : 1.5708,
child: const Icon(
Icons.arrow_forward_ios,
color: Colors.white,
size: 16,
),
child: Row(
children: [
const SizedBox(width: 12),
if (widget.user != null)
Text(
'${widget.user!.firstName} ${widget.user!.lastName}',
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(width: 12),
Transform.rotate(
angle: _isDropdownOpen ? -1.5708 : 1.5708,
child: const Icon(
Icons.arrow_forward_ios,
color: Colors.white,
size: 16,
),
),
],
),
),
],

View File

@ -92,13 +92,6 @@ class DesktopAppBar extends StatelessWidget {
if (rightBody != null) rightBody!,
const SizedBox(width: 24),
_UserAvatar(),
const SizedBox(width: 12),
if (user != null)
Text(
'${user.firstName} ${user.lastName}',
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(width: 12),
UserDropdownMenu(user: user),
],
);
@ -146,14 +139,6 @@ class TabletAppBar extends StatelessWidget {
if (rightBody != null) rightBody!,
const SizedBox(width: 16),
_UserAvatar(),
if (user != null) ...[
const SizedBox(width: 8),
Text(
'${user.firstName} ${user.lastName}',
style:
Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 14),
),
],
UserDropdownMenu(user: user),
],
);
@ -215,14 +200,6 @@ class MobileAppBar extends StatelessWidget {
return Row(
children: [
_UserAvatar(),
if (user != null) ...[
const SizedBox(width: 8),
Text(
'${user.firstName} ${user.lastName}',
style:
Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 14),
),
],
UserDropdownMenu(user: user),
],
);

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/web_layout/web_app_bar.dart';
import 'menu_sidebar.dart';
class WebScaffold extends StatelessWidget with HelperResponsiveLayout {
@ -28,14 +28,11 @@ class WebScaffold extends StatelessWidget with HelperResponsiveLayout {
SizedBox(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
child: SvgPicture.asset(
Assets.webBackground,
child: Image.asset(
Assets.webBackgroundPng,
fit: BoxFit.cover,
),
),
Container(
color: Colors.white.withOpacity(0.7),
),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [

View File

@ -1,906 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: e051259913915ea5bc8fe18664596bea08592fd123930605d562969cd7315fcd
url: "https://pub.dev"
source: hosted
version: "1.3.51"
args:
dependency: transitive
description:
name: args
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
bloc:
dependency: "direct main"
description:
name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev"
source: hosted
version: "8.1.4"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.19.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
csslib:
dependency: transitive
description:
name: csslib
sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f"
url: "https://pub.dev"
source: hosted
version: "0.17.3"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.8"
data_table_2:
dependency: "direct main"
description:
name: data_table_2
sha256: f02ec9b24f44420816a87370ff4f4e533e15b274f6267e4c9a88a585ad1a0473
url: "https://pub.dev"
source: hosted
version: "2.5.15"
dio:
dependency: "direct main"
description:
name: dio
sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714
url: "https://pub.dev"
source: hosted
version: "5.5.0+1"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
dropdown_button2:
dependency: "direct main"
description:
name: dropdown_button2
sha256: b0fe8d49a030315e9eef6c7ac84ca964250155a6224d491c1365061bc974a9e1
url: "https://pub.dev"
source: hosted
version: "2.3.9"
dropdown_search:
dependency: "direct main"
description:
name: dropdown_search
sha256: "55106e8290acaa97ed15bea1fdad82c3cf0c248dd410e651f5a8ac6870f783ab"
url: "https://pub.dev"
source: hosted
version: "5.0.6"
equatable:
dependency: "direct main"
description:
name: equatable
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
url: "https://pub.dev"
source: hosted
version: "2.0.5"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
firebase_analytics:
dependency: "direct main"
description:
name: firebase_analytics
sha256: "47428047a0778f72af53a3c7cb5d556e1cb25e2327cc8aa40d544971dc6245b2"
url: "https://pub.dev"
source: hosted
version: "11.4.2"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: "1076f4b041f76143e14878c70f0758f17fe5910c0cd992db9e93bd3c3584512b"
url: "https://pub.dev"
source: hosted
version: "4.3.2"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: "8f6dd64ea6d28b7f5b9e739d183a9e1c7f17027794a3e9aba1879621d42426ef"
url: "https://pub.dev"
source: hosted
version: "0.5.10+8"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "93dc4dd12f9b02c5767f235307f609e61ed9211047132d07f9e02c668f0bfc33"
url: "https://pub.dev"
source: hosted
version: "3.11.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf
url: "https://pub.dev"
source: hosted
version: "5.4.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "0e13c80f0de8acaa5d0519cbe23c8b4cc138a2d5d508b5755c861bdfc9762678"
url: "https://pub.dev"
source: hosted
version: "2.20.0"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
sha256: "6273ed71bcd8a6fb4d0ca13d3abddbb3301796807efaad8782b5f90156f26f03"
url: "https://pub.dev"
source: hosted
version: "4.3.2"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: "94f3986e1a10e5a883f2ad5e3d719aef98a8a0f9a49357f6e45b7d3696ea6a97"
url: "https://pub.dev"
source: hosted
version: "3.8.2"
firebase_database:
dependency: "direct main"
description:
name: firebase_database
sha256: cd2354dfef68e52c0713b5efbb7f4e10dfc2aff2f945c7bc8db34d1934170627
url: "https://pub.dev"
source: hosted
version: "11.3.2"
firebase_database_platform_interface:
dependency: transitive
description:
name: firebase_database_platform_interface
sha256: d430983f4d877c9f72f88b3d715cca9a50021dd7ccd8e3ae6fb79603853317de
url: "https://pub.dev"
source: hosted
version: "0.2.6+2"
firebase_database_web:
dependency: transitive
description:
name: firebase_database_web
sha256: f64edae62c5beaa08e9e611a0736d64ab11a812983a0aa132695d2d191311ea7
url: "https://pub.dev"
source: hosted
version: "0.2.6+8"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
fl_chart:
dependency: "direct main"
description:
name: fl_chart
sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef"
url: "https://pub.dev"
source: hosted
version: "0.69.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_bloc:
dependency: "direct main"
description:
name: flutter_bloc
sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2
url: "https://pub.dev"
source: hosted
version: "8.1.5"
flutter_dotenv:
dependency: "direct main"
description:
name: flutter_dotenv
sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77"
url: "https://pub.dev"
source: hosted
version: "5.1.0"
flutter_html:
dependency: "direct main"
description:
name: flutter_html
sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee"
url: "https://pub.dev"
source: hosted
version: "3.0.0-beta.2"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0"
url: "https://pub.dev"
source: hosted
version: "9.2.2"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
url: "https://pub.dev"
source: hosted
version: "1.2.1"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
url: "https://pub.dev"
source: hosted
version: "2.0.10+1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
get_it:
dependency: "direct main"
description:
name: get_it
sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1
url: "https://pub.dev"
source: hosted
version: "7.7.0"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459"
url: "https://pub.dev"
source: hosted
version: "14.2.7"
graphview:
dependency: "direct main"
description:
name: graphview
sha256: bdba183583b23c30c71edea09ad5f0beef612572d3e39e855467a925bd08392f
url: "https://pub.dev"
source: hosted
version: "1.2.0"
html:
dependency: transitive
description:
name: html
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
url: "https://pub.dev"
source: hosted
version: "0.15.4"
http:
dependency: transitive
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.19.0"
intl_phone_field:
dependency: "direct main"
description:
name: intl_phone_field
sha256: "73819d3dfcb68d2c85663606f6842597c3ddf6688ac777f051b17814fe767bbf"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
url: "https://pub.dev"
source: hosted
version: "3.0.0"
list_counter:
dependency: transitive
description:
name: list_counter
sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237
url: "https://pub.dev"
source: hosted
version: "1.0.2"
logging:
dependency: transitive
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.15.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
number_pagination:
dependency: "direct main"
description:
name: number_pagination
sha256: "75d3a28616196e7c8df431d0fb7c48e811e462155f4cf3b5b4167b3408421327"
url: "https://pub.dev"
source: hosted
version: "1.1.6"
path:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
url: "https://pub.dev"
source: hosted
version: "1.0.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
url: "https://pub.dev"
source: hosted
version: "2.1.3"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: e84c8a53fe1510ef4582f118c7b4bdf15b03002b51d7c2b66983c65843d61193
url: "https://pub.dev"
source: hosted
version: "2.2.8"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev"
source: hosted
version: "2.4.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
provider:
dependency: transitive
description:
name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "6.1.2"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: c3f888ba2d659f3e75f4686112cc1e71f46177f74452d40d8307edc332296ead
url: "https://pub.dev"
source: hosted
version: "2.3.0"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.12.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.7.3"
time_picker_spinner:
dependency: "direct main"
description:
name: time_picker_spinner
sha256: "53d824801d108890d22756501e7ade9db48b53dac1ec41580499dd4ebd128e3c"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.dev"
source: hosted
version: "6.3.1"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
url: "https://pub.dev"
source: hosted
version: "6.3.14"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
uuid:
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "14.3.0"
web:
dependency: transitive
description:
name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "1.1.0"
win32:
dependency: transitive
description:
name: win32
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
url: "https://pub.dev"
source: hosted
version: "5.5.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.4"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
sdks:
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.27.0"