Compare commits

..

84 Commits

Author SHA1 Message Date
db84a9aa5e fix a logic 2025-04-14 16:03:34 +03:00
140f4ff5e2 Refactor AC device controls and toggle widget 2025-04-14 09:57:25 +03:00
cbaeecc968 Merge pull request #139 from SyncrowIOT/SP-1189-FE-Add-Button-Not-clickable-Opening-Pop-up-in-Community-Screen
Sp 1189 fe add button not clickable opening pop up in community screen
2025-04-13 16:22:37 +03:00
2a95720cb0 Merge pull request #140 from SyncrowIOT/SP-1345-FE-SOS-Button-UI-Issue
Refactor SosDeviceControlsView
2025-04-13 16:20:55 +03:00
4fae2d6be0 Refactor SosDeviceControlsView 2025-04-13 16:19:16 +03:00
5b84076572 Merge pull request #138 from SyncrowIOT/SP-1192-FE-Implement-the-schedule-on-the-web-for-the-AC-device
Sp 1192 fe implement the schedule on the web for the ac device
2025-04-13 15:02:02 +03:00
9bf37243a6 remove unused code and make a limitation for the nobody time picker 2025-04-13 14:55:55 +03:00
acad0e8c9c SP-1189-FE-Add-Button-Not-clickable-Opening-Pop-up-in-Community-Screen 2025-04-13 14:50:07 +03:00
79f5ef7871 simplify space selection logic. 2025-04-13 13:12:48 +03:00
6493f02bcc simplify if statements readabaility. 2025-04-13 13:08:23 +03:00
c7c8898763 removed redundant code. 2025-04-13 12:52:38 +03:00
62ee9a72d6 moved SidebarHeader and SidebarAddCommunityButton to their own files. 2025-04-13 12:47:05 +03:00
55695ca5db refactor: improve readability and structure in SidebarWidget's space tile handling 2025-04-13 12:33:50 +03:00
c2f5a8df10 refactor: streamline context usage and improve readability in SidebarWidget 2025-04-13 12:31:17 +03:00
cd9821679e added trailing commas. 2025-04-13 12:29:38 +03:00
bfd3d4542e refactor: simplify onSearchChanged callback to use expressions. 2025-04-13 12:28:50 +03:00
35a99ccda7 removed unnecessary comments from SidebarWidget. 2025-04-13 12:27:04 +03:00
978934399e fixed invalid use of a private type in a public API in SidebarWidget. 2025-04-13 12:25:28 +03:00
bc32fe7941 removed unused method and its use. 2025-04-13 12:25:03 +03:00
cb6d50d367 remove unnecessary print statement 2025-04-13 12:21:35 +03:00
97eb1c152b Implement a countdown timer for the AC and fix bugs in the 'Forgot Password' 2025-04-13 12:18:56 +03:00
c23176706f Merge pull request #136 from SyncrowIOT/SP-1367-FE-Save-Flow-edit-or-delete-the-device-functions
Implementing the Ceiling Sensor in Routine WEB
2025-04-09 13:19:53 +03:00
387ef99728 fix: handle nullable uuid in GatewayModel.fromJson 2025-04-09 12:33:20 +03:00
694a5a7dda Refactor GatewayModel.fromJson to use a loop for status parsing instead of firstWhere. 2025-04-09 12:32:44 +03:00
c90b9a1912 bugfix/ range index errorof automations in routines. 2025-04-09 12:24:59 +03:00
7860292276 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1367-FE-Save-Flow-edit-or-delete-the-device-functions 2025-04-09 12:19:53 +03:00
550acc4953 Merge pull request #137 from SyncrowIOT/fix-remove-option-from-then-dialog-routins
add dialogType to devices and add parameter in showSwitchFunctionsDialog
2025-04-09 12:18:33 +03:00
fd6b737556 Refactor one_gang_switch_dialog.dart to use one_gang_switch instead of ac in function names and variables 2025-04-09 12:17:26 +03:00
3d5825adca Merge branch 'SP-1366-FE-Add-Gateway-Device-Card-to-Devices-Section' of https://github.com/SyncrowIOT/web into SP-1367-FE-Save-Flow-edit-or-delete-the-device-functions 2025-04-09 11:21:20 +03:00
9c97e2879a Refactor constructor syntax for Gateway functions to use initializer list 2025-04-09 10:57:22 +03:00
fda96025e9 uncommented code. 2025-04-09 10:41:38 +03:00
92aa574944 update GatewayMasterState.getOperationalValues to match what the api expects. 2025-04-09 10:21:49 +03:00
d08ab8caac add dialogType to devices and add parameter in showSwitchFunctionsDialog 2025-04-09 10:09:56 +03:00
774f21a55b Refactor RoutineDevices to consolidate device data preparation in a single map to remove code duplication. 2025-04-09 09:59:15 +03:00
9b69ec31e9 Refactor RoutineDevices to use a class-level constant for allowed product types. 2025-04-09 09:57:18 +03:00
a242377ea6 Fully refactored CreateRoutine/Gateway feature. 2025-04-09 09:46:45 +03:00
3a0c8edf86 SP-1365/ Gateway Functions Popup (for “Then” section). 2025-04-08 16:42:31 +03:00
970f7ed16f SP-1365 2025-04-08 16:42:12 +03:00
006bd4c8ae SP-1364/ Gateway Conditions Popup (for “If” section) 2025-04-08 16:20:22 +03:00
b6752683fd Add Gateway dialog and functions integration 2025-04-08 15:08:53 +03:00
b0846b2fef Add Gateway operational value classes and implementations 2025-04-08 15:08:33 +03:00
8a244dcd23 Created GatewayModel. 2025-04-08 15:08:15 +03:00
9d4395e204 Added gateway product type to IfContainer. 2025-04-08 15:08:01 +03:00
46f318734a Add SVG icons for active bell and gear 2025-04-08 15:06:17 +03:00
7accf1d4c8 SP-1366- Add Gateway Device Card to Devices Section. 2025-04-08 13:07:53 +03:00
c2476b9719 Merge pull request #132 from SyncrowIOT/fix_routine_popup_and_wall_sensor_bugs
Fix routine popup and wall sensor bugs
2025-04-07 16:43:48 +03:00
a56f4e488e Refactor routine_view_card.dart to adjust the size of the CircularProgressIndicator
Fix routine popup
Update wall_presence_sensor.dart to handle null selectedOperationName
2025-04-07 16:42:44 +03:00
6bd9fb7e4e Refactor routine_view_card.dart to adjust the size o 2025-04-07 16:22:21 +03:00
d264409d29 - Refactor the WpsFunctions class in wps_functions.dart to use 'cm' instead of 'temp' for the description of operational values.
- Update the WallPresenceSensor class in wall_presence_sensor.dart to use the selected operation name
2025-04-07 14:27:36 +03:00
ca44f3bf55 fix routine popup 2025-04-07 12:53:12 +03:00
9949a0a0bf Merge pull request #131 from SyncrowIOT/SP-1281-FE-Save-Display-Configured-Sensor-as-a-Card
save presence state and edit selected value
2025-04-07 09:30:38 +03:00
8dc4081a89 save presence state and edit selected value 2025-04-06 16:38:34 +03:00
52498f4e6b Merge pull request #130 from SyncrowIOT/routine_ui_issue
Refactor SVG icons and update asset paths and fix RoutinesView issues
2025-04-06 16:33:49 +03:00
37440f288c Merge branch 'dev' into routine_ui_issue 2025-04-06 16:27:34 +03:00
973ae82bec Merge pull request #125 from SyncrowIOT/SP-1271-rework
SP-1271-rework
2025-04-06 11:59:46 +03:00
8c4dec4483 Merge pull request #129 from SyncrowIOT/SP-1279
add WallPresenceSensor and add new icons for presence state and selec…
2025-04-06 11:57:54 +03:00
ad922577da Refactor SVG icons and update asset paths and fix RoutinesView issues 2025-04-06 11:28:59 +03:00
49c743befb remove debug print statements 2025-04-06 10:04:36 +03:00
4fc259491e add WallPresenceSensor and add new icons for presence state and selected value 2025-04-06 09:57:12 +03:00
64f61a6c3d Merged with dev 2025-04-06 01:17:30 +03:00
ab3f268f29 Added pagination and search logic in space tree 2025-04-06 01:14:16 +03:00
b345822366 Merge pull request #128 from SyncrowIOT:SP-1200
fixing duplicate issue
2025-04-03 10:50:31 +04:00
76793a3c0b Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1200 2025-04-03 10:48:56 +04:00
c330f11206 Merge pull request #127 from SyncrowIOT/SP-1246
updated endpoints for devices
2025-04-03 10:23:34 +04:00
9cb62795a6 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1246 2025-04-03 10:17:06 +04:00
a034202d76 updated endpoints 2025-04-03 10:14:24 +04:00
869799f08e Merge pull request #126 from SyncrowIOT/fix_routine_popup
Refactor routine creation UI for improved code organization
2025-03-27 15:06:38 +03:00
573e7b42ed Refactor routine creation UI for improved code organization 2025-03-27 15:04:33 +03:00
22935f7007 SP-1271-rework 2025-03-27 14:52:26 +03:00
5bf5120883 Merge pull request #124 from SyncrowIOT/SP-1271-Fix-Deployment
SP-1271-Fix-Deployment
2025-03-27 13:45:59 +03:00
f307aaff9d Removed the usage of spacing property because the GitHub action doesnt support the version that has it, so instead opted for using Padding. 2025-03-27 11:08:57 +03:00
aea18fb293 Merge pull request #123 from SyncrowIOT/SP-1271-FE-Remove-Community-Field-from-Device-Management-Screen
Sp 1271 fe remove community field from device management screen
2025-03-26 12:57:16 +03:00
835dfe8785 Rename controller variables in DeviceSearchFilters to be private to control access control. 2025-03-26 12:40:25 +03:00
f670ae78aa Refactor device search filters layout for improved code organization and to remove any code duplication. 2025-03-26 12:39:51 +03:00
d3128a9c9c Refactor search button callback for improved readability 2025-03-26 12:37:03 +03:00
b65f172f9d Removed memory leak bug from DeviceSearchFilters widget. 2025-03-26 12:36:26 +03:00
dad18b77de Removed community filter field from DeviceSearchFilters. 2025-03-26 12:35:04 +03:00
77a9aa2f19 Added include spaces to the communites api 2025-03-26 04:29:33 +03:00
b77dc860cb Merge pull request #122 from SyncrowIOT/SP-1106
Sp 1106
2025-03-24 15:34:05 +03:00
80c43b8046 Merge pull request #121 from SyncrowIOT:bugfix/fix-subspace-update
fixed issue in subspace update
2025-03-19 21:49:51 +04:00
2d3345c1d9 fixed issue in subspace updatw 2025-03-19 21:48:44 +04:00
de5030d9f0 Merge branch 'dev' into SP-1200 2025-03-19 14:12:13 +04:00
8c15fffd42 duplicate 2025-03-19 12:25:37 +04:00
3750fa8329 removed left and right button 2025-03-14 15:02:22 +04:00
77 changed files with 3946 additions and 1354 deletions

View File

@ -0,0 +1,18 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8326 16.8575C7.55614 16.8575 4.89062 14.192 4.89062 10.9156C4.89062 7.63915 7.55614 4.97363 10.8326 4.97363C14.1091 4.97363 16.7746 7.63915 16.7746 10.9156C16.7746 14.192 14.1091 16.8575 10.8326 16.8575Z" fill="#FFE645"/>
<path d="M10.8333 4.97363C10.8332 4.97363 10.833 4.97363 10.833 4.97363V16.8575H10.8333C14.1098 16.8575 16.7753 14.192 16.7753 10.9156C16.7753 7.63915 14.1098 4.97363 10.8333 4.97363Z" fill="#FFF1B9"/>
<path d="M10.833 21.8334C10.4824 21.8334 10.1982 21.5493 10.1982 21.1986V18.8289C10.1982 18.4784 10.4824 18.1941 10.833 18.1941C11.1836 18.1941 11.4678 18.4784 11.4678 18.8289V21.1986C11.4678 21.5493 11.1836 21.8334 10.833 21.8334Z" fill="#FFE645"/>
<path d="M21.0317 11.6348H18.7461C18.3955 11.6348 18.1113 11.3506 18.1113 11C18.1113 10.6496 18.3955 10.3652 18.7461 10.3652H21.0317C21.3824 10.3652 21.6665 10.6496 21.6665 11C21.6665 11.3506 21.3824 11.6348 21.0317 11.6348Z" fill="#FFCF2C"/>
<path d="M2.92042 11.6348H0.634766C0.284157 11.6348 0 11.3506 0 11C0 10.6496 0.284157 10.3652 0.634766 10.3652H2.92042C3.27103 10.3652 3.55518 10.6496 3.55518 11C3.55518 11.3506 3.27103 11.6348 2.92042 11.6348Z" fill="#FFE645"/>
<path d="M17.7243 7.65695C17.5049 7.65695 17.2915 7.54306 17.174 7.33941C16.9988 7.03591 17.1027 6.64761 17.4064 6.47239L19.3481 5.3513C19.6517 5.17591 20.04 5.28005 20.2152 5.58372C20.3905 5.88721 20.2865 6.27551 19.9828 6.45073L18.0412 7.57182C17.9412 7.62951 17.8321 7.65695 17.7243 7.65695Z" fill="#FFCF2C"/>
<path d="M2.03686 16.7139C1.8175 16.7139 1.60409 16.6 1.48656 16.3963C1.31117 16.0928 1.41531 15.7045 1.71881 15.5293L3.69865 14.3862C4.00231 14.2108 4.39045 14.315 4.56567 14.6186C4.74106 14.9221 4.63708 15.3104 4.33342 15.4856L2.35358 16.6287C2.25373 16.6864 2.14447 16.7139 2.03686 16.7139Z" fill="#FFE645"/>
<path d="M5.76902 20.4051C5.6614 20.4051 5.5523 20.3778 5.4523 20.32C5.14863 20.1448 5.04466 19.7565 5.21988 19.453L6.36279 17.4735C6.53801 17.17 6.92631 17.0658 7.22981 17.2412C7.53347 17.4164 7.63744 17.8047 7.46222 18.1082L6.31931 20.0877C6.20178 20.2914 5.98838 20.4051 5.76902 20.4051Z" fill="#FFE645"/>
<path d="M3.94197 7.65681C3.83419 7.65681 3.72509 7.62937 3.62524 7.57168L1.64557 6.42877C1.34207 6.25355 1.23793 5.86525 1.41332 5.56158C1.58854 5.25809 1.97667 5.15395 2.28033 5.32933L4.26001 6.47224C4.56351 6.64746 4.66748 7.03576 4.49226 7.33926C4.37473 7.54291 4.16132 7.65681 3.94197 7.65681Z" fill="#FFE645"/>
<path d="M19.6649 16.7342C19.5572 16.7342 19.4481 16.7067 19.348 16.6491L17.333 15.4857C17.0295 15.3103 16.9255 14.9221 17.1007 14.6185C17.276 14.315 17.6643 14.2108 17.9678 14.3862L19.9828 15.5496C20.2865 15.7248 20.3905 16.1131 20.2152 16.4166C20.0977 16.6203 19.8843 16.7342 19.6649 16.7342Z" fill="#FFCF2C"/>
<path d="M15.8973 20.4051C15.6778 20.4051 15.4645 20.2913 15.347 20.0877L14.2043 18.1085C14.029 17.8048 14.133 17.4167 14.4367 17.2415C14.7402 17.0661 15.1285 17.1701 15.3037 17.4737L16.4464 19.4529C16.6217 19.7566 16.5177 20.1447 16.214 20.3199C16.114 20.3778 16.0049 20.4051 15.8973 20.4051Z" fill="#FFCF2C"/>
<path d="M10.833 3.72144C10.4824 3.72144 10.1982 3.43728 10.1982 3.08667V0.801514C10.1982 0.450905 10.4824 0.166748 10.833 0.166748C11.1836 0.166748 11.4678 0.450905 11.4678 0.801514V3.08667C11.4678 3.43728 11.1836 3.72144 10.833 3.72144Z" fill="#FFE645"/>
<path d="M14.7534 4.75952C14.6458 4.75952 14.5365 4.73208 14.4367 4.67422C14.133 4.499 14.029 4.11086 14.2043 3.8072L15.3472 1.82786C15.5224 1.5242 15.9105 1.42005 16.2142 1.59544C16.5178 1.77066 16.6218 2.15896 16.4466 2.46262L15.3037 4.44197C15.1862 4.64562 14.9728 4.75952 14.7534 4.75952Z" fill="#FFCF2C"/>
<path d="M6.91292 4.75919C6.69356 4.75919 6.48015 4.64529 6.36262 4.44164L5.21988 2.46262C5.04466 2.15896 5.14863 1.77066 5.4523 1.59544C5.75579 1.42005 6.14409 1.5242 6.31931 1.82786L7.46206 3.80687C7.63728 4.11053 7.5333 4.49867 7.22964 4.67405C7.1298 4.73175 7.02053 4.75919 6.91292 4.75919Z" fill="#FFE645"/>
<path d="M10.8333 18.1941C10.8332 18.1941 10.833 18.1941 10.833 18.1941V21.8334H10.8333C11.1839 21.8334 11.4681 21.5493 11.4681 21.1986V18.8289C11.4681 18.4784 11.1839 18.1941 10.8333 18.1941Z" fill="#FFCF2C"/>
<path d="M10.8333 0.166748C10.8332 0.166748 10.833 0.166748 10.833 0.166748V3.72144H10.8333C11.1839 3.72144 11.4681 3.43728 11.4681 3.08667V0.801514C11.4681 0.450905 11.1839 0.166748 10.8333 0.166748Z" fill="#FFCF2C"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,12 @@
<svg width="16" height="22" viewBox="0 0 16 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.3977 14.7672V18.2386C15.3977 19.0611 14.7285 19.7299 13.906 19.7299H1.49132C0.668788 19.7299 0 19.0611 0 18.2386V14.7672C0 10.522 3.45363 7.06836 7.69885 7.06836H7.73063C11.9615 7.08575 15.3977 10.5326 15.3977 14.7672Z" fill="#FF5B5B"/>
<path d="M15.3975 14.7672V18.2386C15.3975 19.0611 14.7283 19.7299 13.9058 19.7299H7.73047V7.06836C11.9613 7.08575 15.3975 10.5326 15.3975 14.7672Z" fill="#FF193D"/>
<path d="M15.3977 15.5405V18.239C15.3977 19.0615 14.7285 19.7303 13.906 19.7303H1.49132C0.668788 19.7303 0 19.0615 0 18.239V15.5405H15.3977Z" fill="#7C8287"/>
<path d="M15.3977 21.1981C15.3977 21.5492 15.1131 21.8334 14.7624 21.8334H0.635315C0.284205 21.8334 0 21.5492 0 21.1981C0 20.8474 0.284205 20.5627 0.635315 20.5627H14.7623C15.1131 20.5627 15.3977 20.8473 15.3977 21.1981Z" fill="#DCE6EB"/>
<path d="M8.36829 0.802063V4.67286C8.36829 5.02355 8.08366 5.30817 7.73297 5.30817C7.38228 5.30817 7.09766 5.02355 7.09766 4.67286V0.802063C7.09761 0.451377 7.38224 0.166748 7.73297 0.166748C8.0837 0.166748 8.36829 0.451377 8.36829 0.802063Z" fill="#FFB64C"/>
<path d="M2.87856 6.65271C2.67031 6.65271 2.4663 6.55047 2.34472 6.36262L0.357991 3.29471C0.167266 3.00023 0.251393 2.60684 0.545924 2.41612C0.840412 2.22535 1.23376 2.30952 1.42452 2.60405L3.41125 5.67195C3.60198 5.96644 3.51785 6.35983 3.22332 6.55055C3.11659 6.61966 2.99688 6.65271 2.87856 6.65271Z" fill="#FFB64C"/>
<path d="M12.3087 6.6527C12.1903 6.6527 12.0706 6.61965 11.9639 6.55054C11.6694 6.35982 11.5852 5.96643 11.7759 5.67194L13.7627 2.60404C13.9534 2.30947 14.3467 2.22538 14.6413 2.41611C14.9358 2.60683 15.0199 3.00022 14.8292 3.29471L12.8425 6.36261C12.7209 6.55042 12.5169 6.6527 12.3087 6.6527Z" fill="#FF9F00"/>
<path d="M15.3975 21.1981C15.3975 21.5492 15.1129 21.8334 14.7622 21.8334H7.73047V20.5627H14.7622C15.1129 20.5627 15.3975 20.8473 15.3975 21.1981Z" fill="#C8D2DC"/>
<path d="M15.3975 15.5405V18.239C15.3975 19.0615 14.7283 19.7303 13.9058 19.7303H7.73047V15.5405H15.3975Z" fill="#596C76"/>
<path d="M8.36774 0.802063V4.67286C8.36774 5.02355 8.08311 5.30817 7.73242 5.30817V0.166748C8.08311 0.166748 8.36774 0.451377 8.36774 0.802063Z" fill="#FF9F00"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,21 @@
<svg width="23" height="23" viewBox="0 0 23 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_7572_5102)">
<path d="M11.255 22.5148H11.2389C9.82071 22.5148 8.66699 21.361 8.66699 19.9429V19.3419C8.66699 18.9913 8.95123 18.707 9.30183 18.707H13.1921C13.5427 18.707 13.8269 18.9913 13.8269 19.3419V19.9429C13.8269 21.3611 12.6731 22.5148 11.255 22.5148Z" fill="#FFA81C"/>
<path d="M13.1912 18.707H11.2461V22.5148H11.2542C12.6723 22.5148 13.826 21.361 13.826 19.9429V19.3419C13.826 18.9912 13.5418 18.707 13.1912 18.707Z" fill="#FF9300"/>
<path d="M12.5157 4.81334H9.97762C9.62701 4.81334 9.34277 4.5291 9.34277 4.1785V2.74955C9.34277 1.69974 10.1968 0.845703 11.2466 0.845703C12.2964 0.845703 13.1505 1.69978 13.1505 2.74955V4.1785C13.1505 4.5291 12.8663 4.81334 12.5157 4.81334Z" fill="#FFA81C"/>
<path d="M12.5151 4.81334C12.8657 4.81334 13.1499 4.5291 13.1499 4.1785V2.74955C13.1499 1.69974 12.2959 0.845703 11.2461 0.845703V4.81334H12.5151Z" fill="#FF9300"/>
<path d="M17.9733 19.9777H4.51899C3.96952 19.9777 3.52246 19.5306 3.52246 18.9811V18.0735C3.52246 17.0793 3.88851 16.1245 4.55319 15.3852C5.00799 14.8792 5.25846 14.226 5.25846 13.5457V9.43293C5.25846 6.13133 7.94448 3.44531 11.2461 3.44531C14.5477 3.44531 17.2337 6.13133 17.2337 9.43293V13.5457C17.2337 14.226 17.4842 14.8792 17.939 15.3851C18.6037 16.1245 18.9697 17.0792 18.9697 18.0734V18.9811C18.9698 19.5306 18.5228 19.9777 17.9733 19.9777Z" fill="#FFCF2C"/>
<path d="M17.9732 19.9776C18.5227 19.9776 18.9698 19.5306 18.9698 18.9811V18.0734C18.9698 17.0792 18.6037 16.1245 17.939 15.3851C17.4842 14.8792 17.2337 14.226 17.2337 13.5457V9.43293C17.2337 6.13133 14.5477 3.44531 11.2461 3.44531V19.9776H17.9732Z" fill="#FFC12E"/>
<path d="M3.52246 18.0734V18.9811C3.52246 19.5306 3.96952 19.9776 4.51899 19.9776H17.9732C18.5227 19.9776 18.9698 19.5306 18.9698 18.9811V18.0734C18.9698 17.638 18.8995 17.2102 18.7648 16.8047H3.72747C3.59272 17.2102 3.52246 17.638 3.52246 18.0734Z" fill="#FFF566"/>
<path d="M11.2461 16.8047V19.9776H17.9732C18.5227 19.9776 18.9698 19.5306 18.9698 18.9811V18.0734C18.9698 17.638 18.8995 17.2102 18.7647 16.8047H11.2461V16.8047Z" fill="#FFE645"/>
<path d="M1.04282 7.85721C0.998428 7.85721 0.953397 7.85255 0.908238 7.84278C0.565508 7.7688 0.347673 7.43097 0.421653 7.08824C0.930035 4.73345 2.22744 2.57028 4.07483 0.997187C4.3418 0.769872 4.74247 0.802079 4.96975 1.06897C5.19706 1.33594 5.16494 1.73657 4.89797 1.96388C3.2616 3.35728 2.11262 5.2723 1.66273 7.35619C1.59852 7.65376 1.33536 7.85721 1.04282 7.85721Z" fill="#FF8B6E"/>
<path d="M21.4417 7.85717C21.1491 7.85717 20.886 7.65377 20.8218 7.35619C20.3719 5.2723 19.2229 3.35728 17.5866 1.96393C17.3196 1.73661 17.2875 1.33594 17.5148 1.06901C17.7421 0.80204 18.1427 0.769875 18.4097 0.997233C20.2571 2.57024 21.5545 4.73345 22.0628 7.08825C22.1368 7.43098 21.919 7.7688 21.5763 7.84278C21.5311 7.85247 21.4861 7.85717 21.4417 7.85717Z" fill="#FF674F"/>
<path d="M3.48329 8.6005C3.4444 8.6005 3.40495 8.5969 3.36534 8.58945C3.02075 8.5247 2.79394 8.19284 2.85869 7.84829C3.20714 5.99413 4.18429 4.28141 5.61006 3.02565C5.87314 2.79385 6.27431 2.81937 6.50603 3.08245C6.73779 3.34557 6.71231 3.74671 6.44923 3.97842C5.23495 5.04796 4.40293 6.50556 4.1065 8.08276C4.04919 8.38778 3.78268 8.6005 3.48329 8.6005Z" fill="#FF8B6E"/>
<path d="M19.0004 8.60042C18.701 8.60042 18.4345 8.38771 18.3772 8.08273C18.0808 6.50557 17.2489 5.04793 16.0346 3.97843C15.7715 3.74667 15.7461 3.34554 15.9778 3.08241C16.2095 2.81934 16.6107 2.7939 16.8738 3.02562C18.2995 4.28138 19.2766 5.99405 19.625 7.84817C19.6898 8.19277 19.4629 8.52458 19.1183 8.58933C19.0788 8.59682 19.0393 8.60042 19.0004 8.60042Z" fill="#FF674F"/>
</g>
<defs>
<clipPath id="clip0_7572_5102">
<rect width="21.67" height="21.67" fill="white" transform="translate(0.407227 0.845703)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,17 @@
<svg width="22" height="14" viewBox="0 0 22 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.6663 11.1257L7.02441 8.58667L10.833 13.6648H21.6663V11.1257Z" fill="#FFB54C"/>
<path d="M0 11.1257V13.6648H10.8333V8.58667L0 11.1257Z" fill="#FFCC4A"/>
<path d="M21.6663 11.1257V5.41284H20.0688L19.4341 6.04761L18.7993 5.41284H17.2018L16.5671 6.04761L15.9323 5.41284H14.3348L13.7 6.04761L13.0653 5.41284H11.4678L8.29395 8.26929L10.833 11.1257H21.6663Z" fill="#FFCC4A"/>
<path d="M10.8333 5.41284H10.1986H8.60107L7.96631 6.04761L7.33154 5.41284H5.73405L5.09928 6.04761L4.46452 5.41284H2.86702L2.23226 6.04761L1.59749 5.41284H0V11.1257H10.8333V5.41284Z" fill="#FFE278"/>
<path d="M21.0316 1.60449H10.833L10.1982 2.23926L10.833 2.87402H21.0316V1.60449Z" fill="#ABD5ED"/>
<path d="M10.8333 1.60449H1.26953V0.334961H0V4.14355H1.26953V2.87402H10.8333V1.60449Z" fill="#BFEBFF"/>
<path d="M20.3965 0.334961H21.666V4.14355H20.3965V0.334961Z" fill="#ABD5ED"/>
<path d="M1.59766 5.41284H2.86719V8.58667H1.59766V5.41284Z" fill="#587AA1"/>
<path d="M4.46484 5.41284H5.73438V7.31714H4.46484V5.41284Z" fill="#587AA1"/>
<path d="M7.33203 5.41284H8.60156V8.58667H7.33203V5.41284Z" fill="#587AA1"/>
<path d="M11.4678 7.31714H10.833L10.5156 6.99976V5.73022L10.833 5.41284H11.4678V7.31714Z" fill="#455F80"/>
<path d="M10.1982 5.41284H10.833V7.31714H10.1982V5.41284Z" fill="#587AA1"/>
<path d="M13.0654 5.41284H14.335V8.58667H13.0654V5.41284Z" fill="#455F80"/>
<path d="M15.9326 5.41284H17.2021V7.31714H15.9326V5.41284Z" fill="#455F80"/>
<path d="M18.7998 5.41284H20.0693V8.58667H18.7998V5.41284Z" fill="#455F80"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,16 @@
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.9326 7.99996C12.9326 8.4282 12.8042 8.82647 12.5838 9.15837C12.2083 9.72412 11.5654 9.84106 10.8355 9.84106C9.67727 9.84106 9.05078 9.15816 9.05078 7.99996C9.05078 7.26923 9.11211 6.62577 9.67883 6.25036C10.0104 6.03074 10.408 5.90283 10.8354 5.90283C11.9937 5.90287 12.9326 6.84175 12.9326 7.99996Z" fill="#E5646E"/>
<path d="M11.4252 9.50734C10.267 9.50734 9.32807 8.56846 9.32807 7.41026C9.32807 6.98202 9.45843 6.5824 9.67877 6.25049C9.11302 6.62607 8.73828 7.27012 8.73828 8.00009C8.73828 9.15829 9.67716 10.0972 10.8354 10.0972C11.5653 10.0972 12.2082 9.72425 12.5837 9.15851C12.2519 9.3788 11.8534 9.50734 11.4252 9.50734Z" fill="#DB4655"/>
<path d="M3.66787 0.133789C3.84673 0.133789 4.02559 0.202058 4.16209 0.338597C4.16848 0.344987 4.1747 0.351463 4.18079 0.358023L3.79505 1.32708C2.01269 3.10948 1.03102 5.47931 1.03102 7.99997C1.03102 10.5206 2.01269 12.8905 3.79505 14.6729L4.18101 15.6417C4.17483 15.6483 4.16852 15.6549 4.16209 15.6613C3.88935 15.9344 3.44668 15.9344 3.1736 15.6613C1.12705 13.6149 0 10.8941 0 7.99997C0 5.10588 1.12705 2.38502 3.17364 0.338597C3.31001 0.202058 3.489 0.133789 3.66787 0.133789Z" fill="#FFE6E7"/>
<path d="M7.6231 4.08813C7.80196 4.08813 7.98099 4.1564 8.11732 4.29294C8.1312 4.30682 8.14437 4.32109 8.15681 4.33582L7.68282 5.28138C6.18391 6.78047 6.18391 9.21957 7.68282 10.7187L8.15774 11.6633C8.145 11.6783 8.13154 11.693 8.11732 11.7072C7.84458 11.9803 7.40157 11.9803 7.12884 11.7072C5.08466 9.66318 5.08466 6.33695 7.12884 4.29294C7.26521 4.15636 7.44424 4.08813 7.6231 4.08813Z" fill="#FF9A9F"/>
<path d="M5.64357 2.11108C5.82243 2.11108 6.0013 2.17935 6.13779 2.31589C6.14575 2.32385 6.15345 2.33189 6.16094 2.34014L5.56438 3.30433C2.97545 5.89361 2.97545 10.1066 5.56438 12.6959L6.16145 13.6596C6.15383 13.668 6.14596 13.6762 6.13783 13.6843C5.86476 13.9574 5.42243 13.9574 5.14935 13.6843C2.01532 10.5501 2.01532 5.45005 5.14935 2.31581C5.28585 2.17935 5.46471 2.11108 5.64357 2.11108Z" fill="#FFCCCF"/>
<path d="M6.29102 7.99972C6.29102 9.50473 7.0294 10.836 8.1565 11.6629C8.38886 11.3884 8.37511 10.9772 8.11608 10.7184C6.61717 9.21929 6.61717 6.78019 8.11608 5.28106C8.37511 5.0222 8.38793 4.61001 8.15557 4.33545C7.02852 5.1623 6.29102 6.49471 6.29102 7.99972Z" fill="#FF8086"/>
<path d="M3.49609 7.99983C3.49609 10.2771 4.53592 12.313 6.16362 13.6593C6.413 13.385 6.405 12.9604 6.14001 12.6956C3.55107 10.1064 3.55107 5.89335 6.14001 3.30408C6.405 3.03925 6.41249 2.61411 6.16316 2.33984C4.53541 3.68609 3.49609 5.72253 3.49609 7.99983Z" fill="#FF9A9F"/>
<path d="M0.699219 7.99986C0.699219 11.0495 2.05 13.7834 4.18124 15.6416C4.43535 15.3675 4.42896 14.9393 4.16232 14.6728C2.37992 12.8903 1.39825 10.5205 1.39825 7.99986C1.39825 5.47919 2.37992 3.10937 4.16232 1.32697C4.42896 1.06049 4.43514 0.632002 4.18103 0.35791C2.04979 2.21607 0.699219 4.95022 0.699219 7.99986Z" fill="#FFCCCF"/>
<path d="M18.0014 15.8662C17.8226 15.8662 17.6437 15.7979 17.5072 15.6613C17.5008 15.655 17.4946 15.6485 17.4885 15.6419L17.8742 14.6729C19.6566 12.8905 20.6383 10.5206 20.6383 7.99997C20.6383 5.4793 19.6566 3.10948 17.8742 1.32708L17.4883 0.358277C17.4945 0.351632 17.5008 0.345072 17.5072 0.338596C17.7799 0.06552 18.2226 0.06552 18.4957 0.338596C20.5422 2.38502 21.6693 5.10588 21.6693 7.99997C21.6693 10.8941 20.5422 13.6149 18.4956 15.6613C18.3593 15.7979 18.1803 15.8662 18.0014 15.8662Z" fill="#FFE6E7"/>
<path d="M14.0464 11.912C13.8675 11.912 13.6885 11.8438 13.5521 11.7072C13.5383 11.6933 13.5251 11.6791 13.5127 11.6644L13.9866 10.7187C15.4856 9.21966 15.4856 6.78055 13.9866 5.28143L13.5117 4.33687C13.5245 4.32181 13.5379 4.30716 13.5521 4.29294C13.8249 4.01987 14.2679 4.01987 14.5406 4.29294C16.5848 6.33695 16.5848 9.66318 14.5406 11.7072C14.4043 11.8438 14.2252 11.912 14.0464 11.912Z" fill="#FF9A9F"/>
<path d="M16.0257 13.8892C15.8468 13.8892 15.668 13.8209 15.5315 13.6844C15.5235 13.6764 15.5158 13.6684 15.5083 13.6601L16.1049 12.6959C18.6938 10.1066 18.6938 5.89361 16.1049 3.30433L15.5078 2.34065C15.5154 2.33223 15.5233 2.32398 15.5314 2.31589C15.8045 2.04281 16.2468 2.04281 16.5199 2.31589C19.6539 5.45009 19.6539 10.5502 16.5199 13.6844C16.3834 13.8209 16.2046 13.8892 16.0257 13.8892Z" fill="#FFCCCF"/>
<path d="M15.3797 8.00012C15.3797 6.49511 14.6413 5.16376 13.5142 4.33691C13.2819 4.61147 13.2956 5.02261 13.5546 5.28147C15.0536 6.78055 15.0536 9.21966 13.5546 10.7188C13.2956 10.9776 13.2828 11.3898 13.5152 11.6644C14.6422 10.8375 15.3797 9.50513 15.3797 8.00012Z" fill="#FF8086"/>
<path d="M18.175 8.00005C18.175 5.72275 17.1351 3.68687 15.5074 2.34058C15.2581 2.61484 15.2661 3.03944 15.531 3.30426C18.12 5.89353 18.12 10.1065 15.531 12.6958C15.2661 12.9606 15.2586 13.3858 15.5079 13.66C17.1356 12.3138 18.175 10.2774 18.175 8.00005Z" fill="#FF9A9F"/>
<path d="M20.9707 7.99989C20.9707 4.95025 19.6199 2.21636 17.4887 0.358154C17.2346 0.632246 17.241 1.06048 17.5076 1.32696C19.29 3.10936 20.2716 5.47918 20.2716 7.99985C20.2716 10.5205 19.29 12.8903 17.5076 14.6727C17.241 14.9392 17.2348 15.3677 17.4889 15.6418C19.6201 13.7837 20.9707 11.0495 20.9707 7.99989Z" fill="#FFCCCF"/>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

3
assets/icons/gear.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="22" height="23" viewBox="0 0 22 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.412 9.9034L18.9398 9.28324C18.788 8.81496 18.5981 8.35938 18.3703 7.92902C18.7373 7.30886 19.522 6.00531 19.522 6.00531C19.6613 5.75219 19.6233 5.43578 19.4208 5.23324L17.6362 3.43609C17.4338 3.23364 17.1047 3.19563 16.8642 3.3475L14.9404 4.49922C14.4975 4.27141 14.0419 4.08152 13.5862 3.92969L12.9661 1.45754C12.9028 1.17902 12.6496 0.976562 12.3586 0.976562H9.82734C9.53629 0.976562 9.28312 1.17902 9.21989 1.4575C9.21989 1.4575 8.78953 3.18293 8.59973 3.92965C8.10614 4.09418 7.62516 4.29663 7.16957 4.5498L5.16984 3.34746C4.91672 3.19558 4.60031 3.23359 4.39781 3.43605L2.61328 5.23324C2.39817 5.43578 2.36016 5.75219 2.51199 6.00531L3.75234 8.06828C3.5498 8.46063 3.38527 8.86563 3.24609 9.28324L0.773948 9.9034C0.495427 9.96672 0.292969 10.2198 0.292969 10.5109V13.0422C0.292969 13.3332 0.495427 13.5864 0.773906 13.6496L3.24609 14.2698C3.39797 14.7381 3.60051 15.2064 3.82832 15.6494L2.72723 17.4845C2.57535 17.7376 2.61328 18.054 2.82844 18.2692L4.61301 20.0537C4.81547 20.2563 5.13188 20.2942 5.38504 20.1423C5.38504 20.1423 6.62531 19.4082 7.2329 19.0412C7.67578 19.2817 8.13136 19.4715 8.59973 19.6234L9.21989 22.0956C9.28317 22.374 9.53629 22.5765 9.82734 22.5765H12.3586C12.6496 22.5765 12.9028 22.374 12.9661 22.0956L13.5862 19.6234C14.0671 19.4715 14.5354 19.269 14.9911 19.0286C15.6112 19.3955 16.8642 20.1423 16.8642 20.1423C17.1046 20.2942 17.4337 20.2563 17.6362 20.0537L19.4208 18.2692C19.6232 18.054 19.6613 17.7376 19.522 17.4845L18.383 15.5987C18.6108 15.1684 18.788 14.7254 18.9398 14.2698L21.412 13.6496C21.6905 13.5864 21.8929 13.3332 21.8929 13.0422V10.5109C21.893 10.2198 21.6905 9.96672 21.412 9.9034ZM11.093 16.2063C8.65031 16.2063 6.66328 14.2192 6.66328 11.7766C6.66328 9.33391 8.65031 7.34688 11.093 7.34688C13.5356 7.34688 15.5227 9.33391 15.5227 11.7766C15.5227 14.2192 13.5356 16.2063 11.093 16.2063Z" fill="#A1A7B3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,21 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.91415 8.48211C6.17473 8.04425 4.3403 8.07332 2.60916 8.56611C1.74635 8.81143 0.920517 9.16778 0.154693 9.62511C0.00421161 9.71499 -0.0449192 9.90978 0.0449212 10.0603C0.130234 10.2033 0.336744 10.2556 0.480159 10.1701C1.19846 9.74106 1.97317 9.40684 2.78283 9.17663C4.40699 8.71422 6.12775 8.68689 7.75919 9.09766C7.92922 9.14032 8.10166 9.03732 8.14445 8.86737C8.18723 8.69742 8.0841 8.52489 7.91415 8.48211Z" fill="#6BBEF6"/>
<path d="M7.37225 10.4417C5.99155 10.1036 4.53693 10.1313 3.16562 10.5219C2.47009 10.7197 1.80528 11.0068 1.1896 11.3753C1.0392 11.4653 0.990237 11.6602 1.08025 11.8106C1.1397 11.91 1.24495 11.9651 1.3529 11.9651C1.40838 11.9651 1.46453 11.9505 1.51557 11.92C2.0836 11.58 2.69716 11.3151 3.33942 11.1324C4.60502 10.7719 5.94733 10.7463 7.22126 11.0583C7.39138 11.0999 7.56332 10.9958 7.60504 10.8255C7.64677 10.6553 7.54254 10.4835 7.37225 10.4417Z" fill="#6BBEF6"/>
<path d="M6.83178 12.4028C5.81116 12.1632 4.73634 12.1888 3.72363 12.4772C3.19665 12.627 2.6929 12.8452 2.22626 13.1255C2.07599 13.2158 2.02737 13.4108 2.11763 13.561C2.20697 13.7097 2.4048 13.7587 2.55312 13.6696C2.97186 13.4181 3.42406 13.2223 3.89735 13.0877C4.80646 12.8289 5.77096 12.8057 6.68663 13.0208C6.85739 13.0605 7.02809 12.9549 7.06817 12.7844C7.10824 12.6138 7.00241 12.4429 6.83178 12.4028Z" fill="#6BBEF6"/>
<path d="M14.4727 19.6416L13.6738 21.4527C13.5717 21.6842 13.3426 21.8336 13.0896 21.8336H8.12563C7.79678 21.8336 7.57643 21.4956 7.70914 21.1947L9.38433 17.397L13.3135 19.1303L14.4727 19.6416Z" fill="#FFDDCE"/>
<path d="M14.475 19.6415L13.6761 21.4526C13.574 21.6841 13.3448 21.8335 13.0918 21.8335H12.123L13.3158 19.1301L14.475 19.6415Z" fill="#FFCBBE"/>
<path d="M14.1855 13.4249L16.3003 14.3579L19.2365 7.70213C19.4938 7.119 19.229 6.43743 18.645 6.1798C18.061 5.92217 17.3791 6.18606 17.1218 6.7692L14.1855 13.4249Z" fill="#FFDDCE"/>
<path d="M18.6453 6.17967C18.4607 6.09821 18.2662 6.06906 18.0786 6.08543C18.326 6.40921 18.3944 6.85397 18.2185 7.25273L15.8676 12.5814C15.6524 13.0693 15.8731 13.6392 16.3607 13.8543L16.4962 13.9141L19.2368 7.70201C19.4941 7.11887 19.2292 6.43726 18.6453 6.17967Z" fill="#FFCBBE"/>
<path d="M12.0723 12.492L14.187 13.425L16.7716 7.56632C17.0291 6.98276 16.7647 6.30102 16.1812 6.04356L16.1797 6.04289C15.5961 5.78542 14.9144 6.04983 14.6569 6.63334L12.0723 12.492Z" fill="#FFDDCE"/>
<path d="M16.1808 6.04379L16.1789 6.04295C15.9946 5.96165 15.8006 5.93254 15.6132 5.949C15.8606 6.27286 15.9288 6.71808 15.7526 7.11744L13.757 11.6407C13.5398 12.1331 13.7626 12.7082 14.2546 12.9252L14.3821 12.9814L16.7708 7.56693C17.0284 6.98312 16.7642 6.30121 16.1808 6.04379Z" fill="#FFCBBE"/>
<path d="M8.56284 14.6994C7.94352 16.1032 8.58154 17.7448 9.98852 18.3655L13.7612 20.0298C14.9419 20.5507 16.3209 20.0175 16.8407 18.8391L21.1141 9.15261C21.2426 8.86126 21.2407 8.54557 21.1335 8.26982C21.0262 7.99412 20.8142 7.75968 20.5222 7.63091C19.9384 7.37337 19.2564 7.63705 18.9994 8.21972L16.4886 13.9111L12.2593 12.0453C12.2942 11.9675 14.112 7.84707 14.112 7.84707C14.2405 7.55571 14.2384 7.23939 14.1314 6.96428C14.0246 6.68875 13.8121 6.45414 13.5201 6.32537C12.9363 6.06782 12.2545 6.33104 11.9973 6.91418L8.56284 14.6994Z" fill="#FFDDCE"/>
<path d="M13.1203 6.51804C13.2273 6.79311 13.2294 7.10947 13.1008 7.40083C13.1008 7.40083 12.0882 9.6961 11.5601 10.8928C11.3879 11.2831 11.5646 11.7387 11.9547 11.9108L12.2666 12.0484C12.3015 11.9705 14.1192 7.85012 14.1192 7.85012C14.2478 7.55876 14.2456 7.24244 14.1387 6.96733C14.0319 6.6918 13.8194 6.45719 13.5275 6.32842C13.3429 6.247 13.1485 6.21776 12.9608 6.23409C13.0274 6.32105 13.0811 6.41673 13.1203 6.51804Z" fill="#FFCBBE"/>
<path d="M21.1416 8.27269C21.0344 7.99699 20.8223 7.76255 20.5303 7.63377C20.3457 7.55235 20.1513 7.52316 19.9636 7.53953C20.0301 7.62649 20.0837 7.72213 20.1231 7.8234C20.2304 8.0991 20.2322 8.41483 20.1037 8.70619L17.2515 15.1713C17.102 15.5102 16.7231 15.686 16.3689 15.5776C15.1754 15.2122 13.3924 15.1243 12.5647 17.0007C11.6818 19.0018 13.3307 19.8367 13.3307 19.8367L13.7693 20.0326C14.9499 20.5535 16.3289 20.0203 16.8488 18.842L21.1221 9.15548C21.2507 8.86412 21.2488 8.54839 21.1416 8.27269Z" fill="#FFCBBE"/>
<path d="M14.0754 16.7326C14.8301 16.2038 15.3009 16.203 16.2573 16.2547C16.5879 16.2725 17.1315 16.1379 17.3359 15.8777L19.3641 12.8099C19.8043 12.2496 20.616 12.1527 21.1758 12.5935C21.7348 13.0337 21.8308 13.8434 21.3901 14.402L18.8737 18.2526C18.8737 18.2526 17.9272 19.6558 16.9484 20.0884C15.5668 20.6991 13.919 20.3242 13.0262 19.5722C13.0262 19.5722 12.8878 19.3576 13.0925 18.2919C13.2237 17.6089 14.0754 16.7326 14.0754 16.7326Z" fill="#FFDDCE"/>
<path d="M21.1759 12.5933C20.874 12.3556 20.4991 12.2745 20.1486 12.3378C20.5354 12.7953 20.5607 13.4764 20.1751 13.9651L17.6587 17.8158C17.6587 17.8158 16.7122 19.2189 15.7334 19.6515C15.0065 19.9729 14.206 20.0211 13.4805 19.8853C14.4122 20.4147 15.7761 20.6063 16.9485 20.0881C17.9273 19.6555 18.8737 18.2523 18.8737 18.2523L21.3902 14.4017C21.8308 13.8432 21.7349 13.0335 21.1759 12.5933Z" fill="#FFCBBE"/>
<path d="M14.2333 4.09028H7.43247C6.34901 4.09028 5.4707 3.21197 5.4707 2.12851C5.4707 1.04505 6.34901 0.166748 7.43247 0.166748H14.2333C15.3167 0.166748 16.195 1.04505 16.195 2.12851C16.195 3.21197 15.3167 4.09028 14.2333 4.09028Z" fill="#5C90B9"/>
<path d="M14.2336 0.166748H12.9258C14.0092 0.166748 14.8875 1.04505 14.8875 2.12851C14.8875 3.21197 14.0092 4.09028 12.9258 4.09028H14.2336C15.3171 4.09028 16.1954 3.21197 16.1954 2.12851C16.1954 1.04505 15.3171 0.166748 14.2336 0.166748Z" fill="#4A80AA"/>
<path d="M10.8338 4.09028C11.92 4.09028 12.8004 3.21197 12.8004 2.12851C12.8004 1.04506 11.92 0.166748 10.8338 0.166748C9.74768 0.166748 8.86719 1.04506 8.86719 2.12851C8.86719 3.21197 9.74768 4.09028 10.8338 4.09028Z" fill="#FFD064"/>
<path d="M10.8329 4.09028C11.9163 4.09028 12.7946 3.21197 12.7946 2.12851C12.7946 1.04506 11.9163 0.166748 10.8329 0.166748C9.74941 0.166748 8.87109 1.04506 8.87109 2.12851C8.87109 3.21197 9.74941 4.09028 10.8329 4.09028Z" fill="#FFD064"/>
<path d="M10.834 2.78329C11.1954 2.78329 11.4884 2.49033 11.4884 2.12895C11.4884 1.76757 11.1954 1.47461 10.834 1.47461C10.4726 1.47461 10.1797 1.76757 10.1797 2.12895C10.1797 2.49033 10.4726 2.78329 10.834 2.78329Z" fill="#5C90B9"/>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -0,0 +1,5 @@
<svg width="17" height="21" viewBox="0 0 17 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.629 7.72751C16.7022 7.47106 16.444 7.24286 16.1963 7.34188L15.166 7.75403C16.1394 3.617 13.6255 1.13192 12.3309 0.160156L11.9303 0.68629C12.295 4.20985 10.8697 6.77552 10.8697 6.77552L9.6861 6.06286C7.63047 8.86439 7.26813 11.637 7.26813 11.637L6.22457 10.9497L5.84278 15.599L4.19531 17.1113C4.19531 17.1113 9.12215 19.2668 13.862 14.8542C14.0281 14.6996 13.9924 14.4272 13.7895 14.3258L12.2619 13.562C13.7729 12.6635 15.8697 10.3879 16.629 7.72751Z" fill="#1479FF"/>
<path d="M6.94955 12.0658C7.20748 12.3235 7.64893 12.1695 7.69061 11.8073L7.74029 11.3759C7.74315 11.3515 8.03272 8.98838 9.75631 6.48396L10.3884 7.11599C10.5932 7.32076 10.9371 7.27255 11.0777 7.01939L11.2124 6.77685C11.2795 6.65646 12.7674 3.91306 12.3306 0.160279C12.2926 0.131763 12.2554 0.104185 12.2196 0.0782866C11.9917 -0.0867917 11.68 0.0943414 11.709 0.374303C12.0737 3.89786 10.6483 6.46353 10.6483 6.46353L9.94327 5.75849C9.80448 5.6197 9.57174 5.63474 9.45561 5.79298C7.40002 8.59451 7.099 11.3035 7.099 11.3035L6.50061 10.7051C6.32693 10.5315 6.03064 10.6084 5.96029 10.8437C5.23283 13.2767 5.71564 15.7659 5.71564 15.7659L5.71514 15.7664L6.34936 15.6438C6.34525 15.6222 5.97377 13.6265 6.44041 11.5575L6.94955 12.0658Z" fill="#D5EAFF"/>
<path d="M13.2564 6.00624C13.3141 5.83764 13.224 5.65428 13.0554 5.59659C12.8865 5.5394 12.7031 5.62905 12.6457 5.7976C11.8359 8.16772 10.4981 10.2792 8.9927 12.0909C8.98586 10.3632 9.52113 9.3046 9.5316 9.28437C9.61414 9.12683 9.55367 8.93194 9.39641 8.8489C9.23793 8.76601 9.04348 8.82636 8.96031 8.98394C8.92824 9.04499 8.19051 10.4827 8.37992 12.7999C4.65574 16.9746 0.236005 19.3733 0.170849 19.408C0.0136223 19.492 -0.0459481 19.6873 0.0381926 19.8446C0.0961615 19.9534 0.207724 20.0154 0.323037 20.0154C0.374404 20.0154 0.426084 20.0032 0.474599 19.9774C0.530303 19.9477 3.69996 18.2289 6.9675 15.1631C7.06207 15.1709 7.47437 15.2031 7.91512 15.2031C8.76023 15.2031 10.083 15.1232 11.1436 14.6989C11.3091 14.6326 11.3897 14.4449 11.3236 14.2793C11.2574 14.1139 11.068 14.034 10.9041 14.0996C9.80613 14.5389 8.34164 14.5695 7.6 14.552C9.86817 12.3043 12.09 9.41976 13.2564 6.00624Z" fill="#B8DDFF"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,6 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8334 21.8334C7.93969 21.8334 5.21921 20.7065 3.17302 18.6604C1.12688 16.6143 0 13.8938 0 11.0001C0 8.10644 1.12688 5.38596 3.17298 3.33977C5.21921 1.29363 7.93961 0.166748 10.8334 0.166748C13.727 0.166748 16.4475 1.29363 18.4936 3.33977C20.5398 5.38596 21.6667 8.1064 21.6667 11.0001C21.6667 13.8939 20.5397 16.6143 18.4936 18.6605C16.4475 20.7067 13.727 21.8334 10.8334 21.8334ZM10.8334 3.12052C6.48853 3.12052 2.95378 6.65528 2.95378 11.0001C2.95378 15.345 6.48853 18.8797 10.8334 18.8797C15.1782 18.8797 18.713 15.3449 18.713 11.0001C18.713 6.65532 15.1781 3.12052 10.8334 3.12052Z" fill="#7DD2F0"/>
<g opacity="0.1">
<path d="M5.14215 18.6604C3.09605 16.6143 1.96917 13.8938 1.96917 11.0001C1.96917 8.1064 3.09605 5.38592 5.14215 3.33981C6.95445 1.52752 9.29585 0.437158 11.8179 0.211393C11.4924 0.182194 11.1641 0.166748 10.8334 0.166748C7.93969 0.166748 5.21921 1.29363 3.17302 3.33977C1.12688 5.38588 0 8.1064 0 11C0 13.8937 1.12688 16.6142 3.17298 18.6603C5.21913 20.7065 7.93961 21.8334 10.8333 21.8334C11.164 21.8334 11.4924 21.818 11.8179 21.7888C9.29585 21.563 6.95449 20.4726 5.14215 18.6604Z" fill="black"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,18 @@
<svg width="22" height="20" viewBox="0 0 22 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8333 1.71851L0.472669 15.9196C-0.702346 17.5301 0.447998 19.7934 2.44166 19.7934H19.225C21.2187 19.7934 22.369 17.5301 21.194 15.9196L10.8333 1.71851Z" fill="#F1FAFF"/>
<path d="M21.1931 15.9183L10.8333 1.71851L9.06055 4.14839L17.6475 15.9183C18.8229 17.5294 17.6722 19.7933 15.6779 19.7933H19.2235C21.2178 19.7934 22.3685 17.5294 21.1931 15.9183Z" fill="#C7EEFB"/>
<path d="M12.9102 6.05994H8.76926C7.63838 6.05994 6.72168 5.14319 6.72168 4.01236V2.12199C6.72168 2.00515 6.81643 1.9104 6.93327 1.9104H14.7463C14.8631 1.9104 14.9579 2.00515 14.9579 2.12199V4.01236C14.9579 5.14319 14.0411 6.05994 12.9102 6.05994Z" fill="#E0DDE2"/>
<path d="M14.748 1.9104H13.5893V2.63688C13.5893 3.77034 12.6704 4.6892 11.537 4.6892H7.40546C7.19886 4.6892 6.99959 4.65835 6.81152 4.6016C7.06628 5.44539 7.84934 6.05998 8.77624 6.05998H12.9078C14.0412 6.05998 14.9601 5.14112 14.9601 4.00766V2.12246C14.9601 2.00536 14.8651 1.9104 14.748 1.9104Z" fill="#C8C1C9"/>
<path d="M16.3024 2.66033H5.37858C5.26174 2.66033 5.16699 2.56558 5.16699 2.44874V0.41838C5.16699 0.301538 5.26174 0.206787 5.37858 0.206787H16.3024C16.4192 0.206787 16.514 0.301538 16.514 0.41838V2.44874C16.514 2.56562 16.4193 2.66033 16.3024 2.66033Z" fill="#E0DDE2"/>
<path d="M16.3019 0.206787H15.0629V0.997127C15.0629 1.11426 14.9679 1.20923 14.8508 1.20923H5.16699V2.44827C5.16699 2.56541 5.26195 2.66037 5.37909 2.66037H16.3019C16.4191 2.66037 16.514 2.56541 16.514 2.44827V0.418887C16.514 0.30175 16.4191 0.206787 16.3019 0.206787Z" fill="#C8C1C9"/>
<path d="M10.841 4.82612C11.141 4.82612 11.3842 4.58293 11.3842 4.28294C11.3842 3.98294 11.141 3.73975 10.841 3.73975C10.541 3.73975 10.2979 3.98294 10.2979 4.28294C10.2979 4.58293 10.541 4.82612 10.841 4.82612Z" fill="#FA2A3B"/>
<path d="M10.7136 10.264C11.2545 10.264 11.6929 9.82553 11.6929 9.28469C11.6929 8.74385 11.2545 8.30542 10.7136 8.30542C10.1728 8.30542 9.73438 8.74385 9.73438 9.28469C9.73438 9.82553 10.1728 10.264 10.7136 10.264Z" fill="#62D8F9"/>
<path d="M10.7138 8.30566C10.6442 8.30566 10.5762 8.31307 10.5107 8.32686C10.9541 8.42039 11.2869 8.81373 11.2869 9.28494C11.2869 9.75614 10.9541 10.1495 10.5107 10.243C10.5763 10.2568 10.6442 10.2642 10.7138 10.2642C11.2546 10.2642 11.6931 9.82576 11.6931 9.28494C11.6931 8.74412 11.2547 8.30566 10.7138 8.30566Z" fill="#00BEF7"/>
<path d="M14.1217 13.3881L12.4086 13.1096L11.4778 11.3116C11.3944 11.1236 11.2634 10.8661 10.9741 10.78L9.88702 10.4538C9.76776 10.4247 9.65308 10.4141 9.4497 10.446L7.13145 10.9844C6.99264 11.0167 6.86865 11.0946 6.7794 11.2057L5.54163 12.7463C5.32157 13.0202 5.3652 13.4207 5.63913 13.6408C5.75661 13.7352 5.89736 13.7811 6.03718 13.7811C6.22338 13.7811 6.40789 13.6997 6.53357 13.5433L7.6336 12.1741L8.72165 11.9215L8.08273 14.0507C8.07045 14.0866 8.06652 14.1328 8.04896 14.234L7.68849 16.7928L6.00734 18.2515C5.74192 18.4818 5.71344 18.8836 5.94374 19.149C6.06959 19.294 6.24657 19.3683 6.4246 19.3683C6.57238 19.3683 6.72087 19.3171 6.84127 19.2126L8.70227 17.598C8.81826 17.4973 8.89393 17.3582 8.91534 17.2062L9.21009 15.114L9.24932 15.1257L10.8419 16.9844L11.7847 19.0313C11.892 19.2643 12.1223 19.4016 12.363 19.4016C12.452 19.4016 12.5425 19.3828 12.6287 19.343C12.9479 19.196 13.0874 18.8181 12.9405 18.499L11.9606 16.3716C11.936 16.3182 11.9042 16.2685 11.8659 16.2238L10.5325 14.6676L10.9911 13.1393L11.4257 13.9789C11.5183 14.1577 11.69 14.282 11.8886 14.3143L13.9174 14.6441C13.952 14.6497 13.9864 14.6524 14.0203 14.6524C14.3267 14.6524 14.5968 14.4304 14.6475 14.1182C14.7039 13.7713 14.4685 13.4445 14.1217 13.3881Z" fill="#62D8F9"/>
<path d="M8.99707 10.5511C9.20045 10.5192 9.31454 10.5321 9.4338 10.5611L10.5209 10.8873C10.8102 10.9735 10.9411 11.231 11.0245 11.419L12.0585 13.4273C12.085 13.4787 12.1345 13.5144 12.1917 13.5232L14.645 13.9017C14.5979 13.6445 14.3947 13.4324 14.1218 13.3881L12.4088 13.1096L11.4779 11.3116C11.3945 11.1236 11.2635 10.8661 10.9742 10.78L9.88716 10.4538C9.7679 10.4247 9.65322 10.4141 9.44984 10.446L8.99707 10.5511Z" fill="#00BEF7"/>
<path d="M11.9615 16.3715C11.9369 16.3181 11.905 16.2684 11.8667 16.2237L10.5333 14.6676L10.9919 13.1396L10.9337 13.0271C10.8579 12.8807 10.6424 12.9008 10.595 13.0587L10.1416 14.5696C10.1032 14.6976 10.1325 14.8363 10.2194 14.9377L11.4134 16.3311C11.4516 16.3758 11.4835 16.4256 11.5081 16.4789L12.4879 18.6063C12.6191 18.8912 12.5216 19.2223 12.2723 19.3948C12.3025 19.3992 12.3331 19.4015 12.3638 19.4015C12.4528 19.4015 12.5433 19.3827 12.6295 19.343C12.9487 19.196 13.0882 18.8181 12.9413 18.499L11.9615 16.3715Z" fill="#00BEF7"/>
<path d="M3.47827 18.4406C3.31064 18.4406 3.17032 18.3095 3.16092 18.1401C3.08839 16.833 4.09282 15.7105 5.39991 15.6379C5.57574 15.6288 5.72551 15.7624 5.7352 15.9379C5.74493 16.1133 5.61065 16.2634 5.4352 16.2732C4.47834 16.3263 3.74305 17.148 3.79616 18.1048C3.8059 18.2802 3.67162 18.4304 3.49617 18.4401C3.4902 18.4404 3.48419 18.4406 3.47827 18.4406Z" fill="#00BEF7"/>
<path d="M4.76838 18.4539C4.60076 18.4539 4.46044 18.3228 4.45104 18.1534C4.41524 17.5085 4.91082 16.9547 5.5557 16.9189C5.73081 16.9093 5.88121 17.0435 5.89094 17.219C5.90068 17.3944 5.76636 17.5445 5.59091 17.5542C5.29634 17.5706 5.06994 17.8236 5.08627 18.1182C5.09601 18.2936 4.96169 18.4437 4.78624 18.4535C4.78032 18.4537 4.77431 18.4539 4.76838 18.4539Z" fill="#00BEF7"/>
<path d="M17.0106 13.6226C17.0076 13.6226 17.0046 13.6226 17.0016 13.6225C16.826 13.6176 16.6876 13.4712 16.6925 13.2956C16.7191 12.3377 15.9615 11.5366 15.0036 11.5099C14.828 11.505 14.6896 11.3587 14.6945 11.183C14.6994 11.0074 14.8466 10.8691 15.0213 10.8739C16.3299 10.9103 17.365 12.0047 17.3285 13.3134C17.3237 13.4859 17.1822 13.6226 17.0106 13.6226Z" fill="#00BEF7"/>
<path d="M15.7216 13.6714C15.7186 13.6714 15.7156 13.6713 15.7126 13.6712C15.537 13.6663 15.3986 13.52 15.4035 13.3444C15.4075 13.2015 15.3555 13.0656 15.2574 12.9618C15.1591 12.8579 15.0263 12.7985 14.8835 12.7946C14.7079 12.7897 14.5695 12.6433 14.5743 12.4677C14.5793 12.2921 14.724 12.1534 14.9012 12.1586C15.214 12.1673 15.5046 12.2973 15.7196 12.5246C15.9346 12.752 16.0482 13.0494 16.0395 13.3621C16.0346 13.5347 15.8932 13.6714 15.7216 13.6714Z" fill="#00BEF7"/>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,29 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.18147 12.617H0.352631C0.157897 12.617 0 12.4719 0 12.2928C0 12.1137 0.157897 11.9685 0.352631 11.9685H5.18147C5.37624 11.9685 5.5341 12.1137 5.5341 12.2928C5.5341 12.4719 5.37624 12.617 5.18147 12.617Z" fill="#C5D3DD"/>
<path d="M5.37006 14.6529H1.70029C1.50555 14.6529 1.34766 14.5077 1.34766 14.3287C1.34766 14.1496 1.50555 14.0044 1.70029 14.0044H5.3701C5.56483 14.0044 5.72273 14.1496 5.72273 14.3287C5.72269 14.5077 5.56483 14.6529 5.37006 14.6529Z" fill="#C5D3DD"/>
<path d="M6.10302 16.3546H3.87607C3.68133 16.3546 3.52344 16.2094 3.52344 16.0303C3.52344 15.8512 3.68133 15.7061 3.87607 15.7061H6.10302C6.29775 15.7061 6.45565 15.8512 6.45565 16.0303C6.45565 16.2094 6.29779 16.3546 6.10302 16.3546Z" fill="#C5D3DD"/>
<path d="M5.70018 10.5809H2.03037C1.83563 10.5809 1.67773 10.4357 1.67773 10.2566C1.67773 10.0776 1.83563 9.93237 2.03037 9.93237H5.70018C5.89495 9.93237 6.05281 10.0776 6.05281 10.2566C6.05281 10.4358 5.89491 10.5809 5.70018 10.5809Z" fill="#C5D3DD"/>
<path d="M6.43505 8.87901H4.2081C4.01332 8.87901 3.85547 8.73382 3.85547 8.55474C3.85547 8.37565 4.01337 8.23047 4.2081 8.23047H6.43505C6.62978 8.23047 6.78768 8.37565 6.78768 8.55474C6.78768 8.73382 6.62978 8.87901 6.43505 8.87901Z" fill="#C5D3DD"/>
<path d="M9.54009 6.56555L7.89129 4.64779C7.77455 4.51203 7.56983 4.4965 7.43398 4.61333L6.40778 5.4956C6.34258 5.55167 6.30233 5.63136 6.29584 5.71709C6.28936 5.80283 6.31725 5.88766 6.37332 5.9529L8.02212 7.87067C8.08624 7.94525 8.17695 7.98351 8.26813 7.98351C8.34297 7.98351 8.4182 7.95775 8.47938 7.90513L9.50559 7.02286C9.57078 6.96678 9.61104 6.8871 9.61752 6.80136C9.62405 6.71558 9.59616 6.63075 9.54009 6.56555Z" fill="#FEDF30"/>
<path d="M8.57688 4.05987L7.88066 3.25006C7.76388 3.1143 7.5592 3.09882 7.42335 3.2156L5.02887 5.27423C4.96367 5.33031 4.92341 5.40999 4.91693 5.49573C4.91049 5.58147 4.93833 5.6663 4.99441 5.73154L5.69063 6.54135C5.75475 6.61593 5.84542 6.65419 5.93664 6.65419C6.01149 6.65419 6.08672 6.62842 6.1479 6.5758L8.54238 4.51718C8.60758 4.4611 8.64783 4.38142 8.65432 4.29568C8.6608 4.2099 8.63292 4.12507 8.57688 4.05987Z" fill="#D7AA02"/>
<path d="M20.2699 5.71709C20.2634 5.63135 20.2231 5.55167 20.1579 5.49559L19.1317 4.61332C18.9959 4.49654 18.7912 4.51198 18.6744 4.64778L17.0256 6.56555C16.9696 6.63075 16.9417 6.71558 16.9482 6.80136C16.9547 6.8871 16.9949 6.96678 17.0601 7.02286L18.0863 7.90513C18.1476 7.95774 18.2227 7.98351 18.2976 7.98351C18.3888 7.98351 18.4795 7.94525 18.5436 7.87067L20.1924 5.9529C20.2485 5.88766 20.2763 5.80287 20.2699 5.71709Z" fill="#FEDF30"/>
<path d="M21.5385 5.27382L19.144 3.21519C19.0788 3.15912 18.994 3.13093 18.9082 3.13771C18.8225 3.14416 18.7428 3.18445 18.6867 3.24965L17.9905 4.05946C17.8737 4.19526 17.8892 4.39998 18.025 4.51672L20.4195 6.57535C20.4785 6.62611 20.5536 6.65374 20.6309 6.65374C20.6391 6.65374 20.6472 6.65343 20.6553 6.65283C20.7411 6.64634 20.8207 6.60609 20.8768 6.54085L21.573 5.73104C21.6898 5.59532 21.6743 5.39056 21.5385 5.27382Z" fill="#D7AA02"/>
<path d="M12.3359 2.71826H14.3258V5.56015H12.3359V2.71826Z" fill="#0055A3"/>
<path d="M14.5486 0.899902H10.8925C10.5495 0.899902 10.2715 1.17795 10.2715 1.52094V2.27541C10.2715 2.6184 10.5495 2.89645 10.8925 2.89645H14.529L15.412 2.65843V1.153L14.5486 0.899902Z" fill="#0473CE"/>
<path d="M15.7658 0.899902H14.4688C14.8117 0.899902 15.0898 1.17795 15.0898 1.52094V2.27541C15.0898 2.6184 14.8117 2.89645 14.4688 2.89645H15.7658C16.1088 2.89645 16.3869 2.6184 16.3869 2.27541V1.52094C16.3869 1.17795 16.1088 0.899902 15.7658 0.899902Z" fill="#0055A3"/>
<path d="M11.1894 20.8213C6.73857 19.6401 4.08809 15.0744 5.26933 10.6236C6.22178 7.03487 9.37457 4.61661 12.8933 4.43364C13.7386 4.38971 21.1366 5.74662 21.1366 12.5075C21.1366 17.4616 16.6175 21.3883 12.6796 21.0765C12.1846 21.0373 11.6863 20.9532 11.1894 20.8213Z" fill="#26A6FE"/>
<path d="M13.3282 4.42456C13.11 4.42456 12.8937 4.43303 12.6797 4.44951C16.9815 4.78048 20.369 8.37579 20.369 12.7625C20.369 17.1491 16.9814 20.7444 12.6797 21.0754C12.8937 21.0919 13.11 21.1003 13.3282 21.1003C17.9331 21.1003 21.6661 17.3674 21.6661 12.7625C21.6661 8.15754 17.9331 4.42456 13.3282 4.42456Z" fill="#0593FC"/>
<path d="M13.3291 19.2553C16.9152 19.2553 19.8222 16.3483 19.8222 12.7622C19.8222 9.17613 16.9152 6.26904 13.3291 6.26904C9.74302 6.26904 6.83594 9.17613 6.83594 12.7622C6.83594 16.3483 9.74302 19.2553 13.3291 19.2553Z" fill="#FFF3EF"/>
<path d="M14.167 6.32385C13.6602 6.25778 13.1594 6.25229 12.6719 6.30141C12.7378 6.30798 12.8038 6.31524 12.8699 6.32385C16.426 6.78721 18.933 10.0456 18.4696 13.6016C18.0723 16.6508 15.6199 18.9285 12.687 19.2238C16.1652 19.5707 19.3119 17.0915 19.7667 13.6016C20.2301 10.0456 17.723 6.78721 14.167 6.32385Z" fill="#F1F0F0"/>
<path d="M13.3291 8.12532C13.1625 8.12532 13.0273 7.99021 13.0273 7.82353V6.98977C13.0273 6.8231 13.1625 6.68799 13.3291 6.68799C13.4958 6.68799 13.6309 6.8231 13.6309 6.98977V7.82353C13.6309 7.99021 13.4958 8.12532 13.3291 8.12532Z" fill="#B6C4CF"/>
<path d="M16.8487 9.56667C16.7714 9.56667 16.6942 9.53722 16.6353 9.47829C16.5174 9.36043 16.5174 9.16937 16.6353 9.05151L17.2249 8.46195C17.3427 8.34404 17.5337 8.34409 17.6517 8.46195C17.7695 8.57981 17.7695 8.77087 17.6516 8.88873L17.062 9.47829C17.0031 9.53718 16.9259 9.56667 16.8487 9.56667Z" fill="#B6C4CF"/>
<path d="M19.1512 13.0738H18.3174C18.1507 13.0738 18.0156 12.9387 18.0156 12.772C18.0156 12.6053 18.1507 12.4702 18.3174 12.4702H19.1512C19.3178 12.4702 19.453 12.6053 19.453 12.772C19.453 12.9387 19.3178 13.0738 19.1512 13.0738Z" fill="#B6C4CF"/>
<path d="M17.4656 17.1814C17.3884 17.1814 17.3111 17.1519 17.2522 17.093L16.6626 16.5035C16.5448 16.3856 16.5448 16.1945 16.6626 16.0767C16.7805 15.9588 16.9715 15.9588 17.0894 16.0767L17.679 16.6662C17.7969 16.7841 17.7969 16.9752 17.679 17.093C17.6201 17.152 17.5428 17.1814 17.4656 17.1814Z" fill="#B6C4CF"/>
<path d="M13.3682 18.8934C13.2015 18.8934 13.0664 18.7583 13.0664 18.5916V17.7578C13.0664 17.5912 13.2015 17.4561 13.3682 17.4561C13.5349 17.4561 13.67 17.5912 13.67 17.7578V18.5916C13.67 18.7583 13.5349 18.8934 13.3682 18.8934Z" fill="#B6C4CF"/>
<path d="M9.26075 17.2073C9.18353 17.2073 9.10627 17.1779 9.04738 17.1189C8.92952 17.001 8.92952 16.81 9.04738 16.6921L9.63694 16.1026C9.7548 15.9847 9.94586 15.9847 10.0637 16.1026C10.1816 16.2204 10.1816 16.4115 10.0637 16.5293L9.47416 17.1189C9.41523 17.1778 9.33797 17.2073 9.26075 17.2073Z" fill="#B6C4CF"/>
<path d="M8.38164 13.1114H7.54788C7.38121 13.1114 7.24609 12.9763 7.24609 12.8096C7.24609 12.6429 7.38121 12.5078 7.54788 12.5078H8.38164C8.54831 12.5078 8.68342 12.6429 8.68342 12.8096C8.68342 12.9763 8.54831 13.1114 8.38164 13.1114Z" fill="#B6C4CF"/>
<path d="M9.82297 9.59328C9.74575 9.59328 9.66849 9.56383 9.6096 9.5049L9.02004 8.91534C8.90218 8.79748 8.90218 8.60642 9.02004 8.48856C9.1379 8.3707 9.32896 8.37065 9.44682 8.48856L10.0364 9.07812C10.1542 9.19598 10.1542 9.38704 10.0364 9.5049C9.97745 9.56383 9.90019 9.59328 9.82297 9.59328Z" fill="#B6C4CF"/>
<path d="M13.3282 12.6168C13.149 12.6168 13.0039 12.4717 13.0039 12.2926V10.1121C13.0039 9.93303 13.149 9.78784 13.3282 9.78784C13.5073 9.78784 13.6524 9.93303 13.6524 10.1121V12.2926C13.6524 12.4717 13.5073 12.6168 13.3282 12.6168Z" fill="#678D98"/>
<path d="M16.0561 15.7395C15.9731 15.7395 15.8901 15.7078 15.8268 15.6445L13.4661 13.2838C13.3394 13.1571 13.3394 12.9518 13.4661 12.8252C13.5928 12.6986 13.7981 12.6986 13.9247 12.8252L16.2854 15.1859C16.412 15.3126 16.412 15.5179 16.2854 15.6445C16.2221 15.7078 16.1391 15.7395 16.0561 15.7395Z" fill="#678D98"/>
<path d="M13.3724 13.3816C13.0131 13.3816 12.7207 13.0893 12.7207 12.73C12.7207 12.3707 13.013 12.0784 13.3724 12.0784C13.7316 12.0784 14.024 12.3707 14.024 12.73C14.024 13.0893 13.7316 13.3816 13.3724 13.3816Z" fill="#3E5959"/>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,17 +0,0 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_6675_32326)">
<path d="M18 3C9.71584 3 3 9.71572 3 18C3 26.2843 9.71584 33 18 33C26.2842 33 33 26.2843 33 18C33 9.71572 26.2842 3 18 3ZM23.1844 18.7951L15.6844 23.4826C15.5326 23.5774 15.3601 23.625 15.1875 23.625C15.0312 23.625 14.8746 23.5861 14.7329 23.5073C14.4349 23.3421 14.25 23.0285 14.25 22.6875V13.3125C14.25 12.9715 14.4349 12.6579 14.7329 12.4927C15.0309 12.3265 15.3953 12.3366 15.6844 12.5174L23.1844 17.2049C23.4584 17.3766 23.625 17.6769 23.625 18C23.625 18.3231 23.4584 18.6235 23.1844 18.7951Z" fill="#F4F4F4"/>
</g>
<defs>
<filter id="filter0_d_6675_32326" x="0" y="0" width="36" height="36" 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.5 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6675_32326"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6675_32326" result="shape"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,30 +0,0 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_7280_5211)">
<circle cx="18" cy="18" r="15" fill="#F4F4F4"/>
</g>
<g filter="url(#filter1_i_7280_5211)">
<path d="M25.1663 13.187C24.8231 12.8439 24.2666 12.8439 23.9234 13.1871L16.1621 20.9484L12.0766 16.8628C11.7334 16.5196 11.1768 16.5196 10.8336 16.8628C10.4904 17.206 10.4904 17.7625 10.8336 18.1057L15.5406 22.8127C15.7122 22.9844 15.9372 23.0701 16.1621 23.0701C16.3869 23.0701 16.6119 22.9843 16.7835 22.8127L25.1663 14.43C25.5095 14.0868 25.5095 13.5303 25.1663 13.187Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d_7280_5211" x="0" y="0" width="36" height="36" 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.5 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_7280_5211"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_7280_5211" result="shape"/>
</filter>
<filter id="filter1_i_7280_5211" x="10.5762" y="12.9297" width="14.8475" height="10.1406" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<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="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_7280_5211"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -201,20 +201,17 @@ class ForgetPasswordWebPage extends StatelessWidget {
!state.isButtonEnabled &&
state.remainingTime != 1
? null
: () {
:
() {
if (forgetBloc
.forgetEmailKey.currentState!
.validate() ||
forgetBloc
.forgetRegionKey.currentState!
.validate()) {
if (forgetBloc
.forgetRegionKey.currentState!
.validate()) {
forgetBloc.add(StartTimerEvent());
}
.forgetEmailKey
.currentState!
.validate()) {
forgetBloc.add(
StartTimerEvent());
}
},
child: Text(
'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}',
style: TextStyle(

View File

@ -13,6 +13,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
late AcStatusModel deviceStatus;
final String deviceId;
Timer? _timer;
Timer? _countdownTimer;
AcBloc({required this.deviceId}) : super(AcsInitialState()) {
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
@ -21,7 +22,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
on<AcBatchControlEvent>(_onAcBatchControl);
on<AcFactoryResetEvent>(_onFactoryReset);
on<AcStatusUpdated>(_onAcStatusUpdated);
on<OnClose>(_onClose);
on<IncreaseTimeEvent>(_handleIncreaseTime);
on<DecreaseTimeEvent>(_handleDecreaseTime);
on<UpdateTimerEvent>(_handleUpdateTimer);
on<ToggleScheduleEvent>(_handleToggleTimer);
on<ApiCountdownValueEvent>(_handleApiCountdownValue);
}
bool timerActive = false;
int scheduledHours = 0;
int scheduledMinutes = 0;
FutureOr<void> _onFetchAcStatus(
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async {
@ -30,8 +40,23 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.countdown1 != 0) {
// Convert API value to minutes
final totalMinutes = deviceStatus.countdown1 * 6;
scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
timerActive = true;
_startCountdownTimer(emit);
}
emit(ACStatusLoaded(
status: deviceStatus,
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
isTimerActive: timerActive,
));
_listenToChanges(event.deviceId);
emit(ACStatusLoaded(deviceStatus));
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
@ -70,31 +95,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) {
deviceStatus = event.deviceStatus;
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
}
// Future<void> testFirebaseConnection() async {
// // Reference to a test node in your database
// final testRef = FirebaseDatabase.instance.ref("test");
// // Write a test value
// await testRef.set("Hello, Firebase!");
// // Listen for changes on the test node
// testRef.onValue.listen((DatabaseEvent event) {
// final data = event.snapshot.value;
// print("Data from Firebase: $data");
// // If you see "Hello, Firebase!" printed in your console, it means the connection works.
// });
// }
FutureOr<void> _onAcControl(
AcControlEvent event, Emitter<AcsState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value, emit);
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce(
isBatch: false,
@ -151,7 +161,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _revertValueAndEmit(
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
_updateLocalValue(code, oldValue, emit);
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
}
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) {
@ -184,11 +194,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
if (value is bool) {
deviceStatus = deviceStatus.copyWith(childLock: value);
}
case 'countdown_time':
if (value is int) {
deviceStatus = deviceStatus.copyWith(countdown1: value);
}
break;
default:
break;
}
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
}
dynamic _getValueByCode(String code) {
@ -203,6 +218,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
return deviceStatus.fanSpeedsString;
case 'child_lock':
return deviceStatus.childLock;
case 'countdown_time':
return deviceStatus.countdown1;
default:
return null;
}
@ -216,7 +233,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
@ -228,7 +245,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
_updateLocalValue(event.code, event.value, emit);
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce(
isBatch: true,
@ -257,4 +274,144 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
emit(AcsFailedState(error: e.toString()));
}
}
void _onClose(OnClose event, Emitter<AcsState> emit) {
_countdownTimer?.cancel();
_timer?.cancel();
}
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded;
int newHours = scheduledHours;
int newMinutes = scheduledMinutes + 30;
newHours += newMinutes ~/ 60;
newMinutes = newMinutes % 60;
if (newHours > 23) {
newHours = 23;
newMinutes = 59;
}
scheduledHours = newHours;
scheduledMinutes = newMinutes;
emit(currentState.copyWith(
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
));
}
void _handleDecreaseTime(DecreaseTimeEvent event, Emitter<AcsState> emit) {
if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded;
int totalMinutes = (scheduledHours * 60) + scheduledMinutes;
totalMinutes = (totalMinutes - 30).clamp(0, 1440);
scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
emit(currentState.copyWith(
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
));
}
Future<void> _handleToggleTimer(
ToggleScheduleEvent event, Emitter<AcsState> emit) async {
if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded;
timerActive = !timerActive;
if (timerActive) {
final totalMinutes = scheduledHours * 60 + scheduledMinutes;
if (totalMinutes <= 0) {
timerActive = false;
emit(currentState.copyWith(isTimerActive: timerActive));
return;
}
try {
final scaledValue = totalMinutes ~/ 6;
await _runDebounce(
isBatch: false,
deviceId: deviceId,
code: 'countdown_time',
value: scaledValue,
oldValue: scaledValue,
emit: emit,
);
_startCountdownTimer(emit);
emit(currentState.copyWith(isTimerActive: timerActive));
} catch (e) {
timerActive = false;
emit(AcsFailedState(error: e.toString()));
}
} else {
await _runDebounce(
isBatch: false,
deviceId: deviceId,
code: 'countdown_time',
value: 0,
oldValue: 0,
emit: emit,
);
_countdownTimer?.cancel();
scheduledHours = 0;
scheduledMinutes = 0;
emit(currentState.copyWith(
isTimerActive: timerActive,
scheduledHours: 0,
scheduledMinutes: 0,
));
}
}
void _startCountdownTimer(Emitter<AcsState> emit) {
_countdownTimer?.cancel();
int totalSeconds = (scheduledHours * 3600) + (scheduledMinutes * 60);
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (totalSeconds > 0) {
totalSeconds--;
scheduledHours = totalSeconds ~/ 3600;
scheduledMinutes = (totalSeconds % 3600) ~/ 60;
add(UpdateTimerEvent());
} else {
_countdownTimer?.cancel();
timerActive = false;
scheduledHours = 0;
scheduledMinutes = 0;
add(TimerCompletedEvent());
}
});
}
void _handleUpdateTimer(UpdateTimerEvent event, Emitter<AcsState> emit) {
if (state is ACStatusLoaded) {
final currentState = state as ACStatusLoaded;
emit(currentState.copyWith(
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
isTimerActive: timerActive,
));
}
}
void _handleApiCountdownValue(
ApiCountdownValueEvent event, Emitter<AcsState> emit) {
if (state is ACStatusLoaded) {
final totalMinutes = event.apiValue * 6;
final scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
_startCountdownTimer(
emit,
);
add(UpdateTimerEvent());
}
}
@override
Future<void> close() {
add(OnClose());
return super.close();
}
}

View File

@ -8,6 +8,7 @@ sealed class AcsEvent extends Equatable {
@override
List<Object> get props => [];
}
class AcUpdated extends AcsEvent {}
class AcFetchDeviceStatusEvent extends AcsEvent {
@ -18,10 +19,12 @@ class AcFetchDeviceStatusEvent extends AcsEvent {
@override
List<Object> get props => [deviceId];
}
class AcStatusUpdated extends AcsEvent {
final AcStatusModel deviceStatus;
AcStatusUpdated(this.deviceStatus);
}
class AcFetchBatchStatusEvent extends AcsEvent {
final List<String> devicesIds;
@ -73,3 +76,30 @@ class AcFactoryResetEvent extends AcsEvent {
@override
List<Object> get props => [deviceId, factoryResetModel];
}
class OnClose extends AcsEvent {}
class IncreaseTimeEvent extends AcsEvent {
@override
List<Object> get props => [];
}
class DecreaseTimeEvent extends AcsEvent {
@override
List<Object> get props => [];
}
class ToggleScheduleEvent extends AcsEvent {}
class TimerCompletedEvent extends AcsEvent {}
class UpdateTimerEvent extends AcsEvent {
}
class ApiCountdownValueEvent extends AcsEvent {
final int apiValue;
const ApiCountdownValueEvent(this.apiValue);
}

View File

@ -2,8 +2,9 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
abstract class AcsState extends Equatable {
const AcsState();
final bool isTimerActive;
const AcsState({this.isTimerActive = false});
@override
List<Object> get props => [];
}
@ -15,8 +16,30 @@ class AcsLoadingState extends AcsState {}
class ACStatusLoaded extends AcsState {
final AcStatusModel status;
final DateTime timestamp;
final int scheduledHours;
final int scheduledMinutes;
final bool isTimerActive;
ACStatusLoaded(this.status) : timestamp = DateTime.now();
ACStatusLoaded({
required this.status,
this.scheduledHours = 0,
this.scheduledMinutes = 0,
this.isTimerActive = false,
}) : timestamp = DateTime.now();
ACStatusLoaded copyWith({
AcStatusModel? status,
int? scheduledHours,
int? scheduledMinutes,
bool? isTimerActive,
int? remainingTime,
}) {
return ACStatusLoaded(
status: status ?? this.status,
scheduledHours: scheduledHours ?? this.scheduledHours,
scheduledMinutes: scheduledMinutes ?? this.scheduledMinutes,
isTimerActive: isTimerActive ?? this.isTimerActive,
);
}
@override
List<Object> get props => [status, timestamp];
@ -40,3 +63,14 @@ class AcsFailedState extends AcsState {
@override
List<Object> get props => [error];
}
class TimerRunInProgress extends AcsState {
final int remainingTime;
const TimerRunInProgress(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}

View File

@ -11,6 +11,7 @@ class AcStatusModel {
final bool childLock;
final TempModes acMode;
final FanSpeeds acFanSpeed;
final int countdown1;
AcStatusModel({
required this.uuid,
@ -18,6 +19,7 @@ class AcStatusModel {
required this.modeString,
required this.tempSet,
required this.currentTemp,
required this.countdown1,
required this.fanSpeedsString,
required this.childLock,
}) : acMode = getACMode(modeString),
@ -30,6 +32,7 @@ class AcStatusModel {
late int currentTemp;
late String fanSpeeds;
late bool childLock;
late int _countdown1 = 0;
for (var status in jsonList) {
switch (status.code) {
@ -51,6 +54,9 @@ class AcStatusModel {
case 'child_lock':
childLock = status.value ?? false;
break;
case 'countdown_time':
_countdown1 = status.value ?? 0;
break;
}
}
@ -62,6 +68,7 @@ class AcStatusModel {
currentTemp: currentTemp,
fanSpeedsString: fanSpeeds,
childLock: childLock,
countdown1: _countdown1,
);
}
@ -73,6 +80,7 @@ class AcStatusModel {
int? currentTemp,
String? fanSpeedsString,
bool? childLock,
int? countdown1,
}) {
return AcStatusModel(
uuid: uuid ?? this.uuid,
@ -82,6 +90,7 @@ class AcStatusModel {
currentTemp: currentTemp ?? this.currentTemp,
fanSpeedsString: fanSpeedsString ?? this.fanSpeedsString,
childLock: childLock ?? this.childLock,
countdown1: countdown1 ?? this.countdown1,
);
}

View File

@ -10,11 +10,10 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
const AcDeviceControlsView({super.key, required this.device});
const AcDeviceControlsView({super.key, required this.device});
final AllDevicesModel device;
@ -23,11 +22,13 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => AcBloc(deviceId: device.uuid!)
..add(AcFetchDeviceStatusEvent(device.uuid!)),
child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) {
final acBloc = BlocProvider.of<AcBloc>(context);
if (state is ACStatusLoaded) {
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50),
@ -78,56 +79,101 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
),
ToggleWidget(
label: '',
labelWidget: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
labelWidget: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {},
icon: const Icon(
Icons.remove,
size: 28,
color: ColorsManager.greyColor,
Container(
width: MediaQuery.of(context).size.width,
decoration: const ShapeDecoration(
color: ColorsManager.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(30)),
),
),
),
Text(
'06',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text(
'h',
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor),
),
Text(
'30',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text('m',
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor)),
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {},
icon: const Icon(
Icons.add,
size: 28,
color: ColorsManager.greyColor,
Center(
child: SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {
if (acBloc.timerActive == false) {
context
.read<AcBloc>()
.add(DecreaseTimeEvent());
}
},
icon: const Icon(Icons.remove,
color: ColorsManager.greyColor),
),
Text(
acBloc.scheduledHours
.toString()
.padLeft(2, '0'),
style: Theme.of(context)
.textTheme
.titleLarge!
.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text(
'h',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.blackColor,
),
),
Text(
acBloc.scheduledMinutes
.toString()
.padLeft(2, '0'),
style: Theme.of(context)
.textTheme
.titleLarge!
.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text(
'm',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.blackColor,
),
),
IconButton(
onPressed: () {
if (acBloc.timerActive == false) {
context
.read<AcBloc>()
.add(IncreaseTimeEvent());
}
},
icon: const Icon(Icons.add,
color: ColorsManager.greyColor),
),
],
),
),
),
],
),
value: false,
value: acBloc.timerActive,
code: 'ac_schedule',
deviceId: device.uuid!,
icon: Assets.acSchedule,
onChange: (value) {},
onChange: (value) {
context.read<AcBloc>().add(ToggleScheduleEvent());
},
),
ToggleWidget(
deviceId: device.uuid!,

View File

@ -8,6 +8,8 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gateway.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/enum/device_types.dart';
@ -148,7 +150,9 @@ class AllDevicesModel {
productName = json['productName']?.toString();
if (json['spaces'] != null && json['spaces'] is List) {
spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList();
spaces = (json['spaces'] as List)
.map((space) => DeviceSpaceModel.fromJson(space))
.toList();
}
}
@ -196,7 +200,8 @@ SOS
String tempIcon = '';
if (type == DeviceType.LightBulb) {
tempIcon = Assets.lightBulb;
} else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) {
} else if (type == DeviceType.CeilingSensor ||
type == DeviceType.WallSensor) {
tempIcon = Assets.sensors;
} else if (type == DeviceType.AC) {
tempIcon = Assets.ac;
@ -226,6 +231,8 @@ SOS
// tempIcon = Assets.gang3touch;
} else if (type == DeviceType.WaterLeak) {
tempIcon = Assets.waterLeakNormal;
} else if (type == DeviceType.WaterLeak) {
tempIcon = Assets.waterLeakNormal;
} else {
tempIcon = Assets.logoHorizontal;
}
@ -241,38 +248,95 @@ SOS
switch (productType) {
case 'AC':
return [
SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
ModeFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
CurrentTempFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
LevelFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
SwitchFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ModeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
TempSetFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
CurrentTempFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
LevelFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ChildLockFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
];
case '1G':
return [
OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
OneGangCountdownFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
OneGangCountdownFunction(
deviceId: uuid ?? '', deviceName: name ?? ''),
];
case '2G':
return [
TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown1Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
];
case '3G':
return [
ThreeGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch3Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown3Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch1Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangSwitch2Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangSwitch3Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangCountdown1Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangCountdown3Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
];
case 'WPS':
return [
//IF Functions
PresenceStateFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
CurrentDistanceFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
IlluminanceValueFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
PresenceTimeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
//THEN Functions
FarDetectionFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
MotionSensitivityFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
MotionLessSensitivityFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
IndicatorFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
NoOneTimeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
];
case 'GW':
return [
GatewaySwitchAlarmSound(
deviceId: uuid ?? '',
deviceName: name ?? '',
type: 'BOTH',
),
GatewayMasterState(
deviceId: uuid ?? '',
deviceName: name ?? '',
type: 'BOTH',
),
GatewayFactoryReset(
deviceId: uuid ?? '',
deviceName: name ?? '',
type: 'BOTH',
),
];
default:
return [];
}

View File

@ -2,14 +2,17 @@ import 'package:flutter/foundation.dart';
class FactoryResetModel {
final List<String> devicesUuid;
final String operationType;
FactoryResetModel({
required this.devicesUuid,
this.operationType = "RESET",
});
factory FactoryResetModel.fromJson(Map<String, dynamic> json) {
return FactoryResetModel(
devicesUuid: List<String>.from(json['devicesUuid']),
operationType: "RESET",
);
}

View File

@ -12,77 +12,68 @@ class DeviceSearchFilters extends StatefulWidget {
State<DeviceSearchFilters> createState() => _DeviceSearchFiltersState();
}
class _DeviceSearchFiltersState extends State<DeviceSearchFilters> with HelperResponsiveLayout {
final TextEditingController communityController = TextEditingController();
final TextEditingController unitNameController = TextEditingController();
final TextEditingController productNameController = TextEditingController();
class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
with HelperResponsiveLayout {
final _unitNameController = TextEditingController();
final _productNameController = TextEditingController();
List<Widget> get _widgets => [
_buildSearchField("Space Name", _unitNameController, 200),
_buildSearchField("Device Name / Product Name", _productNameController, 300),
_buildSearchResetButtons(),
];
@override
Widget build(BuildContext context) {
return isExtraLargeScreenSize(context)
? Row(
children: [
_buildSearchField("Community", communityController, 200),
const SizedBox(width: 20),
_buildSearchField("Space Name", unitNameController, 200),
const SizedBox(width: 20),
_buildSearchField("Device Name / Product Name", productNameController, 300),
const SizedBox(width: 20),
_buildSearchResetButtons(),
],
)
: Wrap(
spacing: 20,
runSpacing: 10,
children: [
_buildSearchField(
"Community",
communityController,
200,
),
_buildSearchField("Space Name", unitNameController, 200),
_buildSearchField(
"Device Name / Product Name",
productNameController,
300,
),
_buildSearchResetButtons(),
],
);
if (isExtraLargeScreenSize(context)) {
return Row(
children: _widgets
.map((e) => Padding(padding: const EdgeInsets.all(10), child: e))
.toList(),
);
}
return Wrap(
spacing: 20,
runSpacing: 10,
children: _widgets,
);
}
Widget _buildSearchField(String title, TextEditingController controller, double width) {
return Container(
child: StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
onSubmitted: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
productName: productNameController.text,
unitName: unitNameController.text,
community: communityController.text,
searchField: true));
},
onChanged: (p0) {},
),
Widget _buildSearchField(
String title,
TextEditingController controller,
double width,
) {
return StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
onSubmitted: () {
final searchDevicesEvent = SearchDevices(
productName: _productNameController.text,
unitName: _unitNameController.text,
searchField: true,
);
context.read<DeviceManagementBloc>().add(searchDevicesEvent);
},
onChanged: (p0) {},
);
}
Widget _buildSearchResetButtons() {
return SearchResetButtons(
onSearch: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
community: communityController.text,
unitName: unitNameController.text,
productName: productNameController.text,
searchField: true));
},
onSearch: () => context.read<DeviceManagementBloc>().add(
SearchDevices(
unitName: _unitNameController.text,
productName: _productNameController.text,
searchField: true,
),
),
onReset: () {
communityController.clear();
unitNameController.clear();
productNameController.clear();
_unitNameController.clear();
_productNameController.clear();
context.read<DeviceManagementBloc>()
..add(ResetFilters())
..add(FetchDevices(context));

View File

@ -0,0 +1,49 @@
class GatewayModel {
final String uuid;
final bool switchAlarmSound;
final String masterState;
final bool factoryReset;
final String alarmActive;
GatewayModel({
required this.uuid,
required this.switchAlarmSound,
required this.masterState,
required this.factoryReset,
required this.alarmActive,
});
factory GatewayModel.fromJson(Map<String, dynamic> json) {
final status = json['status'] as List<dynamic>? ?? <dynamic>[];
bool? switchAlarmSound;
String? masterState;
bool? factoryReset;
String? alarmActive;
for (final item in status) {
switch (item['code']) {
case 'switch_alarm_sound':
switchAlarmSound = item['value'] as bool;
break;
case 'master_state':
masterState = item['value'] as String;
break;
case 'factory_reset':
factoryReset = item['value'] as bool;
break;
case 'alarm_active':
alarmActive = item['value'] as String;
break;
}
}
return GatewayModel(
uuid: json['uuid'] as String? ?? '',
switchAlarmSound: switchAlarmSound ?? false,
masterState: masterState ?? '',
factoryReset: factoryReset ?? false,
alarmActive: alarmActive ?? '',
);
}
}

View File

@ -62,9 +62,6 @@ class ToggleWidget extends StatelessWidget {
)),
if (showToggle)
Container(
height: 20,
width: 35,
padding: const EdgeInsets.only(right: 16, top: 10),
child: CupertinoSwitch(
value: value,
activeColor: ColorsManager.dialogBlueTitle,

View File

@ -13,7 +13,8 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la
import '../models/sos_status_model.dart';
class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
class SosDeviceControlsView extends StatelessWidget
with HelperResponsiveLayout {
const SosDeviceControlsView({
super.key,
required this.device,
@ -24,7 +25,8 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SosDeviceBloc()..add(GetDeviceStatus(device.uuid!)),
create: (context) =>
SosDeviceBloc()..add(GetDeviceStatus(device.uuid!)),
child: BlocBuilder<SosDeviceBloc, SosDeviceState>(
builder: (context, state) {
if (state is SosDeviceLoadingState) {
@ -63,7 +65,8 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout
));
}
Widget _buildStatusControls(BuildContext context, SosStatusModel deviceStatus) {
Widget _buildStatusControls(
BuildContext context, SosStatusModel deviceStatus) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
@ -85,7 +88,7 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout
IconNameStatusContainer(
isFullIcon: false,
name: deviceStatus.sosStatus == 'sos' ? 'SOS' : 'Normal',
icon: deviceStatus.sosStatus == 'sos' ? Assets.sos : Assets.sosNormal,
icon: deviceStatus.sosStatus == 'sos' ? Assets.sosNormal : Assets.sos,
onTap: () {},
status: false,
textColor: ColorsManager.blackColor,

View File

@ -95,6 +95,77 @@ class CountdownInchingView extends StatelessWidget {
);
}
Row _hourMinutesSecondWheel(
BuildContext context, WaterHeaterDeviceStatusLoaded state) {
final isCountDown =
state.scheduleMode?.name == ScheduleModes.countdown.name;
late bool isActive;
if (isCountDown &&
state.countdownRemaining != null &&
state.isCountdownActive == true) {
isActive = true;
} else if (!isCountDown &&
state.countdownRemaining != null &&
state.isInchingActive == true) {
isActive = true;
} else {
isActive = false;
}
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_buildPickerColumn(
context,
'h',
isCountDown
? (state.countdownHours ?? 0)
: (state.inchingHours ?? 0),
24, (value) {
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
hours: value,
minutes: isCountDown
? (state.countdownMinutes ?? 0)
: (state.inchingMinutes ?? 0),
));
}, isActive: isActive),
const SizedBox(width: 10),
_buildPickerColumn(
context,
'm',
isCountDown
? (state.countdownMinutes ?? 0)
: (state.inchingMinutes ?? 0),
60, (value) {
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
hours: isCountDown
? (state.countdownHours ?? 0)
: (state.inchingHours ?? 0),
minutes: value,
));
}, isActive: isActive),
const SizedBox(width: 10),
_buildPickerColumn(
context,
'S',
isCountDown
? (state.countdownMinutes ?? 0)
: (state.inchingMinutes ?? 0),
60, (value) {
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
hours: isCountDown
? (state.countdownHours ?? 0)
: (state.inchingHours ?? 0),
minutes: value,
));
}, isActive: isActive),
],
);
}
Widget _buildPickerColumn(
BuildContext context,
String label,

View File

@ -2,6 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
@ -10,11 +11,12 @@ class CreateRoutineBloc extends Bloc<CreateRoutineEvent, CreateRoutineState> {
on<SpaceOnlyWithDevicesEvent>(_fetchSpaceOnlyWithDevices);
on<SaveCommunityIdAndSpaceIdEvent>(saveSpaceIdCommunityId);
on<ResetSelectedEvent>(resetSelected);
on<FetchCommunityEvent>(_fetchCommunity);
}
String selectedSpaceId = '';
String selectedCommunityId = '';
List<CommunityModel> communities = [];
List<SpaceModel> spacesOnlyWithDevices = [];
Future<void> _fetchSpaceOnlyWithDevices(
@ -30,7 +32,7 @@ class CreateRoutineBloc extends Bloc<CreateRoutineEvent, CreateRoutineState> {
emit(SpaceWithDeviceLoadedState(spacesOnlyWithDevices));
} catch (e) {
emit(SpaceTreeErrorState('Error loading communities and spaces: $e'));
emit(SpaceTreeErrorState('Error loading spaces: $e'));
}
}
@ -48,4 +50,18 @@ class CreateRoutineBloc extends Bloc<CreateRoutineEvent, CreateRoutineState> {
selectedCommunityId = '';
emit(const ResetSelectedState());
}
Future<void> _fetchCommunity(
FetchCommunityEvent event, Emitter<CreateRoutineState> emit) async {
emit(const CommunitiesLoadingState());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
communities =
await CommunitySpaceManagementApi().fetchCommunities(projectUuid);
emit(const CommunityLoadedState());
} catch (e) {
emit(SpaceTreeErrorState('Error loading communities $e'));
}
}
}

View File

@ -41,3 +41,11 @@ class ResetSelectedEvent extends CreateRoutineEvent {
@override
List<Object> get props => [];
}
class FetchCommunityEvent extends CreateRoutineEvent {
const FetchCommunityEvent();
@override
List<Object> get props => [];
}

View File

@ -44,3 +44,10 @@ class ResetSelectedState extends CreateRoutineState {
const ResetSelectedState();
}
class CommunityLoadedState extends CreateRoutineState {
const CommunityLoadedState();
}
class CommunitiesLoadingState extends CreateRoutineState {
const CommunitiesLoadingState();
}

View File

@ -1,19 +1,19 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class CommunityDropdown extends StatelessWidget {
final String? selectedValue;
final List<CommunityModel> communities;
final Function(String?) onChanged;
final TextEditingController _searchController = TextEditingController();
const CommunityDropdown({
CommunityDropdown({
Key? key,
required this.selectedValue,
required this.onChanged,
required this.communities,
}) : super(key: key);
@override
@ -32,63 +32,123 @@ class CommunityDropdown extends StatelessWidget {
),
),
const SizedBox(height: 8),
BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
builder: (context, state) {
List<CommunityModel> communities = state.isSearching
? state.filteredCommunity
: state.communityList;
return SizedBox(
child: DropdownButtonFormField<String>(
dropdownColor: ColorsManager.whiteColors,
value: selectedValue,
items: communities.map((community) {
return DropdownMenuItem<String>(
value: community.uuid,
child: Text(' ${community.name}'),
);
}).toList(),
onChanged: onChanged,
icon: const SizedBox.shrink(),
borderRadius: const BorderRadius.all(Radius.circular(10)),
hint: Padding(
padding: EdgeInsets.only(left: 10),
child: Text(
"Please Select",
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
SizedBox(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
child: DropdownButton2<String>(
underline: const SizedBox(),
value: selectedValue,
items: communities.map((community) {
return DropdownMenuItem<String>(
value: community.uuid,
child: Text(
' ${community.name}',
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
decoration: inputTextFormDeco().copyWith(
contentPadding: EdgeInsets.zero,
suffixIcon: Container(
padding: EdgeInsets.zero,
width: 70,
height: 45,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(10),
topRight: Radius.circular(10),
),
border: Border.all(
color: ColorsManager.textGray,
width: 1.0,
),
);
}).toList(),
onChanged: onChanged,
style: const TextStyle(color: Colors.black),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
" Please Select",
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
child: const Center(
child: Icon(
),
),
customButton: Container(
height: 45,
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.textGray, width: 1.0),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 5,
child: Text(
selectedValue != null
? " ${communities.firstWhere((element) => element.uuid == selectedValue).name}"
: ' Please Select',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: selectedValue != null
? Colors.black
: ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
],
),
),
dropdownStyleData: DropdownStyleData(
maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
),
dropdownSearchData: DropdownSearchData(
searchController: _searchController,
searchInnerWidgetHeight: 50,
searchInnerWidget: Container(
height: 50,
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: TextFormField(
style: const TextStyle(color: Colors.black),
controller: _searchController,
decoration: InputDecoration(
isDense: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 12,
),
hintText: 'Search for community...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
);
},
),
searchMatchFn: (item, searchValue) {
final communityName =
(item.child as Text).data?.toLowerCase() ?? '';
return communityName
.contains(searchValue.toLowerCase().trim());
},
),
onMenuStateChange: (isOpen) {
if (!isOpen) {
_searchController.clear();
}
},
menuItemStyleData: const MenuItemStyleData(
height: 40,
),
),
))
],
),
);

View File

@ -5,11 +5,10 @@ import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routi
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/create_new_routines/commu_dropdown.dart';
import 'package:syncrow_web/pages/routines/create_new_routines/space_dropdown.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateNewRoutinesDialog extends StatefulWidget {
const CreateNewRoutinesDialog({Key? key}) : super(key: key);
const CreateNewRoutinesDialog({super.key});
@override
State<CreateNewRoutinesDialog> createState() =>
@ -19,130 +18,160 @@ class CreateNewRoutinesDialog extends StatefulWidget {
class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
String? _selectedCommunity;
String? _selectedSpace;
void _fetchSpaces(String communityId) {
context
.read<CreateRoutineBloc>()
.add(SpaceOnlyWithDevicesEvent(communityId));
}
@override
Widget build(BuildContext context) {
return BlocBuilder<CreateRoutineBloc, CreateRoutineState>(
builder: (context, state) {
final _bloc = BlocProvider.of<CreateRoutineBloc>(context);
final spaces = _bloc.spacesOnlyWithDevices;
final isLoading = state is SpaceWithDeviceLoadingState;
return BlocProvider(
create: (BuildContext context) =>
CreateRoutineBloc()..add(const FetchCommunityEvent()),
child: BlocBuilder<CreateRoutineBloc, CreateRoutineState>(
builder: (context, state) {
final _bloc = BlocProvider.of<CreateRoutineBloc>(context);
final spaces = _bloc.spacesOnlyWithDevices;
final isLoadingCommunities = state is CommunitiesLoadingState;
final isLoadingSpaces = state is SpaceWithDeviceLoadingState;
String spaceHint = 'Select a community first';
if (_selectedCommunity != null) {
if (isLoadingSpaces) {
spaceHint = 'Loading spaces...';
} else if (spaces.isEmpty) {
spaceHint = 'No spaces available';
} else {
spaceHint = 'Select Space';
}
}
String spaceHint = 'Select a community first';
if (_selectedCommunity != null) {
if (isLoading) {
spaceHint = 'Loading spaces...';
} else if (spaces.isEmpty) {
spaceHint = 'No spaces available';
} else {
spaceHint = 'Select Space';
}
}
return AlertDialog(
backgroundColor: Colors.white,
insetPadding: EdgeInsets.zero,
contentPadding: EdgeInsets.zero,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Text(
'Create New Routines',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColor,
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Divider(),
CommunityDropdown(
selectedValue: _selectedCommunity,
onChanged: (String? newValue) {
setState(() {
_selectedCommunity = newValue;
_selectedSpace = null;
});
if (newValue != null) {
_fetchSpaces(newValue);
}
},
return AlertDialog(
backgroundColor: Colors.white,
insetPadding: EdgeInsets.zero,
contentPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
title: Text(
'Create New Routines',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColor,
),
),
const SizedBox(height: 16),
SpaceDropdown(
hintMessage: spaceHint,
spaces: spaces,
selectedValue: _selectedSpace,
onChanged: (String? newValue) {
setState(() {
_selectedSpace = newValue;
});
},
),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
content: Stack(
children: [
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: ColorsManager.blackColor,
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
const Divider(),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: CommunityDropdown(
communities: _bloc.communities,
selectedValue: _selectedCommunity,
onChanged: (String? newValue) {
setState(() {
_selectedCommunity = newValue;
_selectedSpace = null;
});
if (newValue != null) {
_bloc.add(SpaceOnlyWithDevicesEvent(newValue));
}
},
),
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed:
_selectedCommunity != null && _selectedSpace != null
? () {
Navigator.of(context).pop({
'community': _selectedCommunity,
'space': _selectedSpace,
});
}
: null,
child: Text(
'Next',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: _selectedCommunity != null &&
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: SpaceDropdown(
hintMessage: spaceHint,
spaces: spaces,
selectedValue: _selectedSpace,
onChanged: (String? newValue) {
setState(() {
_selectedSpace = newValue;
});
},
),
),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: ColorsManager.blackColor,
),
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed: _selectedCommunity != null &&
_selectedSpace != null
? ColorsManager.blueColor
: Colors.blue.shade100,
? () {
Navigator.of(context).pop({
'community': _selectedCommunity,
'space': _selectedSpace,
});
}
: null,
child: Text(
'Next',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: _selectedCommunity != null &&
_selectedSpace != null
? ColorsManager.blueColor
: Colors.blue.shade100,
),
),
),
),
],
),
const SizedBox(height: 10),
],
),
if (isLoadingCommunities)
const SizedBox(
height: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: CircularProgressIndicator(
color: ColorsManager.primaryColor,
),
),
],
),
),
),
],
),
],
),
);
},
);
);
},
));
}
}

View File

@ -1,7 +1,8 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceDropdown extends StatelessWidget {
final List<SpaceModel> spaces;
@ -33,62 +34,108 @@ class SpaceDropdown extends StatelessWidget {
),
),
const SizedBox(height: 8),
DropdownButtonFormField<String>(
value: selectedValue,
items: spaces.map((space) {
return DropdownMenuItem<String>(
value: space.uuid,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
' ${space.name}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: ColorsManager.blackColor,
),
),
Text(
' ${space.lastThreeParents}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
),
),
],
));
}).toList(),
onChanged: onChanged,
icon: const SizedBox.shrink(),
borderRadius: const BorderRadius.all(Radius.circular(10)),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
SizedBox(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
),
decoration: inputTextFormDeco().copyWith(
contentPadding: EdgeInsets.zero,
suffixIcon: Container(
width: 70,
height: 45,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(10),
topRight: Radius.circular(10),
),
border: Border.all(
color: ColorsManager.textGray,
width: 1.0,
child: DropdownButton2<String>(
underline: const SizedBox(),
value: selectedValue,
items: spaces.map((space) {
return DropdownMenuItem<String>(
value: space.uuid,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
' ${space.name}',
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: ColorsManager.blackColor,
),
),
Text(
' ${space.lastThreeParents}',
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
),
),
],
),
);
}).toList(),
onChanged: onChanged,
style: TextStyle(color: Colors.black),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
),
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
customButton: Container(
height: 45,
decoration: BoxDecoration(
border:
Border.all(color: ColorsManager.textGray, width: 1.0),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 5,
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
selectedValue != null
? spaces
.firstWhere((e) => e.uuid == selectedValue)
.name
: hintMessage,
style:
Theme.of(context).textTheme.bodySmall!.copyWith(
color: selectedValue != null
? Colors.black
: ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
],
),
),
dropdownStyleData: DropdownStyleData(
maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
),
menuItemStyleData: const MenuItemStyleData(
height: 60,
),
),
),

View File

@ -1,26 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart';
class DeviceDialogHelper {
static Future<Map<String, dynamic>?> showDeviceDialog(
BuildContext context,
Map<String, dynamic> data, {
static Future<Map<String, dynamic>?> showDeviceDialog({
required BuildContext context,
required String dialogType,
required Map<String, dynamic> data,
required bool removeComparetors,
}) async {
final functions = data['functions'] as List<DeviceFunction>;
try {
final result = await _getDialogForDeviceType(
context,
data['productType'],
data,
functions,
dialogType: dialogType,
context: context,
productType: data['productType'],
data: data,
functions: functions,
removeComparetors: removeComparetors,
);
@ -34,32 +38,75 @@ class DeviceDialogHelper {
return null;
}
static Future<Map<String, dynamic>?> _getDialogForDeviceType(BuildContext context,
String productType, Map<String, dynamic> data, List<DeviceFunction> functions,
{required bool removeComparetors}) async {
static Future<Map<String, dynamic>?> _getDialogForDeviceType({
required String dialogType,
required BuildContext context,
required String productType,
required Map<String, dynamic> data,
required List<DeviceFunction> functions,
required bool removeComparetors,
}) async {
final routineBloc = context.read<RoutineBloc>();
final deviceSelectedFunctions =
routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
if (removeComparetors) {
//remove the current temp function in the 'if container'
functions.removeAt(3);
}
switch (productType) {
case 'AC':
return ACHelper.showACFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
return ACHelper.showACFunctionsDialog(
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
removeComparetors: removeComparetors,
dialogType: dialogType,
);
case '1G':
return OneGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
return OneGangSwitchHelper.showSwitchFunctionsDialog(
dialogType: dialogType,
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
removeComparetors: removeComparetors);
case '2G':
return TwoGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
return TwoGangSwitchHelper.showSwitchFunctionsDialog(
dialogType: dialogType,
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
removeComparetors: removeComparetors);
case '3G':
return ThreeGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
return ThreeGangSwitchHelper.showSwitchFunctionsDialog(
dialogType: dialogType,
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
removeComparetors: removeComparetors);
case 'WPS':
return WallPresenceSensor.showWPSFunctionsDialog(
dialogType: dialogType,
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
removeComparetors: removeComparetors);
case 'GW':
return GatewayHelper.showGatewayFunctionsDialog(
context: context,
functions: functions,
uniqueCustomId: data['uniqueCustomId'],
deviceSelectedFunctions: deviceSelectedFunctions,
device: data['device'],
);
default:
return null;
}

View File

@ -5,23 +5,28 @@ import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
abstract class ACFunction extends DeviceFunction<AcStatusModel> {
final String type;
ACFunction({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
required this.type,
});
List<ACOperationalValue> getOperationalValues();
}
class SwitchFunction extends ACFunction {
SwitchFunction({required super.deviceId, required super.deviceName})
SwitchFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
code: 'switch',
operationName: 'Power',
icon: Assets.assetsAcPower,
type: type,
);
@override
@ -40,11 +45,13 @@ class SwitchFunction extends ACFunction {
}
class ModeFunction extends ACFunction {
ModeFunction({required super.deviceId, required super.deviceName})
ModeFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
code: 'mode',
operationName: 'Mode',
icon: Assets.assetsFreezing,
type: type,
);
@override
@ -72,7 +79,8 @@ class TempSetFunction extends ACFunction {
final int max;
final int step;
TempSetFunction({required super.deviceId, required super.deviceName})
TempSetFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 160,
max = 300,
step = 1,
@ -80,6 +88,7 @@ class TempSetFunction extends ACFunction {
code: 'temp_set',
operationName: 'Set Temperature',
icon: Assets.assetsTempreture,
type: type,
);
@override
@ -97,8 +106,10 @@ class TempSetFunction extends ACFunction {
}
class LevelFunction extends ACFunction {
LevelFunction({required super.deviceId, required super.deviceName})
LevelFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
type: type,
code: 'level',
operationName: 'Fan Speed',
icon: Assets.assetsFanSpeed,
@ -130,8 +141,10 @@ class LevelFunction extends ACFunction {
}
class ChildLockFunction extends ACFunction {
ChildLockFunction({required super.deviceId, required super.deviceName})
ChildLockFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
type: type,
code: 'child_lock',
operationName: 'Child Lock',
icon: Assets.assetsChildLock,
@ -157,11 +170,13 @@ class CurrentTempFunction extends ACFunction {
final int max;
final int step;
CurrentTempFunction({required super.deviceId, required super.deviceName})
CurrentTempFunction(
{required super.deviceId, required super.deviceName, required type})
: min = -100,
max = 990,
step = 1,
super(
type: type,
code: 'temp_current',
operationName: 'Current Temperature',
icon: Assets.currentTemp,

View File

@ -3,7 +3,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operation
import 'package:syncrow_web/utils/constants/assets.dart';
class ThreeGangSwitch1Function extends BaseSwitchFunction {
ThreeGangSwitch1Function({required super.deviceId, required super.deviceName})
ThreeGangSwitch1Function({required super.deviceId, required super.deviceName ,required type})
: super(
code: 'switch_1',
operationName: 'Light 1 Switch',
@ -26,7 +26,7 @@ class ThreeGangSwitch1Function extends BaseSwitchFunction {
}
class ThreeGangCountdown1Function extends BaseSwitchFunction {
ThreeGangCountdown1Function({required super.deviceId, required super.deviceName})
ThreeGangCountdown1Function({required super.deviceId, required super.deviceName ,required type})
: super(
code: 'countdown_1',
operationName: 'Light 1 Countdown',
@ -47,7 +47,7 @@ class ThreeGangCountdown1Function extends BaseSwitchFunction {
}
class ThreeGangSwitch2Function extends BaseSwitchFunction {
ThreeGangSwitch2Function({required super.deviceId, required super.deviceName})
ThreeGangSwitch2Function({required super.deviceId, required super.deviceName, required type})
: super(
code: 'switch_2',
operationName: 'Light 2 Switch',
@ -70,7 +70,7 @@ class ThreeGangSwitch2Function extends BaseSwitchFunction {
}
class ThreeGangCountdown2Function extends BaseSwitchFunction {
ThreeGangCountdown2Function({required super.deviceId, required super.deviceName})
ThreeGangCountdown2Function({required super.deviceId, required super.deviceName ,required type})
: super(
code: 'countdown_2',
operationName: 'Light 2 Countdown',
@ -91,7 +91,7 @@ class ThreeGangCountdown2Function extends BaseSwitchFunction {
}
class ThreeGangSwitch3Function extends BaseSwitchFunction {
ThreeGangSwitch3Function({required super.deviceId, required super.deviceName})
ThreeGangSwitch3Function({required super.deviceId, required super.deviceName ,required type})
: super(
code: 'switch_3',
operationName: 'Light 3 Switch',
@ -114,7 +114,7 @@ class ThreeGangSwitch3Function extends BaseSwitchFunction {
}
class ThreeGangCountdown3Function extends BaseSwitchFunction {
ThreeGangCountdown3Function({required super.deviceId, required super.deviceName})
ThreeGangCountdown3Function({required super.deviceId, required super.deviceName ,required type})
: super(
code: 'countdown_3',
operationName: 'Light 3 Countdown',

View File

@ -0,0 +1,111 @@
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class GatewayOperationalValue {
final String icon;
final String description;
final dynamic value;
GatewayOperationalValue({
required this.icon,
required this.description,
required this.value,
});
}
abstract class GatewayFunctions extends DeviceFunction<GatewayOperationalValue> {
final String type;
GatewayFunctions({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
required this.type,
});
List<GatewayOperationalValue> getOperationalValues();
}
final class GatewaySwitchAlarmSound extends GatewayFunctions {
GatewaySwitchAlarmSound({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'switch_alarm_sound',
operationName: 'Switch Alarm Sound',
icon: Assets.activeBell,
);
@override
List<GatewayOperationalValue> getOperationalValues() => [
GatewayOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
GatewayOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
final class GatewayMasterState extends GatewayFunctions {
GatewayMasterState({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'master_state',
operationName: 'Master State',
icon: Assets.gear,
);
@override
List<GatewayOperationalValue> getOperationalValues() {
return [
GatewayOperationalValue(
icon: Assets.assetsAcPower,
description: "Normal",
value: 'normal',
),
GatewayOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "Alarm",
value: 'alarm',
),
];
}
}
final class GatewayFactoryReset extends GatewayFunctions {
GatewayFactoryReset({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'factory_reset',
operationName: 'Factory Reset',
icon: Assets.factoryReset,
);
@override
List<GatewayOperationalValue> getOperationalValues() {
return [
GatewayOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
GatewayOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
}

View File

@ -0,0 +1,289 @@
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
abstract class WpsFunctions extends DeviceFunction<WallSensorModel> {
final String type;
WpsFunctions({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
required this.type,
});
List<WpsOperationalValue> getOperationalValues();
}
// For far_detection (75-600cm in 75cm steps)
class FarDetectionFunction extends WpsFunctions {
final int min;
final int max;
final int step;
final String unit;
FarDetectionFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 75,
max = 600,
step = 75,
unit = 'cm',
super(
type: type,
code: 'far_detection',
operationName: 'Far Detection',
icon: Assets.farDetectionIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
final values = <WpsOperationalValue>[];
for (var value = min; value <= max; value += step) {
values.add(WpsOperationalValue(
icon: Assets.currentDistanceIcon,
description: '$value $unit',
value: value,
));
}
return values;
}
}
// For presence_time (0-65535 minutes)
class PresenceTimeFunction extends WpsFunctions {
final int min;
final int max;
final int step;
final String unit;
PresenceTimeFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 0,
max = 65535,
step = 1,
unit = 'Min',
super(
type: type,
code: 'presence_time',
operationName: 'Presence Time',
icon: Assets.presenceTimeIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
return [
WpsOperationalValue(
icon: icon,
description: 'Custom $unit',
value: null,
)
];
}
}
// For motion_sensitivity_value (1-5 levels)
class MotionSensitivityFunction extends WpsFunctions {
final int min;
final int max;
final int step;
MotionSensitivityFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 1,
max = 5,
step = 1,
super(
type: type,
code: 'motion_sensitivity_value',
operationName: 'Motion Detection Sensitivity',
icon: Assets.motionDetectionSensitivityIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => WpsOperationalValue(
icon: Assets.motionDetectionSensitivityValueIcon,
description: '${min + (index * step)}',
value: min + (index * step),
),
);
}
}
class MotionLessSensitivityFunction extends WpsFunctions {
final int min;
final int max;
final int step;
MotionLessSensitivityFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 1,
max = 5,
step = 1,
super(
type: type,
code: 'motionless_sensitivity',
operationName: 'Motionless Detection Sensitivity',
icon: Assets.motionlessDetectionSensitivityIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => WpsOperationalValue(
icon: Assets.currentDistanceIcon,
description: '${min + (index * step)}',
value: min + (index * step),
),
);
}
}
class IndicatorFunction extends WpsFunctions {
IndicatorFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
type: type,
code: 'indicator',
operationName: 'Indicator',
icon: Assets.IndicatorIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() => [
WpsOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
WpsOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
class NoOneTimeFunction extends WpsFunctions {
final int min;
final int max;
final String unit;
NoOneTimeFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 10,
max = 10000,
unit = '',
super(
type: type,
code: 'no_one_time',
operationName: 'Nobody Time',
icon: Assets.nobodyTime,
);
@override
List<WpsOperationalValue> getOperationalValues() {
return [
WpsOperationalValue(
icon: icon,
description: 'Custom $unit',
value: null,
)
];
}
}
class PresenceStateFunction extends WpsFunctions {
PresenceStateFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
type: type,
code: 'presence_state',
operationName: 'Presence State',
icon: Assets.presenceStateIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() => [
WpsOperationalValue(
icon: Assets.assetsAcPower,
description: "None",
value: 'none',
),
WpsOperationalValue(
icon: Assets.presenceStateIcon,
description: "Presence",
value: 'presence',
),
];
}
class CurrentDistanceFunction extends WpsFunctions {
final int min;
final int max;
final int step;
CurrentDistanceFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 1,
max = 600,
step = 1,
super(
type: type,
code: 'dis_current',
operationName: 'Current Distance',
icon: Assets.currentDistanceIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = [];
for (int cm = min; cm <= max; cm += step) {
values.add(WpsOperationalValue(
icon: Assets.assetsTempreture,
description: "${cm}CM",
value: cm,
));
}
return values;
}
}
class IlluminanceValueFunction extends WpsFunctions {
final int min;
final int max;
final int step;
IlluminanceValueFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
max = 10000,
step = 10,
super(
code: 'illuminance_value',
operationName: 'Illuminance Value',
icon: Assets.IlluminanceIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = [];
for (int lux = min; lux <= max; lux += step) {
values.add(WpsOperationalValue(
icon: Assets.IlluminanceIcon,
description: "$lux Lux",
value: lux,
));
}
return values;
}
}

View File

@ -0,0 +1,11 @@
class WpsOperationalValue {
final String icon;
final String description;
final dynamic value;
WpsOperationalValue({
required this.icon,
required this.description,
required this.value,
});
}

View File

@ -33,8 +33,6 @@ class _RoutinesViewState extends State<RoutinesView> {
communityID: communityId, spaceID: spaceId));
await Future.delayed(const Duration(seconds: 1));
routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true));
await Future.delayed(const Duration(milliseconds:500));
_bloc.add(const ResetSelectedEvent());
}
@override
@ -92,6 +90,7 @@ class _RoutinesViewState extends State<RoutinesView> {
],
),
),
const SizedBox(height: 50),
],
),
)

View File

@ -26,7 +26,9 @@ class IfContainer extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const Text('IF',
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
if (state.isAutomation && state.ifItems.isNotEmpty)
AutomationOperatorSelector(
selectedOperator: state.selectedAutomationOperator),
@ -53,34 +55,48 @@ class IfContainer extends StatelessWidget {
(index) => GestureDetector(
onTap: () async {
if (!state.isTabToRun) {
final result = await DeviceDialogHelper.showDeviceDialog(
context, state.ifItems[index],
removeComparetors: false);
final result = await DeviceDialogHelper
.showDeviceDialog(
context: context,
data: state.ifItems[index],
removeComparetors: false,
dialogType: "IF");
if (result != null) {
context
.read<RoutineBloc>()
.add(AddToIfContainer(state.ifItems[index], false));
} else if (!['AC', '1G', '2G', '3G']
.contains(state.ifItems[index]['productType'])) {
context
.read<RoutineBloc>()
.add(AddToIfContainer(state.ifItems[index], false));
context.read<RoutineBloc>().add(
AddToIfContainer(
state.ifItems[index], false));
} else if (![
'AC',
'1G',
'2G',
'3G',
'WPS'
'GW',
].contains(
state.ifItems[index]['productType'])) {
context.read<RoutineBloc>().add(
AddToIfContainer(
state.ifItems[index], false));
}
}
},
child: DraggableCard(
imagePath: state.ifItems[index]['imagePath'] ?? '',
imagePath:
state.ifItems[index]['imagePath'] ?? '',
title: state.ifItems[index]['title'] ?? '',
deviceData: state.ifItems[index],
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 8),
isFromThen: false,
isFromIf: true,
onRemove: () {
context.read<RoutineBloc>().add(RemoveDragCard(
index: index,
isFromThen: false,
key: state.ifItems[index]['uniqueCustomId']));
context.read<RoutineBloc>().add(
RemoveDragCard(
index: index,
isFromThen: false,
key: state.ifItems[index]
['uniqueCustomId']));
},
),
)),
@ -91,7 +107,6 @@ class IfContainer extends StatelessWidget {
},
onAcceptWithDetails: (data) async {
final uniqueCustomId = const Uuid().v4();
final mutableData = Map<String, dynamic>.from(data.data);
mutableData['uniqueCustomId'] = uniqueCustomId;
@ -101,15 +116,25 @@ class IfContainer extends StatelessWidget {
if (!state.isTabToRun) {
if (mutableData['deviceId'] == 'tab_to_run') {
context.read<RoutineBloc>().add(AddToIfContainer(mutableData, true));
context
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, true));
} else {
final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData,
final result = await DeviceDialogHelper.showDeviceDialog(
dialogType: 'IF',
context: context,
data: mutableData,
removeComparetors: false);
if (result != null) {
context.read<RoutineBloc>().add(AddToIfContainer(mutableData, false));
} else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) {
context.read<RoutineBloc>().add(AddToIfContainer(mutableData, false));
context
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, false));
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW']
.contains(mutableData['productType'])) {
context
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, false));
}
}
}
@ -155,7 +180,9 @@ class AutomationOperatorSelector extends StatelessWidget {
),
),
onPressed: () {
context.read<RoutineBloc>().add(const ChangeAutomationOperator(operator: 'or'));
context
.read<RoutineBloc>()
.add(const ChangeAutomationOperator(operator: 'or'));
},
),
Container(
@ -181,7 +208,9 @@ class AutomationOperatorSelector extends StatelessWidget {
),
),
onPressed: () {
context.read<RoutineBloc>().add(const ChangeAutomationOperator(operator: 'and'));
context
.read<RoutineBloc>()
.add(const ChangeAutomationOperator(operator: 'and'));
},
),
],

View File

@ -31,165 +31,185 @@ class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
? const Center(
child: CircularProgressIndicator(),
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Scenes (Tab to Run)",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.scenes.isEmpty)
Expanded(
child: Text(
: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Scenes (Tab to Run)",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.scenes.isEmpty)
Text(
"No scenes found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
),
if (state.scenes.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 190 : 200,
maxWidth: MediaQuery.sizeOf(context).width * 0.8),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.scenes.length,
itemBuilder: (context, index) {
final scene = state.scenes[index];
final isLoading =
state.loadingSceneId == scene.id;
if (state.scenes.isNotEmpty)
SizedBox(
height: 200,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: state.scenes.length,
itemBuilder: (context, index) {
final scene = state.scenes[index];
final isLoading =
state.loadingSceneId == scene.id;
return Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
isLoading: isLoading,
sceneOnTap: () {
context.read<RoutineBloc>().add(
SceneTrigger(
sceneId: scene.id,
name: scene.name));
},
status: state.scenes[index].status,
communityId:
state.scenes[index].communityId ?? '',
spaceId: state.scenes[index].spaceId,
sceneId: state.scenes[index].sceneTuyaId!,
automationId: state.scenes[index].id,
cardType: 'scenes',
spaceName: state.scenes[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(
createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetSceneDetails(
sceneId: state.scenes[index].id,
isTabToRun: true,
isUpdate: true,
),
);
},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ??
Assets.logoHorizontal,
isFromScenes: true,
iconInBytes: state.scenes[index].iconInBytes,
),
);
}),
return Padding(
padding: EdgeInsets.only(
right:
isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: Column(
children: [
RoutineViewCard(
isLoading: isLoading,
sceneOnTap: () {
context.read<RoutineBloc>().add(
SceneTrigger(
sceneId: scene.id,
name: scene.name));
},
status: state.scenes[index].status,
communityId:
state.scenes[index].communityId ??
'',
spaceId: state.scenes[index].spaceId,
sceneId:
state.scenes[index].sceneTuyaId!,
automationId: state.scenes[index].id,
cardType: 'scenes',
spaceName:
state.scenes[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context)
.add(
const CreateNewRoutineViewEvent(
createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetSceneDetails(
sceneId:
state.scenes[index].id,
isTabToRun: true,
isUpdate: true,
),
);
},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ??
Assets.logoHorizontal,
isFromScenes: true,
iconInBytes:
state.scenes[index].iconInBytes,
),
],
),
);
}),
),
const SizedBox(height: 10),
Text(
"Automations",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text(
"Automations",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 5),
if (state.automations.isEmpty)
Expanded(
child: Text(
const SizedBox(height: 3),
if (state.automations.isEmpty)
Text(
"No automations found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
),
if (state.automations.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 185 : 192,
maxWidth: MediaQuery.sizeOf(context).width * 0.7),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.automations.length,
itemBuilder: (context, index) {
final isLoading = state.automations!
.contains(state.automations[index].id);
if (state.automations.isNotEmpty)
SizedBox(
height: 200,
return Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
isLoading: isLoading,
onChanged: (v) {
// BlocProvider.of<RoutineBloc>(context)
context.read<RoutineBloc>().add(
UpdateAutomationStatus(
automationId:
state.automations[index].id,
automationStatusUpdate:
AutomationStatusUpdate(
spaceUuid: state
.automations[index]
.spaceId,
isEnable: v),
communityId: state
.automations[index].communityId,
),
);
},
status: state.automations[index].status,
communityId: '',
spaceId: state.automations[index].spaceId,
sceneId: '',
automationId: state.automations[index].id,
cardType: 'automations',
spaceName: state.scenes[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(
createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetAutomationDetails(
automationId:
state.automations[index].id,
isAutomation: true,
isUpdate: true),
);
},
textString: state.automations[index].name,
icon: state.automations[index].icon ??
Assets.automation,
),
);
}),
),
],
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: state.automations.length,
itemBuilder: (context, index) {
final isLoading = state.automations!
.contains(state.automations[index].id);
return Column(
children: [
Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context)
? 4.0
: 8.0,
),
child: RoutineViewCard(
isLoading: isLoading,
onChanged: (v) {
context.read<RoutineBloc>().add(
UpdateAutomationStatus(
automationId: state
.automations[index].id,
automationStatusUpdate:
AutomationStatusUpdate(
spaceUuid: state
.automations[
index]
.spaceId,
isEnable: v),
communityId: state
.automations[index]
.communityId,
),
);
},
status: state.automations[index].status,
communityId: '',
spaceId:
state.automations[index].spaceId,
sceneId: '',
automationId:
state.automations[index].id,
cardType: 'automations',
spaceName:
state.automations[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context)
.add(
const CreateNewRoutineViewEvent(
createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetAutomationDetails(
automationId: state
.automations[index].id,
isAutomation: true,
isUpdate: true),
);
},
textString:
state.automations[index].name,
icon: state.automations[index].icon ??
Assets.automation,
),
),
],
);
}),
),
],
),
),
);
},

View File

@ -106,10 +106,10 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
if (widget.isFromScenes ?? false)
InkWell(
onTap: _handleSceneTap,
child: SvgPicture.asset(
child: Image.asset(
_showTemporaryCheck
? Assets.scenesPlayIconCheck
: Assets.scenesPlayIcon,
? Assets.scenesPlayIcon
: Assets.scenesPlayIconCheck,
fit: BoxFit.contain,
),
)
@ -177,6 +177,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
: (widget.icon is String &&
widget.icon.endsWith('.svg'))
? SvgPicture.asset(
height: iconSize,
width: iconSize,
widget.icon,
fit: BoxFit.contain,
)

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
@ -18,6 +17,8 @@ class _RoutineDevicesState extends State<RoutineDevices> {
context.read<RoutineBloc>().add(FetchDevicesInRoutine());
}
static const _allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW'};
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
@ -32,12 +33,8 @@ class _RoutineDevicesState extends State<RoutineDevices> {
}
});
List<AllDevicesModel> deviceList = state.devices
.where((device) =>
device.productType == 'AC' ||
device.productType == '1G' ||
device.productType == '2G' ||
device.productType == '3G')
final deviceList = state.devices
.where((device) => _allowedProductTypes.contains(device.productType))
.toList();
return Wrap(
@ -45,35 +42,32 @@ class _RoutineDevicesState extends State<RoutineDevices> {
runSpacing: 10,
children: deviceList.asMap().entries.map((entry) {
final device = entry.value;
final deviceData = {
'device': device,
'imagePath': device.getDefaultIcon(device.productType),
'title': device.name ?? '',
'deviceId': device.uuid,
'productType': device.productType,
'functions': device.functions,
'uniqueCustomId': '',
};
if (state.searchText != null && state.searchText!.isNotEmpty) {
return device.name!.toLowerCase().contains(state.searchText!.toLowerCase())
return device.name!
.toLowerCase()
.contains(state.searchText!.toLowerCase())
? DraggableCard(
imagePath: device.getDefaultIcon(device.productType),
title: device.name ?? '',
deviceData: {
'device': device,
'imagePath': device.getDefaultIcon(device.productType),
'title': device.name ?? '',
'deviceId': device.uuid,
'productType': device.productType,
'functions': device.functions,
'uniqueCustomId': '',
},
imagePath: deviceData['imagePath'] as String,
title: deviceData['title'] as String,
deviceData: deviceData,
)
: Container();
: const SizedBox.shrink();
} else {
return DraggableCard(
imagePath: device.getDefaultIcon(device.productType),
title: device.name ?? '',
deviceData: {
'device': device,
'imagePath': device.getDefaultIcon(device.productType),
'title': device.name ?? '',
'deviceId': device.uuid,
'productType': device.productType,
'functions': device.functions,
'uniqueCustomId': '',
},
imagePath: deviceData['imagePath'] as String,
title: deviceData['title'] as String,
deviceData: deviceData,
);
}
}).toList(),

View File

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class RoutineDialogFunctionListTile extends StatelessWidget {
const RoutineDialogFunctionListTile({
super.key,
required this.iconPath,
required this.operationName,
required this.onTap,
});
final String iconPath;
final String operationName;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return ListTile(
leading: SvgPicture.asset(
iconPath,
width: 24,
height: 24,
placeholderBuilder: (context) => const SizedBox(
width: 24,
height: 24,
),
),
title: Text(
operationName,
style: context.textTheme.bodyMedium,
),
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
color: ColorsManager.textGray,
),
onTap: onTap,
);
}
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class RoutineDialogSelectionListTile extends StatelessWidget {
const RoutineDialogSelectionListTile({
required this.iconPath,
required this.description,
required this.isSelected,
required this.onTap,
super.key,
});
final bool isSelected;
final String iconPath;
final String description;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return ListTile(
leading: SvgPicture.asset(
iconPath,
width: 24,
height: 24,
placeholderBuilder: (context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
),
),
title: Text(
description,
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
size: 24,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: onTap,
);
}
}

View File

@ -13,29 +13,38 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
class ACHelper {
static Future<Map<String, dynamic>?> showACFunctionsDialog(
BuildContext context,
List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String uniqueCustomId,
bool? removeComparetors,
) async {
List<ACFunction> acFunctions = functions.whereType<ACFunction>().toList();
static Future<Map<String, dynamic>?> showACFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
required AllDevicesModel? device,
required List<DeviceFunctionData>? deviceSelectedFunctions,
required String uniqueCustomId,
required bool? removeComparetors,
required String dialogType,
}) async {
List<ACFunction> acFunctions =
functions.whereType<ACFunction>().where((function) {
if (dialogType == 'THEN') {
return function.type == 'THEN' || function.type == 'BOTH';
}
return function.type == 'IF' || function.type == 'BOTH';
}).toList();
// List<ACFunction> acFunctions = functions.whereType<ACFunction>().toList();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
create: (_) => FunctionBloc()
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData =
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
final selectedFunctionData = state.addedFunctions
.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
@ -65,8 +74,10 @@ class ACHelper {
child: _buildFunctionsList(
context: context,
acFunctions: acFunctions,
onFunctionSelected: (functionCode, operationName) =>
context.read<FunctionBloc>().add(SelectFunction(
onFunctionSelected:
(functionCode, operationName) => context
.read<FunctionBloc>()
.add(SelectFunction(
functionCode: functionCode,
operationName: operationName,
)),
@ -194,7 +205,8 @@ class ACHelper {
);
}
final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction);
final selectedFn =
acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
@ -290,7 +302,8 @@ class ACHelper {
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
isSelected:
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
@ -384,9 +397,13 @@ class ACHelper {
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24,
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: () {
if (!isSelected) {
@ -398,7 +415,8 @@ class ACHelper {
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);

View File

@ -0,0 +1,130 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gateway.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart';
class GatewayDialog extends StatefulWidget {
const GatewayDialog({
required this.uniqueCustomId,
required this.functions,
required this.deviceSelectedFunctions,
required this.device,
super.key,
});
final String? uniqueCustomId;
final List<DeviceFunction> functions;
final List<DeviceFunctionData> deviceSelectedFunctions;
final AllDevicesModel? device;
@override
State<GatewayDialog> createState() => _GatewayDialogState();
}
class _GatewayDialogState extends State<GatewayDialog> {
late final List<GatewayFunctions> _gatewayFunctions;
@override
void initState() {
super.initState();
_gatewayFunctions = widget.functions.whereType<GatewayFunctions>().toList();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
return Container(
width: selectedFunction != null ? 600 : 360,
height: 450,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.only(top: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const DialogHeader('Gateway Conditions'),
Expanded(child: _buildMainContent(context, state)),
_buildDialogFooter(context, state),
],
),
);
},
),
);
}
Widget _buildMainContent(BuildContext context, FunctionBlocState state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
operationName: '',
value: null,
),
);
final selectedGatewayFunctions = _gatewayFunctions.firstWhere(
(f) => f.code == selectedFunction,
orElse: () => GatewaySwitchAlarmSound(
deviceId: '',
deviceName: '',
type: '',
),
);
final operations = selectedGatewayFunctions.getOperationalValues();
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
GatewayFunctionsList(gatewayFunctions: _gatewayFunctions),
if (state.selectedFunction != null)
Expanded(
child: GatewayDialogValueSelector(
operations: operations,
selectedFunction: selectedFunction ?? '',
selectedFunctionData: selectedFunctionData,
gatewayFunctions: _gatewayFunctions,
operationName: selectedOperationName ?? '',
device: widget.device,
),
),
],
);
}
Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) {
return DialogFooter(
onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty
? () {
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
widget.uniqueCustomId ?? '-1',
),
);
Navigator.pop(
context,
{'deviceId': widget.functions.firstOrNull?.deviceId},
);
}
: null,
isConfirmEnabled: state.selectedFunction != null,
);
}
}

View File

@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gateway.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialog_selection_list_tile.dart';
class GatewayDialogValueSelector extends StatelessWidget {
const GatewayDialogValueSelector({
required this.operations,
required this.selectedFunction,
required this.selectedFunctionData,
required this.gatewayFunctions,
required this.device,
required this.operationName,
super.key,
});
final List<GatewayOperationalValue> operations;
final String selectedFunction;
final DeviceFunctionData? selectedFunctionData;
final List<GatewayFunctions> gatewayFunctions;
final AllDevicesModel? device;
final String operationName;
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: operations.length,
itemBuilder: (context, index) {
final operation = operations[index];
final isSelected = selectedFunctionData?.value == operation.value;
return RoutineDialogSelectionListTile(
iconPath: operation.icon,
description: operation.description,
isSelected: isSelected,
onTap: () {
if (!isSelected) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: operationName,
value: operation.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
}
},
);
},
);
}
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/gateway.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class GatewayFunctionsList extends StatelessWidget {
const GatewayFunctionsList({
required this.gatewayFunctions,
super.key,
});
final List<GatewayFunctions> gatewayFunctions;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 360,
child: ListView.separated(
shrinkWrap: false,
itemCount: gatewayFunctions.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Divider(color: ColorsManager.dividerColor),
),
itemBuilder: (context, index) {
final function = gatewayFunctions[index];
return RoutineDialogFunctionListTile(
iconPath: function.icon,
operationName: function.operationName,
onTap: () => context.read<FunctionBloc>().add(
SelectFunction(
functionCode: function.code,
operationName: function.operationName,
),
),
);
},
),
);
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart';
abstract final class GatewayHelper {
const GatewayHelper._();
static Future<Map<String, dynamic>?> showGatewayFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
required String? uniqueCustomId,
required List<DeviceFunctionData> deviceSelectedFunctions,
required AllDevicesModel? device,
}) async {
return showDialog(
context: context,
builder: (context) => BlocProvider<FunctionBloc>(
create: (context) => FunctionBloc()
..add(
InitializeFunctions(deviceSelectedFunctions),
),
child: GatewayDialog(
uniqueCustomId: uniqueCustomId,
functions: functions,
deviceSelectedFunctions: deviceSelectedFunctions,
device: device,
),
),
);
}
}

View File

@ -5,8 +5,10 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
@ -14,29 +16,32 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class OneGangSwitchHelper {
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
BuildContext context,
List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String uniqueCustomId,
bool removeComparetors,
) async {
List<BaseSwitchFunction> acFunctions = functions.whereType<BaseSwitchFunction>().toList();
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog({
required String dialogType,
required BuildContext context,
required List<DeviceFunction> functions,
required AllDevicesModel? device,
required List<DeviceFunctionData>? deviceSelectedFunctions,
required String uniqueCustomId,
required bool removeComparetors,
}) async {
List<BaseSwitchFunction> oneGangFunctions =
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
create: (_) => FunctionBloc()
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData =
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
final selectedFunctionData = state.addedFunctions
.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
@ -61,12 +66,12 @@ class OneGangSwitchHelper {
// Left side: Function list
Expanded(
child: ListView.separated(
itemCount: acFunctions.length,
itemCount: oneGangFunctions.length,
separatorBuilder: (_, __) => const Divider(
color: ColorsManager.dividerColor,
),
itemBuilder: (context, index) {
final function = acFunctions[index];
final function = oneGangFunctions[index];
return ListTile(
leading: SvgPicture.asset(
function.icon,
@ -83,9 +88,12 @@ class OneGangSwitchHelper {
color: ColorsManager.textGray,
),
onTap: () {
context.read<FunctionBloc>().add(SelectFunction(
context
.read<FunctionBloc>()
.add(SelectFunction(
functionCode: function.code,
operationName: function.operationName,
operationName:
function.operationName,
));
},
);
@ -99,7 +107,7 @@ class OneGangSwitchHelper {
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
acFunctions: acFunctions,
acFunctions: oneGangFunctions,
device: device,
operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors,
@ -174,8 +182,14 @@ class OneGangSwitchHelper {
removeComparetors: removeComparetors,
);
}
final selectedFn = acFunctions.firstWhere(
(f) => f.code == selectedFunction,
orElse: () => OneGangSwitchFunction(
deviceId: '',
deviceName: '',
),
);
final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
@ -212,11 +226,11 @@ class OneGangSwitchHelper {
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
],
);
}
@ -257,7 +271,8 @@ class OneGangSwitchHelper {
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
isSelected:
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
@ -305,7 +320,8 @@ class OneGangSwitchHelper {
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
divisions: (((operationalValues.maxValue ?? 0) -
(operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
@ -357,9 +373,13 @@ class OneGangSwitchHelper {
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24,
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: () {
if (!isSelected) {
@ -371,7 +391,8 @@ class OneGangSwitchHelper {
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);

View File

@ -14,29 +14,32 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ThreeGangSwitchHelper {
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
BuildContext context,
List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String uniqueCustomId,
bool removeComparetors,
) async {
List<BaseSwitchFunction> switchFunctions = functions.whereType<BaseSwitchFunction>().toList();
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
required AllDevicesModel? device,
required List<DeviceFunctionData>? deviceSelectedFunctions,
required String uniqueCustomId,
required String dialogType,
required bool removeComparetors,
}) async {
List<BaseSwitchFunction> switchFunctions =
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
create: (_) => FunctionBloc()
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData =
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
final selectedFunctionData = state.addedFunctions
.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
@ -83,9 +86,12 @@ class ThreeGangSwitchHelper {
color: ColorsManager.textGray,
),
onTap: () {
context.read<FunctionBloc>().add(SelectFunction(
context
.read<FunctionBloc>()
.add(SelectFunction(
functionCode: function.code,
operationName: function.operationName,
operationName:
function.operationName,
));
},
);
@ -177,7 +183,8 @@ class ThreeGangSwitchHelper {
);
}
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
final selectedFn =
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
@ -214,11 +221,11 @@ class ThreeGangSwitchHelper {
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
],
);
}
@ -259,7 +266,8 @@ class ThreeGangSwitchHelper {
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
isSelected:
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
@ -307,7 +315,8 @@ class ThreeGangSwitchHelper {
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
divisions: (((operationalValues.maxValue ?? 0) -
(operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
@ -359,9 +368,13 @@ class ThreeGangSwitchHelper {
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24,
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: () {
if (!isSelected) {
@ -373,7 +386,8 @@ class ThreeGangSwitchHelper {
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);

View File

@ -14,29 +14,32 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class TwoGangSwitchHelper {
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
BuildContext context,
List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String uniqueCustomId,
bool removeComparetors,
) async {
List<BaseSwitchFunction> switchFunctions = functions.whereType<BaseSwitchFunction>().toList();
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
required AllDevicesModel? device,
required List<DeviceFunctionData>? deviceSelectedFunctions,
required String uniqueCustomId,
required bool removeComparetors,
required String dialogType,
}) async {
List<BaseSwitchFunction> switchFunctions =
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
create: (_) => FunctionBloc()
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData =
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
final selectedFunctionData = state.addedFunctions
.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
@ -83,9 +86,12 @@ class TwoGangSwitchHelper {
color: ColorsManager.textGray,
),
onTap: () {
context.read<FunctionBloc>().add(SelectFunction(
context
.read<FunctionBloc>()
.add(SelectFunction(
functionCode: function.code,
operationName: function.operationName,
operationName:
function.operationName,
));
},
);
@ -161,7 +167,8 @@ class TwoGangSwitchHelper {
required String operationName,
required bool removeComparetors,
}) {
if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 200;
return _buildTemperatureSelector(
context: context,
@ -175,7 +182,8 @@ class TwoGangSwitchHelper {
);
}
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
final selectedFn =
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
@ -212,11 +220,11 @@ class TwoGangSwitchHelper {
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
],
);
}
@ -257,7 +265,8 @@ class TwoGangSwitchHelper {
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
isSelected:
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
@ -305,7 +314,8 @@ class TwoGangSwitchHelper {
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
divisions: (((operationalValues.maxValue ?? 0) -
(operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
@ -357,9 +367,13 @@ class TwoGangSwitchHelper {
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24,
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: () {
if (!isSelected) {
@ -371,7 +385,8 @@ class TwoGangSwitchHelper {
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);

View File

@ -0,0 +1,169 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class TimeWheelPicker extends StatefulWidget {
final int initialHours;
final int initialMinutes;
final int initialSeconds;
final Function(int, int, int) onTimeChanged;
const TimeWheelPicker({
super.key,
required this.initialHours,
required this.initialMinutes,
required this.initialSeconds,
required this.onTimeChanged,
});
@override
State<TimeWheelPicker> createState() => _TimeWheelPickerState();
}
class _TimeWheelPickerState extends State<TimeWheelPicker> {
late FixedExtentScrollController _hoursController;
late FixedExtentScrollController _minutesController;
late FixedExtentScrollController _secondsController;
@override
void initState() {
super.initState();
_hoursController =
FixedExtentScrollController(initialItem: widget.initialHours);
_minutesController =
FixedExtentScrollController(initialItem: widget.initialMinutes);
_secondsController =
FixedExtentScrollController(initialItem: widget.initialSeconds);
}
@override
void didUpdateWidget(TimeWheelPicker oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.initialHours != widget.initialHours) {
_hoursController.jumpToItem(widget.initialHours);
}
if (oldWidget.initialMinutes != widget.initialMinutes) {
_minutesController.jumpToItem(widget.initialMinutes);
}
if (oldWidget.initialSeconds != widget.initialSeconds) {
_secondsController.jumpToItem(widget.initialSeconds);
}
}
@override
void dispose() {
_hoursController.dispose();
_minutesController.dispose();
_secondsController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildPickerColumn(
label: 'h',
controller: _hoursController,
itemCount: 3,
onChanged: (value) {
_handleTimeChange(
value,
_minutesController.selectedItem,
_secondsController.selectedItem,
);
}),
const SizedBox(width: 5),
_buildPickerColumn(
label: 'm',
controller: _minutesController,
itemCount: 60,
onChanged: (value) {
_handleTimeChange(
_hoursController.selectedItem,
value,
_secondsController.selectedItem,
);
}),
const SizedBox(width: 5),
_buildPickerColumn(
label: 's',
controller: _secondsController,
itemCount: 60,
onChanged: (value) => _handleTimeChange(
_hoursController.selectedItem,
_minutesController.selectedItem,
value,
),
),
],
);
}
void _handleTimeChange(int hours, int minutes, int seconds) {
int total = hours * 3600 + minutes * 60 + seconds;
if (total > 10000) {
hours = 2;
minutes = 46;
seconds = 40;
total = 10000;
WidgetsBinding.instance.addPostFrameCallback((_) {
_hoursController.jumpToItem(hours);
_minutesController.jumpToItem(minutes);
_secondsController.jumpToItem(seconds);
});
}
widget.onTimeChanged(hours, minutes, seconds);
}
Widget _buildPickerColumn({
required String label,
required FixedExtentScrollController controller,
required int itemCount,
required Function(int) onChanged,
}) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 40,
width: 40,
padding: const EdgeInsets.symmetric(horizontal: 5),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(8),
),
child: ListWheelScrollView.useDelegate(
controller: controller,
itemExtent: 40.0,
physics: const FixedExtentScrollPhysics(),
onSelectedItemChanged: onChanged,
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) => Center(
child: Text(
index.toString().padLeft(2),
style: const TextStyle(
fontSize: 18,
color: ColorsManager.blue1,
),
),
),
childCount: itemCount,
),
),
),
const SizedBox(width: 5),
Text(
label,
style: const TextStyle(
color: ColorsManager.blackColor,
fontSize: 18,
),
),
],
);
}
}

View File

@ -0,0 +1,549 @@
import 'package:flutter/material.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/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
class WallPresenceSensor extends StatefulWidget {
final List<DeviceFunction> functions;
final AllDevicesModel? device;
final List<DeviceFunctionData>? deviceSelectedFunctions;
final String? uniqueCustomId;
final String? dialogType;
final bool removeComparetors;
const WallPresenceSensor({
super.key,
required this.functions,
this.device,
this.deviceSelectedFunctions,
this.uniqueCustomId,
this.dialogType,
this.removeComparetors = false,
});
static Future<Map<String, dynamic>?> showWPSFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String? uniqueCustomId,
String? dialogType,
bool removeComparetors = false,
}) async {
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (context) => WallPresenceSensor(
functions: functions,
device: device,
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: uniqueCustomId,
removeComparetors: removeComparetors,
dialogType: dialogType,
),
);
}
@override
State<WallPresenceSensor> createState() => _WallPresenceSensorState();
}
class _WallPresenceSensorState extends State<WallPresenceSensor> {
late final List<WpsFunctions> _wpsFunctions;
@override
void initState() {
super.initState();
_wpsFunctions =
widget.functions.whereType<WpsFunctions>().where((function) {
if (widget.dialogType == 'THEN') {
return function.type == 'THEN' || function.type == 'BOTH';
}
return function.type == 'IF' || function.type == 'BOTH';
}).toList();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()
..add(InitializeFunctions(widget.deviceSelectedFunctions ?? [])),
child: _buildDialogContent(),
);
}
Widget _buildDialogContent() {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
return Container(
width: selectedFunction != null ? 600 : 360,
height: 450,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.only(top: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const DialogHeader('Presence Sensor Condition'),
Expanded(child: _buildMainContent(context, state)),
_buildDialogFooter(context, state),
],
),
);
},
),
);
}
Widget _buildMainContent(BuildContext context, FunctionBlocState state) {
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildFunctionList(context),
if (state.selectedFunction != null) _buildValueSelector(context, state),
],
);
}
Widget _buildFunctionList(BuildContext context) {
return SizedBox(
width: 360,
child: ListView.separated(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: _wpsFunctions.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Divider(color: ColorsManager.dividerColor),
),
itemBuilder: (context, index) {
final function = _wpsFunctions[index];
return ListTile(
leading: SvgPicture.asset(
function.icon,
width: 24,
height: 24,
placeholderBuilder: (context) => const SizedBox(
width: 24,
height: 24,
),
),
title: Text(
function.operationName,
style: context.textTheme.bodyMedium,
),
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
color: ColorsManager.textGray,
),
onTap: () => context.read<FunctionBloc>().add(
SelectFunction(
functionCode: function.code,
operationName: function.operationName,
),
),
);
},
),
);
}
Widget _buildValueSelector(BuildContext context, FunctionBlocState state) {
final selectedFunction = state.selectedFunction!;
final functionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction,
operationName: state.selectedOperationName ?? '',
value: null,
),
);
return Expanded(
child: _ValueSelector(
selectedFunction: selectedFunction,
functionData: functionData,
acFunctions: _wpsFunctions,
device: widget.device,
dialogType: widget.dialogType!,
removeComparators: widget.removeComparetors,
),
);
}
Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) {
return DialogFooter(
onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty
? () {
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
widget.uniqueCustomId!,
),
);
Navigator.pop(
context,
{'deviceId': widget.functions.first.deviceId},
);
}
: null,
isConfirmEnabled: state.selectedFunction != null,
);
}
}
class _ValueSelector extends StatelessWidget {
final String selectedFunction;
final DeviceFunctionData functionData;
final List<WpsFunctions> acFunctions;
final AllDevicesModel? device;
final String dialogType;
final bool removeComparators;
const _ValueSelector({
required this.selectedFunction,
required this.functionData,
required this.acFunctions,
required this.device,
required this.dialogType,
required this.removeComparators,
});
@override
Widget build(BuildContext context) {
final selectedFn =
acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
if (_isSliderFunction(selectedFunction)) {
return _SliderValueSelector(
selectedFunction: selectedFunction,
functionData: functionData,
device: device,
dialogType: dialogType,
);
}
return _OperationalValuesList(
values: values,
selectedValue: functionData.value,
device: device,
operationName: selectedFn.operationName,
selectCode: selectedFunction,
);
}
bool _isSliderFunction(String function) =>
['dis_current', 'presence_time', 'illuminance_value'].contains(function);
}
class _SliderValueSelector extends StatelessWidget {
final String selectedFunction;
final DeviceFunctionData functionData;
final AllDevicesModel? device;
final String dialogType;
const _SliderValueSelector({
required this.selectedFunction,
required this.functionData,
required this.device,
required this.dialogType,
});
@override
Widget build(BuildContext context) {
final initialValue = functionData.value ?? 250;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 20),
_ConditionToggle(
currentCondition: functionData.condition,
selectCode: selectedFunction,
device: device,
operationName: functionData.operationName,
selectedValue: functionData.value,
),
_ValueDisplay(
value: initialValue,
functionCode: selectedFunction,
),
const SizedBox(height: 20),
_FunctionSlider(
initialValue: initialValue,
functionCode: selectedFunction,
functionData: functionData,
device: device,
),
],
);
}
}
class _ConditionToggle extends StatelessWidget {
final String? currentCondition;
final String selectCode;
final AllDevicesModel? device;
final String operationName;
final dynamic selectedValue;
const _ConditionToggle({
this.currentCondition,
required this.selectCode,
this.device,
required this.operationName,
this.selectedValue,
});
@override
Widget build(BuildContext context) {
const conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (index) => _updateCondition(context, conditions[index]),
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected:
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
void _updateCondition(BuildContext context, String condition) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: condition,
value: selectedValue,
),
),
);
}
}
class _ValueDisplay extends StatelessWidget {
final dynamic value;
final String functionCode;
const _ValueDisplay({
required this.value,
required this.functionCode,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
_getDisplayText(),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
String _getDisplayText() {
final intValue = (value as num?)?.toInt() ?? 0;
switch (functionCode) {
case 'presence_time':
return '$intValue Min';
case 'dis_current':
return '$intValue CM';
case 'illuminance_value':
return '$intValue Lux';
default:
return '$intValue';
}
}
}
class _FunctionSlider extends StatelessWidget {
final dynamic initialValue;
final String functionCode;
final DeviceFunctionData functionData;
final AllDevicesModel? device;
const _FunctionSlider({
required this.initialValue,
required this.functionCode,
required this.functionData,
required this.device,
});
@override
Widget build(BuildContext context) {
final (min, max) = _getSliderRange();
return Slider(
value: initialValue is int ? initialValue.toDouble() : min,
min: min,
max: max,
divisions: (max - min).toInt(),
onChanged: (value) => _updateValue(context, value.toInt()),
);
}
(double, double) _getSliderRange() {
switch (functionCode) {
case 'presence_time':
return (0, 65535);
case 'dis_current':
return (1, 600);
case 'illuminance_value':
return (0, 10000);
default:
return (200, 300);
}
}
void _updateValue(BuildContext context, int value) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: functionCode,
operationName: functionData.operationName,
value: value,
condition: functionData.condition,
),
),
);
}
}
class _OperationalValuesList extends StatelessWidget {
final List<WpsOperationalValue> values;
final dynamic selectedValue;
final AllDevicesModel? device;
final String operationName;
final String selectCode;
const _OperationalValuesList({
required this.values,
required this.selectedValue,
required this.device,
required this.operationName,
required this.selectCode,
});
@override
Widget build(BuildContext context) {
return operationName == 'Nobody Time'
? _buildTimeWheel(context)
: ListView.builder(
padding: const EdgeInsets.all(20),
itemCount: values.length,
itemBuilder: (context, index) =>
_buildValueItem(context, values[index]),
);
}
Widget _buildTimeWheel(BuildContext context) {
final currentTotalSeconds = selectedValue as int? ?? 0;
return TimeWheelPicker(
initialHours: currentTotalSeconds ~/ 3600,
initialMinutes: (currentTotalSeconds % 3600) ~/ 60,
initialSeconds: currentTotalSeconds % 60,
onTimeChanged: (h, m, s) => _updateTotalSeconds(context, h, m, s),
);
}
Widget _buildValueItem(BuildContext context, WpsOperationalValue value) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildValueIcon(context, value),
Expanded(child: _buildValueDescription(value)),
_buildValueRadio(context, value),
],
),
);
}
Widget _buildValueIcon(context, WpsOperationalValue value) {
return Column(
children: [
if (_shouldShowTextDescription)
Text(value.description.replaceAll("cm", '')),
SvgPicture.asset(value.icon, width: 25, height: 25),
],
);
}
bool get _shouldShowTextDescription =>
operationName == 'Far Detection' ||
operationName == 'Motionless Detection Sensitivity';
Widget _buildValueDescription(WpsOperationalValue value) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(value.description),
);
}
Widget _buildValueRadio(context, WpsOperationalValue value) {
return Radio<dynamic>(
value: value.value,
groupValue: selectedValue,
onChanged: (_) => _selectValue(context, value.value),
);
}
void _selectValue(BuildContext context, dynamic value) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
),
),
);
}
void _updateTotalSeconds(BuildContext context, int h, int m, int s) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: h * 3600 + m * 60 + s,
),
),
);
}
}

View File

@ -26,7 +26,9 @@ class ThenContainer extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('THEN', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const Text('THEN',
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
state.isLoading && state.isUpdate == true
? const Center(
@ -39,12 +41,17 @@ class ThenContainer extends StatelessWidget {
state.thenItems.length,
(index) => GestureDetector(
onTap: () async {
if (state.thenItems[index]['deviceId'] == 'delay') {
final result = await DelayHelper.showDelayPickerDialog(
context, state.thenItems[index]);
if (state.thenItems[index]
['deviceId'] ==
'delay') {
final result = await DelayHelper
.showDelayPickerDialog(context,
state.thenItems[index]);
if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer({
context
.read<RoutineBloc>()
.add(AddToThenContainer({
...state.thenItems[index],
'imagePath': Assets.delay,
'title': 'Delay',
@ -53,58 +60,87 @@ class ThenContainer extends StatelessWidget {
return;
}
if (state.thenItems[index]['type'] == 'automation') {
if (state.thenItems[index]['type'] ==
'automation') {
final result = await showDialog<bool>(
context: context,
builder: (BuildContext context) => AutomationDialog(
builder: (BuildContext context) =>
AutomationDialog(
automationName:
state.thenItems[index]['name'] ?? 'Automation',
state.thenItems[index]
['name'] ??
'Automation',
automationId:
state.thenItems[index]['deviceId'] ?? '',
uniqueCustomId: state.thenItems[index]
['uniqueCustomId'],
state.thenItems[index]
['deviceId'] ??
'',
uniqueCustomId:
state.thenItems[index]
['uniqueCustomId'],
),
);
if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer({
context
.read<RoutineBloc>()
.add(AddToThenContainer({
...state.thenItems[index],
'imagePath': Assets.automation,
'title': state.thenItems[index]['name'] ??
state.thenItems[index]['title'],
'imagePath':
Assets.automation,
'title':
state.thenItems[index]
['name'] ??
state.thenItems[index]
['title'],
}));
}
return;
}
final result = await DeviceDialogHelper.showDeviceDialog(
context, state.thenItems[index],
removeComparetors: true);
final result = await DeviceDialogHelper
.showDeviceDialog(
context: context,
data: state.thenItems[index],
removeComparetors: true,
dialogType: "THEN");
if (result != null) {
context
.read<RoutineBloc>()
.add(AddToThenContainer(state.thenItems[index]));
} else if (!['AC', '1G', '2G', '3G']
.contains(state.thenItems[index]['productType'])) {
context
.read<RoutineBloc>()
.add(AddToThenContainer(state.thenItems[index]));
context.read<RoutineBloc>().add(
AddToThenContainer(
state.thenItems[index]));
} else if (![
'AC',
'1G',
'2G',
'3G',
'WPS',
"GW",
].contains(state.thenItems[index]
['productType'])) {
context.read<RoutineBloc>().add(
AddToThenContainer(
state.thenItems[index]));
}
},
child: DraggableCard(
imagePath: state.thenItems[index]['imagePath'] ?? '',
title: state.thenItems[index]['title'] ?? '',
imagePath: state.thenItems[index]
['imagePath'] ??
'',
title: state.thenItems[index]
['title'] ??
'',
deviceData: state.thenItems[index],
padding:
const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 8),
isFromThen: true,
isFromIf: false,
onRemove: () {
context.read<RoutineBloc>().add(RemoveDragCard(
index: index,
isFromThen: true,
key: state.thenItems[index]['uniqueCustomId']));
context.read<RoutineBloc>().add(
RemoveDragCard(
index: index,
isFromThen: true,
key: state.thenItems[index]
['uniqueCustomId']));
},
),
))),
@ -129,8 +165,8 @@ class ThenContainer extends StatelessWidget {
}
if (mutableData['type'] == 'automation') {
int index =
state.thenItems.indexWhere((item) => item['deviceId'] == mutableData['deviceId']);
int index = state.thenItems.indexWhere(
(item) => item['deviceId'] == mutableData['deviceId']);
if (index != -1) {
return;
}
@ -155,8 +191,8 @@ class ThenContainer extends StatelessWidget {
}
if (mutableData['type'] == 'tap_to_run' && state.isAutomation) {
int index =
state.thenItems.indexWhere((item) => item['deviceId'] == mutableData['deviceId']);
int index = state.thenItems.indexWhere(
(item) => item['deviceId'] == mutableData['deviceId']);
if (index != -1) {
return;
}
@ -174,7 +210,8 @@ class ThenContainer extends StatelessWidget {
}
if (mutableData['deviceId'] == 'delay') {
final result = await DelayHelper.showDelayPickerDialog(context, mutableData);
final result =
await DelayHelper.showDelayPickerDialog(context, mutableData);
if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer({
@ -186,11 +223,15 @@ class ThenContainer extends StatelessWidget {
return;
}
final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData,
removeComparetors: true);
final result = await DeviceDialogHelper.showDeviceDialog(
context: context,
data: mutableData,
removeComparetors: true,
dialogType: "THEN");
if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
} else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) {
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW']
.contains(mutableData['productType'])) {
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
}
},

View File

@ -1,7 +1,10 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.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/space_model.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
@ -18,7 +21,10 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
on<ClearCachedData>(_clearCachedData);
on<OnCommunityAdded>(_onCommunityAdded);
on<OnCommunityUpdated>(_onCommunityUpdate);
on<PaginationEvent>(_fetchPaginationSpaces);
on<DebouncedSearchEvent>(_onDebouncedSearch);
}
Timer _timer = Timer(const Duration(microseconds: 0), () {});
void _onCommunityUpdate(
OnCommunityUpdated event,
@ -28,17 +34,16 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
try {
final updatedCommunity = event.updatedCommunity;
final updatedCommunities =
List<CommunityModel>.from(state.communityList);
final updatedCommunities = List<CommunityModel>.from(state.communityList);
final index = updatedCommunities
.indexWhere((community) => community.uuid == updatedCommunity.uuid);
final index =
updatedCommunities.indexWhere((community) => community.uuid == updatedCommunity.uuid);
if (index != -1) {
updatedCommunities[index] = updatedCommunity;
emit(state.copyWith(communitiesList: updatedCommunities));
} else {
emit(SpaceTreeErrorState('Community not found in the list.'));
emit(const SpaceTreeErrorState('Community not found in the list.'));
}
} catch (e) {
emit(SpaceTreeErrorState('Error updating community: $e'));
@ -50,48 +55,68 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<CommunityModel> communities =
await CommunitySpaceManagementApi().fetchCommunities(projectUuid);
PaginationModel paginationModel = await CommunitySpaceManagementApi()
.fetchCommunitiesAndSpaces(projectId: projectUuid, page: 1);
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces = await CommunitySpaceManagementApi()
.getSpaceHierarchy(community.uuid, projectUuid);
// List<CommunityModel> updatedCommunities = await Future.wait(
// communities.map((community) async {
// List<SpaceModel> spaces =
// await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid, projectUuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces,
region: community.region,
);
}).toList(),
);
// return CommunityModel(
// uuid: community.uuid,
// createdAt: community.createdAt,
// updatedAt: community.updatedAt,
// name: community.name,
// description: community.description,
// spaces: spaces,
// region: community.region,
// );
// }).toList(),
// );
emit(state.copyWith(
communitiesList: updatedCommunities,
communitiesList: paginationModel.communities,
expandedCommunity: [],
expandedSpaces: []));
expandedSpaces: [],
paginationModel: paginationModel));
} catch (e) {
emit(SpaceTreeErrorState('Error loading communities and spaces: $e'));
}
}
void _onCommunityAdded(
OnCommunityAdded event, Emitter<SpaceTreeState> emit) async {
_fetchPaginationSpaces(PaginationEvent event, Emitter<SpaceTreeState> emit) async {
emit(state.copyWith(paginationIsLoading: true));
PaginationModel paginationModel = event.paginationModel;
List<CommunityModel> communities = List<CommunityModel>.from(event.communities);
try {
if (paginationModel.hasNext && state.searchQuery.isEmpty) {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
paginationModel = await CommunitySpaceManagementApi().fetchCommunitiesAndSpaces(
projectId: projectUuid, page: paginationModel.pageNum, search: state.searchQuery);
communities.addAll(paginationModel.communities);
}
} catch (e) {
emit(SpaceTreeErrorState('Error loading communities and spaces: $e'));
}
emit(state.copyWith(
communitiesList: communities,
paginationModel: paginationModel,
paginationIsLoading: false));
}
void _onCommunityAdded(OnCommunityAdded event, Emitter<SpaceTreeState> emit) async {
final updatedCommunities = List<CommunityModel>.from(state.communityList);
updatedCommunities.add(event.newCommunity);
emit(state.copyWith(communitiesList: updatedCommunities));
}
_onCommunityExpanded(
OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
_onCommunityExpanded(OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedExpandedCommunityList =
List.from(state.expandedCommunities);
List<String> updatedExpandedCommunityList = List.from(state.expandedCommunities);
if (updatedExpandedCommunityList.contains(event.communityId)) {
updatedExpandedCommunityList.remove(event.communityId);
@ -123,19 +148,14 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
}
}
_onCommunitySelected(
OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
_onCommunitySelected(OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedSelectedCommunities =
List.from(state.selectedCommunities.toSet().toList());
List<String> updatedSelectedSpaces =
List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks =
List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces =
Map.from(state.selectedCommunityAndSpaces);
List<String> selectedSpacesInCommunity =
communityAndSpaces[event.communityId] ?? [];
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityId] ?? [];
List<String> childrenIds = _getAllChildIds(event.children);
@ -168,15 +188,11 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
try {
List<String> updatedSelectedCommunities =
List.from(state.selectedCommunities.toSet().toList());
List<String> updatedSelectedSpaces =
List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks =
List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces =
Map.from(state.selectedCommunityAndSpaces);
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List<String> selectedSpacesInCommunity =
communityAndSpaces[event.communityModel.uuid] ?? [];
List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityModel.uuid] ?? [];
List<String> childrenIds = _getAllChildIds(event.children);
bool isChildSelected = false;
@ -199,11 +215,9 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
selectedSpacesInCommunity.addAll(childrenIds);
}
List<String> spaces =
_getThePathToChild(event.communityModel.uuid, event.spaceId);
List<String> spaces = _getThePathToChild(event.communityModel.uuid, event.spaceId);
for (String space in spaces) {
if (!updatedSelectedSpaces.contains(space) &&
!updatedSoldChecks.contains(space)) {
if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) {
updatedSoldChecks.add(space);
}
}
@ -226,9 +240,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
updatedSoldChecks.remove(event.spaceId);
List<String> parents =
_getThePathToChild(event.communityModel.uuid, event.spaceId)
.toSet()
.toList();
_getThePathToChild(event.communityModel.uuid, event.spaceId).toSet().toList();
if (updatedSelectedSpaces.isEmpty) {
updatedSoldChecks.removeWhere(parents.contains);
@ -236,8 +248,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
} else {
// Check if any parent has selected children
for (String space in parents) {
if (!_noChildrenSelected(
event.communityModel, space, updatedSelectedSpaces, parents)) {
if (!_noChildrenSelected(event.communityModel, space, updatedSelectedSpaces, parents)) {
updatedSoldChecks.remove(space);
}
}
@ -262,8 +273,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
}
}
_noChildrenSelected(CommunityModel community, String spaceId,
List<String> selectedSpaces, List<String> parents) {
_noChildrenSelected(
CommunityModel community, String spaceId, List<String> selectedSpaces, List<String> parents) {
if (selectedSpaces.contains(spaceId)) {
return true;
}
@ -285,29 +296,52 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
_onSearch(SearchQueryEvent event, Emitter<SpaceTreeState> emit) async {
try {
List<CommunityModel> communities = List.from(state.communityList);
List<CommunityModel> filteredCommunity = [];
const duration = Duration(seconds: 1);
if (_timer.isActive) {
_timer.cancel(); // clear timer
}
_timer = Timer(duration, () async => add(DebouncedSearchEvent(event.searchQuery)));
// List<CommunityModel> communities = List.from(state.communityList);
// List<CommunityModel> filteredCommunity = [];
// Filter communities and expand only those that match the query
filteredCommunity = communities.where((community) {
final containsQueryInCommunity = community.name
.toLowerCase()
.contains(event.searchQuery.toLowerCase());
final containsQueryInSpaces = community.spaces.any(
(space) => _containsQuery(space, event.searchQuery.toLowerCase()));
// filteredCommunity = communities.where((community) {
// final containsQueryInCommunity =
// community.name.toLowerCase().contains(event.searchQuery.toLowerCase());
// final containsQueryInSpaces =
// community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces;
}).toList();
// return containsQueryInCommunity || containsQueryInSpaces;
// }).toList();
emit(state.copyWith(
filteredCommunity: filteredCommunity,
isSearching: event.searchQuery.isNotEmpty,
searchQuery: event.searchQuery));
// emit(state.copyWith(
// filteredCommunity: filteredCommunity,
// isSearching: event.searchQuery.isNotEmpty,
// searchQuery: event.searchQuery));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
_onDebouncedSearch(DebouncedSearchEvent event, Emitter<SpaceTreeState> emit) async {
emit(state.copyWith(
isSearching: true,
));
PaginationModel paginationModel = const PaginationModel.emptyConstructor();
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
paginationModel = await CommunitySpaceManagementApi()
.fetchCommunitiesAndSpaces(projectId: projectUuid, page: 1, search: event.searchQuery);
} catch (_) {}
emit(state.copyWith(
filteredCommunity: paginationModel.communities,
isSearching: false,
searchQuery: event.searchQuery));
}
_clearAllData(ClearAllData event, Emitter<SpaceTreeState> emit) async {
try {
emit(state.copyWith(
@ -345,13 +379,13 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
}
// Helper function to determine if any space or its children match the search query
bool _containsQuery(SpaceModel space, String query) {
final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren = space.children.any((child) =>
_containsQuery(child, query)); // Recursive check for children
// bool _containsQuery(SpaceModel space, String query) {
// final matchesSpace = space.name.toLowerCase().contains(query);
// final matchesChildren =
// space.children.any((child) => _containsQuery(child, query)); // Recursive check for children
return matchesSpace || matchesChildren;
}
// return matchesSpace || matchesChildren;
// }
List<String> _getAllChildIds(List<SpaceModel> spaces) {
List<String> ids = [];
@ -371,8 +405,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
return children;
}
bool _anySpacesSelectedInCommunity(CommunityModel community,
List<String> selectedSpaces, List<String> partialCheckedList) {
bool _anySpacesSelectedInCommunity(
CommunityModel community, List<String> selectedSpaces, List<String> partialCheckedList) {
bool result = false;
List<String> ids = _getAllChildIds(community.spaces);
for (var id in ids) {
@ -401,8 +435,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
return ids;
}
List<String> _getAllParentsIds(
SpaceModel child, String spaceId, List<String> listIds) {
List<String> _getAllParentsIds(SpaceModel child, String spaceId, List<String> listIds) {
List<String> ids = listIds;
ids.add(child.uuid ?? '');
@ -426,6 +459,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
@override
Future<void> close() async {
_timer.cancel();
super.close();
}
}

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.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/space_model.dart';
@ -11,6 +12,16 @@ class SpaceTreeEvent extends Equatable {
class InitialEvent extends SpaceTreeEvent {}
class PaginationEvent extends SpaceTreeEvent {
final PaginationModel paginationModel;
final List<CommunityModel> communities;
const PaginationEvent(this.paginationModel, this.communities);
@override
List<Object> get props => [paginationModel, communities];
}
class SearchForSpace extends SpaceTreeEvent {
final String searchQuery;
@ -69,6 +80,15 @@ class SearchQueryEvent extends SpaceTreeEvent {
List<Object> get props => [searchQuery];
}
class DebouncedSearchEvent extends SpaceTreeEvent {
final String searchQuery;
const DebouncedSearchEvent(this.searchQuery);
@override
List<Object> get props => [searchQuery];
}
class OnCommunityAdded extends SpaceTreeEvent {
final CommunityModel newCommunity;
const OnCommunityAdded(this.newCommunity);
@ -85,7 +105,6 @@ class OnCommunityUpdated extends SpaceTreeEvent {
List<Object> get props => [updatedCommunity];
}
class ClearAllData extends SpaceTreeEvent {}
class ClearCachedData extends SpaceTreeEvent {}

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.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';
class SpaceTreeState extends Equatable {
@ -12,6 +13,8 @@ class SpaceTreeState extends Equatable {
final List<String> soldCheck;
final bool isSearching;
final String searchQuery;
final PaginationModel paginationModel;
final bool paginationIsLoading;
const SpaceTreeState(
{this.communityList = const [],
@ -23,7 +26,9 @@ class SpaceTreeState extends Equatable {
this.soldCheck = const [],
this.isSearching = false,
this.selectedCommunityAndSpaces = const {},
this.searchQuery = ''});
this.searchQuery = '',
this.paginationModel = const PaginationModel.emptyConstructor(),
this.paginationIsLoading = false});
SpaceTreeState copyWith(
{List<CommunityModel>? communitiesList,
@ -35,7 +40,9 @@ class SpaceTreeState extends Equatable {
List<String>? soldCheck,
bool? isSearching,
Map<String, List<String>>? selectedCommunityAndSpaces,
String? searchQuery}) {
String? searchQuery,
PaginationModel? paginationModel,
bool? paginationIsLoading}) {
return SpaceTreeState(
communityList: communitiesList ?? this.communityList,
filteredCommunity: filteredCommunity ?? this.filteredCommunity,
@ -46,7 +53,9 @@ class SpaceTreeState extends Equatable {
soldCheck: soldCheck ?? this.soldCheck,
isSearching: isSearching ?? this.isSearching,
selectedCommunityAndSpaces: selectedCommunityAndSpaces ?? this.selectedCommunityAndSpaces,
searchQuery: searchQuery ?? this.searchQuery);
searchQuery: searchQuery ?? this.searchQuery,
paginationModel: paginationModel ?? this.paginationModel,
paginationIsLoading: paginationIsLoading ?? this.paginationIsLoading);
}
@override
@ -60,7 +69,9 @@ class SpaceTreeState extends Equatable {
soldCheck,
isSearching,
selectedCommunityAndSpaces,
searchQuery
searchQuery,
paginationModel,
paginationIsLoading
];
}

View File

@ -0,0 +1,20 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
class PaginationModel {
final int pageNum;
final bool hasNext;
final int size;
final List<CommunityModel> communities;
const PaginationModel(
{required this.pageNum,
required this.hasNext,
required this.size,
required this.communities});
const PaginationModel.emptyConstructor()
: pageNum = 1,
hasNext = true,
size = 10,
communities = const [];
}

View File

@ -34,7 +34,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
List<CommunityModel> list = state.isSearching ? state.filteredCommunity : state.communityList;
List<CommunityModel> list =
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
return Container(
height: MediaQuery.sizeOf(context).height,
decoration: widget.isSide == true
@ -61,7 +62,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
borderRadius: const BorderRadius.all(Radius.circular(20)),
border: Border.all(color: ColorsManager.grayBorder)),
child: TextFormField(
style: const TextStyle(color: Colors.black),
style: context.textTheme.bodyMedium
?.copyWith(color: ColorsManager.blackColor),
onChanged: (value) {
context.read<SpaceTreeBloc>().add(SearchQueryEvent(value));
},
@ -94,88 +96,101 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
),
const SizedBox(height: 16),
Expanded(
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: [
Container(
width: MediaQuery.sizeOf(context).width * 0.5,
padding: const EdgeInsets.all(8.0),
child: list.isEmpty
? Center(
child: Text(
'No results found',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w400,
child: state.isSearching
? const Center(child: CircularProgressIndicator())
: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: [
Container(
width: MediaQuery.sizeOf(context).width * 0.5,
padding: const EdgeInsets.all(8.0),
child: list.isEmpty
? Center(
child: Text(
'No results found',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w400,
),
),
),
)
: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: ListView(
controller: _scrollController,
shrinkWrap: true,
children: list
.map(
(community) => CustomExpansionTileSpaceTree(
title: community.name,
isSelected: state.selectedCommunities
.contains(community.uuid),
isSoldCheck: state.selectedCommunities
.contains(community.uuid),
onExpansionChanged: () {
context
.read<SpaceTreeBloc>()
.add(OnCommunityExpanded(community.uuid));
},
isExpanded: state.expandedCommunities
.contains(community.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
community.uuid, community.spaces));
widget.onSelect();
},
children: community.spaces.map((space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded:
state.expandedSpaces.contains(space.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(community, space.uuid ?? '',
space.children));
widget.onSelect();
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
community.uuid, space.uuid ?? ''));
},
isSelected:
state.selectedSpaces.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck: state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces(
context, state, space, community),
);
}).toList(),
),
)
.toList(),
),
),
),
),
],
),
)
: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: NotificationListener(
onNotification: (notification) {
if (notification is ScrollEndNotification &&
notification.metrics.extentAfter == 0) {
// If the user has reached the end of the list Load more data
context.read<SpaceTreeBloc>().add(PaginationEvent(
state.paginationModel, state.communityList));
}
return false;
},
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
controller: _scrollController,
itemBuilder: (context, index) {
return CustomExpansionTileSpaceTree(
title: list[index].name,
isSelected: state.selectedCommunities
.contains(list[index].uuid),
isSoldCheck: state.selectedCommunities
.contains(list[index].uuid),
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(
OnCommunityExpanded(list[index].uuid));
},
isExpanded: state.expandedCommunities
.contains(list[index].uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(list[index].uuid,
list[index].spaces));
widget.onSelect();
},
children: list[index].spaces.map((space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded: state.expandedSpaces
.contains(space.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
list[index],
space.uuid ?? '',
space.children));
widget.onSelect();
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(list[index].uuid,
space.uuid ?? ''));
},
isSelected: state.selectedSpaces
.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck:
state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces(
context, state, space, list[index]),
);
}).toList(),
);
}),
),
),
),
),
],
),
),
if (state.paginationIsLoading) const CircularProgressIndicator(),
// Expanded(
// child: Padding(
// padding: const EdgeInsets.all(8.0),

View File

@ -262,27 +262,6 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
return;
}
if (communities.isEmpty) {
communities = await _api.fetchCommunities(projectUuid);
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces = await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces,
region: community.region,
);
}).toList(),
);
communities = updatedCommunities;
}
emit(BlankState(
spaceModels: prevSpaceModels,
communities: communities,
@ -540,7 +519,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
List<TagModelUpdate> tagUpdates = [];
List<SpaceModel> matchedSpaces =
selectedCommunity.spaces.where((space) => space.uuid == space.uuid).toList();
findMatchingSpaces(selectedCommunity.spaces, space.uuid!);
if (matchedSpaces.isEmpty) continue;
@ -696,27 +675,6 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
var prevSpaceModels = await fetchSpaceModels();
if (communities.isEmpty) {
communities = await _api.fetchCommunities(projectUuid);
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces = await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces,
region: community.region,
);
}).toList(),
);
communities = updatedCommunities;
}
emit(SpaceModelLoaded(
communities: communities,
products: _cachedProducts ?? [],
@ -799,4 +757,18 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
return tagUpdates;
}
List<SpaceModel> findMatchingSpaces(List<SpaceModel> spaces, String targetUuid) {
List<SpaceModel> matched = [];
for (var space in spaces) {
if (space.uuid == targetUuid) {
matched.add(space);
}
matched
.addAll(findMatchingSpaces(space.children, targetUuid)); // Recursively search in children
}
return matched;
}
}

View File

@ -22,11 +22,11 @@ class CommunityModel {
factory CommunityModel.fromJson(Map<String, dynamic> json) {
return CommunityModel(
uuid: json['uuid'],
createdAt: DateTime.parse(json['createdAt']),
updatedAt: DateTime.parse(json['updatedAt']),
name: json['name'],
description: json['description'],
uuid: json['uuid'] ?? '',
createdAt: DateTime.parse(json['createdAt'] ?? ''),
updatedAt: DateTime.parse(json['updatedAt'] ?? ''),
name: json['name'] ?? '',
description: json['description'] ?? '',
region: json['region'] != null ? RegionModel.fromJson(json['region']) : null,
spaces: json['spaces'] != null
? (json['spaces'] as List).map((space) => SpaceModel.fromJson(space)).toList()

View File

@ -301,11 +301,18 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
setState(() {
// Set the first space in the center or use passed position
Offset centerPosition = position ?? ConnectionHelper.getCenterPosition(screenSize);
Offset newPosition;
if (parentIndex != null) {
newPosition =
getBalancedChildPosition(spaces[parentIndex]); // Ensure balanced position
} else {
newPosition = position ?? ConnectionHelper.getCenterPosition(screenSize);
}
SpaceModel newSpace = SpaceModel(
name: name,
icon: icon,
position: centerPosition,
position: newPosition,
isPrivate: false,
children: [],
status: SpaceStatus.newSpace,
@ -425,7 +432,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
Connection(
startSpace: parent,
endSpace: child,
direction: child.incomingConnection?.direction ?? "down",
direction: "down",
),
);
@ -522,6 +529,38 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
);
}
Offset getBalancedChildPosition(SpaceModel parent) {
int totalSiblings = parent.children.length + 1;
double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing
double startX = parent.position.dx - (totalWidth / 2);
Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180);
// Check for overlaps & adjust
while (spaces.any((s) => (s.position - position).distance < 250)) {
position = Offset(position.dx + 250, position.dy);
}
return position;
}
void realignTree() {
void updatePositions(SpaceModel node, double x, double y) {
node.position = Offset(x, y);
int numChildren = node.children.length;
double childStartX = x - ((numChildren - 1) * 250) / 2;
for (int i = 0; i < numChildren; i++) {
updatePositions(node.children[i], childStartX + (i * 250), y + 180);
}
}
if (spaces.isNotEmpty) {
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
}
}
void _onDuplicate(BuildContext parentContext) {
final screenWidth = MediaQuery.of(context).size.width;
@ -604,29 +643,57 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _duplicateSpace(SpaceModel space) {
final Map<SpaceModel, SpaceModel> originalToDuplicate = {};
const double horizontalGap = 200.0;
const double verticalGap = 100.0;
double horizontalGap = 250.0; // Increased spacing
double verticalGap = 180.0; // Adjusted for better visualization
SpaceModel duplicateRecursive(
SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) {
Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy);
print("🟢 Duplicating: ${space.name}");
while (spaces.any((s) =>
(s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) {
newPosition += Offset(horizontalGap, 0);
/// **Find a new position ensuring no overlap**
Offset getBalancedChildPosition(SpaceModel parent) {
int totalSiblings = parent.children.length + 1;
double totalWidth = (totalSiblings - 1) * horizontalGap;
double startX = parent.position.dx - (totalWidth / 2);
Offset position = Offset(
startX + (parent.children.length * horizontalGap), parent.position.dy + verticalGap);
// **Check for overlaps & adjust**
while (spaces.any((s) => (s.position - position).distance < horizontalGap)) {
position = Offset(position.dx + horizontalGap, position.dy);
}
print("🔹 New position for ${parent.name}: (${position.dx}, ${position.dy})");
return position;
}
/// **Realign the entire tree after duplication**
void realignTree() {
void updatePositions(SpaceModel node, double x, double y) {
node.position = Offset(x, y);
print("✅ Adjusted ${node.name} to (${x}, ${y})");
int numChildren = node.children.length;
double childStartX = x - ((numChildren - 1) * horizontalGap) / 2;
for (int i = 0; i < numChildren; i++) {
updatePositions(node.children[i], childStartX + (i * horizontalGap), y + verticalGap);
}
}
if (spaces.isNotEmpty) {
print("🔄 Realigning tree...");
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
}
}
/// **Recursive duplication logic**
SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) {
Offset newPosition = duplicatedParent == null
? Offset(original.position.dx + horizontalGap, original.position.dy)
: getBalancedChildPosition(duplicatedParent);
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final List<SubspaceModel>? duplicatedSubspaces;
final List<Tag>? duplicatedTags;
if (original.spaceModel != null) {
duplicatedTags = [];
duplicatedSubspaces = [];
} else {
duplicatedTags = original.tags;
duplicatedSubspaces = original.subspaces;
}
print(
"🟡 Duplicating ${original.name}${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})");
final duplicated = SpaceModel(
name: duplicatedName,
@ -637,12 +704,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
status: SpaceStatus.newSpace,
parent: duplicatedParent,
spaceModel: original.spaceModel,
subspaces: duplicatedSubspaces,
tags: duplicatedTags,
subspaces: original.subspaces,
tags: original.tags,
);
originalToDuplicate[original] = duplicated;
setState(() {
spaces.add(duplicated);
_updateNodePosition(duplicated, duplicated.position);
@ -651,60 +716,42 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection(
startSpace: duplicatedParent,
endSpace: duplicated,
direction: original.incomingConnection?.direction ?? 'down',
direction: "down",
);
connections.add(newConnection);
duplicated.incomingConnection = newConnection;
duplicatedParent.addOutgoingConnection(newConnection);
duplicatedParent.children.add(duplicated);
print("🔗 Created connection: ${duplicatedParent.name}${duplicated.name}");
}
if (original.parent != null && duplicatedParent == null) {
final originalParent = original.parent!;
final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent;
final parentConnection = Connection(
startSpace: duplicatedParent,
endSpace: duplicated,
direction: original.incomingConnection?.direction ?? "down",
);
connections.add(parentConnection);
duplicated.incomingConnection = parentConnection;
duplicatedParent.addOutgoingConnection(parentConnection);
}
// **Recalculate the whole tree to avoid overlaps**
realignTree();
});
final childrenWithDownDirection = original.children
.where((child) =>
child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted)
.toList();
Offset childStartPosition = childrenWithDownDirection.length == 1
? duplicated.position
: newPosition + Offset(0, verticalGap);
for (final child in original.children) {
final isDownDirection = child.incomingConnection?.direction == "down" ?? false;
if (isDownDirection && childrenWithDownDirection.length == 1) {
childStartPosition = duplicated.position + Offset(0, verticalGap);
} else if (!isDownDirection) {
childStartPosition = duplicated.position + Offset(horizontalGap, 0);
}
final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated);
duplicated.children.add(duplicatedChild);
childStartPosition += Offset(0, verticalGap);
// Recursively duplicate children
for (var child in original.children) {
duplicateRecursive(child, duplicated);
}
return duplicated;
}
/// **Handle root duplication**
if (space.parent == null) {
duplicateRecursive(space, space.position, null);
print("🟠 Duplicating root node: ${space.name}");
SpaceModel duplicatedRoot = duplicateRecursive(space, null);
setState(() {
spaces.add(duplicatedRoot);
realignTree();
});
print("✅ Root duplication successful: ${duplicatedRoot.name}");
} else {
final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!;
duplicateRecursive(space, space.position, duplicatedParent);
duplicateRecursive(space, space.parent);
}
print("🟢 Finished duplication process for: ${space.name}");
}
}

View File

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class SidebarAddCommunityButton extends StatelessWidget {
const SidebarAddCommunityButton({
required this.existingCommunityNames,
super.key,
});
final List<String> existingCommunityNames;
@override
Widget build(BuildContext context) {
return SizedBox.square(
dimension: 30,
child: IconButton(
style: IconButton.styleFrom(
iconSize: 20,
backgroundColor: ColorsManager.circleImageBackground,
shape: const CircleBorder(
side: BorderSide(
color: ColorsManager.lightGrayBorderColor,
width: 3,
),
),
),
onPressed: () => _showCreateCommunityDialog(context),
icon: SvgPicture.asset(Assets.addIcon),
),
);
}
void _showCreateCommunityDialog(BuildContext context) => showDialog<void>(
context: context,
builder: (context) => CreateCommunityDialog(
isEditMode: false,
existingCommunityNames: existingCommunityNames,
onCreateCommunity: (name, description) {
context.read<SpaceManagementBloc>().add(
CreateCommunityEvent(name, description, context),
);
},
),
);
}

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class SidebarHeader extends StatelessWidget {
const SidebarHeader({required this.existingCommunityNames, super.key});
final List<String> existingCommunityNames;
@override
Widget build(BuildContext context) {
return Container(
decoration: subSectionContainerDecoration,
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Communities',
style: context.textTheme.titleMedium?.copyWith(
color: ColorsManager.blackColor,
),
),
SidebarAddCommunityButton(existingCommunityNames: existingCommunityNames),
],
),
);
}
}

View File

@ -1,17 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_header.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
class SidebarWidget extends StatefulWidget {
@ -19,62 +17,59 @@ class SidebarWidget extends StatefulWidget {
final String? selectedSpaceUuid;
const SidebarWidget({
super.key,
required this.communities,
this.selectedSpaceUuid,
super.key,
});
@override
_SidebarWidgetState createState() => _SidebarWidgetState();
State<SidebarWidget> createState() => _SidebarWidgetState();
}
class _SidebarWidgetState extends State<SidebarWidget> {
String _searchQuery = ''; // Track search query
String _searchQuery = '';
String? _selectedSpaceUuid;
String? _selectedId;
@override
void initState() {
_selectedId = widget.selectedSpaceUuid;
super.initState();
_selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID
}
@override
void didUpdateWidget(covariant SidebarWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) {
setState(() {
_selectedId = widget.selectedSpaceUuid;
});
setState(() => _selectedId = widget.selectedSpaceUuid);
}
super.didUpdateWidget(oldWidget);
}
// Function to filter communities based on the search query
List<CommunityModel> _filterCommunities() {
List<CommunityModel> _filteredCommunities() {
if (_searchQuery.isEmpty) {
// Reset the selected community and space UUIDs if there's no query
_selectedSpaceUuid = null;
return widget.communities;
}
// Filter communities and expand only those that match the query
return widget.communities.where((community) {
final containsQueryInCommunity =
community.name.toLowerCase().contains(_searchQuery.toLowerCase());
final containsQueryInSpaces =
community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase()));
final containsQueryInSpaces = community.spaces.any((space) =>
_containsQuery(space: space, query: _searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces;
}).toList();
}
// Helper function to determine if any space or its children match the search query
bool _containsQuery(SpaceModel space, String query) {
bool _containsQuery({
required SpaceModel space,
required String query,
}) {
final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren =
space.children.any((child) => _containsQuery(child, query)); // Recursive check for children
final matchesChildren = space.children.any(
(child) => _containsQuery(space: child, query: query),
);
// If the space or any of its children match the query, expand this space
if (matchesSpace || matchesChildren) {
_selectedSpaceUuid = space.uuid;
}
@ -83,79 +78,35 @@ class _SidebarWidgetState extends State<SidebarWidget> {
}
bool _isSpaceOrChildSelected(SpaceModel space) {
// Return true if the current space or any of its child spaces is selected
if (_selectedSpaceUuid == space.uuid) {
return true;
}
// Recursively check if any child spaces match the query
for (var child in space.children) {
if (_isSpaceOrChildSelected(child)) {
return true;
}
}
return false;
final isSpaceSelected = _selectedSpaceUuid == space.uuid;
final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected);
return isSpaceSelected || anySubSpaceIsSelected;
}
@override
Widget build(BuildContext context) {
final filteredCommunities = _filterCommunities();
final filteredCommunities = _filteredCommunities();
return Container(
width: 300,
decoration: subSectionContainerDecoration,
child: Column(
mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Communities title with the add button
Container(
decoration: subSectionContainerDecoration,
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Communities',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: ColorsManager.blackColor,
)),
GestureDetector(
onTap: () => _navigateToBlank(context),
child: Container(
width: 30,
height: 30,
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
shape: BoxShape.circle,
),
child: Center(
child: SvgPicture.asset(
Assets.roundedAddIcon,
width: 24,
height: 24,
),
),
),
),
],
),
SidebarHeader(
existingCommunityNames:
widget.communities.map((community) => community.name).toList(),
),
// Search bar
CustomSearchBar(
onSearchChanged: (query) {
setState(() {
_searchQuery = query;
});
},
onSearchChanged: (query) => setState(() => _searchQuery = query),
),
const SizedBox(height: 16),
// Community list
Expanded(
child: ListView(
children: filteredCommunities.map((community) {
return _buildCommunityTile(context, community);
}).toList(),
children: filteredCommunities
.map((community) => _buildCommunityTile(context, community))
.toList(),
),
),
],
@ -163,18 +114,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
);
}
void _navigateToBlank(BuildContext context) {
setState(() {
_selectedId = '';
});
context.read<SpaceManagementBloc>().add(
NewCommunityEvent(communities: widget.communities),
);
}
Widget _buildCommunityTile(BuildContext context, CommunityModel community) {
bool hasChildren = community.spaces.isNotEmpty;
return CommunityTile(
title: community.name,
key: ValueKey(community.uuid),
@ -183,7 +123,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
onItemSelected: () {
setState(() {
_selectedId = community.uuid;
_selectedSpaceUuid = null; // Update the selected community
_selectedSpaceUuid = null;
});
context.read<CenterBodyBloc>().add(CommunitySelectedEvent());
@ -192,46 +132,51 @@ class _SidebarWidgetState extends State<SidebarWidget> {
SelectCommunityEvent(selectedCommunity: community),
);
},
onExpansionChanged: (String title, bool expanded) {
_handleExpansionChange(community.uuid, expanded);
},
children: hasChildren
? community.spaces
.where((space) => (space.status != SpaceStatus.deleted ||
space.status != SpaceStatus.parentDeleted))
.map((space) => _buildSpaceTile(space, community))
.toList()
: null,
onExpansionChanged: (title, expanded) {},
children: community.spaces
.where((space) {
final isDeleted = space.status != SpaceStatus.deleted;
final isParentDeleted = space.status != SpaceStatus.parentDeleted;
return (isDeleted || isParentDeleted);
})
.map((space) => _buildSpaceTile(space: space, community: community))
.toList(),
);
}
Widget _buildSpaceTile(SpaceModel space, CommunityModel community, {int depth = 1}) {
bool isExpandedSpace = _isSpaceOrChildSelected(space);
Widget _buildSpaceTile({
required SpaceModel space,
required CommunityModel community,
}) {
final spaceIsExpanded = _isSpaceOrChildSelected(space);
final isSelected = _selectedId == space.uuid;
return Padding(
padding: EdgeInsets.only(left: depth * 16.0),
child: SpaceTile(
title: space.name,
key: ValueKey(space.uuid),
isSelected: _selectedId == space.uuid,
initiallyExpanded: isExpandedSpace,
onExpansionChanged: (bool expanded) {
_handleExpansionChange(space.uuid ?? '', expanded);
},
onItemSelected: () {
setState(() {
_selectedId = space.uuid;
_selectedSpaceUuid = space.uuid;
});
padding: const EdgeInsetsDirectional.only(start: 16.0),
child: SpaceTile(
title: space.name,
key: ValueKey(space.uuid),
isSelected: isSelected,
initiallyExpanded: spaceIsExpanded,
onExpansionChanged: (expanded) {},
onItemSelected: () {
setState(() {
_selectedId = space.uuid;
_selectedSpaceUuid = space.uuid;
});
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(selectedCommunity: community, selectedSpace: space),
);
},
children: space.children.isNotEmpty
? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList()
: [], // Recursively render child spaces if available
));
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(selectedCommunity: community, selectedSpace: space),
);
},
children: space.children
.map(
(childSpace) => _buildSpaceTile(
space: childSpace,
community: community,
),
)
.toList(),
),
);
}
void _handleExpansionChange(String uuid, bool expanded) {}
}

View File

@ -47,18 +47,6 @@ class SpaceCardWidget extends StatelessWidget {
children: [
buildSpaceContainer(index), // Build the space container
if (isHovered) ...[
PlusButtonWidget(
index: index,
direction: 'left',
offset: const Offset(-21, 20),
onButtonTap: onButtonTap,
),
PlusButtonWidget(
index: index,
direction: 'right',
offset: const Offset(140, 20),
onButtonTap: onButtonTap,
),
PlusButtonWidget(
index: index,
direction: 'down',

View File

@ -2,8 +2,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class SpaceHelper {
static SpaceModel? findSpaceByUuid(
String? uuid, List<CommunityModel> communities) {
static SpaceModel? findSpaceByUuid(String? uuid, List<CommunityModel> communities) {
for (var community in communities) {
for (var space in community.spaces) {
if (space.uuid == uuid) return space;
@ -12,8 +11,7 @@ class SpaceHelper {
return null;
}
static SpaceModel? findSpaceByInternalId(
String? internalId, List<SpaceModel> spaces) {
static SpaceModel? findSpaceByInternalId(String? internalId, List<SpaceModel> spaces) {
if (internalId != null) {
for (var space in spaces) {
if (space.internalId == internalId) return space;
@ -23,8 +21,7 @@ class SpaceHelper {
return null;
}
static String generateUniqueSpaceName(
String originalName, List<SpaceModel> spaces) {
static String generateUniqueSpaceName(String originalName, List<SpaceModel> spaces) {
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
int maxNumber = 0;
@ -54,13 +51,10 @@ class SpaceHelper {
return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children
?.any((child) => child.internalId == space.internalId) ==
true;
selectedSpace.children?.any((child) => child.internalId == space.internalId) == true;
}
static bool isNameConflict(
String value, SpaceModel? parentSpace, SpaceModel? editSpace) {
static bool isNameConflict(String value, SpaceModel? parentSpace, SpaceModel? editSpace) {
final siblings = parentSpace?.children
.where((child) => child.internalId != editSpace?.internalId)
.toList() ??
@ -71,19 +65,17 @@ class SpaceHelper {
.toList() ??
[];
final editSiblingConflict =
editSiblings.any((child) => child.name == value);
final editSiblingConflict = editSiblings.any((child) => child.name == value);
final siblingConflict = siblings.any((child) => child.name == value);
final parentConflict = parentSpace?.name == value &&
parentSpace?.internalId != editSpace?.internalId;
final parentConflict =
parentSpace?.name == value && parentSpace?.internalId != editSpace?.internalId;
final parentOfEditSpaceConflict = editSpace?.parent?.name == value &&
editSpace?.parent?.internalId != editSpace?.internalId;
final parentOfEditSpaceConflict =
editSpace?.parent?.name == value && editSpace?.parent?.internalId != editSpace?.internalId;
final childConflict =
editSpace?.children.any((child) => child.name == value) ?? false;
final childConflict = editSpace?.children.any((child) => child.name == value) ?? false;
return siblingConflict ||
parentConflict ||

View File

@ -194,9 +194,10 @@ class VisitorPasswordBloc
emit(DeviceLoaded());
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
data = await AccessMangApi().fetchDevices(projectUuid);
data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid);
emit(TableLoaded(data));
} catch (e) {
print("error: $e");
emit(FailedState(e.toString()));
}
}

View File

@ -6,13 +6,23 @@ import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
class AccessMangApi {
AccessMangApi() {
_validateEndpoints();
}
void _validateEndpoints() {
if (!ApiEndpoints.getDevices.contains('{projectId}')) {
throw Exception("Endpoint 'getDevices' must contain '{projectId}' placeholder.");
}
}
Future<List<PasswordModel>> fetchVisitorPassword(String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.visitorPassword.replaceAll('{projectId}', projectId),
path: ApiEndpoints.visitorPassword,
showServerMessage: true,
expectedResponseModel: (json) {
List<dynamic> jsonData = json;
List<dynamic> jsonData = json['data'] ?? [];
List<PasswordModel> passwordList = jsonData.map((jsonItem) {
return PasswordModel.fromJson(jsonItem);
}).toList();
@ -25,17 +35,22 @@ class AccessMangApi {
}
}
Future fetchDevices(String projectId) async {
Future fetchDoorLockDeviceList(String projectId) async {
try {
// The endpoint structure is already validated during initialization.
final response = await HTTPService().get(
path: ApiEndpoints.getDevices.replaceAll('{projectId}', projectId),
queryParameters: {
'deviceType': 'DOOR_LOCK',
},
showServerMessage: true,
expectedResponseModel: (json) {
List<dynamic> jsonData = json;
List<DeviceModel> passwordList = jsonData.map((jsonItem) {
List<dynamic> jsonData = json['data'] ?? [];
List<DeviceModel> deviceList = jsonData.map((jsonItem) {
return DeviceModel.fromJson(jsonItem);
}).toList();
return passwordList;
return deviceList;
},
);
return response;
@ -52,14 +67,15 @@ class AccessMangApi {
String? invalidTime,
List<String>? devicesUuid}) async {
final response = await HTTPService().post(
path: ApiEndpoints.sendOnlineOneTime,
path: ApiEndpoints.visitorPassword,
body: jsonEncode({
"email": email,
"passwordName": passwordName,
"password": password,
"devicesUuid": devicesUuid,
"effectiveTime": effectiveTime,
"invalidTime": invalidTime
"invalidTime": invalidTime,
"operationType": "ONLINE_ONE_TIME",
}),
showServerMessage: true,
expectedResponseModel: (json) {
@ -84,13 +100,13 @@ class AccessMangApi {
"password": password,
"effectiveTime": effectiveTime,
"invalidTime": invalidTime,
"operationType": "ONLINE_MULTIPLE_TIME",
};
if (scheduleList != null) {
body["scheduleList"] =
scheduleList.map((schedule) => schedule.toJson()).toList();
body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList();
}
final response = await HTTPService().post(
path: ApiEndpoints.sendOnlineMultipleTime,
path: ApiEndpoints.visitorPassword,
body: jsonEncode(body),
showServerMessage: true,
expectedResponseModel: (json) {
@ -105,8 +121,9 @@ class AccessMangApi {
Future postOffLineOneTime(
{String? email, String? passwordName, List<String>? devicesUuid}) async {
final response = await HTTPService().post(
path: ApiEndpoints.sendOffLineOneTime,
path: ApiEndpoints.visitorPassword,
body: jsonEncode({
"operationType": "OFFLINE_ONE_TIME",
"email": email,
"passwordName": passwordName,
"devicesUuid": devicesUuid
@ -126,13 +143,14 @@ class AccessMangApi {
String? invalidTime,
List<String>? devicesUuid}) async {
final response = await HTTPService().post(
path: ApiEndpoints.sendOffLineMultipleTime,
path: ApiEndpoints.visitorPassword,
body: jsonEncode({
"email": email,
"devicesUuid": devicesUuid,
"passwordName": passwordName,
"effectiveTime": effectiveTime,
"invalidTime": invalidTime,
"operationType": "OFFLINE_MULTIPLE_TIME",
}),
showServerMessage: true,
expectedResponseModel: (json) {

View File

@ -23,9 +23,7 @@ class DevicesManagementApi {
: ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId),
showServerMessage: true,
expectedResponseModel: (json) {
List<dynamic> jsonData = communityId.isNotEmpty && spaceId.isNotEmpty
? json['data']
: json;
List<dynamic> jsonData = json['data'];
List<AllDevicesModel> devicesList = jsonData.map((jsonItem) {
return AllDevicesModel.fromJson(jsonItem);
}).toList();
@ -34,7 +32,7 @@ class DevicesManagementApi {
);
return response;
} catch (e) {
debugPrint('fetchDevices Error fetching $e');
debugPrint('Error fetching device $e');
return [];
}
}
@ -45,7 +43,7 @@ class DevicesManagementApi {
path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', uuid),
showServerMessage: true,
expectedResponseModel: (json) {
return DeviceStatus.fromJson(json);
return DeviceStatus.fromJson(json['data']);
},
);
return response;
@ -62,7 +60,7 @@ class DevicesManagementApi {
Future getPowerClampInfo(String deviceId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.powerClamp.replaceAll('{powerClampUuid}', deviceId),
path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', deviceId),
showServerMessage: true,
expectedResponseModel: (json) {
return json;
@ -93,13 +91,13 @@ 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,
'code': code,
'value': value,
'operationType': 'COMMAND',
};
final response = await HTTPService().post(
@ -107,7 +105,7 @@ class DevicesManagementApi {
body: body,
showServerMessage: true,
expectedResponseModel: (json) {
return (json['successResults'] as List).isNotEmpty;
return json['success'] ?? false;
},
);
@ -118,8 +116,7 @@ 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,
@ -128,7 +125,7 @@ class DevicesManagementApi {
if (json == null || json.isEmpty || json == []) {
return devices;
}
for (var device in json['devices']) {
for (var device in json['data']['devices']) {
devices.add(DeviceModel.fromJson(device));
}
return devices;
@ -153,12 +150,10 @@ 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);
return DeviceReport.fromJson(json['data']);
},
);
return response;
@ -174,7 +169,7 @@ class DevicesManagementApi {
.replaceAll('{endTime}', to ?? ''),
showServerMessage: false,
expectedResponseModel: (json) {
return DeviceReport.fromJson(json);
return DeviceReport.fromJson(json['data']);
},
);
return response;
@ -190,7 +185,7 @@ class DevicesManagementApi {
queryParameters: queryParameters,
showServerMessage: true,
expectedResponseModel: (json) {
return DeviceStatus.fromJson(json['status']);
return DeviceStatus.fromJson(json['data']['status']);
},
);
return response;
@ -228,8 +223,7 @@ 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),
@ -246,8 +240,7 @@ 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
@ -270,9 +263,7 @@ 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
@ -293,8 +284,7 @@ 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),

View File

@ -1,29 +1,28 @@
import 'package:flutter/material.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';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
import 'package:syncrow_web/utils/constants/temp_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),
queryParameters: {'page': page},
path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
queryParameters: {
'page': page,
},
expectedResponseModel: (json) {
try {
List<dynamic> jsonData = json['data'] ?? [];
@ -49,11 +48,42 @@ class CommunitySpaceManagementApi {
}
}
Future<PaginationModel> fetchCommunitiesAndSpaces(
{required String projectId, int page = 1, String search = ''}) async {
PaginationModel paginationModel = const PaginationModel.emptyConstructor();
try {
bool hasNext = false;
await HTTPService().get(
path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
queryParameters: {'page': page, 'includeSpaces': true, 'size': 25, 'search': search},
expectedResponseModel: (json) {
try {
List<dynamic> jsonData = json['data'] ?? [];
hasNext = json['hasNext'] ?? false;
int currentPage = json['page'] ?? 1;
List<CommunityModel> communityList = jsonData.map((jsonItem) {
return CommunityModel.fromJson(jsonItem);
}).toList();
page = currentPage + 1;
paginationModel = PaginationModel(
pageNum: page, hasNext: hasNext, size: 25, communities: communityList);
return paginationModel;
} catch (_) {
hasNext = false;
return [];
}
},
);
} catch (_) {}
return paginationModel;
}
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']);
},
@ -65,8 +95,7 @@ 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),
@ -85,8 +114,7 @@ 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
@ -123,8 +151,7 @@ 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
@ -150,8 +177,7 @@ 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
@ -262,8 +288,7 @@ 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
@ -281,17 +306,15 @@ 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;
},
@ -302,17 +325,16 @@ class CommunitySpaceManagementApi {
return [];
}
}
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;
},
);

View File

@ -9,21 +9,8 @@ abstract class ApiEndpoints {
static const String sendOtp = '/authentication/user/send-otp';
static const String verifyOtp = '/authentication/user/verify-otp';
static const String getRegion = '/region';
static const String visitorPassword =
'/projects/{projectId}/visitor-password';
static const String getDevices =
'/projects/{projectId}/visitor-password/devices';
static const String sendOnlineOneTime =
'/visitor-password/temporary-password/online/one-time';
static const String sendOnlineMultipleTime =
'/visitor-password/temporary-password/online/multiple-time';
//offline Password
static const String sendOffLineOneTime =
'/visitor-password/temporary-password/offline/one-time';
static const String sendOffLineMultipleTime =
'/visitor-password/temporary-password/offline/multiple-time';
static const String visitorPassword = '/visitor-passwords';
static const String getDevices = '/projects/{projectId}/devices';
static const String getUser = '/user/{userUuid}';
@ -32,15 +19,15 @@ abstract class ApiEndpoints {
static const String getAllDevices = '/projects/{projectId}/devices';
static const String getSpaceDevices =
'/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices';
static const String getDeviceStatus = '/device/{uuid}/functions/status';
static const String getBatchStatus = '/device/status/batch';
static const String getDeviceStatus = '/devices/{uuid}/functions/status';
static const String getBatchStatus = '/devices/batch';
static const String deviceControl = '/device/{uuid}/control';
static const String deviceBatchControl = '/device/control/batch';
static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices';
static const String deviceControl = '/devices/{uuid}/command';
static const String deviceBatchControl = '/devices/batch';
static const String gatewayApi = '/devices/gateway/{gatewayUuid}/devices';
static const String openDoorLock = '/door-lock/open/{doorLockUuid}';
static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}';
static const String getDeviceLogs = '/devices/{uuid}/report-logs?code={code}';
// Space Module
static const String createSpace =
@ -70,18 +57,13 @@ abstract class ApiEndpoints {
static const String createUserCommunity =
'/projects/{projectId}/communities/user';
static const String getDeviceLogsByDate =
'/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}';
'/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 factoryReset = '/device/factory/reset/{deviceUuid}';
static const String powerClamp =
'/device/{powerClampUuid}/power-clamp/status';
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
static const String listProducts = '/products';

View File

@ -408,7 +408,26 @@ class Assets {
static const String spaceLinkIcon = 'assets/icons/space_link_icon.svg';
static const String successIcon = 'assets/icons/success_icon.svg';
static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.svg';
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png';
static const String scenesPlayIconCheck =
'assets/icons/scenesPlayIconCheck.svg';
'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 farDetectionIcon = 'assets/icons/far_detection_icon.svg';
static const String motionDetectionSensitivityIcon =
'assets/icons/motion_detection_sensitivity_icon.svg';
static const String motionlessDetectionSensitivityIcon =
'assets/icons/motionless_detection_sensitivity_icon.svg';
static const String IndicatorIcon = 'assets/icons/Indicator_icon.svg';
static const String motionDetectionSensitivityValueIcon = 'assets/icons/motion_detection_sensitivity_value_icon.svg';
static const String presenceTimeIcon = 'assets/icons/presence_time_icon.svg';
static const String IlluminanceIcon = 'assets/icons/Illuminance_icon.svg';
static const String gear = 'assets/icons/gear.svg';
static const String activeBell='assets/icons/active_bell.svg';
}