Compare commits

..

49 Commits

Author SHA1 Message Date
f7f3843fa7 Enhance device management UI with improved padding and SVG icon integration 2025-06-10 16:29:14 +03:00
c6b55cb28b Sp 1594 device location api integration (#216)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1594](https://syncrow.atlassian.net/browse/SP-1594)

## Description

1. Implemented `Bloc` and `Services` to integrate the device location
into the side panel of the AQI Analytics module.
2. Fixed bugs in side panel caused by `Expanded` widgets.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1594]:
https://syncrow.atlassian.net/browse/SP-1594?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-04 16:41:08 +03:00
bfd8e964f7 SP-1665-FE-Return-a-readable-error-when-a-connection-error-Exception-… (#233)
…occurs-on-the-charts.

<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1665](https://syncrow.atlassian.net/browse/SP-1665)

## Description

Returns a readable error when an analytics api fails.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1665]:
https://syncrow.atlassian.net/browse/SP-1665?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-04 16:40:25 +03:00
08725201d5 Dev ci/cd enhancements (#234)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

Pulled CI/CD changes from `main` branch into `dev` branch.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [x] 🗑️ Chore
2025-06-04 16:10:00 +03:00
7fe34c61b2 correctly fetch energy management data using spaceUuid. (#235)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1495](https://syncrow.atlassian.net/browse/SP-1495)

## Description

correctly fetch energy management data using `spaceUuid`.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)

[SP-1495]:
https://syncrow.atlassian.net/browse/SP-1495?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-04 16:09:10 +03:00
0c6e4fed80 correctly fetch energy management data using spaceUuid. 2025-06-04 15:09:40 +03:00
69c23525ba Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1665-FE-Return-a-readable-error-when-a-connection-error-Exception-occurs-on-the-charts 2025-06-04 14:49:48 +03:00
3e32968209 Update pull_request_template.md 2025-06-04 14:47:35 +03:00
beb5239c4f enhanced ci/cd by not running the deply jobs on the PR itself, and now we only deploy when we merged a PR to dev or main, and created a separate GitHub action that only builds and install dependencies, which only runs on the PR itself. 2025-06-04 14:47:35 +03:00
3a98f71ff3 SP-1665-FE-Return-a-readable-error-when-a-connection-error-Exception-occurs-on-the-charts. 2025-06-04 14:42:41 +03:00
24a7f3ac2a SP-1594-device-location-api-integration. 2025-06-04 13:06:27 +03:00
ad8e06ac40 Sp 1457 fe edit popup buttons labels to be the same as the design (#231)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1457](https://syncrow.atlassian.net/browse/SP-1457)

## Description

<!--- Describe your changes in detail -->
Edit popup buttons labels to be the same as the design and change
setting icon

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [X]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1457]:
https://syncrow.atlassian.net/browse/SP-1457?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-04 11:05:37 +03:00
5f8eb9de06 Add settings button SVG and refactor settings icon implementation in dynamic table 2025-06-04 10:32:31 +03:00
79b974ee6c re-injected AirQualityDistributionBloc into AnalyticsPage. 2025-06-04 09:36:41 +03:00
651ac6785e removed open weather api keys from .env files. 2025-06-04 09:32:56 +03:00
9fa59ce78b Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1594-device-location-api-integration 2025-06-04 09:29:49 +03:00
e2c44ba85f injected the remote and reverse geocoder dependenies into DeviceLocationBloc. 2025-06-04 09:28:50 +03:00
1edeb664aa Connected data coming from DeviceLocationBloc into the respective widgets. 2025-06-04 09:28:16 +03:00
25a55ad820 made GetDeviceLocationDataParam.toJson method have the correct keys for the API. 2025-06-04 09:27:46 +03:00
e48fc8b82c loads and clears DeviceLocationBloc. 2025-06-04 09:27:21 +03:00
8e8fdf0fc6 Rename dialog buttons for clarity: 'Cancel' to 'Back' and 'Confirm' to 'Save' 2025-06-04 09:26:56 +03:00
8d999f118c Connected RemoteDeviceLocationService to the new BE API, instead of directly fetching the data from OpenWeather Api's. 2025-06-04 09:18:28 +03:00
bcb6e49a01 Deleted FakeDeviceLocationService class, since it is no longer needed. 2025-06-04 09:17:38 +03:00
0d0d51463d Merge pull request #224 from SyncrowIOT/SP-1597-FE-Add-Device-Settings-Column-and-Build-Device-Settings-Dialog-UI
Sp 1597 fe add device settings column and build device settings dialog UI
2025-06-03 16:56:24 +03:00
2797dce637 Rename SettingBlocEvent to SettingEvent for consistency and clarity in event handling. 2025-06-03 16:55:24 +03:00
8827f571f4 Merge pull request #229 from SyncrowIOT/SP-1658-The-Analytics-charts-padding-is-not-aligned-with-the-design
Sp 1658 the analytics charts padding is not aligned with the design
2025-06-03 16:51:05 +03:00
7472aff704 Merge pull request #228 from SyncrowIOT/SP-1671-energy-consumption-api-returns-a-sibling-space-data-instead-of-selected-space-on-analytics-page
SP-1671-energy-consumption-api-returns-a-sibling-space-data-instead-of-selected-space-on-analytics-page
2025-06-03 16:48:26 +03:00
575ba2aed2 Merge pull request #227 from SyncrowIOT/SP-1509-Attatch-SpaceUuid-To-Dropdown
SP-1509 attatch space uuid to analytics device dropdown on energy man…
2025-06-03 16:46:53 +03:00
eb708edc83 Merge pull request #226 from SyncrowIOT/SP-1510-show-date-on-bottom-titles-of-occupancy-chart
SP-1510-show date instead of index in occupancy chart.
2025-06-03 16:44:56 +03:00
906c2d0430 Refactor device management and space management APIs, update event and state classes, and add RemoveDeviceWidget for device removal functionality. 2025-06-03 16:34:00 +03:00
0135b6711e removed getting energy management data using communityUuid. 2025-06-03 16:01:45 +03:00
46feb0ea28 SP-1509 attatch space uuid to analytics device dropdown on energy management tab. 2025-06-03 15:20:30 +03:00
7cc46d464f SP-1510-show date instead of index in occupancy chart. 2025-06-03 12:24:38 +03:00
cabd37a08a remove un use code 2025-06-02 13:30:26 +03:00
98ad4246e1 Merge branch 'dev' into SP-1597-FE-Add-Device-Settings-Column-and-Build-Device-Settings-Dialog-UI 2025-06-02 13:00:28 +03:00
ba08fcf71f Refactor debug print statements in space management API 2025-06-02 12:58:11 +03:00
cf5e05a888 Refactor code by adding new API endpoint for assigning a device to a room and removing redundant code in device management settings. 2025-06-02 12:52:48 +03:00
b0ed844893 made events and state class finals, to better document that they shouldn't be extended. 2025-06-01 14:37:22 +03:00
3d183528c5 Fixed thrown exceptions because of Expanded widgets. 2025-06-01 10:57:49 +03:00
2c4da63266 Injected DeviceLocationBloc into AnalyticsPage. 2025-06-01 10:50:51 +03:00
4ebe65f820 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1594-device-location-api-integration 2025-06-01 09:51:32 +03:00
5654d66b60 Created a remote implementation for DeviceLocationService. 2025-06-01 09:51:08 +03:00
b6879035f0 Implemented geocoding functionality to retrieve and manage device location data using the newly added geocoding package. 2025-05-29 15:47:34 +03:00
8ad048e18d Added geocoding: ^4.0.0 package. 2025-05-29 15:47:24 +03:00
d92b699a2b Created a bloc for loading and managing the state of device location data. 2025-05-29 15:40:44 +03:00
6ffb677c33 Created an interface and its fake implementation for loading device location data. 2025-05-29 15:40:15 +03:00
e7476a084d Created a model class for loading device location data. 2025-05-29 15:39:51 +03:00
511acc186f Created a param class for loading device location data. 2025-05-29 15:39:44 +03:00
a44d4231f1 Add new grey color constant and new icons for settings in assets
Update CreateNewRoutineView to use const constructor
Add SubSpaceModel class for device settings
Add DefaultContainer widget for web layout
Add events and states for device settings bloc
Update API endpoints for device settings
2025-05-29 14:26:24 +03:00
66 changed files with 2317 additions and 288 deletions

View File

@ -7,11 +7,7 @@
-->
## Jira Ticket
<!-- Add your Jira ticket number as a link (e.g., [PROJ-123](https://jira.company.com/browse/PROJ-123)) -->
## Status
**READY/IN DEVELOPMENT/HOLD**
[SP-0000](https://syncrow.atlassian.net/browse/SP-0000)
## Description

View File

@ -4,10 +4,6 @@ on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
jobs:
build_and_deploy_job:

View File

@ -4,18 +4,12 @@ on:
push:
branches:
- dev
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- dev
jobs:
build_and_deploy_job:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:

29
.github/workflows/pr-check.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Pull Request Check
on:
pull_request:
branches:
- dev
- main
jobs:
setup_flutter:
runs-on: ubuntu-latest
name: Setup Flutter and Dependencies
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
submodules: true
lfs: false
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.27.3'
- name: Install dependencies
run: flutter pub get
- name: Run Flutter Build
run: flutter build web --web-renderer canvaskit -t lib/main_dev.dart

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 24C8.79469 24 5.78123 22.7518 3.51469 20.4853C1.24823 18.2188 0 15.2053 0 12C0 8.79469 1.24823 5.78123 3.51469 3.51469C5.78123 1.24823 8.79469 0 12 0C15.2053 0 18.2188 1.24823 20.4853 3.51469C22.7518 5.78123 24 8.79469 24 12C24 15.2053 22.7518 18.2188 20.4853 20.4853C18.2188 22.7518 15.2053 24 12 24ZM12 1.875C9.2955 1.875 6.75291 2.92819 4.84055 4.84055C2.92819 6.75291 1.875 9.2955 1.875 12C1.875 14.7045 2.92819 17.2471 4.84055 19.1595C6.75291 21.0718 9.2955 22.125 12 22.125C14.7045 22.125 17.2471 21.0718 19.1595 19.1595C21.0718 17.2471 22.125 14.7045 22.125 12C22.125 9.2955 21.0718 6.75291 19.1595 4.84055C17.2471 2.92819 14.7045 1.875 12 1.875ZM15.9775 17.3033L12 13.3258L8.02252 17.3033L6.6967 15.9775L10.6742 12L6.6967 8.02252L8.02252 6.6967L12 10.6742L15.9775 6.6967L17.3033 8.02252L13.3258 12L17.3033 15.9775L15.9775 17.3033Z" fill="#999999" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 992 B

View File

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.4854 3.1332L9.8668 0.515228C9.64704 0.295536 9.34903 0.172119 9.03829 0.172119C8.72755 0.172119 8.42953 0.295536 8.20977 0.515228L0.983989 7.74042C0.874797 7.84895 0.788225 7.97806 0.729285 8.12027C0.670346 8.26249 0.640212 8.41499 0.640629 8.56894V11.1875C0.640629 11.4983 0.764094 11.7964 0.983864 12.0161C1.20363 12.2359 1.5017 12.3594 1.8125 12.3594H11.6563C11.8427 12.3594 12.0216 12.2853 12.1534 12.1534C12.2853 12.0216 12.3594 11.8427 12.3594 11.6562C12.3594 11.4698 12.2853 11.2909 12.1534 11.1591C12.0216 11.0272 11.8427 10.9531 11.6563 10.9531H6.32422L12.4854 4.79081C12.5942 4.68199 12.6806 4.55278 12.7395 4.41057C12.7984 4.26836 12.8288 4.11594 12.8288 3.96201C12.8288 3.80807 12.7984 3.65565 12.7395 3.51344C12.6806 3.37123 12.5942 3.24202 12.4854 3.1332ZM4.33204 10.9531H2.04688V8.66796L6.96875 3.74609L9.25391 6.03124L4.33204 10.9531ZM10.25 5.03515L7.96485 2.74999L9.03946 1.67538L11.3246 3.96054L10.25 5.03515Z" fill="#D5D5D5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,24 @@
<svg width="40" height="26" viewBox="0 0 40 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_9292_1537)">
<rect x="3" y="3" width="34" height="20" rx="10" fill="#F5F6F7"/>
<g clip-path="url(#clip0_9292_1537)">
<path d="M20.4393 20H19.5607C18.85 20 18.2718 19.4218 18.2718 18.7112V18.414C17.9697 18.3174 17.6762 18.1956 17.3942 18.0497L17.1835 18.2603C16.6733 18.7711 15.8561 18.7562 15.3607 18.2601L14.7397 17.6391C14.2434 17.1434 14.2291 16.3264 14.7398 15.8163L14.9503 15.6058C14.8044 15.3238 14.6826 15.0303 14.586 14.7281H14.2888C13.5782 14.7281 13 14.15 13 13.4393V12.5607C13 11.85 13.5782 11.2719 14.2888 11.2719H14.586C14.6826 10.9697 14.8044 10.6762 14.9503 10.3942L14.7397 10.1836C14.2293 9.67374 14.2434 8.85666 14.7399 8.36072L15.3609 7.73969C15.8574 7.24247 16.6745 7.23006 17.1838 7.73986L17.3942 7.95032C17.6762 7.80441 17.9698 7.68257 18.2719 7.58602V7.28879C18.2719 6.57816 18.85 6 19.5607 6H20.4393C21.15 6 21.7281 6.57816 21.7281 7.28879V7.58605C22.0302 7.68257 22.3238 7.80441 22.6058 7.95035L22.8164 7.73969C23.3266 7.22886 24.1439 7.24384 24.6393 7.73988L25.2603 8.36086C25.7566 8.85657 25.7708 9.67358 25.2601 10.1837L25.0497 10.3942C25.1956 10.6762 25.3174 10.9697 25.414 11.2719H25.7112C26.4218 11.2719 27 11.85 27 12.5607V13.4393C27 14.15 26.4218 14.7281 25.7112 14.7281H25.414C25.3174 15.0303 25.1956 15.3238 25.0497 15.6058L25.2603 15.8164C25.7707 16.3263 25.7566 17.1434 25.2601 17.6393L24.6391 18.2603C24.1426 18.7576 23.3255 18.77 22.8162 18.2602L22.6058 18.0497C22.3238 18.1956 22.0302 18.3175 21.7281 18.414V18.7113C21.7281 19.4218 21.15 20 20.4393 20ZM17.5313 17.1882C17.9231 17.4199 18.3447 17.595 18.7845 17.7085C18.9656 17.7552 19.0922 17.9185 19.0922 18.1056V18.7112C19.0922 18.9695 19.3024 19.1797 19.5607 19.1797H20.4393C20.6976 19.1797 20.9078 18.9695 20.9078 18.7112V18.1056C20.9078 17.9185 21.0344 17.7552 21.2155 17.7085C21.6553 17.595 22.0769 17.4199 22.4687 17.1882C22.6299 17.0929 22.8351 17.1188 22.9675 17.2513L23.3965 17.6803C23.5815 17.8654 23.8785 17.8611 24.0589 17.6805L24.6803 17.059C24.8603 16.8793 24.8663 16.5822 24.6805 16.3966L24.2513 15.9675C24.1189 15.8351 24.093 15.6298 24.1883 15.4687C24.42 15.0769 24.595 14.6553 24.7085 14.2155C24.7552 14.0344 24.9186 13.9078 25.1056 13.9078H25.7112C25.9695 13.9078 26.1797 13.6977 26.1797 13.4394V12.5607C26.1797 12.3024 25.9695 12.0922 25.7112 12.0922H25.1056C24.9186 12.0922 24.7552 11.9657 24.7085 11.7846C24.595 11.3447 24.42 10.9231 24.1883 10.5314C24.093 10.3702 24.1189 10.165 24.2513 10.0326L24.6803 9.60358C24.8658 9.41835 24.8609 9.1214 24.6805 8.94118L24.0591 8.31979C23.879 8.13943 23.5819 8.13415 23.3967 8.31962L22.9676 8.74879C22.8352 8.88121 22.6299 8.90713 22.4687 8.81181C22.077 8.58013 21.6553 8.4051 21.2155 8.2916C21.0344 8.24487 20.9079 8.08152 20.9079 7.89446V7.28879C20.9079 7.03048 20.6977 6.82031 20.4394 6.82031H19.5607C19.3024 6.82031 19.0922 7.03048 19.0922 7.28879V7.8944C19.0922 8.08146 18.9657 8.24481 18.7845 8.29154C18.3447 8.40505 17.9231 8.58007 17.5314 8.81176C17.3701 8.90705 17.1649 8.88113 17.0325 8.74873L16.6036 8.31973C16.4186 8.13456 16.1216 8.13886 15.9412 8.31954L15.3197 8.94096C15.1398 9.12071 15.1337 9.41775 15.3196 9.60336L15.7487 10.0325C15.8811 10.1649 15.9071 10.3702 15.8118 10.5313C15.5801 10.9231 15.4051 11.3447 15.2916 11.7845C15.2448 11.9656 15.0815 12.0922 14.8944 12.0922H14.2888C14.0305 12.0922 13.8203 12.3024 13.8203 12.5607V13.4393C13.8203 13.6976 14.0305 13.9078 14.2888 13.9078H14.8944C15.0815 13.9078 15.2448 14.0344 15.2915 14.2155C15.405 14.6553 15.5801 15.0769 15.8117 15.4686C15.907 15.6298 15.8811 15.8351 15.7487 15.9675L15.3197 16.3965C15.1343 16.5817 15.1391 16.8786 15.3195 17.0589L15.9409 17.6802C16.121 17.8606 16.4181 17.8659 16.6033 17.6804L17.0325 17.2512C17.13 17.1537 17.333 17.0709 17.5313 17.1882Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M19.9992 16.0461C18.3196 16.0461 16.9531 14.6796 16.9531 13C16.9531 11.3204 18.3196 9.95391 19.9992 9.95391C21.6789 9.95391 23.0453 11.3204 23.0453 13C23.0453 14.6796 21.6789 16.0461 19.9992 16.0461ZM19.9992 10.7742C18.7719 10.7742 17.7734 11.7727 17.7734 13C17.7734 14.2273 18.7719 15.2258 19.9992 15.2258C21.2265 15.2258 22.225 14.2273 22.225 13C22.225 11.7727 21.2265 10.7742 19.9992 10.7742Z" fill="#023DFE" fill-opacity="0.7"/>
</g>
</g>
<defs>
<filter id="filter0_d_9292_1537" x="0" y="0" width="40" height="26" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_9292_1537"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_9292_1537" result="shape"/>
</filter>
<clipPath id="clip0_9292_1537">
<rect width="14" height="14" fill="white" transform="translate(13 6)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -8,6 +8,8 @@ class AnalyticsDevice {
this.isActive,
this.productDevice,
this.spaceUuid,
this.latitude,
this.longitude,
});
final String uuid;
@ -18,22 +20,27 @@ class AnalyticsDevice {
final bool? isActive;
final ProductDevice? productDevice;
final String? spaceUuid;
final double? latitude;
final double? longitude;
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
return AnalyticsDevice(
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,
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(),
spaceUuid: json['spaceUuid'] as String?,
latitude: json['lat'] != null ? double.parse(json['lat'] as String) : null,
longitude: json['lon'] != null ? double.parse(json['lon'] as String) : null,
);
}
}
@ -60,8 +67,12 @@ class ProductDevice {
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,
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?,

View File

@ -0,0 +1,55 @@
import 'package:equatable/equatable.dart';
class DeviceLocationInfo extends Equatable {
const DeviceLocationInfo({
this.airQuality,
this.humidity,
this.city,
this.country,
this.address,
this.temperature,
});
final double? airQuality;
final double? humidity;
final String? city;
final String? country;
final String? address;
final double? temperature;
factory DeviceLocationInfo.fromJson(Map<String, dynamic> json) {
return DeviceLocationInfo(
airQuality: json['aqi'] as double?,
humidity: json['humidity'] as double?,
temperature: json['temperature'] as double?,
);
}
DeviceLocationInfo copyWith({
double? airQuality,
double? humidity,
String? city,
String? country,
String? address,
double? temperature,
}) {
return DeviceLocationInfo(
airQuality: airQuality ?? this.airQuality,
humidity: humidity ?? this.humidity,
city: city ?? this.city,
country: country ?? this.country,
address: address ?? this.address,
temperature: temperature ?? this.temperature,
);
}
@override
List<Object?> get props => [
airQuality,
humidity,
city,
country,
address,
temperature,
];
}

View File

@ -0,0 +1,50 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/device_location_info.dart';
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/device_location_service.dart';
part 'device_location_event.dart';
part 'device_location_state.dart';
class DeviceLocationBloc extends Bloc<DeviceLocationEvent, DeviceLocationState> {
DeviceLocationBloc(
this._deviceLocationService,
) : super(const DeviceLocationState()) {
on<LoadDeviceLocationEvent>(_onLoadDeviceLocation);
on<ClearDeviceLocationEvent>(_onClearDeviceLocation);
}
final DeviceLocationService _deviceLocationService;
Future<void> _onLoadDeviceLocation(
LoadDeviceLocationEvent event,
Emitter<DeviceLocationState> emit,
) async {
emit(const DeviceLocationState(status: DeviceLocationStatus.loading));
try {
final locationInfo = await _deviceLocationService.get(event.param);
emit(
DeviceLocationState(
status: DeviceLocationStatus.success,
locationInfo: locationInfo,
),
);
} catch (e) {
emit(
DeviceLocationState(
status: DeviceLocationStatus.failure,
errorMessage: e.toString(),
),
);
}
}
void _onClearDeviceLocation(
ClearDeviceLocationEvent event,
Emitter<DeviceLocationState> emit,
) {
emit(const DeviceLocationState());
}
}

View File

@ -0,0 +1,21 @@
part of 'device_location_bloc.dart';
sealed class DeviceLocationEvent extends Equatable {
const DeviceLocationEvent();
@override
List<Object?> get props => [];
}
final class LoadDeviceLocationEvent extends DeviceLocationEvent {
const LoadDeviceLocationEvent(this.param);
final GetDeviceLocationDataParam param;
@override
List<Object?> get props => [param];
}
final class ClearDeviceLocationEvent extends DeviceLocationEvent {
const ClearDeviceLocationEvent();
}

View File

@ -0,0 +1,18 @@
part of 'device_location_bloc.dart';
enum DeviceLocationStatus { initial, loading, success, failure }
final class DeviceLocationState extends Equatable {
const DeviceLocationState({
this.status = DeviceLocationStatus.initial,
this.locationInfo,
this.errorMessage,
});
final DeviceLocationStatus status;
final DeviceLocationInfo? locationInfo;
final String? errorMessage;
@override
List<Object?> get props => [status, locationInfo, errorMessage];
}

View File

@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
abstract final class FetchAirQualityDataHelper {
@ -48,6 +50,8 @@ abstract final class FetchAirQualityDataHelper {
const ClearAirQualityDistribution(),
);
context.read<RangeOfAqiBloc>().add(const ClearRangeOfAqiEvent());
context.read<DeviceLocationBloc>().add(const ClearDeviceLocationEvent());
}
static void loadAnalyticsDevices(
@ -61,12 +65,21 @@ abstract final class FetchAirQualityDataHelper {
communityUuid: communityUuid,
spaceUuid: spaceUuid,
deviceTypes: ['AQI'],
requestType: AnalyticsDeviceRequestType.energyManagement,
requestType: AnalyticsDeviceRequestType.occupancy,
),
onSuccess: (device) {
context.read<RealtimeDeviceChangesBloc>()
..add(const RealtimeDeviceChangesClosed())
..add(RealtimeDeviceChangesStarted(device.uuid));
context.read<DeviceLocationBloc>().add(
LoadDeviceLocationEvent(
GetDeviceLocationDataParam(
latitude: device.latitude ?? 0,
longitude: device.longitude ?? 0,
),
),
);
},
),
);

View File

@ -72,9 +72,9 @@ class AqiDeviceInfo extends StatelessWidget {
return Container(
decoration: secondarySection.copyWith(boxShadow: const []),
padding: const EdgeInsetsDirectional.all(20),
child: Expanded(
child: Column(
spacing: 6,
mainAxisSize: MainAxisSize.max,
children: [
const AirQualityEndSideLiveIndicator(),
AirQualityEndSideGaugeAndInfo(
@ -121,7 +121,6 @@ class AqiDeviceInfo extends StatelessWidget {
),
],
),
),
);
},
);

View File

@ -6,7 +6,34 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class AqiLocation extends StatelessWidget {
const AqiLocation({super.key});
const AqiLocation({
required this.city,
required this.country,
required this.address,
super.key,
});
final String? city;
final String? country;
final String? address;
String _getFormattedLocation() {
if (city == null && country == null && address == null) {
return 'N/A';
}
final parts = <String>[];
if (city != null) parts.add(city!);
if (address != null) parts.add(address!);
final locationPart = parts.join(', ');
if (country != null) {
return locationPart.isEmpty ? country! : '$locationPart - $country';
}
return locationPart;
}
@override
Widget build(BuildContext context) {
@ -24,7 +51,7 @@ class AqiLocation extends StatelessWidget {
_buildLocationPin(),
Expanded(
child: Text(
'Business Bay, Dubai - UAE',
_getFormattedLocation(),
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,

View File

@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@ -9,30 +11,37 @@ class AqiLocationInfo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<DeviceLocationBloc, DeviceLocationState>(
builder: (context, state) {
final info = state.locationInfo;
return Container(
decoration: secondarySection.copyWith(boxShadow: const []),
padding: const EdgeInsetsDirectional.all(20),
child: const Column(
child: Column(
spacing: 8,
children: [
AqiLocation(),
AqiLocation(
city: info?.city,
country: info?.country,
address: info?.address,
),
Expanded(
child: Row(
spacing: 8,
children: [
AqiLocationInfoCell(
label: 'Temperature',
value: ' 25°',
value: ' ${info?.temperature?.roundToDouble() ?? '--'}°',
svgPath: Assets.aqiTemperature,
),
AqiLocationInfoCell(
label: 'Humidity',
value: '25%',
value: '${info?.humidity?.roundToDouble() ?? '--'}%',
svgPath: Assets.aqiHumidity,
),
AqiLocationInfoCell(
label: 'Air Quality',
value: ' 120',
value: ' ${info?.airQuality?.roundToDouble() ?? '--'}',
svgPath: Assets.aqiAirQuality,
),
],
@ -41,5 +50,7 @@ class AqiLocationInfo extends StatelessWidget {
],
),
);
},
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'analytics_devices_event.dart';
part 'analytics_devices_state.dart';
@ -36,6 +37,13 @@ class AnalyticsDevicesBloc
if (devices.isNotEmpty) {
event.onSuccess(devices.first);
}
} on APIException catch (e) {
emit(
AnalyticsDevicesState(
status: AnalyticsDevicesStatus.failure,
errorMessage: e.message,
),
);
} catch (e) {
emit(
AnalyticsDevicesState(

View File

@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
@ -18,6 +20,8 @@ import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/fa
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/device_location_details_service_decorator.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/remote_device_location_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/remote_occupancy_service.dart';
@ -108,6 +112,18 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
FakeAirQualityDistributionService(),
),
),
BlocProvider(
create: (context) => DeviceLocationBloc(
DeviceLocationDetailsServiceDecorator(
RemoteDeviceLocationService(_httpService),
Dio(
BaseOptions(
baseUrl: 'https://nominatim.openstreetmap.org/',
),
),
),
),
),
],
child: const AnalyticsPageForm(),
);

View File

@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'energy_consumption_by_phases_event.dart';
part 'energy_consumption_by_phases_state.dart';
@ -31,6 +32,13 @@ class EnergyConsumptionByPhasesBloc
chartData: chartData,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
status: EnergyConsumptionByPhasesStatus.failure,
errorMessage: e.message,
),
);
} catch (e) {
emit(
state.copyWith(

View File

@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'energy_consumption_per_device_event.dart';
part 'energy_consumption_per_device_state.dart';
@ -13,7 +14,8 @@ class EnergyConsumptionPerDeviceBloc
this._energyConsumptionPerDeviceService,
) : super(const EnergyConsumptionPerDeviceState()) {
on<LoadEnergyConsumptionPerDeviceEvent>(_onLoadEnergyConsumptionPerDeviceEvent);
on<ClearEnergyConsumptionPerDeviceEvent>(_onClearEnergyConsumptionPerDeviceEvent);
on<ClearEnergyConsumptionPerDeviceEvent>(
_onClearEnergyConsumptionPerDeviceEvent);
}
final EnergyConsumptionPerDeviceService _energyConsumptionPerDeviceService;
@ -31,6 +33,13 @@ class EnergyConsumptionPerDeviceBloc
chartData: chartData,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
status: EnergyConsumptionPerDeviceStatus.failure,
errorMessage: e.message,
),
);
} catch (e) {
emit(
state.copyWith(

View File

@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'power_clamp_info_event.dart';
part 'power_clamp_info_state.dart';
@ -31,6 +32,13 @@ class PowerClampInfoBloc extends Bloc<PowerClampInfoEvent, PowerClampInfoState>
powerClampModel: powerClampModel,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
status: PowerClampInfoStatus.error,
errorMessage: e.message,
),
);
} catch (e) {
emit(
state.copyWith(

View File

@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'total_energy_consumption_event.dart';
part 'total_energy_consumption_state.dart';
@ -31,6 +32,13 @@ class TotalEnergyConsumptionBloc
status: TotalEnergyConsumptionStatus.loaded,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
errorMessage: e.message,
status: TotalEnergyConsumptionStatus.failure,
),
);
} catch (e) {
emit(
state.copyWith(

View File

@ -16,7 +16,6 @@ import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_
abstract final class FetchEnergyManagementDataHelper {
const FetchEnergyManagementDataHelper._();
// static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa';
static AnalyticsDevice? getSelectedDevice(BuildContext context) {
return context.read<AnalyticsDevicesBloc>().state.selectedDevice;
}
@ -48,7 +47,6 @@ abstract final class FetchEnergyManagementDataHelper {
loadTotalEnergyConsumption(
context,
selectedDate: selectedDate0,
communityId: communityId,
spaceId: spaceId,
);
final selectedDevice = getSelectedDevice(context);
@ -61,7 +59,6 @@ abstract final class FetchEnergyManagementDataHelper {
}
loadEnergyConsumptionPerDevice(
context,
communityId: communityId,
spaceId: spaceId,
selectedDate: selectedDate0,
);
@ -84,12 +81,10 @@ abstract final class FetchEnergyManagementDataHelper {
static void loadTotalEnergyConsumption(
BuildContext context, {
DateTime? selectedDate,
required String communityId,
required String spaceId,
}) {
final param = GetTotalEnergyConsumptionParam(
spaceId: spaceId,
communityId: communityId,
monthDate: selectedDate,
);
context.read<TotalEnergyConsumptionBloc>().add(
@ -100,12 +95,10 @@ abstract final class FetchEnergyManagementDataHelper {
static void loadEnergyConsumptionPerDevice(
BuildContext context, {
DateTime? selectedDate,
required String communityId,
required String spaceId,
}) {
final param = GetEnergyConsumptionPerDeviceParam(
spaceId: spaceId,
communityId: communityId,
monthDate: selectedDate,
);
context.read<EnergyConsumptionPerDeviceBloc>().add(

View File

@ -1,34 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
class AnalyticsEnergyManagementView extends StatefulWidget {
class AnalyticsEnergyManagementView extends StatelessWidget {
const AnalyticsEnergyManagementView({super.key});
@override
State<AnalyticsEnergyManagementView> createState() =>
_AnalyticsEnergyManagementViewState();
}
class _AnalyticsEnergyManagementViewState
extends State<AnalyticsEnergyManagementView> {
@override
void initState() {
final spaceTreeBloc = context.read<SpaceTreeBloc>();
final communityId = spaceTreeBloc.state.selectedCommunities.firstOrNull;
final spaceId = spaceTreeBloc.state.selectedSpaces.firstOrNull;
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
communityId: communityId ?? '',
spaceId: spaceId ?? '',
);
super.initState();
}
static const _padding = EdgeInsetsDirectional.all(32);
@override
Widget build(BuildContext context) {

View File

@ -41,7 +41,7 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
.color;
return Tooltip(
message: '${device.name}\n${device.productDevice?.uuid ?? ''}',
message: '${device.name}\n${device.spaceUuid ?? ''}',
child: ChartInformativeCell(title: Text(device.name), color: deviceColor),
);
}

View File

@ -41,7 +41,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
AnalyticsErrorWidget(state.errorMessage),
AnalyticsSidebarHeader(
title: 'Smart Power Clamp',
showSpaceUuid: true,
showSpaceUuidInDevicesDropdown: true,
onChanged: (device) {
FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
context,

View File

@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart';
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/api_exception.dart';
part 'occupancy_event.dart';
part 'occupancy_state.dart';
@ -23,6 +24,8 @@ class OccupancyBloc extends Bloc<OccupancyEvent, OccupancyState> {
try {
final chartData = await _occupacyService.load(event.param);
emit(state.copyWith(chartData: chartData, status: OccupancyStatus.loaded));
} on APIException catch (e) {
emit(state.copyWith(status: OccupancyStatus.failure, errorMessage: e.message));
} catch (e) {
emit(state.copyWith(status: OccupancyStatus.failure, errorMessage: '$e'));
}

View File

@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'occupancy_heat_map_event.dart';
part 'occupancy_heat_map_state.dart';
@ -30,6 +31,13 @@ class OccupancyHeatMapBloc
heatMapData: occupancyHeatMap,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
status: OccupancyHeatMapStatus.failure,
errorMessage: e.message,
),
);
} catch (e) {
emit(
state.copyWith(

View File

@ -16,7 +16,7 @@ class OccupancyChart extends StatelessWidget {
Widget build(BuildContext context) {
return BarChart(
BarChartData(
maxY: 100.0,
maxY: 100.001,
gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 20,
@ -134,7 +134,7 @@ class OccupancyChart extends StatelessWidget {
alignment: AlignmentDirectional.bottomCenter,
fit: BoxFit.scaleDown,
child: Text(
(value + 1).toString(),
chartData[value.toInt()].date.day.toString(),
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.greyColor,
fontSize: 8,

View File

@ -0,0 +1,11 @@
class GetDeviceLocationDataParam {
const GetDeviceLocationDataParam({
required this.latitude,
required this.longitude,
});
final double latitude;
final double longitude;
Map<String, dynamic> toJson() => {'lat': latitude, 'lon': longitude};
}

View File

@ -2,18 +2,15 @@ class GetEnergyConsumptionPerDeviceParam {
const GetEnergyConsumptionPerDeviceParam({
this.monthDate,
this.spaceId,
this.communityId,
});
final DateTime? monthDate;
final String? spaceId;
final String? communityId;
Map<String, dynamic> toJson() => {
'monthDate':
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
'communityUuid': communityId,
if (spaceId != null) 'spaceUuid': spaceId,
'groupByDevice': true,
};
}

View File

@ -1,20 +1,17 @@
class GetTotalEnergyConsumptionParam {
final DateTime? monthDate;
final String? spaceId;
final String? communityId;
const GetTotalEnergyConsumptionParam({
this.monthDate,
this.spaceId,
this.communityId,
});
Map<String, dynamic> toJson() {
return {
'monthDate':
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
'communityUuid': communityId,
if (spaceId != null) 'spaceUuid': spaceId,
'groupByDevice': false,
};
}

View File

@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteEnergyManagementAnalyticsDevicesService
@ -9,6 +11,8 @@ final class RemoteEnergyManagementAnalyticsDevicesService
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load analytics devices';
@override
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
try {
@ -29,8 +33,14 @@ final class RemoteEnergyManagementAnalyticsDevicesService
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
throw Exception('Failed to load total energy consumption: $e');
throw APIException('$_defaultErrorMessage: $e');
}
}
}

View File

@ -1,7 +1,9 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService {
@ -9,6 +11,8 @@ class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load analytics devices';
@override
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
try {
@ -26,8 +30,15 @@ class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService
final result = requests.map((e) => e.first).toList();
return result;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
throw Exception('Failed to load total energy consumption: $e');
final formattedErrorMessage = [_defaultErrorMessage, e.toString()].join(': ');
throw APIException(formattedErrorMessage);
}
}
@ -54,8 +65,14 @@ class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
rethrow;
throw APIException('$_defaultErrorMessage: $e');
}
}
}

View File

@ -0,0 +1,40 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/device_location_info.dart';
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/device_location_service.dart';
class DeviceLocationDetailsServiceDecorator implements DeviceLocationService {
const DeviceLocationDetailsServiceDecorator(this._decoratee, this._dio);
final DeviceLocationService _decoratee;
final Dio _dio;
@override
Future<DeviceLocationInfo> get(GetDeviceLocationDataParam param) async {
try {
final deviceLocationInfo = await _decoratee.get(param);
final response = await _dio.get<Map<String, dynamic>>(
'reverse',
queryParameters: {
'format': 'json',
'lat': param.latitude,
'lon': param.longitude,
},
);
final data = response.data;
if (data != null) {
final addressData = data['address'] as Map<String, dynamic>;
return deviceLocationInfo.copyWith(
city: addressData['city'],
country: addressData['country_code'].toString().toUpperCase(),
address: addressData['state'],
);
}
return deviceLocationInfo;
} catch (e) {
throw Exception('Failed to load device location info: ${e.toString()}');
}
}
}

View File

@ -0,0 +1,6 @@
import 'package:syncrow_web/pages/analytics/models/device_location_info.dart';
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
abstract interface class DeviceLocationService {
Future<DeviceLocationInfo> get(GetDeviceLocationDataParam param);
}

View File

@ -0,0 +1,37 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/device_location_info.dart';
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/device_location_service.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class RemoteDeviceLocationService implements DeviceLocationService {
const RemoteDeviceLocationService(this._httpService);
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load device location';
@override
Future<DeviceLocationInfo> get(GetDeviceLocationDataParam param) async {
try {
final response = await _httpService.get(
path: '/weather',
queryParameters: param.toJson(),
expectedResponseModel: (data) {
final response = data as Map<String, dynamic>;
final location = response['data'] as Map<String, dynamic>;
return DeviceLocationInfo.fromJson(location);
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
throw Exception(errorMessage);
} catch (e) {
throw Exception('$_defaultErrorMessage: $e');
}
}
}

View File

@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteEnergyConsumptionByPhasesService
@ -9,6 +11,8 @@ final class RemoteEnergyConsumptionByPhasesService
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load energy consumption per phase';
@override
Future<List<PhasesEnergyConsumption>> load(
GetEnergyConsumptionByPhasesParam param,
@ -28,8 +32,15 @@ final class RemoteEnergyConsumptionByPhasesService
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
throw Exception('Failed to load energy consumption per phase: $e');
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
}
}
}

View File

@ -1,8 +1,10 @@
import 'package:dio/dio.dart';
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';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class RemoteEnergyConsumptionPerDeviceService
@ -11,6 +13,8 @@ class RemoteEnergyConsumptionPerDeviceService
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load energy consumption per device';
@override
Future<List<DeviceEnergyDataModel>> load(
GetEnergyConsumptionPerDeviceParam param,
@ -23,8 +27,15 @@ class RemoteEnergyConsumptionPerDeviceService
expectedResponseModel: _EnergyConsumptionPerDeviceMapper.map,
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
throw Exception('Failed to load energy consumption per device: $e');
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
}
}
}

View File

@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
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/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteOccupancyService implements OccupacyService {
@ -8,6 +10,8 @@ final class RemoteOccupancyService implements OccupacyService {
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load occupancy';
@override
Future<List<Occupacy>> load(GetOccupancyParam param) async {
try {
@ -25,8 +29,15 @@ final class RemoteOccupancyService implements OccupacyService {
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
throw Exception('Failed to load energy consumption per phase: $e');
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
}
}
}

View File

@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
@ -8,6 +10,8 @@ final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load occupancy heat map';
@override
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) async {
try {
@ -28,8 +32,15 @@ final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
throw Exception('Failed to load total energy consumption:');
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
}
}
}

View File

@ -1,5 +1,7 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemotePowerClampInfoService implements PowerClampInfoService {
@ -7,6 +9,8 @@ final class RemotePowerClampInfoService implements PowerClampInfoService {
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to fetch power clamp info';
@override
Future<PowerClampModel> getInfo(String deviceId) async {
try {
@ -20,8 +24,15 @@ final class RemotePowerClampInfoService implements PowerClampInfoService {
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
throw Exception('Failed to fetch power clamp info: $e');
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
}
}
}

View File

@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class RemoteTotalEnergyConsumptionService implements TotalEnergyConsumptionService {
@ -8,6 +10,8 @@ class RemoteTotalEnergyConsumptionService implements TotalEnergyConsumptionServi
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load total energy consumption';
@override
Future<List<EnergyDataModel>> load(
GetTotalEnergyConsumptionParam param,
@ -21,8 +25,15 @@ class RemoteTotalEnergyConsumptionService implements TotalEnergyConsumptionServi
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
throw Exception('Failed to load total energy consumption: $e');
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
}
}
}

View File

@ -10,13 +10,13 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
class AnalyticsSidebarHeader extends StatelessWidget {
const AnalyticsSidebarHeader({
required this.title,
this.showSpaceUuid = false,
this.showSpaceUuidInDevicesDropdown = false,
this.onChanged,
super.key,
});
final String title;
final bool showSpaceUuid;
final bool showSpaceUuidInDevicesDropdown;
final void Function(AnalyticsDevice device)? onChanged;
@override
@ -49,6 +49,7 @@ class AnalyticsSidebarHeader extends StatelessWidget {
alignment: AlignmentDirectional.centerEnd,
fit: BoxFit.scaleDown,
child: AnalyticsDeviceDropdown(
showSpaceUuid: showSpaceUuidInDevicesDropdown,
onChanged: (value) {
context.read<AnalyticsDevicesBloc>().add(
SelectAnalyticsDeviceEvent(value),

View File

@ -162,19 +162,21 @@ class _DynamicTableState extends State<DynamicTable> {
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: _horizontalBodyScrollController,
child: Container(
color: ColorsManager.whiteColors,
child: SizedBox(
width: widget.size.width,
child: widget.isEmpty
? _buildEmptyState()
: Column(
children:
List.generate(widget.data.length, (rowIndex) {
children: List.generate(widget.data.length,
(rowIndex) {
final row = widget.data[rowIndex];
return Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(
rowIndex, widget.size.height * 0.08),
_buildRowCheckbox(rowIndex,
widget.size.height * 0.08),
...row.asMap().entries.map((entry) {
return _buildTableCell(
entry.value.toString(),
@ -193,6 +195,7 @@ class _DynamicTableState extends State<DynamicTable> {
),
),
),
),
],
),
);
@ -310,7 +313,12 @@ class _DynamicTableState extends State<DynamicTable> {
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
if (isSettingsColumn) {
return _buildSettingsIcon(rowIndex, size);
return buildSettingsIcon(
width: 120,
height: 60,
iconSize: 40,
onTap: () => widget.onSettingsPressed?.call(rowIndex),
);
}
Color? statusColor;
@ -364,22 +372,63 @@ class _DynamicTableState extends State<DynamicTable> {
);
}
Widget _buildSettingsIcon(int rowIndex, double size) {
return Container(
height: size,
width: 120,
padding: const EdgeInsets.all(5.0),
Widget buildSettingsIcon(
{double width = 120,
double height = 60,
double iconSize = 40,
VoidCallback? onTap}) {
return Column(
children: [
Container(
padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10),
margin: const EdgeInsets.only(right: 15),
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
border: Border(
bottom: BorderSide(color: ColorsManager.boxDivider, width: 1.0),
bottom: BorderSide(
color: ColorsManager.boxDivider,
width: 1.0,
),
color: Colors.white,
),
alignment: Alignment.center,
child: IconButton(
icon: SvgPicture.asset(Assets.settings),
onPressed: () => widget.onSettingsPressed?.call(rowIndex),
),
width: width,
child: Padding(
padding: const EdgeInsets.only(
right: 16.0,
left: 17.0,
),
child: Container(
width: 50,
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
borderRadius: BorderRadius.circular(height / 2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.17),
blurRadius: 14,
offset: const Offset(0, 4),
),
],
),
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: SvgPicture.asset(
Assets.settings, // ضع المسار الصحيح هنا
width: 40,
height: 22,
color: ColorsManager
.primaryColor, // نفس لون الأيقونة في الصورة
),
),
),
),
),
),
),
],
);
}
}

View File

@ -95,7 +95,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return const RoutinesView();
}
if (state.createRoutineView) {
return CreateNewRoutineView();
return const CreateNewRoutineView();
}
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(

View File

@ -6,9 +6,11 @@ import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/device_settings_panel.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
@ -58,7 +60,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
'Low Battery ($lowBatteryCount)',
];
final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control';
final buttonLabel =
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
return Row(
children: [
@ -105,18 +108,23 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
if (selectedDevices.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceControlDialog(
builder: (context) =>
DeviceControlDialog(
device: selectedDevices.first,
),
);
} else if (selectedDevices.length > 1) {
final productTypes = selectedDevices
.map((device) => device.productType)
} else if (selectedDevices.length >
1) {
final productTypes =
selectedDevices
.map((device) =>
device.productType)
.toSet();
if (productTypes.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceBatchControlDialog(
builder: (context) =>
DeviceBatchControlDialog(
devices: selectedDevices,
),
);
@ -130,7 +138,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: isControlButtonEnabled ? Colors.white : Colors.grey,
color: isControlButtonEnabled
? Colors.white
: Colors.grey,
),
),
),
@ -166,29 +176,40 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
'Installation Date and Time',
'Status',
'Last Offline Date and Time',
'Settings'
],
data: devicesToShow.map((device) {
final combinedSpaceNames = device.spaces != null
? device.spaces!.map((space) => space.spaceName).join(' > ') +
? device.spaces!
.map((space) => space.spaceName)
.join(' > ') +
(device.community != null
? ' > ${device.community!.name}'
: '')
: (device.community != null ? device.community!.name : '');
: (device.community != null
? device.community!.name
: '');
return [
device.name ?? '',
device.productName ?? '',
device.uuid ?? '',
(device.spaces != null && device.spaces!.isNotEmpty)
(device.spaces != null &&
device.spaces!.isNotEmpty)
? device.spaces![0].spaceName
: '',
combinedSpaceNames,
device.batteryLevel != null ? '${device.batteryLevel}%' : '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
device.batteryLevel != null
? '${device.batteryLevel}%'
: '-',
formatDateTime(
DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
formatDateTime(
DateTime.fromMillisecondsSinceEpoch(
(device.updateTime ?? 0) * 1000)),
'Settings',
];
}).toList(),
onSelectionChanged: (selectedRows) {
@ -202,6 +223,10 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
.map((device) => device.uuid!)
.toList(),
isEmpty: devicesToShow.isEmpty,
onSettingsPressed: (rowIndex) {
final device = devicesToShow[rowIndex];
showDeviceSettingsSidebar(context, device);
},
),
),
)
@ -213,4 +238,37 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
},
);
}
void showDeviceSettingsSidebar(BuildContext context, AllDevicesModel device) {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: "Device Settings",
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: Alignment.centerRight,
child: Material(
child: Container(
width: MediaQuery.of(context).size.width * 0.3,
color: ColorsManager.whiteColors,
child: DeviceSettingsPanel(
device: device,
onClose: () => Navigator.of(context).pop(),
),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(anim1),
child: child,
);
},
);
}
}

View File

@ -0,0 +1,165 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
part 'setting_bloc_event.dart';
class SettingDeviceBloc extends Bloc<DeviceSettingEvent, DeviceSettingsState> {
final String deviceId;
SettingDeviceBloc({
required this.deviceId,
}) : super(const DeviceSettingsInitial()) {
on<DeviceSettingInitialInfo>(_fetchDeviceInfo);
on<SettingBlocSaveName>(_saveName);
on<ChangeNameEvent>(_changeName);
on<SettingBlocDeleteDevice>(_deleteDevice);
on<SettingBlocFetchRooms>(_fetchRooms);
on<SettingBlocAssignRoom>(_onAssignDevice);
}
final TextEditingController nameController = TextEditingController();
List<SubSpaceModel> roomsList = [];
bool isEditingName = false;
bool _validateInputs() {
final nameError = _fullNameValidator(nameController.text);
if (nameError != null) {
CustomSnackBar.displaySnackBar(nameError);
return true;
}
return false;
}
String? _fullNameValidator(String? value) {
if (value == null) return 'name is required';
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
return 'name must be between 2 and 30 characters long';
}
if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
return 'Only alphanumeric characters, space, dash and single quote are allowed';
}
return null;
}
Future<void> _saveName(
SettingBlocSaveName event, Emitter<DeviceSettingsState> emit) async {
if (_validateInputs()) return;
try {
emit(DeviceSettingsLoading());
await DevicesManagementApi.putDeviceName(
deviceId: deviceId, deviceName: nameController.text);
add(DeviceSettingInitialInfo());
CustomSnackBar.displaySnackBar('Save Successfully');
emit(DeviceSettingsUpdate(deviceName: nameController.text));
} catch (e) {
emit(DeviceSettingsError(message: e.toString()));
}
}
Future<void> _fetchDeviceInfo(
DeviceSettingInitialInfo event, Emitter<DeviceSettingsState> emit) async {
try {
emit(DeviceSettingsLoading());
var response = await DevicesManagementApi.getDeviceInfo(deviceId);
DeviceInfoModel deviceInfo = DeviceInfoModel.fromJson(response);
nameController.text = deviceInfo.name;
emit(DeviceSettingsUpdate(
deviceName: nameController.text,
deviceInfo: deviceInfo,
roomsList: roomsList,
));
} catch (e) {
emit(DeviceSettingsError(message: e.toString()));
}
}
bool editName = false;
final FocusNode focusNode = FocusNode();
void _changeName(ChangeNameEvent event, Emitter<DeviceSettingsState> emit) {
emit(DeviceSettingsInitial(
deviceName: nameController.text,
deviceId: deviceId,
isEditingName: event.value ?? false,
editingNameValue: event.value?.toString() ?? '',
deviceInfo: state.deviceInfo,
));
editName = event.value!;
if (editName) {
Future.delayed(const Duration(milliseconds: 500), () {
focusNode.requestFocus();
});
} else {
add(const SettingBlocSaveName());
focusNode.unfocus();
}
emit(DeviceSettingsUpdate(
deviceName: nameController.text,
deviceInfo: state.deviceInfo,
roomsList: roomsList,
));
}
void _deleteDevice(
SettingBlocDeleteDevice event, Emitter<DeviceSettingsState> emit) async {
try {
emit(DeviceSettingsLoading());
await DevicesManagementApi.resetDevice(devicesUuid: deviceId);
CustomSnackBar.displaySnackBar('Reset Successfully');
emit(DeviceSettingsUpdate(
deviceName: nameController.text,
deviceInfo: state.deviceInfo,
roomsList: roomsList,
));
} catch (e) {
emit(DeviceSettingsError(message: e.toString()));
return;
}
}
void _onAssignDevice(
SettingBlocAssignRoom event, Emitter<DeviceSettingsState> emit) async {
try {
emit(DeviceSettingsLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await CommunitySpaceManagementApi.assignDeviceToRoom(
communityId: event.communityUuid,
spaceId: event.spaceUuid,
subSpaceId: event.subSpaceUuid,
deviceId: deviceId,
projectId: projectUuid);
add(DeviceSettingInitialInfo());
CustomSnackBar.displaySnackBar('Save Successfully');
emit(DeviceSettingsSaveSelectionSuccess());
} catch (e) {
emit(DeviceSettingsError(message: e.toString()));
return;
}
}
void _fetchRooms(
SettingBlocFetchRooms event, Emitter<DeviceSettingsState> emit) async {
try {
emit(DeviceSettingsLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
roomsList = await CommunitySpaceManagementApi.getSubSpaceBySpaceId(
communityId: event.communityUuid,
spaceId: event.spaceUuid,
projectId: projectUuid);
emit(DeviceSettingsUpdate(
deviceName: nameController.text,
deviceInfo: state.deviceInfo,
roomsList: roomsList,
));
} catch (e) {
emit(DeviceSettingsError(message: e.toString()));
return;
}
}
}

View File

@ -0,0 +1,69 @@
part of 'setting_bloc_bloc.dart';
abstract class DeviceSettingEvent extends Equatable {
const DeviceSettingEvent();
@override
List<Object?> get props => [];
}
class SettingBlocSaveDeviceName extends DeviceSettingEvent {
final String deviceName;
final String deviceId;
const SettingBlocSaveDeviceName(
{required this.deviceName, required this.deviceId});
@override
List<Object?> get props => [deviceName, deviceId];
}
class SettingBlocStartEditingName extends DeviceSettingEvent {}
class SettingBlocCancelEditingName extends DeviceSettingEvent {}
class SettingBlocChangeEditingNameValue extends DeviceSettingEvent {
final String value;
const SettingBlocChangeEditingNameValue(this.value);
@override
List<Object?> get props => [value];
}
class SettingBlocFetchRooms extends DeviceSettingEvent {
final String communityUuid;
final String spaceUuid;
const SettingBlocFetchRooms(
{required this.communityUuid, required this.spaceUuid});
@override
List<Object?> get props => [communityUuid, spaceUuid];
}
class SettingBlocSaveName extends DeviceSettingEvent {
const SettingBlocSaveName();
}
class DeviceSettingInitialInfo extends DeviceSettingEvent {}
class ChangeNameEvent extends DeviceSettingEvent {
final bool? value;
const ChangeNameEvent({this.value});
}
class SettingBlocDeleteDevice extends DeviceSettingEvent {}
class SettingBlocAssignRoom extends DeviceSettingEvent {
final String communityUuid;
final String spaceUuid;
final String subSpaceUuid;
const SettingBlocAssignRoom({
required this.communityUuid,
required this.spaceUuid,
required this.subSpaceUuid,
});
@override
List<Object?> get props => [spaceUuid, communityUuid, subSpaceUuid];
}

View File

@ -0,0 +1,81 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
abstract class DeviceSettingsState extends Equatable {
const DeviceSettingsState({this.deviceInfo});
final DeviceInfoModel? deviceInfo;
@override
List<Object?> get props => [deviceInfo];
}
class DeviceSettingsInitial extends DeviceSettingsState {
final String deviceName;
final String deviceId;
final bool isEditingName;
final String editingNameValue;
const DeviceSettingsInitial({
this.deviceName = '',
this.deviceId = '',
this.isEditingName = false,
this.editingNameValue = '',
super.deviceInfo,
});
DeviceSettingsInitial copyWith({
String? deviceName,
String? deviceId,
bool? isEditingName,
String? editingNameValue,
}) =>
DeviceSettingsInitial(
deviceName: deviceName ?? this.deviceName,
deviceId: deviceId ?? this.deviceId,
isEditingName: isEditingName ?? this.isEditingName,
editingNameValue: editingNameValue ?? this.editingNameValue,
);
@override
List<Object?> get props =>
[deviceName, deviceId, isEditingName, editingNameValue];
}
class DeviceSettingsLoading extends DeviceSettingsState {}
class DeviceSettingsUpdate extends DeviceSettingsState {
final String? deviceName;
final List<SubSpaceModel> roomsList;
const DeviceSettingsUpdate({
this.deviceName,
super.deviceInfo,
this.roomsList = const [],
});
@override
List<Object?> get props => [deviceName, deviceInfo, roomsList];
}
class DeviceSettingsError extends DeviceSettingsState {
final String message;
const DeviceSettingsError({required this.message});
@override
List<Object?> get props => [message];
}
class DeviceSettingsFetchRooms extends DeviceSettingsState {
final List<SubSpaceModel> roomsList;
const DeviceSettingsFetchRooms({required this.roomsList});
@override
List<Object?> get props => [roomsList];
}
class DeviceSettingsSaveSelectionSuccess extends DeviceSettingsState {}
class ChangeNameState extends DeviceSettingsState {}

View File

@ -0,0 +1,28 @@
import 'package:syncrow_web/utils/constants/assets.dart';
class DeviceIconTypeHelper {
static const Map<String, String> _iconMap = {
'AC': Assets.ac,
'GW': Assets.gateway,
'CPS': Assets.sensors,
'DL': Assets.doorLock,
'WPS': Assets.sensors,
'3G': Assets.gangSwitch,
'2G': Assets.twoGang,
'1G': Assets.oneGang,
'CUR': Assets.curtain,
'WH': Assets.waterHeater,
'DS': Assets.doorSensor,
'1GT': Assets.oneTouchSwitch,
'2GT': Assets.twoTouchSwitch,
'3GT': Assets.threeTouchSwitch,
'GD': Assets.garageDoor,
'WL': Assets.waterLeakNormal,
'NCPS': Assets.sensors,
};
static String getDeviceIconByTypeCode(String? typeCode) {
if (typeCode == null) return Assets.logoHorizontal;
return _iconMap[typeCode] ?? Assets.logoHorizontal;
}
}

View File

@ -0,0 +1,130 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.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/device_managment/device_setting/settings_model/device_info_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/web_layout/default_container.dart';
class DeviceManagementContent extends StatelessWidget {
const DeviceManagementContent({
super.key,
required this.device,
required this.subSpaces,
required this.deviceInfo,
});
final AllDevicesModel device;
final List<SubSpaceModel> subSpaces;
final DeviceInfoModel deviceInfo;
@override
Widget build(BuildContext context) {
Widget infoRow(
{required String label,
required String value,
Widget? trailing,
required Color? valueColor}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: context.theme.textTheme.bodyMedium!.copyWith(
fontSize: 14,
color: ColorsManager.grayColor,
),
),
const SizedBox(width: 15),
Expanded(
child: Text(
value,
textAlign: TextAlign.end,
style: context.theme.textTheme.bodyMedium!
.copyWith(fontSize: 14, color: valueColor),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 12),
trailing ?? const SizedBox.shrink(),
],
),
);
}
return DefaultContainer(
padding: EdgeInsets.zero,
child: Column(
children: [
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.all(10.0),
child: InkWell(
onTap: () {
showSubSpaceDialog(
context,
communityUuid: device.community!.uuid!,
spaceUuid: device.spaces!.first.uuid!,
subSpaces: subSpaces,
selected: device.subspace!.uuid,
);
},
child: infoRow(
label: 'Sub-Space:',
value: deviceInfo.subspace.subspaceName,
valueColor: ColorsManager.blackColor,
trailing: SvgPicture.asset(
Assets.arrowDown,
width: 10,
height: 10,
color: ColorsManager.greyColor,
)),
),
),
const Divider(color: ColorsManager.dividerColor),
Padding(
padding: const EdgeInsets.all(10.0),
child: infoRow(
label: 'Virtual Address:',
value: deviceInfo.productUuid,
valueColor: ColorsManager.blackColor,
trailing: InkWell(
onTap: () {
Clipboard.setData(
ClipboardData(text: device.productUuid ?? ''),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Virtual Address copied to clipboard'),
),
);
},
child: const Icon(
Icons.copy,
size: 15,
color: ColorsManager.greyColor,
),
),
),
),
const Divider(color: ColorsManager.dividerColor),
Padding(
padding: const EdgeInsets.all(10.0),
child: infoRow(
label: 'MAC Address:',
valueColor: ColorsManager.blackColor,
value: deviceInfo.macAddress,
),
),
const SizedBox(height: 5),
],
),
);
}
}

View File

@ -0,0 +1,218 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
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/device_managment/device_setting/device_icon_type_helper.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/device_management_content.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/remove_device_widget.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/web_layout/default_container.dart';
class DeviceSettingsPanel extends StatelessWidget {
final VoidCallback? onClose;
final AllDevicesModel device;
const DeviceSettingsPanel({super.key, this.onClose, required this.device});
@override
Widget build(BuildContext context) {
final sectionTitle = context.theme.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.bold,
color: ColorsManager.grayColor,
);
return BlocProvider(
create: (context) => SettingDeviceBloc(
deviceId: device.uuid ?? '',
)
..add(DeviceSettingInitialInfo())
..add(SettingBlocFetchRooms(
communityUuid: device.community!.uuid!,
spaceUuid: device.spaces!.first.uuid!,
)),
child: Builder(
builder: (context) {
return BlocBuilder<SettingDeviceBloc, DeviceSettingsState>(
builder: (context, state) {
final _bloc = context.read<SettingDeviceBloc>();
final iconPath = DeviceIconTypeHelper.getDeviceIconByTypeCode(
device.productType);
final deviceInfo = state is DeviceSettingsUpdate
? state.deviceInfo ?? DeviceInfoModel.empty()
: DeviceInfoModel.empty();
final subSpaces =
state is DeviceSettingsUpdate ? state.roomsList ?? [] : [];
return Stack(
children: [
Container(
width: MediaQuery.of(context).size.width * 0.3,
color: ColorsManager.grey25,
padding: const EdgeInsets.all(10),
child: ListView(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: SvgPicture.asset(Assets.closeSettingsIcon),
onPressed:
onClose ?? () => Navigator.of(context).pop(),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Device Settings',
style: context.theme.textTheme.titleLarge!
.copyWith(
fontWeight: FontWeight.w700,
color: ColorsManager.vividBlue
.withOpacity(0.7),
fontSize: 24),
),
],
),
const SizedBox(height: 24),
DefaultContainer(
borderRadius: BorderRadius.circular(15),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 15),
child: CircleAvatar(
radius: 38,
backgroundColor:
ColorsManager.grayBorder.withOpacity(0.5),
child: CircleAvatar(
backgroundColor: ColorsManager.whiteColors,
radius: 36,
child: SvgPicture.asset(
iconPath,
fit: BoxFit.cover,
),
),
),
),
const SizedBox(width: 25),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 15),
Text(
'Device Name:',
style: context.textTheme.bodyMedium!
.copyWith(
color: ColorsManager.grayColor,
),
),
SizedBox(
height: 35,
child: Row(
children: [
SizedBox(
height: 50,
width: 190,
child: TextFormField(
scrollPadding: EdgeInsets.zero,
maxLength: 30,
style: const TextStyle(
color: ColorsManager.blackColor,
fontSize: 16,
),
textAlign: TextAlign.start,
focusNode: _bloc.focusNode,
controller: _bloc.nameController,
enabled: _bloc.editName,
onFieldSubmitted: (value) {
_bloc.add(const ChangeNameEvent(
value: false));
},
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
fillColor: Colors.white10,
counterText: '',
),
),
),
Column(
children: [
SizedBox(
width: 15,
height: 25,
child: Visibility(
visible:
_bloc.editName != true,
replacement: const SizedBox(),
child: InkWell(
onTap: () {
_bloc.add(
const ChangeNameEvent(
value: true));
},
child: SvgPicture.asset(
Assets
.editNameIconSettings,
color: ColorsManager
.lightGrayBorderColor,
height: 15,
width: 15,
),
),
),
),
],
),
],
),
),
],
),
),
],
),
),
const SizedBox(height: 32),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text('Device Management', style: sectionTitle),
),
DeviceManagementContent(
device: device,
subSpaces: subSpaces.cast<SubSpaceModel>(),
deviceInfo: deviceInfo,
),
const SizedBox(height: 32),
RemoveDeviceWidget(bloc: _bloc),
],
),
),
if (state is DeviceSettingsLoading)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.1),
child: const Center(
child: CircularProgressIndicator(
color: ColorsManager.primaryColor,
),
),
),
),
],
);
},
);
},
),
);
}
}

View File

@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/web_layout/default_container.dart';
class RemoveDeviceWidget extends StatelessWidget {
const RemoveDeviceWidget({
super.key,
required SettingDeviceBloc bloc,
}) : _bloc = bloc;
final SettingDeviceBloc _bloc;
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: InkWell(
onTap: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(
'Remove Device',
style: context.textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w700,
color: ColorsManager.red,
),
),
content: Text(
'Are you sure you want to remove this device?',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.grayColor,
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.grayColor,
),
),
),
TextButton(
onPressed: () {
_bloc.add(SettingBlocDeleteDevice());
Navigator.of(context).pop();
},
child: Text(
'Remove',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.red,
),
),
),
],
);
},
);
},
child: DefaultContainer(
padding: const EdgeInsets.all(25),
child: Center(
child: Text(
'Remove Device',
style: context.textTheme.bodyMedium!.copyWith(
fontSize: 14,
color: ColorsManager.red,
fontWeight: FontWeight.w700),
),
),
),
),
);
}
}

View File

@ -0,0 +1,183 @@
class DeviceInfoModel {
final int activeTime;
final String category;
final String categoryName;
final int createTime;
final String gatewayId;
final String icon;
final String ip;
final String lat;
final String localKey;
final String lon;
final String model;
final String name;
final String nodeId;
final bool online;
final String ownerId;
final String productName;
final bool sub;
final String timeZone;
final int updateTime;
final String uuid;
final String productUuid;
final String productType;
final String permissionType;
final String macAddress;
final Subspace subspace;
DeviceInfoModel({
required this.activeTime,
required this.category,
required this.categoryName,
required this.createTime,
required this.gatewayId,
required this.icon,
required this.ip,
required this.lat,
required this.localKey,
required this.lon,
required this.model,
required this.name,
required this.nodeId,
required this.online,
required this.ownerId,
required this.productName,
required this.sub,
required this.timeZone,
required this.updateTime,
required this.uuid,
required this.productUuid,
required this.productType,
required this.permissionType,
required this.macAddress,
required this.subspace,
});
factory DeviceInfoModel.fromJson(Map<String, dynamic> json) {
return DeviceInfoModel(
activeTime: json['activeTime'] as int? ?? 0,
category: json['category'] ?? '',
categoryName: json['categoryName'] as String? ?? '',
createTime: json['createTime'] as int? ?? 0,
gatewayId: json['gatewayId'] as String? ?? '',
icon: json['icon'] as String? ?? '',
ip: json['ip'] as String? ?? '',
lat: json['lat'] as String? ?? '',
localKey: json['localKey'] as String? ?? '',
lon: json['lon'] as String? ?? '',
model: json['model'] as String? ?? '',
name: json['name'] as String? ?? '',
nodeId: json['nodeId'] as String? ?? '',
online: json['online'] as bool? ?? false,
ownerId: json['ownerId'] as String? ?? '',
productName: json['productName'] as String? ?? '',
sub: json['sub'] as bool? ?? false,
timeZone: json['timeZone'] as String? ?? '',
updateTime: json['updateTime'] as int? ?? 0,
uuid: json['uuid'] as String? ?? '',
productUuid: json['productUuid'] as String? ?? '',
productType: json['productType'] as String? ?? '',
permissionType: json['permissionType'] as String? ?? '',
macAddress: json['macAddress'] as String? ?? '',
subspace:
Subspace.fromJson(json['subspace'] as Map<String, dynamic>? ?? {}),
);
}
Map<String, dynamic> toJson() {
return {
'activeTime': activeTime,
'category': category,
'categoryName': categoryName,
'createTime': createTime,
'gatewayId': gatewayId,
'icon': icon,
'ip': ip,
'lat': lat,
'localKey': localKey,
'lon': lon,
'model': model,
'name': name,
'nodeId': nodeId,
'online': online,
'ownerId': ownerId,
'productName': productName,
'sub': sub,
'timeZone': timeZone,
'updateTime': updateTime,
'uuid': uuid,
'productUuid': productUuid,
'productType': productType,
'permissionType': permissionType,
'macAddress': macAddress,
'subspace': subspace.toJson(),
};
}
static DeviceInfoModel empty() {
return DeviceInfoModel(
activeTime: 0,
category: '',
categoryName: '',
createTime: 0,
gatewayId: '',
icon: '',
ip: '',
lat: '',
localKey: '',
lon: '',
model: '',
name: '',
nodeId: '',
online: false,
ownerId: '',
productName: '',
sub: false,
timeZone: '',
updateTime: 0,
uuid: '',
productUuid: '',
productType: '',
permissionType: '',
macAddress: '',
subspace: Subspace(
uuid: '',
createdAt: '',
updatedAt: '',
subspaceName: '',
),
);
}
}
class Subspace {
final String uuid;
final String createdAt;
final String updatedAt;
final String subspaceName;
Subspace({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.subspaceName,
});
factory Subspace.fromJson(Map<String, dynamic> json) {
return Subspace(
uuid: json['uuid'] as String? ?? '',
createdAt: json['createdAt'] as String? ?? '',
updatedAt: json['updatedAt'] as String? ?? '',
subspaceName: json['subspaceName'] as String? ?? '',
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt,
'updatedAt': updatedAt,
'subspaceName': subspaceName,
};
}
}

View File

@ -0,0 +1,35 @@
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
class SubSpaceModel {
final String? id;
final String? name;
List<DeviceModel>? devices;
SubSpaceModel({
required this.id,
required this.name,
required this.devices,
});
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'devices': devices?.map((device) => device.toJson()).toList(),
};
}
factory SubSpaceModel.fromJson(Map<String, dynamic> json) {
List<DeviceModel> devices = [];
if (json['devices'] != null) {
for (var device in json['devices']) {
devices.add(DeviceModel.fromJson(device));
}
}
return SubSpaceModel(
id: json['uuid'] as String? ?? '',
name: json['subspaceName'] as String? ?? '',
devices: devices.isNotEmpty ? devices : null as List<DeviceModel>?,
);
}
}

View File

@ -0,0 +1,115 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/subspace_dialog_buttons.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SubSpaceDialog extends StatefulWidget {
final List<SubSpaceModel> subSpaces;
final String? selected;
final void Function(SubSpaceModel?) onConfirmed;
const SubSpaceDialog({
Key? key,
required this.subSpaces,
this.selected,
required this.onConfirmed,
}) : super(key: key);
@override
State<SubSpaceDialog> createState() => _SubSpaceDialogState();
}
class _SubSpaceDialogState extends State<SubSpaceDialog> {
String? _selectedId;
@override
void initState() {
super.initState();
_selectedId = widget.selected;
}
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: ColorsManager.whiteColors,
insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 60),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
),
child: Container(
width: MediaQuery.of(context).size.width * 0.35,
padding: const EdgeInsets.fromLTRB(0, 24, 0, 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Sub-Space',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
color: ColorsManager.blueColor,
fontSize: 20),
),
const Divider(),
const SizedBox(height: 10),
...widget.subSpaces.map((space) {
return RadioListTile<String>(
value: space.id!,
groupValue: _selectedId,
onChanged: (value) {
setState(() {
_selectedId = value;
});
},
activeColor: Color(0xFF2962FF),
title: Text(
space.name ?? 'Unnamed Sub-Space',
style: context.textTheme.bodyMedium?.copyWith(
fontSize: 15,
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400,
),
),
controlAffinity: ListTileControlAffinity.trailing,
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
);
}).toList(),
const SizedBox(height: 12),
const Divider(height: 1, thickness: 1),
SubSpaceDialogButtons(selectedId: _selectedId, widget: widget),
],
),
),
);
}
}
void showSubSpaceDialog(
BuildContext context, {
required List<SubSpaceModel> subSpaces,
String? selected,
required String communityUuid,
required String spaceUuid,
}) {
showDialog(
context: context,
barrierDismissible: true,
builder: (ctx) => SubSpaceDialog(
subSpaces: subSpaces,
selected: selected,
onConfirmed: (selectedModel) {
if (selectedModel != null) {
context.read<SettingDeviceBloc>().add(
SettingBlocAssignRoom(
communityUuid: communityUuid,
spaceUuid: spaceUuid,
subSpaceUuid: selectedModel.id ?? '',
),
);
}
},
),
);
}

View File

@ -0,0 +1,114 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SubSpaceDialogButtons extends StatelessWidget {
const SubSpaceDialogButtons({
super.key,
required String? selectedId,
required this.widget,
}) : _selectedId = selectedId;
final String? _selectedId;
final SubSpaceDialog widget;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 50,
child: Row(
children: [
Expanded(
child: Container(
decoration: const BoxDecoration(
border: Border(
right: BorderSide(
color: ColorsManager.dividerColor,
width: 0.5,
),
),
),
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.textGray,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
),
),
Expanded(
child: Container(
decoration: const BoxDecoration(
border: Border(
left: BorderSide(
color: ColorsManager.dividerColor,
width: 0.5,
),
),
),
child: TextButton(
onPressed: _selectedId == null
? null
: () {
final selectedModel = widget.subSpaces.firstWhere(
(space) => space.id == _selectedId,
orElse: () =>
SubSpaceModel(id: null, name: '', devices: []));
widget.onConfirmed(selectedModel);
Navigator.of(context).pop();
},
child: Text(
'Confirm',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.secondaryColor,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
),
),
),
],
),
);
}
}
void showSubSpaceDialog(
BuildContext context, {
required List<SubSpaceModel> subSpaces,
String? selected,
required String communityUuid,
required String spaceUuid,
}) {
showDialog(
context: context,
barrierDismissible: true,
builder: (ctx) => SubSpaceDialog(
subSpaces: subSpaces,
selected: selected,
onConfirmed: (selectedModel) {
if (selectedModel != null) {
context.read<SettingDeviceBloc>().add(
SettingBlocAssignRoom(
communityUuid: communityUuid,
spaceUuid: spaceUuid,
subSpaceUuid: selectedModel.id ?? '',
),
);
}
},
),
);
}

View File

@ -110,11 +110,11 @@ class SaveRoutineHelper {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DialogFooterButton(
text: 'Cancel',
text: 'Back',
onTap: () => Navigator.pop(context),
),
DialogFooterButton(
text: 'Confirm',
text: 'Save',
onTap: () {
if (state.isAutomation) {
if (state.isUpdate ?? false) {

View File

@ -91,7 +91,8 @@ class DevicesManagementApi {
}
}
Future<bool> deviceBatchControl(List<String> uuids, String code, dynamic value) async {
Future<bool> deviceBatchControl(
List<String> uuids, String code, dynamic value) async {
try {
final body = {
'devicesUuid': uuids,
@ -116,7 +117,8 @@ class DevicesManagementApi {
}
}
static Future<List<DeviceModel>> getDevicesByGatewayId(String gatewayId) async {
static Future<List<DeviceModel>> getDevicesByGatewayId(
String gatewayId) async {
final response = await HTTPService().get(
path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId),
showServerMessage: false,
@ -150,7 +152,9 @@ class DevicesManagementApi {
String code,
) async {
final response = await HTTPService().get(
path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code),
path: ApiEndpoints.getDeviceLogs
.replaceAll('{uuid}', uuid)
.replaceAll('{code}', code),
showServerMessage: false,
expectedResponseModel: (json) {
return DeviceReport.fromJson(json['data']);
@ -223,7 +227,8 @@ class DevicesManagementApi {
}
}
Future<bool> addScheduleRecord(ScheduleEntry sendSchedule, String uuid) async {
Future<bool> addScheduleRecord(
ScheduleEntry sendSchedule, String uuid) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid),
@ -240,7 +245,8 @@ class DevicesManagementApi {
}
}
Future<List<ScheduleModel>> getDeviceSchedules(String uuid, String category) async {
Future<List<ScheduleModel>> getDeviceSchedules(
String uuid, String category) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getScheduleByDeviceId
@ -263,7 +269,9 @@ class DevicesManagementApi {
}
Future<bool> updateScheduleRecord(
{required bool enable, required String uuid, required String scheduleId}) async {
{required bool enable,
required String uuid,
required String scheduleId}) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.updateScheduleByDeviceId
@ -284,7 +292,8 @@ class DevicesManagementApi {
}
}
Future<bool> editScheduleRecord(String uuid, ScheduleEntry newSchedule) async {
Future<bool> editScheduleRecord(
String uuid, ScheduleEntry newSchedule) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid),
@ -335,4 +344,46 @@ class DevicesManagementApi {
return false;
}
}
static Future<Map<String, dynamic>> putDeviceName(
{required String deviceId, required String deviceName}) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.deviceByUuid.replaceAll('{deviceUuid}', deviceId),
body: {"deviceName": deviceName},
expectedResponseModel: (json) {
return json['data'];
},
);
return response;
} catch (e) {
rethrow;
}
}
static Future getDeviceInfo(String deviceId) async {
final response = await HTTPService().get(
path: ApiEndpoints.deviceByUuid.replaceAll('{deviceUuid}', deviceId),
showServerMessage: false,
expectedResponseModel: (json) {
return json['data'] as Map<String, dynamic>;
});
return response;
}
static Future resetDevice({
String? devicesUuid,
}) async {
final response = await HTTPService().post(
path: ApiEndpoints.resetDevice.replaceAll('{deviceUuid}', devicesUuid!),
showServerMessage: false,
body: {
"devicesUuid": [devicesUuid]
},
expectedResponseModel: (json) {
return json;
},
);
return response;
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/pages/space_tree/model/pagination_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
@ -12,14 +13,16 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
class CommunitySpaceManagementApi {
// Community Management APIs
Future<List<CommunityModel>> fetchCommunities(String projectId, {int page = 1}) async {
Future<List<CommunityModel>> fetchCommunities(String projectId,
{int page = 1}) async {
try {
List<CommunityModel> allCommunities = [];
bool hasNext = true;
while (hasNext) {
await HTTPService().get(
path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
path: ApiEndpoints.getCommunityList
.replaceAll('{projectId}', projectId),
queryParameters: {
'page': page,
},
@ -55,8 +58,14 @@ class CommunitySpaceManagementApi {
try {
bool hasNext = false;
await HTTPService().get(
path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
queryParameters: {'page': page, 'includeSpaces': true, 'size': 25, 'search': search},
path:
ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
queryParameters: {
'page': page,
'includeSpaces': true,
'size': 25,
'search': search
},
expectedResponseModel: (json) {
try {
List<dynamic> jsonData = json['data'] ?? [];
@ -68,7 +77,10 @@ class CommunitySpaceManagementApi {
page = currentPage + 1;
paginationModel = PaginationModel(
pageNum: page, hasNext: hasNext, size: 25, communities: communityList);
pageNum: page,
hasNext: hasNext,
size: 25,
communities: communityList);
return paginationModel;
} catch (_) {
hasNext = false;
@ -83,7 +95,8 @@ class CommunitySpaceManagementApi {
Future<CommunityModel?> getCommunityById(String communityId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getCommunityById.replaceAll('{communityId}', communityId),
path: ApiEndpoints.getCommunityById
.replaceAll('{communityId}', communityId),
expectedResponseModel: (json) {
return CommunityModel.fromJson(json['data']);
},
@ -95,7 +108,8 @@ class CommunitySpaceManagementApi {
}
}
Future<CommunityModel?> createCommunity(String name, String description, String projectId) async {
Future<CommunityModel?> createCommunity(
String name, String description, String projectId) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId),
@ -114,7 +128,8 @@ class CommunitySpaceManagementApi {
}
}
Future<bool> updateCommunity(String communityId, String name, String projectId) async {
Future<bool> updateCommunity(
String communityId, String name, String projectId) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.updateCommunity
@ -151,7 +166,8 @@ class CommunitySpaceManagementApi {
}
}
Future<SpacesResponse> fetchSpaces(String communityId, String projectId) async {
Future<SpacesResponse> fetchSpaces(
String communityId, String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.listSpaces
@ -177,7 +193,8 @@ class CommunitySpaceManagementApi {
}
}
Future<SpaceModel?> getSpace(String communityId, String spaceId, String projectId) async {
Future<SpaceModel?> getSpace(
String communityId, String spaceId, String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getSpace
@ -289,7 +306,8 @@ class CommunitySpaceManagementApi {
}
}
Future<bool> deleteSpace(String communityId, String spaceId, String projectId) async {
Future<bool> deleteSpace(
String communityId, String spaceId, String projectId) async {
try {
final response = await HTTPService().delete(
path: ApiEndpoints.deleteSpace
@ -307,15 +325,17 @@ class CommunitySpaceManagementApi {
}
}
Future<List<SpaceModel>> getSpaceHierarchy(String communityId, String projectId) async {
Future<List<SpaceModel>> getSpaceHierarchy(
String communityId, String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getSpaceHierarchy
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', projectId),
expectedResponseModel: (json) {
final spaceModels =
(json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
final spaceModels = (json['data'] as List)
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
.toList();
return spaceModels;
},
@ -327,15 +347,17 @@ class CommunitySpaceManagementApi {
}
}
Future<List<SpaceModel>> getSpaceOnlyWithDevices({String? communityId, String? projectId}) async {
Future<List<SpaceModel>> getSpaceOnlyWithDevices(
{String? communityId, String? projectId}) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.spaceOnlyWithDevices
.replaceAll('{communityId}', communityId!)
.replaceAll('{projectId}', projectId!),
expectedResponseModel: (json) {
final spaceModels =
(json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
final spaceModels = (json['data'] as List)
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
.toList();
return spaceModels;
},
);
@ -345,4 +367,59 @@ class CommunitySpaceManagementApi {
return [];
}
}
static Future<List<SubSpaceModel>> getSubSpaceBySpaceId(
{required String communityId,
required String spaceId,
required String projectId}) async {
try {
final path = ApiEndpoints.listSubspace
.replaceFirst('{communityUuid}', communityId)
.replaceFirst('{spaceUuid}', spaceId)
.replaceAll('{projectUuid}', projectId);
final response = await HTTPService().get(
path: path,
queryParameters: {"page": 1, "pageSize": 10},
showServerMessage: false,
expectedResponseModel: (json) {
List<SubSpaceModel> rooms = [];
if (json['data'] != null) {
for (var subspace in json['data']) {
rooms.add(SubSpaceModel.fromJson(subspace));
}
}
return rooms;
},
);
return response;
} catch (error, stackTrace) {
return [];
}
}
static Future<Map<String, dynamic>> assignDeviceToRoom(
{required String communityId,
required String spaceId,
required String subSpaceId,
required String deviceId,
required String projectId}) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.assignDeviceToRoom
.replaceAll('{projectUuid}', projectId)
.replaceAll('{communityUuid}', communityId)
.replaceAll('{spaceUuid}', spaceId)
.replaceAll('{subSpaceUuid}', subSpaceId)
.replaceAll('{deviceUuid}', deviceId),
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
rethrow;
}
}
}

View File

@ -83,4 +83,7 @@ abstract class ColorsManager {
static const Color maxPurpleDot = Color(0xFF5F00BD);
static const Color minBlue = Color(0xFF93AAFD);
static const Color minBlueDot = Color(0xFF023DFE);
static const Color grey25 = Color(0xFFF9F9F9);
}

View File

@ -60,9 +60,12 @@ abstract class ApiEndpoints {
'/devices/{uuid}/report-logs?code={code}&startTime={startTime}&endTime={endTime}';
static const String scheduleByDeviceId = '/schedule/{deviceUuid}';
static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}';
static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}';
static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}';
static const String getScheduleByDeviceId =
'/schedule/{deviceUuid}?category={category}';
static const String deleteScheduleByDeviceId =
'/schedule/{deviceUuid}/{scheduleUuid}';
static const String updateScheduleByDeviceId =
'/schedule/enable/{deviceUuid}';
static const String factoryReset = '/devices/batch';
//product
@ -124,4 +127,13 @@ abstract class ApiEndpoints {
'/projects/{projectId}/communities/{communityId}/spaces/{unitUuid}/automations';
static const String spaceOnlyWithDevices =
'/projects/{projectId}/communities/{communityId}/spaces?onlyWithDevices=true';
static const String listSubspace =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces';
static const String deviceByUuid = '/devices/{deviceUuid}';
static const String resetDevice = '/factory/reset/{deviceUuid}';
static const String assignDeviceToRoom =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
}

View File

@ -14,7 +14,8 @@ 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 =
@ -33,7 +34,8 @@ 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";
@ -70,19 +72,22 @@ 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 =
@ -99,12 +104,15 @@ 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";
@ -154,10 +162,12 @@ 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';
@ -205,7 +215,8 @@ 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';
@ -276,13 +287,16 @@ 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 =
@ -291,7 +305,8 @@ 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 =
@ -310,7 +325,8 @@ 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 =
@ -323,7 +339,8 @@ 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 =
@ -367,12 +384,14 @@ 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';
@ -393,9 +412,11 @@ 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 =
@ -418,29 +439,44 @@ 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';
@ -452,6 +488,13 @@ class Assets {
'assets/icons/refresh_status_icon.svg';
static const String energyConsumedIcon =
'assets/icons/energy_consumed_icon.svg';
static const String closeSettingsIcon =
'assets/icons/close_settings_icon.svg';
static const String editNameIconSettings =
'assets/icons/edit_name_icon_settings.svg';
static const String locationPin = 'assets/icons/location_pin.svg';
static const String aqiTemperature = 'assets/icons/aqi_temperature.svg';
static const String aqiHumidity = 'assets/icons/aqi_humidity.svg';

View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
class DefaultContainer extends StatelessWidget {
const DefaultContainer({
super.key,
required this.child,
this.height,
this.width,
this.color,
this.boxConstraints,
this.margin,
this.padding,
this.onTap,
this.borderRadius,
});
final double? height;
final double? width;
final Widget child;
final BoxConstraints? boxConstraints;
final EdgeInsets? margin;
final EdgeInsets? padding;
final Color? color;
final Function()? onTap;
final BorderRadius? borderRadius;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
child: Container(
height: height,
width: width,
margin: margin ?? const EdgeInsets.only(right: 3, bottom: 3),
constraints: boxConstraints,
decoration: BoxDecoration(
color: color ?? Colors.white,
borderRadius: borderRadius ?? BorderRadius.circular(20),
),
padding: padding ?? const EdgeInsets.all(10),
child: child,
),
);
}
}

View File

@ -61,6 +61,7 @@ dependencies:
firebase_crashlytics: ^4.3.2
firebase_database: ^11.3.2
bloc: ^9.0.0
geocoding: ^4.0.0
gauge_indicator: ^0.4.3