mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 23:27:25 +00:00
Compare commits
147 Commits
Fix-Factor
...
bugfix/add
Author | SHA1 | Date | |
---|---|---|---|
3c7edae88a | |||
56c2d11535 | |||
3aa5bff758 | |||
28d1e5a5a7 | |||
fe036a8190 | |||
82e145de9d | |||
ebeb514a5b | |||
6b7e02ee53 | |||
b01136b6e9 | |||
fe1dbb66ac | |||
ea88f54d20 | |||
ccce7bb671 | |||
b4de07de2f | |||
acefe6b433 | |||
63bc7a56de | |||
7b3635deae | |||
58755eafe1 | |||
ce225818fb | |||
8762a7aaa8 | |||
8dc833b2c3 | |||
13cef151aa | |||
ab23be9828 | |||
687b68ab22 | |||
25614c3dd0 | |||
7cbe20ae88 | |||
349fe6c555 | |||
9779f3783c | |||
fe3db663b6 | |||
888d444752 | |||
bab5e06968 | |||
d7b6174dee | |||
6ef0b2f9d1 | |||
3ceb03826e | |||
52608b1f35 | |||
ac2996629e | |||
51c52c66cb | |||
0f56273d99 | |||
34d4d892d9 | |||
3193fd26fe | |||
43802a9fad | |||
6e0b1775f0 | |||
233fb2ee2c | |||
b26928b3d5 | |||
6fc35a7b9a | |||
756457927c | |||
f30d7c0117 | |||
976d6e385a | |||
ff07e7509d | |||
17a582ab99 | |||
09fb604acc | |||
2068df173d | |||
bfc2a381d2 | |||
778257644d | |||
c8e540e938 | |||
ba20998067 | |||
75b0b24543 | |||
c03b8f290c | |||
2c684a9495 | |||
fbc45b465f | |||
c9c939c59f | |||
e1a2465130 | |||
86164e746a | |||
4a5176cf22 | |||
2bb7a6950a | |||
46860619a0 | |||
d1bb7b129f | |||
7adce3b94c | |||
91f93d4395 | |||
42d6b64e58 | |||
b11d4186fb | |||
24130be665 | |||
8c960bd5f1 | |||
fb8ccdf0a6 | |||
1975a1b6f4 | |||
367d6717e7 | |||
8c637e40ff | |||
05b3180510 | |||
177a4711fe | |||
d7a37c6519 | |||
b901791079 | |||
5d16555748 | |||
bd15f30b8a | |||
5f2684bdf4 | |||
b4ef22ef0a | |||
d8afb562eb | |||
67a164e6d2 | |||
cf20bdcd42 | |||
32b45ea5d7 | |||
62fb8b3097 | |||
065bd33511 | |||
b9ab782c01 | |||
4d5adf948c | |||
e45f57ca03 | |||
84264391d9 | |||
2e4f904d3a | |||
c46cfb48a8 | |||
a0dd128557 | |||
34fa426163 | |||
1407c173b0 | |||
9431eb79c1 | |||
70f1f39fce | |||
f912b41fd8 | |||
977875f1f2 | |||
8136804694 | |||
ce253b2034 | |||
024fbcdb83 | |||
a8430a7d3d | |||
7ef6020dd8 | |||
2a77483f46 | |||
a6fc99443b | |||
ae95d06482 | |||
18c886753d | |||
62bf4f2944 | |||
726c173a76 | |||
d538b3667e | |||
01d5cb48cc | |||
3216d6b879 | |||
52e1ff94de | |||
0cc867a4ea | |||
3de7606a00 | |||
f709b92e12 | |||
f1667d4458 | |||
b4f03ab6c3 | |||
4c38c50649 | |||
8b441aaf46 | |||
afdd44e098 | |||
fc1d394509 | |||
dce44e20ec | |||
91c4c772b5 | |||
9044645f95 | |||
7699453e6d | |||
d1a21be983 | |||
db8e5a4aa6 | |||
fa5bb350c3 | |||
920827d763 | |||
d3902d622e | |||
a4432656ab | |||
90e0d2f52b | |||
08e5e17910 | |||
f57348e5cd | |||
be168aed93 | |||
a66784473f | |||
c0a963ded5 | |||
7945cefe53 | |||
7d0e50fb1d | |||
117f6190dd | |||
748c67fd8b |
@ -31,7 +31,7 @@ jobs:
|
||||
run: flutter pub get
|
||||
|
||||
- name: Build Flutter Web App
|
||||
run: flutter build web --release --dart-define=FLAVOR=production
|
||||
run: flutter build web --web-renderer canvaskit -t lib/main.dart
|
||||
|
||||
- name: Build And Deploy
|
||||
id: builddeploy
|
||||
|
@ -31,7 +31,7 @@ jobs:
|
||||
run: flutter pub get
|
||||
|
||||
- name: Build Flutter Web App
|
||||
run: flutter build web --release --dart-define=FLAVOR=development
|
||||
run: flutter build web --web-renderer canvaskit -t lib/main_dev.dart
|
||||
|
||||
- name: Build And Deploy
|
||||
id: builddeploy
|
||||
|
33
.vscode/launch.json
vendored
33
.vscode/launch.json
vendored
@ -10,11 +10,12 @@
|
||||
"type": "dart",
|
||||
|
||||
"args": [
|
||||
|
||||
"--dart-define",
|
||||
|
||||
"FLAVOR=development"
|
||||
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main_dev.dart",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
@ -28,11 +29,12 @@
|
||||
"type": "dart",
|
||||
|
||||
"args": [
|
||||
|
||||
"--dart-define",
|
||||
|
||||
"FLAVOR=staging"
|
||||
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main_staging.dart",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
@ -46,11 +48,12 @@
|
||||
"type": "dart",
|
||||
|
||||
"args": [
|
||||
|
||||
"--dart-define",
|
||||
|
||||
"FLAVOR=production"
|
||||
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main.dart",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
|
@ -16,7 +16,12 @@ samples, guidance on mobile development, and a full API reference.
|
||||
|
||||
## USEFUL COMMANDS
|
||||
|
||||
Run on chrome: flutter run -d chrome --dart-define=FLAVOR='ENV_NAME'
|
||||
- Building for the Web
|
||||
- CanvasKit
|
||||
- `flutter build web --web-renderer canvaskit -t lib/main_dev.dart --output=build/web_dev` - build for DEVELOPMENT.
|
||||
- `flutter build web --web-renderer canvaskit -t lib/main_staging.dart --output=build/web_stg` - build for STAGING.
|
||||
- `flutter build web --web-renderer canvaskit -t lib/main.dart --output=build/web` - build for PRODUCTION.
|
||||
|
||||
- run command: `flutter run -d chrome --target=lib/main_dev.dart`
|
||||
|
||||
Build: flutter build web --release --dart-define=FLAVOR='ENV_NAME'
|
||||
|
||||
|
17
assets/icons/disappe_delay_icon.svg
Normal file
17
assets/icons/disappe_delay_icon.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_8194_10048)">
|
||||
<path d="M11.4732 16.7316C8.31096 16.7316 5.73828 14.1589 5.73828 10.9967C5.73828 7.8344 8.31096 5.26172 11.4732 5.26172C14.6355 5.26172 17.2081 7.8344 17.2081 10.9967C17.2081 14.1589 14.6355 16.7316 11.4732 16.7316Z" fill="#EBF6FF"/>
|
||||
<path d="M17.2076 10.9967C17.2076 7.8344 14.6349 5.26172 11.4727 5.26172V16.7316C14.6349 16.7316 17.2076 14.1589 17.2076 10.9967Z" fill="#D7E7F8"/>
|
||||
<path d="M11.4721 17.3699C7.95827 17.3699 5.09961 14.5112 5.09961 10.9974C5.09961 7.48362 7.95827 4.625 11.472 4.625C14.9858 4.625 17.8445 7.48366 17.8445 10.9974C17.8445 14.5112 14.9858 17.3699 11.4721 17.3699ZM11.4721 5.89473C8.6584 5.89473 6.36934 8.18379 6.36934 10.9974C6.36934 13.811 8.6584 16.1001 11.472 16.1001C14.2857 16.1001 16.5748 13.8111 16.5748 10.9974C16.5748 8.18375 14.2857 5.89473 11.4721 5.89473Z" fill="#9FE066"/>
|
||||
<path d="M11.4728 11.6318H8.28525C7.93464 11.6318 7.65039 11.3475 7.65039 10.9969C7.65039 10.6463 7.93464 10.3621 8.28525 10.3621H10.8379V8.95127C10.8379 8.60066 11.1222 8.31641 11.4728 8.31641C11.8234 8.31641 12.1076 8.60066 12.1076 8.95127V10.9969C12.1077 11.3475 11.8234 11.6318 11.4728 11.6318Z" fill="#394949"/>
|
||||
<path d="M12.1075 10.9969V8.95127C12.1075 8.60066 11.8233 8.31641 11.4727 8.31641V11.6318C11.8233 11.6318 12.1075 11.3475 12.1075 10.9969Z" fill="#151F1F"/>
|
||||
<path d="M16.5753 10.9974C16.5753 13.8111 14.2863 16.1001 11.4727 16.1001V17.3698C14.9865 17.3698 17.8451 14.5112 17.8451 10.9974C17.8451 7.48366 14.9864 4.625 11.4727 4.625V5.89473C14.2863 5.89473 16.5753 8.18379 16.5753 10.9974Z" fill="#4ACA7B"/>
|
||||
<path d="M18.6832 3.78752C16.7572 1.86147 14.1963 0.800781 11.4725 0.800781C8.74869 0.800781 6.18782 1.86147 4.26177 3.78752C2.67775 5.37158 1.67966 7.38516 1.37492 9.56545L1.08378 9.2743C0.835882 9.02641 0.433887 9.02641 0.185951 9.2743C-0.0619838 9.52219 -0.0619838 9.92419 0.185951 10.1721L1.46097 11.4471C1.58494 11.5711 1.74742 11.6331 1.90986 11.6331C2.0723 11.6331 2.23482 11.5711 2.35875 11.4471L3.63377 10.1721C3.8817 9.92423 3.8817 9.52224 3.63377 9.2743C3.38587 9.02641 2.98388 9.02641 2.73594 9.2743L2.70678 9.30346C3.50083 5.18845 7.12907 2.07051 11.4725 2.07051C16.3953 2.07051 20.4003 6.07548 20.4003 10.9983C20.4003 15.921 16.3953 19.926 11.4725 19.926C7.46868 19.926 3.9294 17.234 2.86558 13.3794C2.77234 13.0413 2.42266 12.8429 2.08474 12.9363C1.74674 13.0295 1.54837 13.3792 1.64165 13.7171C2.22378 15.8264 3.50315 17.7286 5.24408 19.0732C7.04184 20.4618 9.19555 21.1957 11.4725 21.1957C14.1963 21.1957 16.7571 20.135 18.6832 18.209C20.6093 16.2829 21.67 13.7221 21.67 10.9983C21.67 8.27439 20.6093 5.71361 18.6832 3.78752Z" fill="#FF4D5B"/>
|
||||
<path d="M20.4004 10.9983C20.4004 15.9211 16.3955 19.926 11.4727 19.926V21.1958C14.1965 21.1958 16.7573 20.135 18.6834 18.209C20.6095 16.2829 21.6702 13.7221 21.6702 10.9983C21.6702 8.27439 20.6094 5.71361 18.6834 3.78752C16.7573 1.86147 14.1965 0.800781 11.4727 0.800781V2.07051C16.3955 2.07051 20.4004 6.07548 20.4004 10.9983Z" fill="#DE0062"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_8194_10048">
|
||||
<rect width="21.67" height="21.67" fill="white" transform="translate(0 0.164062)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
13
assets/icons/indent_level_icon.svg
Normal file
13
assets/icons/indent_level_icon.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_8194_10065)">
|
||||
<path d="M17.6069 21.8341H4.06313C1.81893 21.8341 0 20.0151 0 17.7709V4.22719C0 1.98299 1.81893 0.164062 4.06313 0.164062H17.6069C19.8511 0.164062 21.67 1.98299 21.67 4.22719V17.7709C21.67 20.0151 19.8511 21.8341 17.6069 21.8341Z" fill="#E3F8FA"/>
|
||||
<path d="M15.1312 7.18375L10.955 5.60387C10.8771 5.57475 10.7925 5.57475 10.7153 5.60387L6.53904 7.18375C6.40767 7.23386 6.32031 7.35982 6.32031 7.50067V9.30605C6.32031 9.41711 6.37516 9.5214 6.46591 9.58438C6.55868 9.64736 6.67516 9.6609 6.77877 9.6223L10.8351 8.08779L14.8915 9.62298C14.9301 9.6372 14.9707 9.64465 15.0113 9.64465C15.0791 9.64465 15.1461 9.62433 15.2043 9.58438C15.2958 9.5214 15.3499 9.41711 15.3499 9.30605V7.5C15.3499 7.35914 15.2626 7.23318 15.1312 7.18375Z" fill="#8CE1EB"/>
|
||||
<path d="M15.1312 13.9572L10.955 12.3773C10.8771 12.3482 10.7925 12.3482 10.7153 12.3773L6.53904 13.9572C6.40767 14.0073 6.32031 14.1333 6.32031 14.2741V16.0795C6.32031 16.1906 6.37516 16.2948 6.46591 16.3578C6.55868 16.4215 6.67516 16.435 6.77877 16.3957L10.8351 14.8612L14.8915 16.3964C14.9301 16.4106 14.9707 16.4181 15.0113 16.4181C15.0791 16.4181 15.1461 16.3978 15.2043 16.3578C15.2958 16.2948 15.3499 16.1906 15.3499 16.0795V14.2734C15.3499 14.1326 15.2626 14.0066 15.1312 13.9572Z" fill="#26C6DA"/>
|
||||
<path d="M15.1312 10.5705L10.955 8.99059C10.8771 8.96147 10.7925 8.96147 10.7153 8.99059L6.53904 10.5705C6.40767 10.6206 6.32031 10.7465 6.32031 10.8874V12.6928C6.32031 12.8038 6.37516 12.9081 6.46591 12.9711C6.55868 13.0348 6.67516 13.0483 6.77877 13.009L10.8351 11.4745L14.8915 13.0097C14.9301 13.0239 14.9707 13.0314 15.0113 13.0314C15.0791 13.0314 15.1461 13.0111 15.2043 12.9711C15.2958 12.9081 15.3499 12.8038 15.3499 12.6928V10.8867C15.3499 10.7459 15.2626 10.6199 15.1312 10.5705Z" fill="#26C6DA"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_8194_10065">
|
||||
<rect width="21.67" height="21.67" fill="white" transform="translate(0 0.164062)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
21
assets/icons/target_confirm_time_icon.svg
Normal file
21
assets/icons/target_confirm_time_icon.svg
Normal 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="M20.7802 9.0399L20.0667 9.18046C19.7057 9.25157 19.4663 9.5894 19.5103 9.95466C19.6254 10.9106 19.5871 11.9137 19.3549 12.9433C18.5334 16.5849 15.4143 19.3394 11.6982 19.6957C6.22309 20.2206 1.65927 15.6884 2.13085 10.223C2.48684 6.09727 5.78343 2.73681 9.90242 2.3098C12.864 2.00274 15.5631 3.17762 17.3531 5.18175L18.396 4.25096C16.4588 2.08197 13.6036 0.750365 10.4397 0.87082C5.29437 1.06665 1.04625 5.19944 0.720226 10.3382C0.34608 16.2355 5.01846 21.1352 10.8352 21.1352C16.4331 21.1352 20.9712 16.5971 20.9712 10.9992C20.9712 10.3288 20.9044 9.674 20.7802 9.0399Z" fill="#FFF0D2"/>
|
||||
<path d="M10.8355 12.7451C11.8006 12.7451 12.583 11.9627 12.583 10.9976C12.583 10.0324 11.8006 9.25 10.8355 9.25C9.8703 9.25 9.08789 10.0324 9.08789 10.9976C9.08789 11.9627 9.8703 12.7451 10.8355 12.7451Z" fill="#FFF0D2"/>
|
||||
<path d="M10.8358 16.5911C10.6426 16.5911 10.4863 16.4346 10.4863 16.2416V10.9989C10.4863 10.9062 10.5232 10.8173 10.5887 10.7517L14.4334 6.90709C14.5699 6.77055 14.7911 6.77055 14.9276 6.90709C15.0642 7.04363 15.0642 7.26482 14.9276 7.40131L11.1854 11.1436V16.2416C11.1854 16.4347 11.029 16.5911 10.8358 16.5911Z" fill="#7A4646"/>
|
||||
<path d="M10.8356 12.0463C11.4147 12.0463 11.8842 11.5769 11.8842 10.9978C11.8842 10.4187 11.4147 9.94922 10.8356 9.94922C10.2566 9.94922 9.78711 10.4187 9.78711 10.9978C9.78711 11.5769 10.2566 12.0463 10.8356 12.0463Z" fill="#AA6E4D"/>
|
||||
<path d="M10.8358 4.35899C10.6426 4.35899 10.4863 4.20248 10.4863 4.00948V3.31045C10.4863 3.11745 10.6427 2.96094 10.8358 2.96094C11.0291 2.96094 11.1854 3.11745 11.1854 3.31045V4.00948C11.1854 4.20248 11.029 4.35899 10.8358 4.35899Z" fill="#DE966C"/>
|
||||
<path d="M10.8358 19.0387C10.6426 19.0387 10.4863 18.8822 10.4863 18.6892V17.9901C10.4863 17.7971 10.6427 17.6406 10.8358 17.6406C11.0291 17.6406 11.1854 17.7971 11.1854 17.9901V18.6892C11.1854 18.8822 11.029 19.0387 10.8358 19.0387Z" fill="#DE966C"/>
|
||||
<path d="M18.5251 11.3475H17.8261C17.6329 11.3475 17.4766 11.1909 17.4766 10.998C17.4766 10.805 17.6329 10.6484 17.8261 10.6484H18.5251C18.7183 10.6484 18.8746 10.805 18.8746 10.998C18.8746 11.1909 18.7183 11.3475 18.5251 11.3475Z" fill="#DE966C"/>
|
||||
<path d="M3.84546 11.3475H3.14639C2.95318 11.3475 2.79688 11.1909 2.79688 10.998C2.79688 10.805 2.95322 10.6484 3.14639 10.6484H3.84541C4.03863 10.6484 4.19493 10.805 4.19493 10.998C4.19493 11.1909 4.03863 11.3475 3.84546 11.3475Z" fill="#DE966C"/>
|
||||
<path d="M4.78099 7.85338C4.72161 7.85338 4.66151 7.83836 4.60657 7.80661L4.00108 7.4571C3.83382 7.36051 3.77651 7.14682 3.87309 6.9796C3.96968 6.81233 4.18371 6.75502 4.35059 6.85161L4.95608 7.20112C5.12335 7.29771 5.18066 7.5114 5.08407 7.67862C5.01923 7.79074 4.90178 7.85338 4.78099 7.85338Z" fill="#F7B97E"/>
|
||||
<path d="M17.4939 15.1933C17.4345 15.1933 17.3744 15.1782 17.3195 15.1465L16.714 14.797C16.5467 14.7004 16.4894 14.4867 16.586 14.3195C16.6826 14.152 16.8966 14.0949 17.0635 14.1915L17.669 14.541C17.8362 14.6376 17.8935 14.8513 17.797 15.0185C17.7321 15.1306 17.6147 15.1933 17.4939 15.1933Z" fill="#F7B97E"/>
|
||||
<path d="M14.3298 5.29299C14.2704 5.29299 14.2103 5.27796 14.1554 5.24622C13.9881 5.14964 13.9308 4.93594 14.0274 4.76872L14.3769 4.16323C14.4735 3.99579 14.6875 3.93866 14.8544 4.03524C15.0217 4.13182 15.079 4.34552 14.9824 4.51274L14.6329 5.11823C14.568 5.23035 14.4506 5.29299 14.3298 5.29299Z" fill="#F7B97E"/>
|
||||
<path d="M6.98995 18.0077C6.93057 18.0077 6.87047 17.9927 6.81553 17.961C6.64827 17.8644 6.59096 17.6507 6.68755 17.4834L7.03706 16.878C7.13331 16.7107 7.34768 16.6536 7.51456 16.75C7.68183 16.8466 7.73913 17.0602 7.64255 17.2275L7.29304 17.833C7.2282 17.9451 7.11079 18.0077 6.98995 18.0077Z" fill="#F7B97E"/>
|
||||
<path d="M16.8891 7.85337C16.7682 7.85337 16.6508 7.79073 16.586 7.67861C16.4894 7.51135 16.5467 7.2977 16.714 7.20111L17.3195 6.8516C17.486 6.75502 17.7004 6.81236 17.797 6.97959C17.8935 7.14685 17.8362 7.36051 17.669 7.45709L17.0635 7.8066C17.0085 7.83835 16.9485 7.85337 16.8891 7.85337Z" fill="#F7B97E"/>
|
||||
<path d="M4.17617 15.1933C4.05533 15.1933 3.93792 15.1306 3.87308 15.0185C3.7765 14.8512 3.83385 14.6376 4.00107 14.541L4.60656 14.1915C4.77277 14.0949 4.98714 14.1521 5.08406 14.3195C5.18065 14.4867 5.1233 14.7004 4.95608 14.797L4.35059 15.1465C4.29565 15.1782 4.23555 15.1933 4.17617 15.1933Z" fill="#F7B97E"/>
|
||||
<path d="M7.34013 5.29298C7.2193 5.29298 7.10189 5.23034 7.03705 5.11822L6.68754 4.51273C6.59095 4.34547 6.6483 4.13181 6.81553 4.03523C6.98207 3.93865 7.1961 3.99583 7.29303 4.16322L7.64254 4.76871C7.73912 4.93597 7.68178 5.14963 7.51455 5.24621C7.45962 5.27795 7.39956 5.29298 7.34013 5.29298Z" fill="#F7B97E"/>
|
||||
<path d="M14.68 18.0077C14.5591 18.0077 14.4417 17.9451 14.3769 17.833L14.0274 17.2275C13.9308 17.0602 13.9881 16.8465 14.1554 16.75C14.3219 16.6535 14.5363 16.7107 14.6329 16.8779L14.9824 17.4834C15.079 17.6507 15.0216 17.8644 14.8544 17.9609C14.7994 17.9927 14.7394 18.0077 14.68 18.0077Z" fill="#F7B97E"/>
|
||||
<path d="M10.8358 11.3475C11.0289 11.3475 11.1854 11.191 11.1854 10.998C11.1854 10.8049 11.0289 10.6484 10.8358 10.6484C10.6428 10.6484 10.4863 10.8049 10.4863 10.998C10.4863 11.191 10.6428 11.3475 10.8358 11.3475Z" fill="#7A4646"/>
|
||||
<path d="M10.8356 1.56212C5.42443 1.56212 1.07697 6.11655 1.41743 11.6012C1.70913 16.3007 5.53397 20.1256 10.2335 20.4173C15.7181 20.7578 20.2726 16.4103 20.2726 10.9991C20.2726 10.2541 20.1854 9.5297 20.0217 8.83474C19.9829 8.66988 20.0623 8.5019 20.2172 8.43346C20.4105 8.34805 20.6773 8.24402 20.8922 8.16267C21.0866 8.08907 21.3059 8.19949 21.3557 8.40125C21.6326 9.52411 21.7356 10.7154 21.6299 11.9463C21.4062 14.551 20.2433 16.8986 18.4888 18.652C18.4888 18.652 14.9619 21.4157 11.4687 21.4157C4.94621 21.4157 0.410156 16.7271 0.410156 10.1916C0.410156 7.40625 3.18409 3.34816 3.18409 3.34816C5.04237 1.49153 7.56422 0.300058 10.3375 0.175287C13.4409 0.035659 16.2657 1.20796 18.3212 3.17781L18.9202 2.71389C19.1388 2.5446 19.4582 2.68418 19.4824 2.95967L19.7199 5.66482C19.7422 5.91826 19.4949 6.10982 19.2551 6.02483L16.6954 5.11791C16.4348 5.02556 16.3795 4.68142 16.5982 4.51212L17.2071 4.04055C15.5283 2.50215 13.2923 1.56212 10.8356 1.56212Z" fill="#FB5F7A"/>
|
||||
<path d="M18.485 18.6485C18.2212 18.3848 17.7996 18.3723 17.5193 18.6183C15.5456 20.3511 12.893 21.3292 10.0123 21.102C5.09917 20.7145 1.11879 16.7339 0.731479 11.8208C0.50441 8.94025 1.48256 6.28788 3.21519 4.3143C3.46126 4.03403 3.44709 3.61138 3.18336 3.34766C1.37642 5.15274 0.197009 7.58634 0.0232257 10.2799C-0.397562 16.8019 4.95607 22.1858 11.468 21.8155C14.1882 21.6609 16.6595 20.4788 18.4881 18.6515L18.485 18.6485Z" fill="#F74455"/>
|
||||
</svg>
|
After Width: | Height: | Size: 6.5 KiB |
23
assets/icons/trigger_level_icon.svg
Normal file
23
assets/icons/trigger_level_icon.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_8194_10077)">
|
||||
<path d="M17.6 18.673C17.3521 18.4251 17.3521 18.0231 17.6 17.7752C19.4054 15.9698 20.3996 13.5678 20.3996 11.0115C20.3996 8.45525 19.4054 6.05318 17.6 4.24784C17.3521 3.99994 17.3521 3.59795 17.6 3.35001C17.8479 3.10208 18.2499 3.10208 18.4978 3.35001C20.543 5.39516 21.6694 8.11606 21.6694 11.0115C21.6694 13.9069 20.543 16.6278 18.4978 18.673C18.2503 18.9205 17.8484 18.9213 17.6 18.673Z" fill="#7AFFE4"/>
|
||||
<path d="M15.6781 16.7502C15.4302 16.5023 15.4302 16.1003 15.6781 15.8524C16.9704 14.5602 17.6821 12.8408 17.6821 11.011C17.6821 9.18128 16.9705 7.46194 15.6781 6.1697C15.4302 5.92176 15.4302 5.51981 15.6781 5.27187C15.926 5.02398 16.328 5.02394 16.576 5.27187C18.1081 6.80393 18.9519 8.84213 18.9519 11.0111C18.9519 13.18 18.1081 15.2182 16.576 16.7503C16.3279 16.9982 15.926 16.9981 15.6781 16.7502Z" fill="#00DDC1"/>
|
||||
<path d="M13.7543 14.8285C13.5064 14.5805 13.5064 14.1785 13.7543 13.9306C14.5331 13.152 14.9619 12.1158 14.9619 11.0131C14.9619 9.91034 14.5331 8.87416 13.7543 8.09548C13.5064 7.84758 13.5064 7.44559 13.7543 7.19765C14.0022 6.94976 14.4042 6.94972 14.6521 7.19765C15.6707 8.21619 16.2317 9.5712 16.2317 11.0131C16.2317 12.455 15.6707 13.81 14.6521 14.8285C14.4042 15.0764 14.0022 15.0763 13.7543 14.8285Z" fill="#7AFFE4"/>
|
||||
<path d="M17.6 18.6732C17.8484 18.9216 18.2503 18.9207 18.4978 18.6732C20.543 16.6281 21.6694 13.9072 21.6694 11.0117H20.3996C20.3996 13.568 19.4054 15.97 17.6 17.7754C17.3521 18.0233 17.3521 18.4253 17.6 18.6732Z" fill="#00DDC1"/>
|
||||
<path d="M15.6781 16.7509C15.926 16.9988 16.3279 16.9989 16.5759 16.7509C18.1081 15.2189 18.9518 13.1807 18.9518 11.0117H17.6821C17.6821 12.8415 16.9704 14.5608 15.6781 15.8531C15.4302 16.101 15.4302 16.503 15.6781 16.7509Z" fill="#00B4BC"/>
|
||||
<path d="M13.7543 14.8272C14.0022 15.0751 14.4041 15.0751 14.6521 14.8272C15.6707 13.8086 16.2316 12.4536 16.2316 11.0117H14.9619C14.9619 12.1144 14.533 13.1506 13.7543 13.9293C13.5064 14.1772 13.5064 14.5792 13.7543 14.8272Z" fill="#00DDC1"/>
|
||||
<path d="M3.17152 18.673C1.12633 16.6278 0 13.9069 0 11.0115C0 8.11604 1.12633 5.39515 3.17152 3.35C3.41946 3.10206 3.82141 3.10211 4.06935 3.35C4.31728 3.59793 4.31728 3.99989 4.06935 4.24782C2.26396 6.05316 1.26973 8.45523 1.26973 11.0115C1.26973 13.5677 2.26396 15.9698 4.06935 17.7751C4.31728 18.023 4.31728 18.425 4.06935 18.673C3.82141 18.921 3.41937 18.9209 3.17152 18.673Z" fill="#7AFFE4"/>
|
||||
<path d="M5.09462 16.7503C3.56253 15.2182 2.71875 13.18 2.71875 11.0111C2.71875 8.84212 3.56253 6.80391 5.09466 5.27186C5.3426 5.02396 5.74459 5.02396 5.99249 5.27186C6.24038 5.51979 6.24038 5.92175 5.99249 6.16968C4.70016 7.46197 3.98848 9.1813 3.98848 11.0111C3.98848 12.8408 4.70016 14.5602 5.99249 15.8524C6.24042 16.1003 6.24042 16.5023 5.99249 16.7502C5.74489 16.9978 5.34294 16.9985 5.09462 16.7503Z" fill="#00DDC1"/>
|
||||
<path d="M7.01704 14.8285C5.99847 13.81 5.4375 12.455 5.4375 11.0131C5.4375 9.5712 5.99847 8.21619 7.01704 7.19765C7.26498 6.94972 7.66697 6.94976 7.91486 7.19765C8.1628 7.44559 8.16276 7.84758 7.91486 8.09548C7.1361 8.87416 6.70723 9.91034 6.70723 11.0131C6.70723 12.1158 7.1361 13.152 7.91486 13.9306C8.1628 14.1785 8.1628 14.5805 7.91486 14.8285C7.66727 15.0761 7.26531 15.0768 7.01704 14.8285Z" fill="#7AFFE4"/>
|
||||
<path d="M4.06935 18.6732C4.31728 18.4253 4.31728 18.0233 4.06935 17.7754C2.26396 15.97 1.26973 13.568 1.26973 11.0117H0C0 13.9072 1.12633 16.6281 3.17152 18.6732C3.41937 18.9211 3.82141 18.9212 4.06935 18.6732Z" fill="#00DDC1"/>
|
||||
<path d="M5.99249 16.7509C6.24038 16.5029 6.24038 16.101 5.99249 15.8531C4.70016 14.5609 3.98848 12.8415 3.98848 11.0117H2.71875C2.71875 13.1807 3.56253 15.2189 5.09466 16.7509C5.34294 16.9992 5.74489 16.9985 5.99249 16.7509Z" fill="#00B4BC"/>
|
||||
<path d="M7.91486 14.8271C8.1628 14.5792 8.16276 14.1772 7.91486 13.9293C7.1361 13.1506 6.70723 12.1144 6.70723 11.0117H5.4375C5.4375 12.4536 5.99846 13.8086 7.01704 14.8272C7.26527 15.0754 7.66722 15.0747 7.91486 14.8271Z" fill="#00DDC1"/>
|
||||
<path d="M10.835 13.6857C12.3123 13.6857 13.5099 12.4881 13.5099 11.0108C13.5099 9.53353 12.3123 8.33594 10.835 8.33594C9.35775 8.33594 8.16016 9.53353 8.16016 11.0108C8.16016 12.4881 9.35775 13.6857 10.835 13.6857Z" fill="#00DDC1"/>
|
||||
<path d="M10.835 13.6866C12.31 13.6866 13.5099 12.4866 13.5099 11.0117H8.16016C8.16016 12.4867 9.36009 13.6866 10.835 13.6866Z" fill="#00B4BC"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_8194_10077">
|
||||
<rect width="21.67" height="21.67" fill="white" transform="translate(0 0.164062)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
26
lib/common/widgets/empty_search_result_widget.dart
Normal file
26
lib/common/widgets/empty_search_result_widget.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class EmptySearchResultWidget extends StatelessWidget {
|
||||
const EmptySearchResultWidget({
|
||||
this.message = 'No results found',
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.lightGreyColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
53
lib/common/widgets/sidebar_communities_list.dart
Normal file
53
lib/common/widgets/sidebar_communities_list.dart
Normal file
@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SidebarCommunitiesList extends StatelessWidget {
|
||||
const SidebarCommunitiesList({
|
||||
required this.communities,
|
||||
required this.itemBuilder,
|
||||
required this.scrollController,
|
||||
required this.onScrollToEnd,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<CommunityModel> communities;
|
||||
final Widget Function(BuildContext context, int index) itemBuilder;
|
||||
final ScrollController scrollController;
|
||||
final void Function() onScrollToEnd;
|
||||
|
||||
bool _onNotification(ScrollEndNotification notification) {
|
||||
final hasReachedEnd = notification.metrics.extentAfter == 0;
|
||||
if (hasReachedEnd) {
|
||||
onScrollToEnd.call();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: context.screenWidth * 0.5,
|
||||
child: Scrollbar(
|
||||
scrollbarOrientation: ScrollbarOrientation.left,
|
||||
thumbVisibility: true,
|
||||
controller: scrollController,
|
||||
child: NotificationListener<ScrollEndNotification>(
|
||||
onNotification: _onNotification,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsetsDirectional.only(start: 16),
|
||||
itemCount: communities.length,
|
||||
controller: scrollController,
|
||||
itemBuilder: itemBuilder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -16,12 +16,12 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.da
|
||||
import 'package:syncrow_web/services/locator.dart';
|
||||
import 'package:syncrow_web/utils/app_routes.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||
import 'package:syncrow_web/utils/navigation_service.dart';
|
||||
import 'package:syncrow_web/utils/theme/theme.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
try {
|
||||
const environment =
|
||||
String.fromEnvironment('FLAVOR', defaultValue: 'development');
|
||||
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
|
||||
await dotenv.load(fileName: '.env.$environment');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
@ -33,9 +33,7 @@ Future<void> main() async {
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
MyApp({
|
||||
super.key,
|
||||
});
|
||||
MyApp({super.key});
|
||||
|
||||
final GoRouter _router = GoRouter(
|
||||
initialLocation: RoutesConst.auth,
|
||||
@ -56,11 +54,10 @@ class MyApp extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
create: (context) => CreateRoutineBloc(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
),
|
||||
@ -81,6 +78,8 @@ class MyApp extends StatelessWidget {
|
||||
PointerDeviceKind.unknown,
|
||||
},
|
||||
),
|
||||
key: NavigationService.navigatorKey,
|
||||
// scaffoldMessengerKey: NavigationService.snackbarKey,
|
||||
theme: myTheme,
|
||||
routerConfig: _router,
|
||||
));
|
||||
|
87
lib/main_staging.dart
Normal file
87
lib/main_staging.dart
Normal file
@ -0,0 +1,87 @@
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/firebase_options_prod.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||
import 'package:syncrow_web/services/locator.dart';
|
||||
import 'package:syncrow_web/utils/app_routes.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||
import 'package:syncrow_web/utils/navigation_service.dart';
|
||||
import 'package:syncrow_web/utils/theme/theme.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
try {
|
||||
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'staging');
|
||||
await dotenv.load(fileName: '.env.$environment');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptionsStaging.currentPlatform,
|
||||
);
|
||||
initialSetup();
|
||||
} catch (_) {}
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
MyApp({super.key});
|
||||
|
||||
final GoRouter _router = GoRouter(
|
||||
initialLocation: RoutesConst.auth,
|
||||
routes: AppRoutes.getRoutes(),
|
||||
redirect: (context, state) async {
|
||||
String checkToken = await AuthBloc.getTokenAndValidate();
|
||||
final loggedIn = checkToken == 'Success';
|
||||
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
||||
|
||||
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
|
||||
if (loggedIn && goingToLogin) return RoutesConst.home;
|
||||
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
create: (context) => CreateRoutineBloc(),
|
||||
),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
),
|
||||
BlocProvider<RoutineBloc>(
|
||||
create: (context) => RoutineBloc(),
|
||||
),
|
||||
BlocProvider<SpaceTreeBloc>(
|
||||
create: (context) => SpaceTreeBloc()..add(InitialEvent()),
|
||||
),
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.stylus,
|
||||
PointerDeviceKind.unknown,
|
||||
},
|
||||
),
|
||||
key: NavigationService.navigatorKey,
|
||||
// scaffoldMessengerKey: NavigationService.snackbarKey,
|
||||
theme: myTheme,
|
||||
routerConfig: _router,
|
||||
));
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/auth/model/region_model.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/token.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/user_model.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
import 'package:syncrow_web/services/auth_api.dart';
|
||||
@ -432,9 +433,13 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
}
|
||||
|
||||
static Future<void> logout(BuildContext context) async {
|
||||
final storage = FlutterSecureStorage();
|
||||
ProjectManager.clearProjectUUID();
|
||||
const storage = FlutterSecureStorage();
|
||||
context.read<SpaceTreeBloc>().add(ClearAllData());
|
||||
storage.deleteAll();
|
||||
user = null;
|
||||
context.read<HomeBloc>().user = null;
|
||||
await Future.wait<void>([
|
||||
ProjectManager.clearProjectUUID(),
|
||||
storage.deleteAll(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -55,12 +55,12 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
final isSmallScreen = isSmallScreenSize(context);
|
||||
final isMediumScreen = isMediumScreenSize(context);
|
||||
Size size = MediaQuery.of(context).size;
|
||||
late ScrollController _scrollController;
|
||||
_scrollController = ScrollController();
|
||||
late ScrollController scrollController;
|
||||
scrollController = ScrollController();
|
||||
|
||||
void _scrollToCenter() {
|
||||
final double middlePosition = _scrollController.position.maxScrollExtent / 2;
|
||||
_scrollController.animateTo(
|
||||
void scrollToCenter() {
|
||||
final double middlePosition = scrollController.position.maxScrollExtent / 2;
|
||||
scrollController.animateTo(
|
||||
middlePosition,
|
||||
duration: const Duration(seconds: 1),
|
||||
curve: Curves.easeInOut,
|
||||
@ -68,7 +68,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToCenter();
|
||||
scrollToCenter();
|
||||
});
|
||||
|
||||
return Stack(
|
||||
@ -76,7 +76,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
FirstLayer(
|
||||
second: Center(
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
controller: scrollController,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Container(
|
||||
@ -199,7 +199,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
width: size.width * 0.9,
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<String>(
|
||||
style: TextStyle(color: Colors.black),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
isExpanded: true,
|
||||
hint: Text(
|
||||
'Select your region/country',
|
||||
@ -336,6 +336,16 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
obscureText: loginBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: loginBloc.loginPasswordController,
|
||||
onFieldSubmitted: (value) {
|
||||
if (loginBloc.loginFormKey.currentState!.validate()) {
|
||||
loginBloc.add(LoginButtonPressed(
|
||||
username: loginBloc.loginEmailController.text,
|
||||
password: value,
|
||||
));
|
||||
} else {
|
||||
loginBloc.add(ChangeValidateEvent());
|
||||
}
|
||||
},
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
hintStyle: Theme.of(context)
|
||||
@ -393,7 +403,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
Transform.scale(
|
||||
scale: 1.2,
|
||||
child: Checkbox(
|
||||
fillColor: MaterialStateProperty.all<Color>(Colors.white),
|
||||
fillColor: WidgetStateProperty.all<Color>(Colors.white),
|
||||
activeColor: Colors.white,
|
||||
value: loginBloc.isChecked,
|
||||
checkColor: Colors.black,
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_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';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SearchResetButtons extends StatelessWidget {
|
||||
const SearchResetButtons({
|
||||
@ -17,8 +17,10 @@ class SearchResetButtons extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 25),
|
||||
|
@ -60,7 +60,15 @@ class _CurrentTempState extends State<CurrentTemp> {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(CurrentTemp oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.tempSet != widget.tempSet) {
|
||||
setState(() {
|
||||
_adjustedValue = _initialAdjustedValue(widget.tempSet);
|
||||
});
|
||||
}
|
||||
}
|
||||
@override
|
||||
void dispose() {
|
||||
_debounce?.cancel();
|
||||
|
@ -9,6 +9,8 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart';
|
||||
@ -104,6 +106,9 @@ mixin RouteControlsBasedCode {
|
||||
);
|
||||
case 'SOS':
|
||||
return SosDeviceControlsView(device: device);
|
||||
|
||||
case 'NCPS':
|
||||
return FlushMountedPresenceSensorControlView(device: device);
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
@ -194,6 +199,10 @@ mixin RouteControlsBasedCode {
|
||||
return SOSBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'NCPS':
|
||||
return FlushMountedPresenceSensorBatchControlView(
|
||||
devicesIds: devices.where((e) => (e.productType == 'NCPS')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.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/flush/flush_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';
|
||||
@ -155,13 +156,17 @@ class AllDevicesModel {
|
||||
batteryLevel = int.tryParse(json['battery']?.toString() ?? '');
|
||||
productName = json['productName']?.toString();
|
||||
deviceTags = json['deviceTag'] != null && json['deviceTag'] is List
|
||||
? (json['deviceTag'] as List).map((tag) => DeviceTagModel.fromJson(tag)).toList()
|
||||
? (json['deviceTag'] as List)
|
||||
.map((tag) => DeviceTagModel.fromJson(tag))
|
||||
.toList()
|
||||
: [];
|
||||
deviceSubSpace = json['subspace'] != null
|
||||
? DeviceSubSpace.fromJson(json['subspace'])
|
||||
: DeviceSubSpace(subspaceName: '');
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +214,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;
|
||||
@ -239,6 +245,8 @@ SOS
|
||||
// tempIcon = Assets.gang3touch;
|
||||
} else if (type == DeviceType.WaterLeak) {
|
||||
tempIcon = Assets.waterLeakNormal;
|
||||
} else if (type == DeviceType.NCPS) {
|
||||
tempIcon = Assets.sensors;
|
||||
} else {
|
||||
tempIcon = Assets.logoHorizontal;
|
||||
}
|
||||
@ -254,51 +262,75 @@ SOS
|
||||
switch (productType) {
|
||||
case 'AC':
|
||||
return [
|
||||
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'),
|
||||
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 ?? '', 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'),
|
||||
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'),
|
||||
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'),
|
||||
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 [
|
||||
@ -323,6 +355,30 @@ SOS
|
||||
uuid: uuid ?? '',
|
||||
name: name ?? '',
|
||||
);
|
||||
case 'NCPS':
|
||||
return [
|
||||
FlushPresenceDelayFunction(
|
||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF',),
|
||||
|
||||
FlushIlluminanceFunction(
|
||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
|
||||
//THEN Functions
|
||||
FlushSensitivityFunction(
|
||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
|
||||
FlushNearDetectionFunction(
|
||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
|
||||
FlushMaxDetectDistFunction(
|
||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
|
||||
FlushTargetConfirmTimeFunction(
|
||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
|
||||
FlushDisappeDelayFunction(
|
||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
|
||||
FlushIndentLevelFunction(
|
||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
|
||||
FlushTriggerLevelFunction(
|
||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
|
||||
];
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
@ -454,5 +510,6 @@ SOS
|
||||
"3GT": DeviceType.ThreeTouch,
|
||||
"GD": DeviceType.GarageDoor,
|
||||
"WL": DeviceType.WaterLeak,
|
||||
"NCPS": DeviceType.NCPS,
|
||||
};
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
return DeviceManagementBody(
|
||||
devices: deviceState.filteredDevices);
|
||||
} else {
|
||||
return const Center(child: Text('Error fetching Devices'));
|
||||
return const DeviceManagementBody(devices: []);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -72,6 +72,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
child: state is DeviceManagementLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: isLargeScreenSize(context)
|
||||
|
@ -14,29 +14,29 @@ class DeviceSearchFilters extends StatefulWidget {
|
||||
|
||||
class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
||||
with HelperResponsiveLayout {
|
||||
final _unitNameController = TextEditingController();
|
||||
final _productNameController = TextEditingController();
|
||||
late final TextEditingController _unitNameController;
|
||||
late final TextEditingController _productNameController;
|
||||
|
||||
List<Widget> get _widgets => [
|
||||
_buildSearchField("Space Name", _unitNameController, 200),
|
||||
_buildSearchField("Device Name / Product Name", _productNameController, 300),
|
||||
_buildSearchResetButtons(),
|
||||
];
|
||||
@override
|
||||
void initState() {
|
||||
_unitNameController = TextEditingController();
|
||||
_productNameController = TextEditingController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isExtraLargeScreenSize(context)) {
|
||||
return Row(
|
||||
children: _widgets
|
||||
.map((e) => Padding(padding: const EdgeInsets.all(10), child: e))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
runAlignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
spacing: 20,
|
||||
runSpacing: 10,
|
||||
children: _widgets,
|
||||
children: [
|
||||
_buildSearchField("Space Name", _unitNameController, 200),
|
||||
_buildSearchField("Device Name / Product Name", _productNameController, 300),
|
||||
_buildSearchResetButtons(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,8 @@ import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dar
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLayout {
|
||||
class CeilingSensorControlsView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const CeilingSensorControlsView({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
@ -31,29 +32,35 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
||||
..add(CeilingInitialEvent(device.uuid ?? '')),
|
||||
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
|
||||
if (state is CeilingLoadingInitialState ||
|
||||
state is CeilingReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is CeilingUpdateState) {
|
||||
return _buildGridView(
|
||||
context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium);
|
||||
return _buildGridView(context, state.ceilingSensorModel,
|
||||
isExtraLarge, isLarge, isMedium);
|
||||
} else if (state is CeilingReportsState) {
|
||||
return ReportsTable(
|
||||
report: state.deviceReport,
|
||||
onRowTap: (index) {},
|
||||
onClose: () {
|
||||
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
|
||||
context
|
||||
.read<CeilingSensorBloc>()
|
||||
.add(BackToCeilingGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is ShowCeilingDescriptionState) {
|
||||
return DescriptionView(
|
||||
description: state.description,
|
||||
onClose: () {
|
||||
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
|
||||
context
|
||||
.read<CeilingSensorBloc>()
|
||||
.add(BackToCeilingGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is CeilingReportsFailedState) {
|
||||
final model = context.read<CeilingSensorBloc>().deviceStatus;
|
||||
return _buildGridView(context, model, isExtraLarge, isLarge, isMedium);
|
||||
return _buildGridView(
|
||||
context, model, isExtraLarge, isLarge, isMedium);
|
||||
}
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
},
|
||||
@ -61,8 +68,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge,
|
||||
bool isLarge, bool isMedium) {
|
||||
Widget _buildGridView(BuildContext context, CeilingSensorModel model,
|
||||
bool isExtraLarge, bool isLarge, bool isMedium) {
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
@ -143,8 +150,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CeilingSensorBloc>().add(
|
||||
GetCeilingDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!));
|
||||
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
|
||||
code: 'presence_state', deviceUuid: device.uuid!));
|
||||
},
|
||||
child: const PresenceStaticWidget(
|
||||
icon: Assets.illuminanceRecordIcon,
|
||||
@ -153,9 +160,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context
|
||||
.read<CeilingSensorBloc>()
|
||||
.add(GetCeilingDeviceReportsEvent(code: '', deviceUuid: device.uuid!));
|
||||
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
|
||||
code: '', deviceUuid: device.uuid!));
|
||||
},
|
||||
child: const PresenceStaticWidget(
|
||||
icon: Assets.helpDescriptionIcon,
|
||||
|
@ -0,0 +1,250 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
part 'flush_mounted_presence_sensor_event.dart';
|
||||
part 'flush_mounted_presence_sensor_state.dart';
|
||||
|
||||
class FlushMountedPresenceSensorBloc
|
||||
extends Bloc<FlushMountedPresenceSensorEvent, FlushMountedPresenceSensorState> {
|
||||
final String deviceId;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
|
||||
late FlushMountedPresenceSensorModel deviceStatus;
|
||||
FlushMountedPresenceSensorBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(FlushMountedPresenceSensorInitialState()) {
|
||||
on<FlushMountedPresenceSensorFetchStatusEvent>(
|
||||
_onFlushMountedPresenceSensorFetchStatusEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorFetchBatchStatusEvent>(
|
||||
_onFlushMountedPresenceSensorFetchBatchStatusEvent);
|
||||
on<FlushMountedPresenceSensorChangeValueEvent>(
|
||||
_onFlushMountedPresenceSensorChangeValueEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorBatchControlEvent>(
|
||||
_onFlushMountedPresenceSensorBatchControlEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorGetDeviceReportsEvent>(
|
||||
_onFlushMountedPresenceSensorGetDeviceReportsEvent);
|
||||
on<FlushMountedPresenceSensorShowDescriptionEvent>(
|
||||
_onFlushMountedPresenceSensorShowDescriptionEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorBackToGridViewEvent>(
|
||||
_onFlushMountedPresenceSensorBackToGridViewEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorFactoryResetEvent>(
|
||||
_onFlushMountedPresenceSensorFactoryResetEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorStatusUpdatedEvent>(
|
||||
_onFlushMountedPresenceSensorStatusUpdatedEvent,
|
||||
);
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorFetchStatusEvent(
|
||||
FlushMountedPresenceSensorFetchStatusEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorLoadingInitialState());
|
||||
try {
|
||||
final response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||
deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status);
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
_listenToChanges(deviceId);
|
||||
} catch (e) {
|
||||
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFlushMountedPresenceSensorFetchBatchStatusEvent(
|
||||
FlushMountedPresenceSensorFetchBatchStatusEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorLoadingInitialState());
|
||||
try {
|
||||
final response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status);
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
} catch (e) {
|
||||
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
final ref = FirebaseDatabase.instance.ref(
|
||||
'device-status/$deviceId',
|
||||
);
|
||||
|
||||
ref.onValue.listen((event) {
|
||||
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
List<Status> statusList = [];
|
||||
eventsMap['status'].forEach((element) {
|
||||
statusList.add(
|
||||
Status(code: element['code'], value: element['value']),
|
||||
);
|
||||
});
|
||||
|
||||
deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList);
|
||||
if (!isClosed) {
|
||||
add(FlushMountedPresenceSensorStatusUpdatedEvent(deviceStatus));
|
||||
}
|
||||
});
|
||||
} catch (_) {
|
||||
log(
|
||||
'Error listening to changes',
|
||||
name: 'FlushMountedPresenceSensorBloc._listenToChanges',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorChangeValueEvent(
|
||||
FlushMountedPresenceSensorChangeValueEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
|
||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
try {
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (_) {
|
||||
await _reloadDeviceStatus();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFlushMountedPresenceSensorBatchControlEvent(
|
||||
FlushMountedPresenceSensorBatchControlEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
|
||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
|
||||
try {
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
} catch (_) {
|
||||
await _reloadDeviceStatus();
|
||||
}
|
||||
}
|
||||
|
||||
void _updateDeviceFunctionFromCode(String code, int value) {
|
||||
switch (code) {
|
||||
case FlushMountedPresenceSensorModel.codeFarDetection:
|
||||
deviceStatus.farDetection = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codeSensitivity:
|
||||
deviceStatus.sensitivity = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codeNoneDelay:
|
||||
deviceStatus.noneDelay = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codePresenceDelay:
|
||||
deviceStatus.presenceDelay = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codeNearDetection:
|
||||
deviceStatus.nearDetection = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codeOccurDistReduce:
|
||||
deviceStatus.occurDistReduce = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codeSensiReduce:
|
||||
deviceStatus.sensiReduce = value;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _reloadDeviceStatus() async {
|
||||
await Future.delayed(const Duration(milliseconds: 500), () {
|
||||
add(FlushMountedPresenceSensorFetchStatusEvent());
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onFlushMountedPresenceSensorGetDeviceReportsEvent(
|
||||
FlushMountedPresenceSensorGetDeviceReportsEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorDeviceReportsLoadingState());
|
||||
|
||||
try {
|
||||
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
|
||||
.then((value) {
|
||||
emit(FlushMountedPresenceSensorDeviceReportsState(
|
||||
deviceReport: value, code: event.code));
|
||||
});
|
||||
} catch (e) {
|
||||
emit(FlushMountedPresenceSensorDeviceReportsFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorShowDescriptionEvent(
|
||||
FlushMountedPresenceSensorShowDescriptionEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) {
|
||||
emit(FlushMountedPresenceSensorShowDescriptionState(
|
||||
description: event.description));
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorBackToGridViewEvent(
|
||||
FlushMountedPresenceSensorBackToGridViewEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) {
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
}
|
||||
|
||||
Future<void> _onFlushMountedPresenceSensorFactoryResetEvent(
|
||||
FlushMountedPresenceSensorFactoryResetEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(
|
||||
const FlushMountedPresenceSensorFailedState(
|
||||
error: 'Something went wrong with factory reset, please try again',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorStatusUpdatedEvent(
|
||||
FlushMountedPresenceSensorStatusUpdatedEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) {
|
||||
deviceStatus = event.model;
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
part of 'flush_mounted_presence_sensor_bloc.dart';
|
||||
|
||||
sealed class FlushMountedPresenceSensorEvent extends Equatable {
|
||||
const FlushMountedPresenceSensorEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorFetchStatusEvent
|
||||
extends FlushMountedPresenceSensorEvent {}
|
||||
|
||||
class FlushMountedPresenceSensorStatusUpdatedEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
const FlushMountedPresenceSensorStatusUpdatedEvent(this.model);
|
||||
|
||||
final FlushMountedPresenceSensorModel model;
|
||||
|
||||
@override
|
||||
List<Object> get props => [model];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorChangeValueEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final int value;
|
||||
final String code;
|
||||
|
||||
const FlushMountedPresenceSensorChangeValueEvent({
|
||||
required this.value,
|
||||
required this.code,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [value, code];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorFetchBatchStatusEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final List<String> devicesIds;
|
||||
const FlushMountedPresenceSensorFetchBatchStatusEvent(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorGetDeviceReportsEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final String deviceUuid;
|
||||
final String code;
|
||||
const FlushMountedPresenceSensorGetDeviceReportsEvent({
|
||||
required this.deviceUuid,
|
||||
required this.code,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceUuid, code];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorShowDescriptionEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final String description;
|
||||
const FlushMountedPresenceSensorShowDescriptionEvent({required this.description});
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorBackToGridViewEvent
|
||||
extends FlushMountedPresenceSensorEvent {}
|
||||
|
||||
class FlushMountedPresenceSensorBatchControlEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final List<String> deviceIds;
|
||||
final String code;
|
||||
final dynamic value;
|
||||
|
||||
const FlushMountedPresenceSensorBatchControlEvent({
|
||||
required this.deviceIds,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceIds, code, value];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorFactoryResetEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final String deviceId;
|
||||
final FactoryResetModel factoryReset;
|
||||
|
||||
const FlushMountedPresenceSensorFactoryResetEvent({
|
||||
required this.deviceId,
|
||||
required this.factoryReset,
|
||||
});
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
part of 'flush_mounted_presence_sensor_bloc.dart';
|
||||
|
||||
sealed class FlushMountedPresenceSensorState extends Equatable {
|
||||
const FlushMountedPresenceSensorState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorInitialState
|
||||
extends FlushMountedPresenceSensorState {}
|
||||
|
||||
class FlushMountedPresenceSensorLoadingInitialState
|
||||
extends FlushMountedPresenceSensorState {}
|
||||
|
||||
class FlushMountedPresenceSensorUpdateState extends FlushMountedPresenceSensorState {
|
||||
final FlushMountedPresenceSensorModel model;
|
||||
const FlushMountedPresenceSensorUpdateState({required this.model});
|
||||
|
||||
@override
|
||||
List<Object> get props => [model];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorLoadingNewSate
|
||||
extends FlushMountedPresenceSensorState {
|
||||
final FlushMountedPresenceSensorModel model;
|
||||
const FlushMountedPresenceSensorLoadingNewSate({required this.model});
|
||||
|
||||
@override
|
||||
List<Object> get props => [model];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorFailedState extends FlushMountedPresenceSensorState {
|
||||
final String error;
|
||||
|
||||
const FlushMountedPresenceSensorFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorDeviceReportsLoadingState
|
||||
extends FlushMountedPresenceSensorState {}
|
||||
|
||||
class FlushMountedPresenceSensorDeviceReportsState
|
||||
extends FlushMountedPresenceSensorState {
|
||||
const FlushMountedPresenceSensorDeviceReportsState({
|
||||
required this.deviceReport,
|
||||
required this.code,
|
||||
});
|
||||
|
||||
final DeviceReport deviceReport;
|
||||
final String code;
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceReport, code];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorDeviceReportsFailedState
|
||||
extends FlushMountedPresenceSensorState {
|
||||
const FlushMountedPresenceSensorDeviceReportsFailedState({required this.error});
|
||||
|
||||
final String error;
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorShowDescriptionState
|
||||
extends FlushMountedPresenceSensorState {
|
||||
const FlushMountedPresenceSensorShowDescriptionState({required this.description});
|
||||
|
||||
final String description;
|
||||
@override
|
||||
List<Object> get props => [description];
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
|
||||
abstract final class FlushMountedPresenceSensorBlocFactory {
|
||||
const FlushMountedPresenceSensorBlocFactory._();
|
||||
|
||||
static FlushMountedPresenceSensorBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return FlushMountedPresenceSensorBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService: DebouncedControlDeviceService(
|
||||
decoratee: RemoteControlDeviceService(),
|
||||
),
|
||||
batchControlDevicesService: DebouncedBatchControlDevicesService(
|
||||
decoratee: RemoteBatchControlDevicesService(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class FlushMountedPresenceSensorModel {
|
||||
FlushMountedPresenceSensorModel({
|
||||
required this.presenceState,
|
||||
required this.farDetection,
|
||||
required this.illuminance,
|
||||
required this.sensitivity,
|
||||
required this.occurDistReduce,
|
||||
required this.noneDelay,
|
||||
required this.presenceDelay,
|
||||
required this.nearDetection,
|
||||
required this.sensiReduce,
|
||||
required this.checkingResult,
|
||||
});
|
||||
|
||||
static const String codePresenceState = 'presence_state';
|
||||
static const String codeSensitivity = 'sensitivity';
|
||||
static const String codeNearDetection = 'near_detection';
|
||||
static const String codeFarDetection = 'far_detection';
|
||||
static const String codeCheckingResult = 'checking_result';
|
||||
static const String codePresenceDelay = 'presence_delay';
|
||||
static const String codeNoneDelay = 'none_delay';
|
||||
static const String codeOccurDistReduce = 'occur_dist_reduce';
|
||||
static const String codeIlluminance = 'illum_value';
|
||||
static const String codeSensiReduce = 'sensi_reduce';
|
||||
|
||||
String presenceState;
|
||||
int sensitivity;
|
||||
int nearDetection;
|
||||
int farDetection;
|
||||
String checkingResult;
|
||||
int presenceDelay;
|
||||
int noneDelay;
|
||||
int occurDistReduce;
|
||||
int illuminance;
|
||||
int sensiReduce;
|
||||
|
||||
factory FlushMountedPresenceSensorModel.fromJson(List<Status> jsonList) {
|
||||
String presenceState = 'none';
|
||||
int sensitivity = 0;
|
||||
int nearDetection = 0;
|
||||
int farDetection = 0;
|
||||
String checkingResult = 'none';
|
||||
int presenceDelay = 0;
|
||||
int noneDelay = 0;
|
||||
int occurDistReduce = 0;
|
||||
int illuminance = 0;
|
||||
int sensiReduce = 0;
|
||||
|
||||
for (var status in jsonList) {
|
||||
switch (status.code) {
|
||||
case codePresenceState:
|
||||
presenceState = status.value ?? 'presence';
|
||||
break;
|
||||
case codeSensitivity:
|
||||
sensitivity = status.value ?? 0;
|
||||
break;
|
||||
case codeNearDetection:
|
||||
nearDetection = status.value ?? 0;
|
||||
break;
|
||||
case codeFarDetection:
|
||||
farDetection = status.value ?? 0;
|
||||
break;
|
||||
case codeCheckingResult:
|
||||
checkingResult = status.value ?? 'check_success';
|
||||
break;
|
||||
case codePresenceDelay:
|
||||
presenceDelay = status.value ?? 0;
|
||||
break;
|
||||
case codeNoneDelay:
|
||||
noneDelay = status.value ?? 0;
|
||||
break;
|
||||
case codeOccurDistReduce:
|
||||
occurDistReduce = status.value ?? 0;
|
||||
break;
|
||||
case codeIlluminance:
|
||||
illuminance = status.value ?? 0;
|
||||
break;
|
||||
case codeSensiReduce:
|
||||
sensiReduce = status.value ?? 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return FlushMountedPresenceSensorModel(
|
||||
presenceState: presenceState,
|
||||
sensitivity: sensitivity,
|
||||
nearDetection: nearDetection,
|
||||
farDetection: farDetection,
|
||||
checkingResult: checkingResult,
|
||||
presenceDelay: presenceDelay,
|
||||
noneDelay: noneDelay,
|
||||
occurDistReduce: occurDistReduce,
|
||||
illuminance: illuminance,
|
||||
sensiReduce: sensiReduce,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,185 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const FlushMountedPresenceSensorBatchControlView({
|
||||
required this.devicesIds,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
|
||||
deviceId: devicesIds.first,
|
||||
)..add(FlushMountedPresenceSensorFetchBatchStatusEvent(devicesIds)),
|
||||
child: BlocBuilder<FlushMountedPresenceSensorBloc,
|
||||
FlushMountedPresenceSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is FlushMountedPresenceSensorLoadingInitialState ||
|
||||
state is FlushMountedPresenceSensorDeviceReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is FlushMountedPresenceSensorUpdateState) {
|
||||
return _buildGridView(context, state.model);
|
||||
}
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView(
|
||||
BuildContext context,
|
||||
FlushMountedPresenceSensorModel model,
|
||||
) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
PresenceUpdateData(
|
||||
value: model.sensitivity.toDouble(),
|
||||
title: 'Sensitivity:',
|
||||
minValue: 0,
|
||||
maxValue: 9,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeSensitivity,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.nearDetection / 100).clamp(0.0, double.infinity),
|
||||
title: 'Nearest Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
maxValue: 9.5,
|
||||
steps: 0.1,
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeNearDetection,
|
||||
value: (value * 100).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.farDetection / 100).clamp(0.0, double.infinity),
|
||||
title: 'Max Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
maxValue: 9.5,
|
||||
steps: 0.1,
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeFarDetection,
|
||||
value: (value * 100).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.sensiReduce.toDouble(),
|
||||
title: 'Trigger Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.occurDistReduce.toDouble(),
|
||||
title: 'Indent Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.presenceDelay / 10).toDouble(),
|
||||
title: 'Target Confirm Time:',
|
||||
description: 's',
|
||||
minValue: 0.0,
|
||||
maxValue: 0.5,
|
||||
steps: 0.1,
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
value: (value * 10).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: ((model.noneDelay / 10).toDouble()),
|
||||
description: 's',
|
||||
title: 'Disappe Delay:',
|
||||
minValue: 20,
|
||||
maxValue: 300,
|
||||
steps: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeNoneDelay,
|
||||
value: (value * 10).round(),
|
||||
),
|
||||
),
|
||||
),
|
||||
FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorFactoryResetEvent(
|
||||
deviceId: devicesIds.first,
|
||||
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
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/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/table/description_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class FlushMountedPresenceSensorControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const FlushMountedPresenceSensorControlView({required this.device, super.key});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
|
||||
deviceId: device.uuid ?? '-1',
|
||||
)..add(FlushMountedPresenceSensorFetchStatusEvent()),
|
||||
child: BlocBuilder<FlushMountedPresenceSensorBloc,
|
||||
FlushMountedPresenceSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is FlushMountedPresenceSensorLoadingInitialState ||
|
||||
state is FlushMountedPresenceSensorDeviceReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is FlushMountedPresenceSensorUpdateState) {
|
||||
return _buildGridView(context, state.model);
|
||||
} else if (state is FlushMountedPresenceSensorDeviceReportsState) {
|
||||
return ReportsTable(
|
||||
report: state.deviceReport,
|
||||
thirdColumnTitle:
|
||||
state.code == 'illuminance_value' ? "Value" : 'Status',
|
||||
thirdColumnDescription:
|
||||
state.code == 'illuminance_value' ? "Lux" : null,
|
||||
onRowTap: (index) {},
|
||||
onClose: () {
|
||||
context
|
||||
.read<FlushMountedPresenceSensorBloc>()
|
||||
.add(FlushMountedPresenceSensorBackToGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is FlushMountedPresenceSensorShowDescriptionState) {
|
||||
return DescriptionView(
|
||||
description: state.description,
|
||||
onClose: () {
|
||||
context
|
||||
.read<FlushMountedPresenceSensorBloc>()
|
||||
.add(FlushMountedPresenceSensorBackToGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is FlushMountedPresenceSensorDeviceReportsFailedState) {
|
||||
final model =
|
||||
context.read<FlushMountedPresenceSensorBloc>().deviceStatus;
|
||||
return _buildGridView(context, model);
|
||||
}
|
||||
return const Center(
|
||||
child: Text('Error fetching status', textAlign: TextAlign.center),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView(
|
||||
BuildContext context,
|
||||
FlushMountedPresenceSensorModel model,
|
||||
) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
PresenceState(
|
||||
value: model.presenceState,
|
||||
),
|
||||
PresenceDisplayValue(
|
||||
value: model.illuminance.toString(),
|
||||
postfix: 'Lux',
|
||||
description: 'Illuminance Value',
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.sensitivity.toDouble(),
|
||||
title: 'Sensitivity:',
|
||||
minValue: 0,
|
||||
maxValue: 9,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeSensitivity,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.nearDetection / 100).clamp(0.0, double.infinity),
|
||||
title: 'Nearest Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
maxValue: 9.5,
|
||||
steps: 0.1,
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeNearDetection,
|
||||
value: (value * 100).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.farDetection / 100).clamp(0.0, double.infinity),
|
||||
title: 'Max Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
maxValue: 9.5,
|
||||
steps: 0.1,
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeFarDetection,
|
||||
value: (value * 100).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.sensiReduce.toDouble(),
|
||||
title: 'Trigger Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.occurDistReduce.toDouble(),
|
||||
title: 'Indent Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.presenceDelay / 10).toDouble(),
|
||||
valuesPercision: 1,
|
||||
title: 'Target Confirm Time:',
|
||||
description: 's',
|
||||
minValue: 0.0,
|
||||
maxValue: 0.5,
|
||||
steps: 0.1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
value: (value * 10).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.noneDelay / 10).toDouble(),
|
||||
description: 's',
|
||||
title: 'Disappe Delay:',
|
||||
minValue: 20,
|
||||
maxValue: 300,
|
||||
steps: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeNoneDelay,
|
||||
value: (value * 10).round(),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorGetDeviceReportsEvent(
|
||||
code: 'presence_state',
|
||||
deviceUuid: device.uuid!,
|
||||
),
|
||||
),
|
||||
child: const PresenceStaticWidget(
|
||||
icon: Assets.presenceRecordIcon,
|
||||
description: 'Presence Record',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -106,14 +106,14 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
|
||||
ScheduleEntry newSchedule = ScheduleEntry(
|
||||
category: event.category,
|
||||
time: formatTimeOfDayToISO(event.time),
|
||||
function: Status(code: 'switch_1', value: event.functionOn),
|
||||
function: Status(code: 'doorcontact_state', value: event.functionOn),
|
||||
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
|
||||
);
|
||||
bool success =
|
||||
await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId);
|
||||
if (success) {
|
||||
add(FetchGarageDoorSchedulesEvent(
|
||||
deviceId: deviceId, category: 'switch_1'));
|
||||
deviceId: deviceId, category: 'doorcontact_state'));
|
||||
} else {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
@ -149,7 +149,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
|
||||
final updatedSchedules = deviceStatus.schedules?.map((schedule) {
|
||||
if (schedule.scheduleId == event.scheduleId) {
|
||||
return schedule.copyWith(
|
||||
function: Status(code: 'switch_1', value: event.functionOn),
|
||||
function:
|
||||
Status(code: 'doorcontact_state', value: event.functionOn),
|
||||
enable: event.enable,
|
||||
);
|
||||
}
|
||||
@ -274,7 +275,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
|
||||
|
||||
Future<void> _onBatchControl(
|
||||
GarageDoorBatchControlEvent event, Emitter<GarageDoorState> emit) async {
|
||||
final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false;
|
||||
final oldValue =
|
||||
event.code == 'doorcontact_state' ? deviceStatus.switch1 : false;
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(GarageDoorBatchStatusLoaded(deviceStatus));
|
||||
@ -409,7 +411,7 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
|
||||
void _revertValue(
|
||||
String code, dynamic oldValue, Emitter<GarageDoorState> emit) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
case 'doorcontact_state':
|
||||
if (oldValue is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: oldValue);
|
||||
}
|
||||
@ -468,10 +470,11 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
|
||||
deviceStatus = deviceStatus.copyWith(voiceControl1: value);
|
||||
}
|
||||
break;
|
||||
case 'door_contact_state':
|
||||
case 'doorcontact_state':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(doorContactState: value);
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -490,14 +493,14 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
|
||||
scheduleId: event.scheduleId,
|
||||
category: event.category,
|
||||
time: formatTimeOfDayToISO(event.time),
|
||||
function: Status(code: 'switch_1', value: event.functionOn),
|
||||
function: Status(code: 'doorcontact_state', value: event.functionOn),
|
||||
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
|
||||
);
|
||||
bool success = await DevicesManagementApi()
|
||||
.editScheduleRecord(deviceId, newSchedule);
|
||||
if (success) {
|
||||
add(FetchGarageDoorSchedulesEvent(
|
||||
deviceId: deviceId, category: 'switch_1'));
|
||||
deviceId: deviceId, category: 'doorcontact_state'));
|
||||
} else {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ 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 GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout {
|
||||
class GarageDoorControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const GarageDoorControlView({required this.deviceId, super.key});
|
||||
@ -22,7 +23,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)),
|
||||
create: (context) => GarageDoorBloc(deviceId: deviceId)
|
||||
..add(GarageDoorInitialEvent(deviceId)),
|
||||
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
|
||||
builder: (context, state) {
|
||||
if (state is GarageDoorLoadingState) {
|
||||
@ -34,7 +36,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
|
||||
garageDoorSensor: true,
|
||||
onRowTap: (index) {},
|
||||
onClose: () {
|
||||
context.read<GarageDoorBloc>().add(BackToGarageDoorGridViewEvent());
|
||||
context
|
||||
.read<GarageDoorBloc>()
|
||||
.add(BackToGarageDoorGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is GarageDoorLoadedState) {
|
||||
@ -71,11 +75,14 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
|
||||
children: [
|
||||
IconNameStatusContainer(
|
||||
isFullIcon: false,
|
||||
name: status.switch1 ? 'Opened' : 'Closed',
|
||||
icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor,
|
||||
name: status.doorContactState ? 'Opened' : 'Closed',
|
||||
icon: status.doorContactState ? Assets.openedDoor : Assets.closedDoor,
|
||||
onTap: () {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
GarageDoorControlEvent(deviceId: status.uuid, value: !status.switch1, code: 'switch_1'),
|
||||
GarageDoorControlEvent(
|
||||
deviceId: status.uuid,
|
||||
value: !status.switch1,
|
||||
code: 'doorcontact_state'),
|
||||
);
|
||||
},
|
||||
status: status.switch1,
|
||||
@ -84,7 +91,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
|
||||
IconNameStatusContainer(
|
||||
onTap: () {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'),
|
||||
FetchGarageDoorSchedulesEvent(
|
||||
deviceId: deviceId, category: 'doorcontact_state'),
|
||||
);
|
||||
showDialog(
|
||||
context: context,
|
||||
@ -107,7 +115,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<GarageDoorBloc>().add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid));
|
||||
context
|
||||
.read<GarageDoorBloc>()
|
||||
.add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.remove,
|
||||
@ -125,7 +135,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
|
||||
),
|
||||
Text(
|
||||
'h',
|
||||
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
|
||||
style: context.textTheme.bodySmall!
|
||||
.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
Text(
|
||||
(status.delay.inMinutes % 60).toString().padLeft(2, '0'),
|
||||
@ -136,11 +147,14 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
|
||||
),
|
||||
Text(
|
||||
'm',
|
||||
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
|
||||
style: context.textTheme.bodySmall!
|
||||
.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<GarageDoorBloc>().add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid));
|
||||
context
|
||||
.read<GarageDoorBloc>()
|
||||
.add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
@ -158,7 +172,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
|
||||
onChange: (value) {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
GarageDoorControlEvent(
|
||||
deviceId: status.uuid, value: value ? status.delay.inSeconds : 0, code: 'countdown_1'),
|
||||
deviceId: status.uuid,
|
||||
value: value ? status.delay.inSeconds : 0,
|
||||
code: 'countdown_1'),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -167,7 +183,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
|
||||
name: 'Records',
|
||||
icon: Assets.records,
|
||||
onTap: () {
|
||||
context.read<GarageDoorBloc>().add(FetchGarageDoorRecordsEvent(code: 'switch_1', deviceId: status.uuid));
|
||||
context.read<GarageDoorBloc>().add(FetchGarageDoorRecordsEvent(
|
||||
code: 'doorcontact_state', deviceId: status.uuid));
|
||||
},
|
||||
status: false,
|
||||
textColor: ColorsManager.blackColor,
|
||||
|
@ -217,29 +217,31 @@ class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
|
||||
try {
|
||||
var status =
|
||||
await DevicesManagementApi().getPowerClampInfo(event.deviceId);
|
||||
deviceStatus = PowerClampModel.fromJson(status);
|
||||
|
||||
deviceStatus = PowerClampModel.fromJson(status as Map<String, Object?>? ??{});
|
||||
final phaseADataPoints = deviceStatus.status.phaseA.dataPoints;
|
||||
final phaseBDataPoints = deviceStatus.status.phaseB.dataPoints;
|
||||
final phaseCDataPoints = deviceStatus.status.phaseC.dataPoints;
|
||||
phaseData = [
|
||||
{
|
||||
'name': 'Phase A',
|
||||
'voltage': '${deviceStatus.status.phaseA.dataPoints[0].value / 10} V',
|
||||
'current': '${deviceStatus.status.phaseA.dataPoints[1].value / 10} A',
|
||||
'activePower': '${deviceStatus.status.phaseA.dataPoints[2].value} W',
|
||||
'powerFactor': '${deviceStatus.status.phaseA.dataPoints[3].value}',
|
||||
'voltage': '${(phaseADataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
|
||||
'current': '${(phaseADataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
|
||||
'activePower': '${phaseADataPoints.elementAtOrNull(2)?.value??'N/A'} W',
|
||||
'powerFactor': '${phaseADataPoints.elementAtOrNull(3)?.value??'N/A'}',
|
||||
},
|
||||
{
|
||||
'name': 'Phase B',
|
||||
'voltage': '${deviceStatus.status.phaseB.dataPoints[0].value / 10} V',
|
||||
'current': '${deviceStatus.status.phaseB.dataPoints[1].value / 10} A',
|
||||
'activePower': '${deviceStatus.status.phaseB.dataPoints[2].value} W',
|
||||
'powerFactor': '${deviceStatus.status.phaseB.dataPoints[3].value}',
|
||||
'voltage': '${(phaseBDataPoints .elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
|
||||
'current': '${(phaseBDataPoints .elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
|
||||
'activePower': '${phaseBDataPoints.elementAtOrNull(2)?.value??'N/A'} W',
|
||||
'powerFactor': '${phaseBDataPoints.elementAtOrNull(3)?.value??'N/A'}',
|
||||
},
|
||||
{
|
||||
'name': 'Phase C',
|
||||
'voltage': '${deviceStatus.status.phaseC.dataPoints[0].value / 10} V',
|
||||
'current': '${deviceStatus.status.phaseC.dataPoints[1].value / 10} A',
|
||||
'activePower': '${deviceStatus.status.phaseC.dataPoints[2].value} W',
|
||||
'powerFactor': '${deviceStatus.status.phaseC.dataPoints[3].value}',
|
||||
'voltage': '${(phaseCDataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
|
||||
'current': '${(phaseCDataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
|
||||
'activePower': '${phaseCDataPoints.elementAtOrNull(2)?.value ?? 'N/A'} W',
|
||||
'powerFactor': '${phaseCDataPoints.elementAtOrNull(3)?.value ?? 'N/A'}',
|
||||
},
|
||||
];
|
||||
emit(GetDeviceStatus());
|
||||
@ -785,7 +787,7 @@ class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
|
||||
void selectDateRange() async {
|
||||
DateTime startDate = dateTime!;
|
||||
DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1)
|
||||
.subtract(Duration(days: 1));
|
||||
.subtract(const Duration(days: 1));
|
||||
String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate);
|
||||
endChartDate = ' - $formattedEndDate';
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ class PowerClampModel {
|
||||
|
||||
factory PowerClampModel.fromJson(Map<String, dynamic> json) {
|
||||
return PowerClampModel(
|
||||
productUuid: json['productUuid'],
|
||||
productType: json['productType'],
|
||||
status: PowerStatus.fromJson(json['status']),
|
||||
productUuid: json['productUuid'] as String? ?? '',
|
||||
productType: json['productType'] as String? ?? '',
|
||||
status: PowerStatus.fromJson(json['status'] as Map<String, dynamic>? ?? {}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ class PowerClampModel {
|
||||
return PowerClampModel(
|
||||
productUuid: productUuid ?? this.productUuid,
|
||||
productType: productType ?? this.productType,
|
||||
status: statusPower ?? this.status,
|
||||
status: statusPower ?? status,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -46,12 +46,10 @@ class PowerStatus {
|
||||
|
||||
factory PowerStatus.fromJson(Map<String, dynamic> json) {
|
||||
return PowerStatus(
|
||||
phaseA: Phase.fromJson(json['phaseA']),
|
||||
phaseB: Phase.fromJson(json['phaseB']),
|
||||
phaseC: Phase.fromJson(json['phaseC']),
|
||||
general: Phase.fromJson(json['general']
|
||||
// List<DataPoint>.from(
|
||||
// json['general'].map((x) => DataPoint.fromJson(x))),
|
||||
phaseA: Phase.fromJson(json['phaseA']as List<dynamic>? ?? []),
|
||||
phaseB: Phase.fromJson(json['phaseB']as List<dynamic>? ?? []),
|
||||
phaseC: Phase.fromJson(json['phaseC']as List<dynamic>? ?? []),
|
||||
general: Phase.fromJson(json['general']as List<dynamic>? ?? []
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -69,30 +67,30 @@ class Phase {
|
||||
}
|
||||
|
||||
class DataPoint {
|
||||
dynamic code;
|
||||
dynamic customName;
|
||||
dynamic dpId;
|
||||
dynamic time;
|
||||
dynamic type;
|
||||
dynamic value;
|
||||
final String? code;
|
||||
final String? customName;
|
||||
final int? dpId;
|
||||
final int? time;
|
||||
final String? type;
|
||||
final dynamic value;
|
||||
|
||||
DataPoint({
|
||||
required this.code,
|
||||
required this.customName,
|
||||
required this.dpId,
|
||||
required this.time,
|
||||
required this.type,
|
||||
required this.value,
|
||||
this.code,
|
||||
this.customName,
|
||||
this.dpId,
|
||||
this.time,
|
||||
this.type,
|
||||
this.value,
|
||||
});
|
||||
|
||||
factory DataPoint.fromJson(Map<String, dynamic> json) {
|
||||
return DataPoint(
|
||||
code: json['code'],
|
||||
customName: json['customName'],
|
||||
dpId: json['dpId'],
|
||||
time: json['time'],
|
||||
type: json['type'],
|
||||
value: json['value'],
|
||||
code: json['code'] as String?,
|
||||
customName: json['customName'] as String?,
|
||||
dpId: json['dpId'] as int?,
|
||||
time: json['time'] as int?,
|
||||
type: json['type'] as String?,
|
||||
value: json['value'] as dynamic,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class EnergyConsumptionPage extends StatefulWidget {
|
||||
@ -10,7 +10,8 @@ class EnergyConsumptionPage extends StatefulWidget {
|
||||
final Widget widget;
|
||||
final Function()? onTap;
|
||||
|
||||
EnergyConsumptionPage({
|
||||
const EnergyConsumptionPage({
|
||||
super.key,
|
||||
required this.chartData,
|
||||
required this.totalConsumption,
|
||||
required this.date,
|
||||
@ -91,11 +92,12 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.11,
|
||||
height: MediaQuery.sizeOf(context).height * 0.09,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(
|
||||
@ -151,7 +153,7 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
||||
child: RotatedBox(
|
||||
quarterTurns: -1,
|
||||
child: Text(_chartData[index].time,
|
||||
style: TextStyle(fontSize: 10)),
|
||||
style: const TextStyle(fontSize: 10)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -190,8 +192,8 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
||||
spots: _chartData
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) => FlSpot(entry.key.toDouble(),
|
||||
entry.value.consumption))
|
||||
.map((entry) => FlSpot(
|
||||
entry.key.toDouble(), entry.value.consumption))
|
||||
.toList(),
|
||||
isCurved: true,
|
||||
color: ColorsManager.primaryColor.withOpacity(0.6),
|
||||
@ -218,7 +220,7 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
border: Border.all(
|
||||
color: Color(0xff023DFE).withOpacity(0.7),
|
||||
color: const Color(0xff023DFE).withOpacity(0.7),
|
||||
width: 10,
|
||||
),
|
||||
),
|
||||
@ -253,11 +255,9 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
||||
child: InkWell(
|
||||
onTap: widget.onTap,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Text(widget.date),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Text(widget.date),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -12,8 +12,7 @@ import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
//Smart Power Clamp
|
||||
class SmartPowerDeviceControl extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const SmartPowerDeviceControl({super.key, required this.deviceId});
|
||||
@ -25,27 +24,27 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
..add(SmartPowerFetchDeviceEvent(deviceId)),
|
||||
child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
|
||||
builder: (context, state) {
|
||||
final _blocProvider = BlocProvider.of<SmartPowerBloc>(context);
|
||||
final blocProvider = BlocProvider.of<SmartPowerBloc>(context);
|
||||
|
||||
if (state is SmartPowerLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is FakeState) {
|
||||
return _buildStatusControls(
|
||||
currentPage: _blocProvider.currentPage,
|
||||
currentPage: blocProvider.currentPage,
|
||||
context: context,
|
||||
blocProvider: _blocProvider,
|
||||
blocProvider: blocProvider,
|
||||
);
|
||||
} else if (state is GetDeviceStatus) {
|
||||
return _buildStatusControls(
|
||||
currentPage: _blocProvider.currentPage,
|
||||
currentPage: blocProvider.currentPage,
|
||||
context: context,
|
||||
blocProvider: _blocProvider,
|
||||
blocProvider: blocProvider,
|
||||
);
|
||||
} else if (state is FilterRecordsState) {
|
||||
return _buildStatusControls(
|
||||
currentPage: _blocProvider.currentPage,
|
||||
currentPage: blocProvider.currentPage,
|
||||
context: context,
|
||||
blocProvider: _blocProvider,
|
||||
blocProvider: blocProvider,
|
||||
);
|
||||
}
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@ -60,7 +59,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
required SmartPowerBloc blocProvider,
|
||||
required int currentPage,
|
||||
}) {
|
||||
PageController _pageController = PageController(initialPage: currentPage);
|
||||
PageController pageController = PageController(initialPage: currentPage);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
child: DeviceControlsContainer(
|
||||
@ -85,25 +84,31 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
PowerClampInfoCard(
|
||||
iconPath: Assets.powerActiveIcon,
|
||||
title: 'Active',
|
||||
value: blocProvider
|
||||
.deviceStatus.status.general.dataPoints[2].value
|
||||
.toString(),
|
||||
value: blocProvider.deviceStatus.status.general.dataPoints
|
||||
.elementAtOrNull(2)
|
||||
?.value
|
||||
.toString() ??
|
||||
'',
|
||||
unit: '',
|
||||
),
|
||||
PowerClampInfoCard(
|
||||
iconPath: Assets.voltMeterIcon,
|
||||
title: 'Current',
|
||||
value: blocProvider
|
||||
.deviceStatus.status.general.dataPoints[1].value
|
||||
.toString(),
|
||||
value: blocProvider.deviceStatus.status.general.dataPoints
|
||||
.elementAtOrNull(1)
|
||||
?.value
|
||||
.toString() ??
|
||||
'',
|
||||
unit: ' A',
|
||||
),
|
||||
PowerClampInfoCard(
|
||||
iconPath: Assets.frequencyIcon,
|
||||
title: 'Frequency',
|
||||
value: blocProvider
|
||||
.deviceStatus.status.general.dataPoints[4].value
|
||||
.toString(),
|
||||
value: blocProvider.deviceStatus.status.general.dataPoints
|
||||
.elementAtOrNull(4)
|
||||
?.value
|
||||
.toString() ??
|
||||
'',
|
||||
unit: ' Hz',
|
||||
),
|
||||
],
|
||||
@ -142,7 +147,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
icon: const Icon(Icons.arrow_left),
|
||||
onPressed: () {
|
||||
blocProvider.add(SmartPowerArrowPressedEvent(-1));
|
||||
_pageController.previousPage(
|
||||
pageController.previousPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
@ -162,7 +167,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
icon: const Icon(Icons.arrow_right),
|
||||
onPressed: () {
|
||||
blocProvider.add(SmartPowerArrowPressedEvent(1));
|
||||
_pageController.nextPage(
|
||||
pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
@ -177,7 +182,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
controller: pageController,
|
||||
onPageChanged: (int page) {
|
||||
blocProvider.add(SmartPowerPageChangedEvent(page));
|
||||
},
|
||||
@ -190,8 +195,8 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
blocProvider.add(SelectDateEvent(context: context));
|
||||
blocProvider.add(FilterRecordsByDateEvent(
|
||||
selectedDate: blocProvider.dateTime!,
|
||||
viewType: blocProvider
|
||||
.views[blocProvider.currentIndex]));
|
||||
viewType:
|
||||
blocProvider.views[blocProvider.currentIndex]));
|
||||
},
|
||||
widget: blocProvider.dateSwitcher(),
|
||||
chartData: blocProvider.energyDataList.isNotEmpty
|
||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class PresenceUpdateData extends StatefulWidget {
|
||||
const PresenceUpdateData({
|
||||
@ -13,6 +14,7 @@ class PresenceUpdateData extends StatefulWidget {
|
||||
required this.maxValue,
|
||||
required this.steps,
|
||||
this.description,
|
||||
this.valuesPercision = 0,
|
||||
});
|
||||
|
||||
final String title;
|
||||
@ -22,6 +24,7 @@ class PresenceUpdateData extends StatefulWidget {
|
||||
final double steps;
|
||||
final Function action;
|
||||
final String? description;
|
||||
final int valuesPercision;
|
||||
|
||||
@override
|
||||
State<PresenceUpdateData> createState() => _CurrentTempState();
|
||||
@ -45,7 +48,7 @@ class _CurrentTempState extends State<PresenceUpdateData> {
|
||||
}
|
||||
|
||||
void _onValueChanged(double newValue) {
|
||||
widget.action(newValue.toInt());
|
||||
widget.action(newValue);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -62,11 +65,14 @@ class _CurrentTempState extends State<PresenceUpdateData> {
|
||||
children: [
|
||||
Text(
|
||||
widget.title,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 10),
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
IncrementDecrementWidget(
|
||||
value: widget.value.toString(),
|
||||
value: widget.value.toStringAsFixed(widget.valuesPercision),
|
||||
description: widget.description ?? '',
|
||||
descriptionColor: ColorsManager.blackColor,
|
||||
onIncrement: () {
|
||||
|
@ -84,6 +84,16 @@ class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(PresenceNoBodyTime oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.value != widget.value) {
|
||||
setState(() {
|
||||
_currentValue = widget.value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DeviceControlsContainer(
|
||||
|
@ -21,6 +21,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
on<ShowDescriptionEvent>(_showDescription);
|
||||
on<BackToGridViewEvent>(_backToGridView);
|
||||
on<WallSensorFactoryResetEvent>(_onFactoryReset);
|
||||
on<WallSensorRealtimeUpdateEvent>(_onRealtimeUpdate);
|
||||
}
|
||||
|
||||
void _fetchWallSensorStatus(
|
||||
@ -30,7 +31,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||
deviceStatus = WallSensorModel.fromJson(response.status);
|
||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||
_listenToChanges(emit, deviceId);
|
||||
_listenToChanges(deviceId);
|
||||
} catch (e) {
|
||||
emit(WallSensorFailedState(error: e.toString()));
|
||||
return;
|
||||
@ -52,28 +53,27 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
}
|
||||
}
|
||||
|
||||
_listenToChanges(Emitter<WallSensorState> emit, deviceId) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
Stream<DatabaseEvent> stream = ref.onValue;
|
||||
void _listenToChanges(String deviceId) {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
ref.onValue.listen((DatabaseEvent event) {
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
if (data == null) return;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
Map<dynamic, dynamic> usersMap =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
List<Status> statusList = [];
|
||||
final statusList = (data['status'] as List?)
|
||||
?.map((e) => Status(code: e['code'], value: e['value']))
|
||||
.toList();
|
||||
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList
|
||||
.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus = WallSensorModel.fromJson(statusList);
|
||||
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
||||
});
|
||||
} catch (_) {}
|
||||
if (statusList != null) {
|
||||
final updatedDeviceStatus = WallSensorModel.fromJson(statusList);
|
||||
if (!isClosed) {
|
||||
add(WallSensorRealtimeUpdateEvent(updatedDeviceStatus));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void _changeValue(
|
||||
WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
|
||||
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
||||
@ -195,4 +195,12 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
emit(WallSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onRealtimeUpdate(
|
||||
WallSensorRealtimeUpdateEvent event,
|
||||
Emitter<WallSensorState> emit,
|
||||
) {
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
|
||||
|
||||
abstract class WallSensorEvent extends Equatable {
|
||||
const WallSensorEvent();
|
||||
@ -70,3 +71,8 @@ class WallSensorFactoryResetEvent extends WallSensorEvent {
|
||||
required this.factoryReset,
|
||||
});
|
||||
}
|
||||
|
||||
class WallSensorRealtimeUpdateEvent extends WallSensorEvent {
|
||||
final WallSensorModel deviceStatus;
|
||||
const WallSensorRealtimeUpdateEvent(this.deviceStatus);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ 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/ceiling_sensor/ceiling_sensor_helper.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.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';
|
||||
@ -116,6 +117,15 @@ class DeviceDialogHelper {
|
||||
deviceSelectedFunctions: deviceSelectedFunctions,
|
||||
device: data['device'],
|
||||
);
|
||||
case 'NCPS':
|
||||
return FlushPresenceSensor.showFlushFunctionsDialog(
|
||||
context: context,
|
||||
functions: functions,
|
||||
uniqueCustomId: data['uniqueCustomId'],
|
||||
deviceSelectedFunctions: deviceSelectedFunctions,
|
||||
dialogType: dialogType,
|
||||
device: data['device'],
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
@ -5,7 +5,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.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/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
@ -15,13 +14,18 @@ class SaveRoutineHelper {
|
||||
static Future<void> showSaveRoutineDialog(BuildContext context) async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
builder: (context) {
|
||||
return BlocBuilder<RoutineBloc, RoutineState>(
|
||||
builder: (context, state) {
|
||||
final selectedConditionLabel = state.selectedAutomationOperator == 'and'
|
||||
? 'All Conditions are met'
|
||||
: 'Any Condition is met';
|
||||
|
||||
return AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Container(
|
||||
width: MediaQuery.sizeOf(context).width * 0.5,
|
||||
width: context.screenWidth * 0.5,
|
||||
height: 500,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@ -29,99 +33,42 @@ class SaveRoutineHelper {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DialogHeader('Create a scene: ${state.routineName ?? ""}'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Left side - IF
|
||||
Expanded(
|
||||
child: ListView(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
const Text(
|
||||
'IF:',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (state.isTabToRun)
|
||||
ListTile(
|
||||
leading: SvgPicture.asset(
|
||||
Assets.tabToRun,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
title: const Text('Tab to run'),
|
||||
),
|
||||
if (state.isAutomation)
|
||||
...state.ifItems.map((item) {
|
||||
final functions =
|
||||
state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||
return functionRow(item, context, functions);
|
||||
}),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
'Create a scene: ${state.routineName ?? ""}',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Right side - THEN items
|
||||
|
||||
Expanded(
|
||||
child: ListView(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
const Text(
|
||||
'THEN:',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...state.thenItems.map((item) {
|
||||
final functions =
|
||||
state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||
return functionRow(item, context, functions);
|
||||
}),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
_buildDivider(),
|
||||
_buildListsLabelRow(selectedConditionLabel),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 24,
|
||||
children: [
|
||||
_buildIfConditions(state, context),
|
||||
Container(
|
||||
width: 1,
|
||||
color: ColorsManager.greyColor.withValues(alpha: 0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
_buildThenActions(state, context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// if (state.errorMessage != null || state.errorMessage!.isNotEmpty)
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// child: Text(
|
||||
// state.errorMessage!,
|
||||
// style: const TextStyle(color: Colors.red),
|
||||
// ),
|
||||
// ),
|
||||
DialogFooter(
|
||||
onCancel: () => Navigator.pop(context),
|
||||
onConfirm: () async {
|
||||
if (state.isAutomation) {
|
||||
if (state.isUpdate ?? false) {
|
||||
context.read<RoutineBloc>().add(const UpdateAutomation());
|
||||
} else {
|
||||
context.read<RoutineBloc>().add(const CreateAutomationEvent());
|
||||
}
|
||||
} else {
|
||||
if (state.isUpdate ?? false) {
|
||||
context.read<RoutineBloc>().add(const UpdateScene());
|
||||
} else {
|
||||
context.read<RoutineBloc>().add(const CreateSceneEvent());
|
||||
}
|
||||
}
|
||||
// if (state.errorMessage == null || state.errorMessage!.isEmpty) {
|
||||
Navigator.pop(context);
|
||||
// }
|
||||
},
|
||||
isConfirmEnabled: true,
|
||||
),
|
||||
_buildDivider(),
|
||||
const SizedBox(height: 8),
|
||||
_buildDialogFooter(context, state),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -132,8 +79,107 @@ class SaveRoutineHelper {
|
||||
);
|
||||
}
|
||||
|
||||
static Container _buildDivider() {
|
||||
return Container(
|
||||
height: 1,
|
||||
width: double.infinity,
|
||||
color: ColorsManager.greyColor,
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildListsLabelRow(String selectedConditionLabel) {
|
||||
const textStyle = TextStyle(
|
||||
fontSize: 16,
|
||||
);
|
||||
return Container(
|
||||
color: ColorsManager.backgroundColor.withValues(alpha: 0.5),
|
||||
padding: const EdgeInsetsDirectional.all(20),
|
||||
child: Row(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Expanded(child: Text('IF: $selectedConditionLabel', style: textStyle)),
|
||||
const Expanded(child: Text('THEN:', style: textStyle)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildDialogFooter(BuildContext context, RoutineState state) {
|
||||
return Row(
|
||||
spacing: 16,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
DialogFooterButton(
|
||||
text: 'Cancel',
|
||||
onTap: () => Navigator.pop(context),
|
||||
),
|
||||
DialogFooterButton(
|
||||
text: 'Confirm',
|
||||
onTap: () {
|
||||
if (state.isAutomation) {
|
||||
if (state.isUpdate ?? false) {
|
||||
context.read<RoutineBloc>().add(const UpdateAutomation());
|
||||
} else {
|
||||
context.read<RoutineBloc>().add(const CreateAutomationEvent());
|
||||
}
|
||||
} else {
|
||||
if (state.isUpdate ?? false) {
|
||||
context.read<RoutineBloc>().add(const UpdateScene());
|
||||
} else {
|
||||
context.read<RoutineBloc>().add(const CreateSceneEvent());
|
||||
}
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
},
|
||||
textColor: ColorsManager.primaryColorWithOpacity,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildThenActions(RoutineState state, BuildContext context) {
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
// shrinkWrap: true,
|
||||
children: state.thenItems.map((item) {
|
||||
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||
return functionRow(item, context, functions);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildIfConditions(RoutineState state, BuildContext context) {
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
// shrinkWrap: true,
|
||||
children: [
|
||||
if (state.isTabToRun)
|
||||
ListTile(
|
||||
leading: SvgPicture.asset(
|
||||
Assets.tabToRun,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
title: const Text('Tab to run'),
|
||||
),
|
||||
if (state.isAutomation)
|
||||
...state.ifItems.map((item) {
|
||||
final functions =
|
||||
state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||
return functionRow(item, context, functions);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget functionRow(
|
||||
dynamic item, BuildContext context, List<DeviceFunctionData> functions) {
|
||||
dynamic item,
|
||||
BuildContext context,
|
||||
List<DeviceFunctionData> functions,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Row(
|
||||
@ -142,19 +188,36 @@ class SaveRoutineHelper {
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: 8,
|
||||
spacing: 17,
|
||||
children: [
|
||||
item['type'] == 'tap_to_run' || item['type'] == 'scene'
|
||||
? Image.memory(
|
||||
base64Decode(item['icon']),
|
||||
width: 22,
|
||||
height: 22,
|
||||
)
|
||||
: SvgPicture.asset(
|
||||
item['imagePath'],
|
||||
width: 22,
|
||||
height: 22,
|
||||
),
|
||||
Container(
|
||||
width: 22,
|
||||
height: 22,
|
||||
padding: const EdgeInsetsDirectional.all(4),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
border: Border.all(
|
||||
color: ColorsManager.neutralGray,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: item['type'] == 'tap_to_run' || item['type'] == 'scene'
|
||||
? Image.memory(
|
||||
base64Decode(item['icon']),
|
||||
width: 12,
|
||||
height: 22,
|
||||
fit: BoxFit.scaleDown,
|
||||
)
|
||||
: SvgPicture.asset(
|
||||
item['imagePath'],
|
||||
width: 12,
|
||||
height: 12,
|
||||
fit: BoxFit.scaleDown,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
@ -166,19 +229,25 @@ class SaveRoutineHelper {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
fontSize: 14,
|
||||
fontSize: 15,
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 16,
|
||||
spacing: 4,
|
||||
children: functions
|
||||
.map((f) => Text(
|
||||
'${f.operationName}: ${f.value}',
|
||||
style: context.textTheme.bodySmall
|
||||
?.copyWith(color: ColorsManager.grayColor, fontSize: 8),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
))
|
||||
.map(
|
||||
(function) => Text(
|
||||
'${function.operationName}: ${function.value}',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 8,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
@ -197,7 +266,13 @@ class SaveRoutineHelper {
|
||||
child: Row(
|
||||
spacing: 2,
|
||||
children: [
|
||||
SizedBox(width: 8, height: 8, child: SvgPicture.asset(Assets.deviceTagIcon)),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
height: 8,
|
||||
child: SvgPicture.asset(
|
||||
Assets.deviceTagIcon,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item['tag'] ?? '',
|
||||
textAlign: TextAlign.center,
|
||||
@ -218,7 +293,12 @@ class SaveRoutineHelper {
|
||||
spacing: 2,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 8, height: 8, child: SvgPicture.asset(Assets.spaceLocationIcon)),
|
||||
width: 8,
|
||||
height: 8,
|
||||
child: SvgPicture.asset(
|
||||
Assets.spaceLocationIcon,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item['subSpace'] ?? '',
|
||||
textAlign: TextAlign.center,
|
||||
|
407
lib/pages/routines/models/flush/flush_functions.dart
Normal file
407
lib/pages/routines/models/flush/flush_functions.dart
Normal file
@ -0,0 +1,407 @@
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
abstract class FlushFunctions
|
||||
extends DeviceFunction<FlushMountedPresenceSensorModel> {
|
||||
final String type;
|
||||
|
||||
FlushFunctions({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.code,
|
||||
required super.operationName,
|
||||
required super.icon,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
List<FlushOperationalValue> getOperationalValues();
|
||||
}
|
||||
|
||||
class FlushPresenceDelayFunction extends FlushFunctions {
|
||||
final int min;
|
||||
FlushPresenceDelayFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 0,
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codePresenceState,
|
||||
operationName: 'Presence State',
|
||||
icon: Assets.presenceStateIcon,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
return [
|
||||
FlushOperationalValue(
|
||||
icon: Assets.nobodyTime,
|
||||
description: 'None',
|
||||
value: "none",
|
||||
),
|
||||
FlushOperationalValue(
|
||||
icon: Assets.presenceStateIcon,
|
||||
description: 'Presence',
|
||||
value: 'presence',
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class FlushSensiReduceFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final int max;
|
||||
final int step;
|
||||
|
||||
FlushSensiReduceFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 1,
|
||||
max = 5,
|
||||
step = 1,
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
operationName: 'Sensitivity Reduction',
|
||||
icon: Assets.motionlessDetectionSensitivityIcon,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
return List.generate(
|
||||
(max - min) ~/ step + 1,
|
||||
(index) => FlushOperationalValue(
|
||||
icon: Assets.currentDistanceIcon,
|
||||
description: '${min + (index * step)}',
|
||||
value: min + (index * step),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class FlushNoneDelayFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final int max;
|
||||
final String unit;
|
||||
|
||||
FlushNoneDelayFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 10,
|
||||
max = 10000,
|
||||
unit = '秒',
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codeNoneDelay,
|
||||
operationName: 'None Delay',
|
||||
icon: Assets.nobodyTime,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
return [
|
||||
FlushOperationalValue(
|
||||
icon: icon,
|
||||
description: 'Custom $unit',
|
||||
value: null,
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class FlushIlluminanceFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final int max;
|
||||
final int step;
|
||||
|
||||
FlushIlluminanceFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 0,
|
||||
max = 2000,
|
||||
step = 0,
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codeIlluminance,
|
||||
operationName: 'Illuminance',
|
||||
icon: Assets.IlluminanceIcon,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
List<FlushOperationalValue> values = [];
|
||||
for (int lux = min; lux <= max; lux += step) {
|
||||
values.add(FlushOperationalValue(
|
||||
icon: Assets.IlluminanceIcon,
|
||||
description: "$lux Lux",
|
||||
value: lux,
|
||||
));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
class FlushOccurDistReduceFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final int max;
|
||||
final int step;
|
||||
|
||||
FlushOccurDistReduceFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 0,
|
||||
max = 100,
|
||||
step = 1,
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
|
||||
operationName: 'Occurrence Distance Reduction',
|
||||
icon: Assets.assetsTempreture,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
return List.generate(
|
||||
(max - min) ~/ step + 1,
|
||||
(index) => FlushOperationalValue(
|
||||
icon: Assets.assetsTempreture,
|
||||
description: '${min + (index * step)}',
|
||||
value: min + (index * step),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// ==== then functions ====
|
||||
class FlushSensitivityFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final int max;
|
||||
final int step;
|
||||
|
||||
FlushSensitivityFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 1,
|
||||
max = 9,
|
||||
step = 1,
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codeSensitivity,
|
||||
operationName: 'Sensitivity',
|
||||
icon: Assets.sensitivity,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
return List.generate(
|
||||
(max - min) ~/ step + 1,
|
||||
(index) => FlushOperationalValue(
|
||||
icon: Assets.motionDetectionSensitivityValueIcon,
|
||||
description: '${min + (index * step)}',
|
||||
value: min + (index * step),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class FlushNearDetectionFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final double max;
|
||||
final int step;
|
||||
final String unit;
|
||||
|
||||
FlushNearDetectionFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 0,
|
||||
max = 9.5,
|
||||
step = 1,
|
||||
unit = 'm',
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codeNearDetection,
|
||||
operationName: 'Nearest Detect Dist',
|
||||
icon: Assets.currentDistanceIcon,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
final values = <FlushOperationalValue>[];
|
||||
for (var value = min; value <= max; value += step) {
|
||||
values.add(FlushOperationalValue(
|
||||
icon: Assets.nobodyTime,
|
||||
description: '$value $unit',
|
||||
value: value * 10,
|
||||
));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
class FlushMaxDetectDistFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final int max;
|
||||
final int step;
|
||||
final String unit;
|
||||
|
||||
FlushMaxDetectDistFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 75,
|
||||
max = 600,
|
||||
step = 75,
|
||||
unit = 'cm',
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codeFarDetection,
|
||||
operationName: 'Max Detect Dist',
|
||||
icon: Assets.currentDistanceIcon,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
final values = <FlushOperationalValue>[];
|
||||
for (var value = min; value <= max; value += step) {
|
||||
values.add(FlushOperationalValue(
|
||||
icon: Assets.nobodyTime,
|
||||
description: '$value $unit',
|
||||
value: value,
|
||||
));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
class FlushTargetConfirmTimeFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final int max;
|
||||
final int step;
|
||||
final String unit;
|
||||
|
||||
FlushTargetConfirmTimeFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 75,
|
||||
max = 600,
|
||||
step = 75,
|
||||
unit = 'cm',
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
operationName: 'Target Confirm Time',
|
||||
icon: Assets.targetConfirmTimeIcon,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
final values = <FlushOperationalValue>[];
|
||||
for (var value = min; value <= max; value += step) {
|
||||
values.add(FlushOperationalValue(
|
||||
icon: Assets.nobodyTime,
|
||||
description: '$value $unit',
|
||||
value: value,
|
||||
));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
class FlushDisappeDelayFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final int max;
|
||||
final int step;
|
||||
final String unit;
|
||||
|
||||
FlushDisappeDelayFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 75,
|
||||
max = 600,
|
||||
step = 75,
|
||||
unit = 'cm',
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codeNoneDelay,
|
||||
operationName: 'Disappe Delay',
|
||||
icon: Assets.DisappeDelayIcon,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
final values = <FlushOperationalValue>[];
|
||||
for (var value = min; value <= max; value += step) {
|
||||
values.add(FlushOperationalValue(
|
||||
icon: Assets.nobodyTime,
|
||||
description: '$value $unit',
|
||||
value: value,
|
||||
));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
class FlushIndentLevelFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final int max;
|
||||
final int step;
|
||||
final String unit;
|
||||
|
||||
FlushIndentLevelFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 75,
|
||||
max = 600,
|
||||
step = 75,
|
||||
unit = 'cm',
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
|
||||
operationName: 'Indent Level',
|
||||
icon: Assets.indentLevelIcon,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
final values = <FlushOperationalValue>[];
|
||||
for (var value = min; value <= max; value += step) {
|
||||
values.add(FlushOperationalValue(
|
||||
icon: Assets.nobodyTime,
|
||||
description: '$value $unit',
|
||||
value: value,
|
||||
));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
class FlushTriggerLevelFunction extends FlushFunctions {
|
||||
final int min;
|
||||
final int max;
|
||||
final int step;
|
||||
final String unit;
|
||||
|
||||
FlushTriggerLevelFunction({
|
||||
required super.deviceId,
|
||||
required super.deviceName,
|
||||
required super.type,
|
||||
}) : min = 75,
|
||||
max = 600,
|
||||
step = 75,
|
||||
unit = 'cm',
|
||||
super(
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
operationName: 'Trigger Level',
|
||||
icon: Assets.triggerLevelIcon,
|
||||
);
|
||||
|
||||
@override
|
||||
List<FlushOperationalValue> getOperationalValues() {
|
||||
final values = <FlushOperationalValue>[];
|
||||
for (var value = min; value <= max; value += step) {
|
||||
values.add(FlushOperationalValue(
|
||||
icon: Assets.nobodyTime,
|
||||
description: '$value $unit',
|
||||
value: value,
|
||||
));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
11
lib/pages/routines/models/flush/flush_operational_value.dart
Normal file
11
lib/pages/routines/models/flush/flush_operational_value.dart
Normal file
@ -0,0 +1,11 @@
|
||||
class FlushOperationalValue {
|
||||
final String icon;
|
||||
final String description;
|
||||
final dynamic value;
|
||||
|
||||
FlushOperationalValue({
|
||||
required this.icon,
|
||||
required this.description,
|
||||
required this.value,
|
||||
});
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/create_new_routines/create_new_routines.dart';
|
||||
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
|
||||
@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routi
|
||||
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class RoutinesView extends StatefulWidget {
|
||||
const RoutinesView({super.key});
|
||||
@ -27,9 +28,10 @@ class _RoutinesViewState extends State<RoutinesView> {
|
||||
if (result == null) return;
|
||||
final communityId = result['community'];
|
||||
final spaceId = result['space'];
|
||||
final _bloc = BlocProvider.of<CreateRoutineBloc>(context);
|
||||
final bloc = BlocProvider.of<CreateRoutineBloc>(context);
|
||||
final routineBloc = context.read<RoutineBloc>();
|
||||
_bloc.add(SaveCommunityIdAndSpaceIdEvent(communityID: communityId, spaceID: spaceId));
|
||||
bloc.add(SaveCommunityIdAndSpaceIdEvent(
|
||||
communityID: communityId, spaceID: spaceId));
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true));
|
||||
}
|
||||
@ -54,22 +56,26 @@ class _RoutinesViewState extends State<RoutinesView> {
|
||||
),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: ListView(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
height: MediaQuery.sizeOf(context).height,
|
||||
child: SizedBox(
|
||||
height: context.screenHeight,
|
||||
width: context.screenWidth,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsetsDirectional.all(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Text(
|
||||
"Create New Routines",
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style:
|
||||
Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
RoutineViewCard(
|
||||
isLoading: false,
|
||||
onChanged: (v) {},
|
||||
@ -84,13 +90,11 @@ class _RoutinesViewState extends State<RoutinesView> {
|
||||
icon: Icons.add,
|
||||
textString: '',
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
const Expanded(child: FetchRoutineScenesAutomation()),
|
||||
const FetchRoutineScenesAutomation(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
@ -28,32 +28,40 @@ class DialogFooter extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildFooterButton(
|
||||
context: context,
|
||||
DialogFooterButton(
|
||||
text: 'Cancel',
|
||||
onTap: onCancel,
|
||||
),
|
||||
if (isConfirmEnabled) ...[
|
||||
Container(width: 1, height: 50, color: ColorsManager.greyColor),
|
||||
_buildFooterButton(
|
||||
context: context,
|
||||
DialogFooterButton(
|
||||
text: 'Confirm',
|
||||
onTap: onConfirm,
|
||||
textColor:
|
||||
isConfirmEnabled ? ColorsManager.primaryColorWithOpacity : Colors.red,
|
||||
textColor: isConfirmEnabled
|
||||
? ColorsManager.primaryColorWithOpacity
|
||||
: Colors.red,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildFooterButton({
|
||||
required BuildContext context,
|
||||
required String text,
|
||||
required VoidCallback? onTap,
|
||||
Color? textColor,
|
||||
}) {
|
||||
class DialogFooterButton extends StatelessWidget {
|
||||
const DialogFooterButton({
|
||||
required this.text,
|
||||
required this.onTap,
|
||||
this.textColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final VoidCallback? onTap;
|
||||
final Color? textColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
|
@ -16,6 +16,7 @@ class DialogHeader extends StatelessWidget {
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
@ -225,7 +225,7 @@ class DraggableCard extends StatelessWidget {
|
||||
if (function.functionCode == 'temp_set' || function.functionCode == 'temp_current') {
|
||||
return '${(function.value / 10).toStringAsFixed(0)}°C';
|
||||
} else if (function.functionCode.contains('countdown')) {
|
||||
final seconds = function.value.toInt();
|
||||
final seconds = function.value?.toInt() ?? 0;
|
||||
if (seconds >= 3600) {
|
||||
final hours = (seconds / 3600).floor();
|
||||
final remainingMinutes = ((seconds % 3600) / 60).floor();
|
||||
|
@ -28,10 +28,12 @@ class IfContainer extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('IF',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
style: TextStyle(
|
||||
fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
if (state.isAutomation && state.ifItems.isNotEmpty)
|
||||
AutomationOperatorSelector(
|
||||
selectedOperator: state.selectedAutomationOperator),
|
||||
selectedOperator:
|
||||
state.selectedAutomationOperator),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@ -55,16 +57,17 @@ class IfContainer extends StatelessWidget {
|
||||
(index) => GestureDetector(
|
||||
onTap: () async {
|
||||
if (!state.isTabToRun) {
|
||||
final result = await DeviceDialogHelper.showDeviceDialog(
|
||||
context: context,
|
||||
data: state.ifItems[index],
|
||||
removeComparetors: false,
|
||||
dialogType: "IF");
|
||||
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));
|
||||
context.read<RoutineBloc>().add(
|
||||
AddToIfContainer(
|
||||
state.ifItems[index], false));
|
||||
} else if (![
|
||||
'AC',
|
||||
'1G',
|
||||
@ -73,25 +76,32 @@ class IfContainer extends StatelessWidget {
|
||||
'WPS',
|
||||
'GW',
|
||||
'CPS',
|
||||
].contains(state.ifItems[index]['productType'])) {
|
||||
context
|
||||
.read<RoutineBloc>()
|
||||
.add(AddToIfContainer(state.ifItems[index], false));
|
||||
'NCPS'
|
||||
].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']));
|
||||
},
|
||||
),
|
||||
)),
|
||||
@ -112,7 +122,9 @@ 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(
|
||||
dialogType: 'IF',
|
||||
@ -121,10 +133,14 @@ class IfContainer extends StatelessWidget {
|
||||
removeComparetors: false);
|
||||
|
||||
if (result != null) {
|
||||
context.read<RoutineBloc>().add(AddToIfContainer(mutableData, false));
|
||||
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS']
|
||||
context
|
||||
.read<RoutineBloc>()
|
||||
.add(AddToIfContainer(mutableData, false));
|
||||
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', 'NCPS']
|
||||
.contains(mutableData['productType'])) {
|
||||
context.read<RoutineBloc>().add(AddToIfContainer(mutableData, false));
|
||||
context
|
||||
.read<RoutineBloc>()
|
||||
.add(AddToIfContainer(mutableData, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,7 +186,9 @@ class AutomationOperatorSelector extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
context.read<RoutineBloc>().add(const ChangeAutomationOperator(operator: 'or'));
|
||||
context
|
||||
.read<RoutineBloc>()
|
||||
.add(const ChangeAutomationOperator(operator: 'or'));
|
||||
},
|
||||
),
|
||||
Container(
|
||||
@ -196,7 +214,9 @@ class AutomationOperatorSelector extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
context.read<RoutineBloc>().add(const ChangeAutomationOperator(operator: 'and'));
|
||||
context
|
||||
.read<RoutineBloc>()
|
||||
.add(const ChangeAutomationOperator(operator: 'and'));
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -8,211 +8,188 @@ 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 FetchRoutineScenesAutomation extends StatefulWidget {
|
||||
const FetchRoutineScenesAutomation({super.key});
|
||||
|
||||
@override
|
||||
State<FetchRoutineScenesAutomation> createState() =>
|
||||
_FetchRoutineScenesState();
|
||||
}
|
||||
|
||||
class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
|
||||
class FetchRoutineScenesAutomation extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
const FetchRoutineScenesAutomation({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RoutineBloc, RoutineState>(
|
||||
builder: (context, state) {
|
||||
return state.isLoading
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: 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)
|
||||
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;
|
||||
if (state.isLoading)
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
|
||||
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: 3),
|
||||
if (state.automations.isEmpty)
|
||||
Text(
|
||||
"No automations found",
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
if (state.automations.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 200,
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildListTitle(context, "Scenes (Tab to Run)"),
|
||||
const SizedBox(height: 10),
|
||||
Visibility(
|
||||
visible: state.scenes.isNotEmpty,
|
||||
replacement: _buildEmptyState(context, "No scenes found"),
|
||||
child: SizedBox(
|
||||
height: 200,
|
||||
child: _buildScenes(state),
|
||||
),
|
||||
),
|
||||
);
|
||||
const SizedBox(height: 10),
|
||||
_buildListTitle(context, "Automations"),
|
||||
const SizedBox(height: 3),
|
||||
Visibility(
|
||||
visible: state.automations.isNotEmpty,
|
||||
replacement:
|
||||
_buildEmptyState(context, "No automations found"),
|
||||
child: SizedBox(
|
||||
height: 200,
|
||||
child: _buildAutomations(state),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAutomations(RoutineState state) {
|
||||
return ListView.builder(
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScenes(RoutineState state) {
|
||||
return ListView.builder(
|
||||
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: 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildListTitle(BuildContext context, String title) {
|
||||
return Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState(BuildContext context, String title) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 100),
|
||||
child: Text(
|
||||
title,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -66,7 +67,6 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Use widget.<mixinMethod> instead of just <mixinMethod>
|
||||
final double cardWidth = widget.isSmallScreenSize(context)
|
||||
? 120
|
||||
: widget.isMediumScreenSize(context)
|
||||
@ -127,22 +127,23 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
)
|
||||
else
|
||||
CupertinoSwitch(
|
||||
activeColor: ColorsManager.primaryColor,
|
||||
activeTrackColor: ColorsManager.primaryColor,
|
||||
value: widget.status == 'enable',
|
||||
onChanged: widget.onChanged,
|
||||
)
|
||||
],
|
||||
)
|
||||
: const SizedBox(),
|
||||
InkWell(
|
||||
onTap: widget.onTap,
|
||||
child: Column(
|
||||
children: [
|
||||
Center(
|
||||
Column(
|
||||
children: [
|
||||
Center(
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: widget.onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.graysColor,
|
||||
borderRadius: BorderRadius.circular(120),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.greyColor,
|
||||
width: 2.0,
|
||||
@ -158,7 +159,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) => Image.asset(
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
Image.asset(
|
||||
Assets.logo,
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
@ -171,7 +173,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
width: iconSize,
|
||||
fit: BoxFit.contain,
|
||||
)
|
||||
: (widget.icon is String && widget.icon.endsWith('.svg'))
|
||||
: (widget.icon is String &&
|
||||
widget.icon.endsWith('.svg'))
|
||||
? SvgPicture.asset(
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
@ -181,51 +184,54 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
: Icon(
|
||||
widget.icon,
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
size: widget.isSmallScreenSize(context) ? 30 : 40,
|
||||
size: widget.isSmallScreenSize(context)
|
||||
? 30
|
||||
: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 3),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
widget.textString,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 3),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
widget.textString,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
|
||||
),
|
||||
if (widget.spaceName != '')
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.spaceLocationIcon,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
if (widget.spaceName != '')
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.spaceLocationIcon,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
Text(
|
||||
widget.spaceName,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize:
|
||||
widget.isSmallScreenSize(context) ? 10 : 12,
|
||||
),
|
||||
Text(
|
||||
widget.spaceName,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -17,7 +17,16 @@ class _RoutineDevicesState extends State<RoutineDevices> {
|
||||
context.read<RoutineBloc>().add(FetchDevicesInRoutine());
|
||||
}
|
||||
|
||||
static const _allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS'};
|
||||
static const _allowedProductTypes = {
|
||||
'AC',
|
||||
'1G',
|
||||
'2G',
|
||||
'3G',
|
||||
'WPS',
|
||||
'GW',
|
||||
'CPS',
|
||||
'NCPS'
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -34,7 +43,8 @@ class _RoutineDevicesState extends State<RoutineDevices> {
|
||||
});
|
||||
|
||||
final deviceList = state.devices
|
||||
.where((device) => _allowedProductTypes.contains(device.productType))
|
||||
.where(
|
||||
(device) => _allowedProductTypes.contains(device.productType))
|
||||
.toList();
|
||||
|
||||
return Wrap(
|
||||
@ -51,12 +61,16 @@ class _RoutineDevicesState extends State<RoutineDevices> {
|
||||
'productType': device.productType,
|
||||
'functions': device.functions,
|
||||
'uniqueCustomId': '',
|
||||
'tag': device.deviceTags!.isNotEmpty ? device.deviceTags![0].name : '',
|
||||
'tag': device.deviceTags!.isNotEmpty
|
||||
? device.deviceTags![0].name
|
||||
: '',
|
||||
'subSpace': device.deviceSubSpace?.subspaceName ?? '',
|
||||
};
|
||||
|
||||
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: deviceData['imagePath'] as String,
|
||||
title: deviceData['title'] as String,
|
||||
|
@ -1,16 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/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/ac/ac_function.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
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 ACHelper {
|
||||
static Future<Map<String, dynamic>?> showACFunctionsDialog({
|
||||
@ -74,13 +75,26 @@ class ACHelper {
|
||||
child: _buildFunctionsList(
|
||||
context: context,
|
||||
acFunctions: acFunctions,
|
||||
onFunctionSelected:
|
||||
(functionCode, operationName) => context
|
||||
.read<FunctionBloc>()
|
||||
.add(SelectFunction(
|
||||
functionCode: functionCode,
|
||||
operationName: operationName,
|
||||
)),
|
||||
device: device,
|
||||
onFunctionSelected: (functionCode, operationName) {
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: functionCode,
|
||||
functionOperationName: operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'temp_set',
|
||||
'temp_current',
|
||||
],
|
||||
defaultValue: functionCode == 'temp_set'
|
||||
? 200
|
||||
: functionCode == 'temp_current'
|
||||
? -100
|
||||
: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// Value selector
|
||||
@ -139,6 +153,7 @@ class ACHelper {
|
||||
required BuildContext context,
|
||||
required List<ACFunction> acFunctions,
|
||||
required Function(String, String) onFunctionSelected,
|
||||
required AllDevicesModel? device,
|
||||
}) {
|
||||
return ListView.separated(
|
||||
shrinkWrap: false,
|
||||
@ -191,8 +206,9 @@ class ACHelper {
|
||||
required String operationName,
|
||||
bool? removeComparators,
|
||||
}) {
|
||||
final initialVal = selectedFunction == 'temp_set' ? 200 : -100;
|
||||
if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') {
|
||||
final initialValue = selectedFunctionData?.value ?? 250;
|
||||
final initialValue = selectedFunctionData?.value ?? initialVal;
|
||||
return _buildTemperatureSelector(
|
||||
context: context,
|
||||
initialValue: initialValue,
|
||||
@ -205,8 +221,7 @@ class ACHelper {
|
||||
);
|
||||
}
|
||||
|
||||
final selectedFn =
|
||||
acFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final values = selectedFn.getOperationalValues();
|
||||
|
||||
return _buildOperationalValuesList(
|
||||
@ -287,7 +302,9 @@ class ACHelper {
|
||||
functionCode: selectCode,
|
||||
operationName: operationName,
|
||||
condition: conditions[index],
|
||||
value: selectedFunctionData?.value,
|
||||
value: selectedFunctionData?.value ?? selectCode == 'temp_set'
|
||||
? 200
|
||||
: -100,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
@ -302,8 +319,7 @@ 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(),
|
||||
);
|
||||
}
|
||||
@ -317,6 +333,7 @@ class ACHelper {
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
final initialVal = selectCode == 'temp_set' ? 200 : -100;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
@ -324,7 +341,7 @@ class ACHelper {
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
'${(initialValue ?? 200) / 10}°C',
|
||||
'${(initialValue ?? initialVal) / 10}°C',
|
||||
style: context.textTheme.headlineMedium!.copyWith(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
),
|
||||
@ -397,9 +414,7 @@ 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
|
||||
@ -415,8 +430,7 @@ class ACHelper {
|
||||
operationName: operationName,
|
||||
value: value.value,
|
||||
condition: selectedFunctionData?.condition,
|
||||
valueDescription:
|
||||
selectedFunctionData?.valueDescription,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -131,7 +131,12 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
CpsFunctionsList(cpsFunctions: _cpsFunctions),
|
||||
CpsFunctionsList(
|
||||
cpsFunctions: _cpsFunctions,
|
||||
device: widget.device,
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
dialogType: widget.dialogType,
|
||||
),
|
||||
if (state.selectedFunction != null)
|
||||
Expanded(
|
||||
child: isToggleFunction
|
||||
|
@ -46,7 +46,7 @@ class CpsDialogSliderSelector extends StatelessWidget {
|
||||
functionCode: selectedFunction,
|
||||
operationName: operationName,
|
||||
condition: condition,
|
||||
value: selectedFunctionData.value,
|
||||
value: selectedFunctionData.value ?? 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1,14 +1,24 @@
|
||||
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/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class CpsFunctionsList extends StatelessWidget {
|
||||
const CpsFunctionsList({required this.cpsFunctions, super.key});
|
||||
const CpsFunctionsList({
|
||||
required this.cpsFunctions,
|
||||
required this.device,
|
||||
required this.selectedFunctionData,
|
||||
required this.dialogType,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<CpsFunctions> cpsFunctions;
|
||||
final AllDevicesModel? device;
|
||||
final DeviceFunctionData? selectedFunctionData;
|
||||
final String dialogType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -26,12 +36,27 @@ class CpsFunctionsList extends StatelessWidget {
|
||||
return RoutineDialogFunctionListTile(
|
||||
iconPath: function.icon,
|
||||
operationName: function.operationName,
|
||||
onTap: () => context.read<FunctionBloc>().add(
|
||||
SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
),
|
||||
),
|
||||
onTap: () => RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName: function.operationName,
|
||||
functionValueDescription: selectedFunctionData?.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'static_max_dis',
|
||||
'presence_reference',
|
||||
'moving_reference',
|
||||
'perceptual_boundary',
|
||||
'moving_boundary',
|
||||
'moving_rigger_time',
|
||||
'moving_static_time',
|
||||
'none_body_time',
|
||||
'moving_max_dis',
|
||||
'moving_range',
|
||||
'presence_range',
|
||||
if (dialogType == "IF") 'sensitivity',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart';
|
||||
|
||||
class FlushOperationalValuesList extends StatelessWidget {
|
||||
final List<FlushOperationalValue> values;
|
||||
final dynamic selectedValue;
|
||||
final AllDevicesModel? device;
|
||||
final String operationName;
|
||||
final String selectCode;
|
||||
final ValueChanged<FlushOperationalValue> onSelect;
|
||||
const FlushOperationalValuesList({
|
||||
required this.values,
|
||||
required this.selectedValue,
|
||||
required this.device,
|
||||
required this.operationName,
|
||||
required this.selectCode,
|
||||
required this.onSelect,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(20),
|
||||
itemCount: values.length,
|
||||
itemBuilder: (context, index) =>
|
||||
_buildValueItem(context, values[index]),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Widget _buildValueItem(BuildContext context, FlushOperationalValue value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(child: _buildValueDescription(value)),
|
||||
_buildValueRadio(context, value),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Widget _buildValueDescription(FlushOperationalValue value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(value.description),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildValueRadio(context, FlushOperationalValue value) {
|
||||
return Radio<dynamic>(
|
||||
value: value.value,
|
||||
groupValue: selectedValue,
|
||||
onChanged: (_) => onSelect(value));
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/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/flush/flush_functions.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/flush_presence_sensor/flush_value_selector_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class FlushPresenceSensor extends StatefulWidget {
|
||||
final List<DeviceFunction> functions;
|
||||
final AllDevicesModel? device;
|
||||
final List<DeviceFunctionData>? deviceSelectedFunctions;
|
||||
final String? uniqueCustomId;
|
||||
final String dialogType;
|
||||
final bool removeComparetors;
|
||||
|
||||
const FlushPresenceSensor({
|
||||
super.key,
|
||||
required this.functions,
|
||||
this.device,
|
||||
this.deviceSelectedFunctions,
|
||||
this.uniqueCustomId,
|
||||
required this.dialogType,
|
||||
this.removeComparetors = false,
|
||||
});
|
||||
|
||||
static Future<Map<String, dynamic>?> showFlushFunctionsDialog({
|
||||
required BuildContext context,
|
||||
required List<DeviceFunction> functions,
|
||||
AllDevicesModel? device,
|
||||
List<DeviceFunctionData>? deviceSelectedFunctions,
|
||||
String? uniqueCustomId,
|
||||
required String dialogType,
|
||||
bool removeComparetors = false,
|
||||
}) async {
|
||||
return showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (context) => FlushPresenceSensor(
|
||||
functions: functions,
|
||||
device: device,
|
||||
deviceSelectedFunctions: deviceSelectedFunctions,
|
||||
uniqueCustomId: uniqueCustomId,
|
||||
removeComparetors: removeComparetors,
|
||||
dialogType: dialogType,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
State<FlushPresenceSensor> createState() => _WallPresenceSensorState();
|
||||
}
|
||||
|
||||
class _WallPresenceSensorState extends State<FlushPresenceSensor> {
|
||||
late final List<FlushFunctions> _flushFunctions;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_flushFunctions =
|
||||
widget.functions.whereType<FlushFunctions>().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: _flushFunctions.length,
|
||||
separatorBuilder: (context, index) => const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 40.0),
|
||||
child: Divider(color: ColorsManager.dividerColor),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final function = _flushFunctions[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: FlushValueSelectorWidget(
|
||||
selectedFunction: selectedFunction,
|
||||
functionData: functionData,
|
||||
flushFunctions: _flushFunctions,
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
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/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_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/flush/flush_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
|
||||
|
||||
class FlushValueSelectorWidget extends StatelessWidget {
|
||||
final String selectedFunction;
|
||||
final DeviceFunctionData functionData;
|
||||
final List<FlushFunctions> flushFunctions;
|
||||
final AllDevicesModel? device;
|
||||
final String dialogType;
|
||||
final bool removeComparators;
|
||||
|
||||
const FlushValueSelectorWidget({
|
||||
required this.selectedFunction,
|
||||
required this.functionData,
|
||||
required this.flushFunctions,
|
||||
required this.device,
|
||||
required this.dialogType,
|
||||
required this.removeComparators,
|
||||
super.key,
|
||||
});
|
||||
|
||||
(double, double) get sliderRange => switch (functionData.functionCode) {
|
||||
FlushMountedPresenceSensorModel.codeOccurDistReduce => (0, 3),
|
||||
FlushMountedPresenceSensorModel.codeNoneDelay => (200, 3000),
|
||||
FlushMountedPresenceSensorModel.codeSensiReduce => (0, 3),
|
||||
FlushMountedPresenceSensorModel.codePresenceDelay => (0, 5),
|
||||
FlushMountedPresenceSensorModel.codeIlluminance => (0.0, 2000.0),
|
||||
FlushMountedPresenceSensorModel.codeFarDetection => (0.0, 9.5),
|
||||
FlushMountedPresenceSensorModel.codeNearDetection => (0.0, 9.5),
|
||||
_ => (0.0, 100.0),
|
||||
};
|
||||
|
||||
double get stepSize => switch (functionData.functionCode) {
|
||||
FlushMountedPresenceSensorModel.codeNoneDelay => 10.0,
|
||||
FlushMountedPresenceSensorModel.codeFarDetection => 0.5,
|
||||
FlushMountedPresenceSensorModel.codeNearDetection => 0.5,
|
||||
FlushMountedPresenceSensorModel.codePresenceDelay => 1.0,
|
||||
FlushMountedPresenceSensorModel.codeOccurDistReduce => 1.0,
|
||||
FlushMountedPresenceSensorModel.codeSensiReduce => 1.0,
|
||||
_ => 1.0,
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedFn = flushFunctions.firstWhere(
|
||||
(f) => f.code == selectedFunction,
|
||||
orElse: () => throw Exception('Function $selectedFunction not found'),
|
||||
);
|
||||
|
||||
if (_isSliderFunction(selectedFunction)) {
|
||||
final isNearDetection =
|
||||
selectedFunction == FlushMountedPresenceSensorModel.codeNearDetection;
|
||||
final isFarDetection =
|
||||
selectedFunction == FlushMountedPresenceSensorModel.codeFarDetection;
|
||||
|
||||
final isDistanceDetection = isNearDetection || isFarDetection;
|
||||
double initialValue = (functionData.value as num?)?.toDouble() ?? 0.0;
|
||||
|
||||
if (isDistanceDetection) {
|
||||
initialValue = initialValue / 100;
|
||||
}
|
||||
return SliderValueSelector(
|
||||
currentCondition: functionData.condition,
|
||||
dialogType: dialogType,
|
||||
sliderRange: sliderRange,
|
||||
displayedValue: getDisplayText,
|
||||
initialValue: initialValue,
|
||||
onConditionChanged: (condition) => context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectedFunction,
|
||||
operationName: functionData.operationName,
|
||||
condition: condition,
|
||||
value: functionData.value ?? 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
onSliderChanged: (value) {
|
||||
final roundedValue = _roundToStep(value, stepSize);
|
||||
final finalValue =
|
||||
isDistanceDetection ? (roundedValue * 100).toInt() : roundedValue;
|
||||
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectedFunction,
|
||||
operationName: functionData.operationName,
|
||||
value: finalValue,
|
||||
condition: functionData.condition,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
unit: _unit,
|
||||
dividendOfRange: stepSize,
|
||||
);
|
||||
}
|
||||
|
||||
return FlushOperationalValuesList(
|
||||
values: selectedFn.getOperationalValues(),
|
||||
selectedValue: functionData.value,
|
||||
device: device,
|
||||
operationName: selectedFn.operationName,
|
||||
selectCode: selectedFunction,
|
||||
onSelect: (selectedValue) async {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectedFunction,
|
||||
operationName: functionData.operationName,
|
||||
value: selectedValue.value,
|
||||
condition: functionData.condition,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
double _roundToStep(double value, double step) {
|
||||
return (value / step).roundToDouble() * step;
|
||||
}
|
||||
|
||||
bool _isSliderFunction(String function) => [
|
||||
FlushMountedPresenceSensorModel.codeOccurDistReduce,
|
||||
FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
FlushMountedPresenceSensorModel.codeNoneDelay,
|
||||
FlushMountedPresenceSensorModel.codeIlluminance,
|
||||
FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
FlushMountedPresenceSensorModel.codeFarDetection,
|
||||
FlushMountedPresenceSensorModel.codeNearDetection,
|
||||
].contains(function);
|
||||
|
||||
String get _unit => switch (functionData.functionCode) {
|
||||
FlushMountedPresenceSensorModel.codeOccurDistReduce => 'Min',
|
||||
FlushMountedPresenceSensorModel.codeSensiReduce => 'Sec',
|
||||
FlushMountedPresenceSensorModel.codeNoneDelay => 'Sec',
|
||||
FlushMountedPresenceSensorModel.codePresenceDelay => 'Sec',
|
||||
FlushMountedPresenceSensorModel.codeIlluminance => 'Lux',
|
||||
FlushMountedPresenceSensorModel.codeFarDetection => 'm',
|
||||
FlushMountedPresenceSensorModel.codeNearDetection => 'm',
|
||||
_ => '',
|
||||
};
|
||||
|
||||
String get getDisplayText {
|
||||
final num? value = functionData.value;
|
||||
double displayValue = value?.toDouble() ?? 0.0;
|
||||
|
||||
if (functionData.functionCode ==
|
||||
FlushMountedPresenceSensorModel.codeNearDetection ||
|
||||
functionData.functionCode ==
|
||||
FlushMountedPresenceSensorModel.codeFarDetection) {
|
||||
displayValue = displayValue / 100;
|
||||
}
|
||||
|
||||
switch (functionData.functionCode) {
|
||||
case FlushMountedPresenceSensorModel.codeFarDetection:
|
||||
case FlushMountedPresenceSensorModel.codeNearDetection:
|
||||
return displayValue.toStringAsFixed(1);
|
||||
case FlushMountedPresenceSensorModel.codeOccurDistReduce:
|
||||
case FlushMountedPresenceSensorModel.codeSensiReduce:
|
||||
case FlushMountedPresenceSensorModel.codePresenceDelay:
|
||||
return displayValue.toStringAsFixed(0);
|
||||
default:
|
||||
return displayValue.toStringAsFixed(0);
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
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/device_functions.dart';
|
||||
|
||||
abstract final class RoutineTapFunctionHelper {
|
||||
const RoutineTapFunctionHelper._();
|
||||
|
||||
static void onTapFunction(
|
||||
BuildContext context, {
|
||||
required String functionCode,
|
||||
required String functionOperationName,
|
||||
required String? functionValueDescription,
|
||||
required String? deviceUuid,
|
||||
required List<String> codesToAddIntoFunctionsWithDefaultValue,
|
||||
int defaultValue = 0,
|
||||
}) {
|
||||
final functionsBloc = context.read<FunctionBloc>();
|
||||
functionsBloc.add(
|
||||
SelectFunction(
|
||||
functionCode: functionCode,
|
||||
operationName: functionOperationName,
|
||||
),
|
||||
);
|
||||
final addedFunctions = functionsBloc.state.addedFunctions;
|
||||
final isFunctionAlreadyAdded = addedFunctions.any(
|
||||
(e) => e.functionCode == functionCode,
|
||||
);
|
||||
final shouldAddFunction =
|
||||
codesToAddIntoFunctionsWithDefaultValue.contains(functionCode);
|
||||
if (!isFunctionAlreadyAdded && shouldAddFunction) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: deviceUuid ?? '',
|
||||
functionCode: functionCode,
|
||||
operationName: functionOperationName,
|
||||
value: defaultValue,
|
||||
condition: '==',
|
||||
valueDescription: functionValueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/
|
||||
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';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -24,21 +25,23 @@ class OneGangSwitchHelper {
|
||||
required String uniqueCustomId,
|
||||
required bool removeComparetors,
|
||||
}) async {
|
||||
List<BaseSwitchFunction> oneGangFunctions = functions.whereType<BaseSwitchFunction>().toList();
|
||||
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 ?? '',
|
||||
@ -84,12 +87,19 @@ class OneGangSwitchHelper {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context.read<FunctionBloc>().add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
));
|
||||
},
|
||||
onTap: () =>
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName:
|
||||
function.operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'countdown_1',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -164,7 +174,7 @@ class OneGangSwitchHelper {
|
||||
required bool removeComparetors,
|
||||
}) {
|
||||
if (selectedFunction == 'countdown_1') {
|
||||
final initialValue = selectedFunctionData?.value ?? 200;
|
||||
final initialValue = selectedFunctionData?.value ?? 0;
|
||||
return _buildCountDownSelector(
|
||||
context: context,
|
||||
initialValue: initialValue,
|
||||
@ -220,11 +230,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),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -250,7 +260,7 @@ class OneGangSwitchHelper {
|
||||
functionCode: selectCode,
|
||||
operationName: operationName,
|
||||
condition: conditions[index],
|
||||
value: selectedFunctionData?.value,
|
||||
value: selectedFunctionData?.value ?? 0,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
@ -301,21 +311,23 @@ class OneGangSwitchHelper {
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
const twelveHoursInSeconds = 43200.0;
|
||||
final operationalValues = SwitchOperationalValue(
|
||||
icon: '',
|
||||
description: "sec",
|
||||
value: 0.0,
|
||||
minValue: 0,
|
||||
maxValue: 86400,
|
||||
maxValue: twelveHoursInSeconds,
|
||||
stepValue: 1,
|
||||
);
|
||||
return Slider(
|
||||
value: (initialValue ?? 0).toDouble(),
|
||||
min: operationalValues.minValue?.toDouble() ?? 0.0,
|
||||
max: operationalValues.maxValue?.toDouble() ?? 0.0,
|
||||
divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
divisions:
|
||||
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
onChanged: (value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
@ -367,7 +379,9 @@ class OneGangSwitchHelper {
|
||||
trailing: Icon(
|
||||
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) {
|
||||
|
@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
|
||||
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';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -85,15 +86,21 @@ class ThreeGangSwitchHelper {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context
|
||||
.read<FunctionBloc>()
|
||||
.add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName:
|
||||
function.operationName,
|
||||
));
|
||||
},
|
||||
onTap: () =>
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName:
|
||||
function.operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'countdown_1',
|
||||
'countdown_2',
|
||||
'countdown_3',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -170,7 +177,7 @@ class ThreeGangSwitchHelper {
|
||||
if (selectedFunction == 'countdown_1' ||
|
||||
selectedFunction == 'countdown_2' ||
|
||||
selectedFunction == 'countdown_3') {
|
||||
final initialValue = selectedFunctionData?.value ?? 200;
|
||||
final initialValue = selectedFunctionData?.value ?? 0;
|
||||
return _buildTemperatureSelector(
|
||||
context: context,
|
||||
initialValue: initialValue,
|
||||
@ -183,8 +190,7 @@ class ThreeGangSwitchHelper {
|
||||
);
|
||||
}
|
||||
|
||||
final selectedFn =
|
||||
switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final values = selectedFn.getOperationalValues();
|
||||
|
||||
return _buildOperationalValuesList(
|
||||
@ -251,7 +257,7 @@ class ThreeGangSwitchHelper {
|
||||
functionCode: selectCode,
|
||||
operationName: operationName,
|
||||
condition: conditions[index],
|
||||
value: selectedFunctionData?.value,
|
||||
value: selectedFunctionData?.value ?? 0,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
@ -266,8 +272,7 @@ 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(),
|
||||
);
|
||||
}
|
||||
@ -303,22 +308,23 @@ class ThreeGangSwitchHelper {
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
const twelveHoursInSeconds = 43200.0;
|
||||
final operationalValues = SwitchOperationalValue(
|
||||
icon: '',
|
||||
description: "sec",
|
||||
value: 0.0,
|
||||
minValue: 0,
|
||||
maxValue: 86400,
|
||||
maxValue: twelveHoursInSeconds,
|
||||
stepValue: 1,
|
||||
);
|
||||
return Slider(
|
||||
value: (initialValue ?? 0).toDouble(),
|
||||
min: operationalValues.minValue?.toDouble() ?? 0.0,
|
||||
max: operationalValues.maxValue?.toDouble() ?? 0.0,
|
||||
divisions: (((operationalValues.maxValue ?? 0) -
|
||||
(operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
divisions:
|
||||
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
onChanged: (value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
@ -368,9 +374,7 @@ 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
|
||||
@ -386,8 +390,7 @@ class ThreeGangSwitchHelper {
|
||||
operationName: operationName,
|
||||
value: value.value,
|
||||
condition: selectedFunctionData?.condition,
|
||||
valueDescription:
|
||||
selectedFunctionData?.valueDescription,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
|
||||
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';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -85,15 +86,20 @@ class TwoGangSwitchHelper {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context
|
||||
.read<FunctionBloc>()
|
||||
.add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName:
|
||||
function.operationName,
|
||||
));
|
||||
},
|
||||
onTap: () =>
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName:
|
||||
function.operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'countdown_1',
|
||||
'countdown_2',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -167,9 +173,8 @@ class TwoGangSwitchHelper {
|
||||
required String operationName,
|
||||
required bool removeComparetors,
|
||||
}) {
|
||||
if (selectedFunction == 'countdown_1' ||
|
||||
selectedFunction == 'countdown_2') {
|
||||
final initialValue = selectedFunctionData?.value ?? 200;
|
||||
if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
|
||||
final initialValue = selectedFunctionData?.value ?? 0;
|
||||
return _buildTemperatureSelector(
|
||||
context: context,
|
||||
initialValue: initialValue,
|
||||
@ -182,8 +187,7 @@ class TwoGangSwitchHelper {
|
||||
);
|
||||
}
|
||||
|
||||
final selectedFn =
|
||||
switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final values = selectedFn.getOperationalValues();
|
||||
|
||||
return _buildOperationalValuesList(
|
||||
@ -250,7 +254,7 @@ class TwoGangSwitchHelper {
|
||||
functionCode: selectCode,
|
||||
operationName: operationName,
|
||||
condition: conditions[index],
|
||||
value: selectedFunctionData?.value,
|
||||
value: selectedFunctionData?.value ?? 0,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
@ -265,8 +269,7 @@ 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(),
|
||||
);
|
||||
}
|
||||
@ -302,22 +305,23 @@ class TwoGangSwitchHelper {
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
const twelveHoursInSeconds = 43200.0;
|
||||
final operationalValues = SwitchOperationalValue(
|
||||
icon: '',
|
||||
description: "sec",
|
||||
value: 0.0,
|
||||
minValue: 0,
|
||||
maxValue: 86400,
|
||||
maxValue: twelveHoursInSeconds,
|
||||
stepValue: 1,
|
||||
);
|
||||
return Slider(
|
||||
value: (initialValue ?? 0).toDouble(),
|
||||
min: operationalValues.minValue?.toDouble() ?? 0.0,
|
||||
max: operationalValues.maxValue?.toDouble() ?? 0.0,
|
||||
divisions: (((operationalValues.maxValue ?? 0) -
|
||||
(operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
divisions:
|
||||
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
onChanged: (value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
@ -367,9 +371,7 @@ 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
|
||||
@ -385,8 +387,7 @@ class TwoGangSwitchHelper {
|
||||
operationName: operationName,
|
||||
value: value.value,
|
||||
condition: selectedFunctionData?.condition,
|
||||
valueDescription:
|
||||
selectedFunctionData?.valueDescription,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
@ -111,13 +112,23 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildFunctionList(context),
|
||||
_buildFunctionList(context, state),
|
||||
if (state.selectedFunction != null) _buildValueSelector(context, state),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFunctionList(BuildContext context) {
|
||||
Widget _buildFunctionList(BuildContext context, FunctionBlocState state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
final selectedFunctionData = state.addedFunctions.firstWhere(
|
||||
(f) => f.functionCode == selectedFunction,
|
||||
orElse: () => DeviceFunctionData(
|
||||
entityId: '',
|
||||
functionCode: selectedFunction ?? '',
|
||||
operationName: '',
|
||||
value: null,
|
||||
),
|
||||
);
|
||||
return SizedBox(
|
||||
width: 360,
|
||||
child: ListView.separated(
|
||||
@ -149,12 +160,18 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () => context.read<FunctionBloc>().add(
|
||||
SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
),
|
||||
),
|
||||
onTap: () => RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName: function.operationName,
|
||||
functionValueDescription: selectedFunctionData.valueDescription,
|
||||
deviceUuid: widget.device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'dis_current',
|
||||
'presence_time',
|
||||
'illuminance_value',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -36,7 +36,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
|
||||
dialogType: dialogType,
|
||||
sliderRange: sliderRange,
|
||||
displayedValue: getDisplayText,
|
||||
initialValue: functionData.value ?? 250,
|
||||
initialValue: functionData.value ?? 0.0,
|
||||
onConditionChanged: (condition) => context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
@ -44,7 +44,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
|
||||
functionCode: selectedFunction,
|
||||
operationName: functionData.operationName,
|
||||
condition: condition,
|
||||
value: functionData.value,
|
||||
value: functionData.value ?? 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -87,7 +87,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
|
||||
final intValue = int.tryParse('${functionData.value ?? ''}');
|
||||
return switch (functionData.functionCode) {
|
||||
'presence_time' => '${intValue ?? '0'}',
|
||||
'dis_current' => '${intValue ?? '250'}',
|
||||
'dis_current' => '${intValue ?? '0'}',
|
||||
'illuminance_value' => '${intValue ?? '0'}',
|
||||
_ => '$intValue',
|
||||
};
|
||||
|
@ -27,7 +27,8 @@ class ThenContainer extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('THEN',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
style: TextStyle(
|
||||
fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 16),
|
||||
state.isLoading && state.isUpdate == true
|
||||
? const Center(
|
||||
@ -40,11 +41,12 @@ class ThenContainer extends StatelessWidget {
|
||||
state.thenItems.length,
|
||||
(index) => GestureDetector(
|
||||
onTap: () async {
|
||||
if (state.thenItems[index]['deviceId'] ==
|
||||
if (state.thenItems[index]
|
||||
['deviceId'] ==
|
||||
'delay') {
|
||||
final result = await DelayHelper
|
||||
.showDelayPickerDialog(
|
||||
context, state.thenItems[index]);
|
||||
.showDelayPickerDialog(context,
|
||||
state.thenItems[index]);
|
||||
|
||||
if (result != null) {
|
||||
context
|
||||
@ -64,14 +66,17 @@ class ThenContainer extends StatelessWidget {
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
AutomationDialog(
|
||||
automationName: state.thenItems[index]
|
||||
['name'] ??
|
||||
'Automation',
|
||||
automationId: state.thenItems[index]
|
||||
['deviceId'] ??
|
||||
'',
|
||||
uniqueCustomId: state.thenItems[index]
|
||||
['uniqueCustomId'],
|
||||
automationName:
|
||||
state.thenItems[index]
|
||||
['name'] ??
|
||||
'Automation',
|
||||
automationId:
|
||||
state.thenItems[index]
|
||||
['deviceId'] ??
|
||||
'',
|
||||
uniqueCustomId:
|
||||
state.thenItems[index]
|
||||
['uniqueCustomId'],
|
||||
),
|
||||
);
|
||||
|
||||
@ -80,11 +85,13 @@ class ThenContainer extends StatelessWidget {
|
||||
.read<RoutineBloc>()
|
||||
.add(AddToThenContainer({
|
||||
...state.thenItems[index],
|
||||
'imagePath': Assets.automation,
|
||||
'title': state.thenItems[index]
|
||||
['name'] ??
|
||||
'imagePath':
|
||||
Assets.automation,
|
||||
'title':
|
||||
state.thenItems[index]
|
||||
['title'],
|
||||
['name'] ??
|
||||
state.thenItems[index]
|
||||
['title'],
|
||||
}));
|
||||
}
|
||||
return;
|
||||
@ -109,8 +116,9 @@ class ThenContainer extends StatelessWidget {
|
||||
'WPS',
|
||||
'CPS',
|
||||
"GW",
|
||||
].contains(
|
||||
state.thenItems[index]['productType'])) {
|
||||
"NCPS"
|
||||
].contains(state.thenItems[index]
|
||||
['productType'])) {
|
||||
context.read<RoutineBloc>().add(
|
||||
AddToThenContainer(
|
||||
state.thenItems[index]));
|
||||
@ -120,7 +128,9 @@ class ThenContainer extends StatelessWidget {
|
||||
imagePath: state.thenItems[index]
|
||||
['imagePath'] ??
|
||||
'',
|
||||
title: state.thenItems[index]['title'] ?? '',
|
||||
title: state.thenItems[index]
|
||||
['title'] ??
|
||||
'',
|
||||
deviceData: state.thenItems[index],
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 8),
|
||||
@ -157,8 +167,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;
|
||||
}
|
||||
@ -183,8 +193,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;
|
||||
}
|
||||
@ -222,7 +232,7 @@ class ThenContainer extends StatelessWidget {
|
||||
dialogType: "THEN");
|
||||
if (result != null) {
|
||||
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
|
||||
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS']
|
||||
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', "NCPS"]
|
||||
.contains(mutableData['productType'])) {
|
||||
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
|
||||
}
|
||||
|
@ -8,19 +8,20 @@ class CustomExpansionTileSpaceTree extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
final bool isSoldCheck;
|
||||
final bool isExpanded;
|
||||
final Function? onExpansionChanged;
|
||||
final Function? onItemSelected;
|
||||
final void Function()? onExpansionChanged;
|
||||
final void Function()? onItemSelected;
|
||||
|
||||
const CustomExpansionTileSpaceTree(
|
||||
{super.key,
|
||||
this.spaceId,
|
||||
required this.title,
|
||||
this.children,
|
||||
this.isExpanded = false,
|
||||
this.onExpansionChanged,
|
||||
this.onItemSelected,
|
||||
required this.isSelected,
|
||||
this.isSoldCheck = false});
|
||||
const CustomExpansionTileSpaceTree({
|
||||
required this.isSelected,
|
||||
required this.title,
|
||||
this.spaceId,
|
||||
this.children,
|
||||
this.onExpansionChanged,
|
||||
this.onItemSelected,
|
||||
this.isExpanded = false,
|
||||
this.isSoldCheck = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -30,50 +31,30 @@ class CustomExpansionTileSpaceTree extends StatelessWidget {
|
||||
children: [
|
||||
Checkbox(
|
||||
value: isSoldCheck ? null : isSelected,
|
||||
onChanged: (bool? value) {
|
||||
if (onItemSelected != null) {
|
||||
onItemSelected!();
|
||||
}
|
||||
},
|
||||
onChanged: (value) => onItemSelected?.call(),
|
||||
tristate: true,
|
||||
side: WidgetStateBorderSide.resolveWith((states) {
|
||||
return const BorderSide(color: ColorsManager.grayBorder);
|
||||
}),
|
||||
side: WidgetStateBorderSide.resolveWith(
|
||||
(states) => const BorderSide(color: ColorsManager.grayBorder),
|
||||
),
|
||||
fillColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return ColorsManager.blue1;
|
||||
} else {
|
||||
return ColorsManager.checkBoxFillColor;
|
||||
}
|
||||
|
||||
return ColorsManager.checkBoxFillColor;
|
||||
}),
|
||||
checkColor: ColorsManager.whiteColors,
|
||||
),
|
||||
if (children != null && children!.isNotEmpty)
|
||||
InkWell(
|
||||
onTap: () {
|
||||
if (onExpansionChanged != null) {
|
||||
onExpansionChanged!();
|
||||
}
|
||||
},
|
||||
child: Icon(
|
||||
isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
size: 16.0,
|
||||
),
|
||||
),
|
||||
_buildExpansionIcon(),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (onItemSelected != null) {
|
||||
onItemSelected!();
|
||||
}
|
||||
},
|
||||
onTap: onItemSelected,
|
||||
child: Text(
|
||||
_capitalizeFirstLetter(title),
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: isSelected
|
||||
? ColorsManager.blackColor // Change color to black when selected
|
||||
: ColorsManager.lightGrayColor, // Gray when not selected
|
||||
? ColorsManager.blackColor
|
||||
: ColorsManager.lightGrayColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
@ -92,6 +73,20 @@ class CustomExpansionTileSpaceTree extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExpansionIcon() {
|
||||
return Visibility(
|
||||
visible: children != null && children!.isNotEmpty,
|
||||
child: InkWell(
|
||||
onTap: onExpansionChanged,
|
||||
child: Icon(
|
||||
isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
size: 16.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _capitalizeFirstLetter(String text) {
|
||||
if (text.isEmpty) return text;
|
||||
return text[0].toUpperCase() + text.substring(1);
|
||||
|
@ -2,6 +2,7 @@ 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/common/widgets/sidebar_communities_list.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
|
||||
@ -23,7 +24,13 @@ class SpaceTreeView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
late final ScrollController _scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_scrollController = ScrollController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@ -34,7 +41,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
|
||||
List<CommunityModel> list =
|
||||
final communities =
|
||||
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
|
||||
return Container(
|
||||
height: MediaQuery.sizeOf(context).height,
|
||||
@ -45,214 +52,144 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
children: [
|
||||
widget.isSide == true
|
||||
? Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.circleRolesBackground,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(20), topLeft: Radius.circular(20)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
border: Border.all(color: ColorsManager.grayBorder)),
|
||||
child: TextFormField(
|
||||
style: context.textTheme.bodyMedium
|
||||
?.copyWith(color: ColorsManager.blackColor),
|
||||
onChanged: (value) {
|
||||
context.read<SpaceTreeBloc>().add(SearchQueryEvent(value));
|
||||
},
|
||||
decoration: textBoxDecoration(radios: 20)!.copyWith(
|
||||
fillColor: Colors.white,
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: SvgPicture.asset(
|
||||
Assets.textFieldSearch,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
hintStyle: context.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12,
|
||||
color: ColorsManager.textGray),
|
||||
if (widget.isSide == true)
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.circleRolesBackground,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(20),
|
||||
topLeft: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
border: Border.all(
|
||||
color: ColorsManager.grayBorder,
|
||||
),
|
||||
),
|
||||
child: TextFormField(
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
onChanged: (value) => context.read<SpaceTreeBloc>().add(
|
||||
SearchQueryEvent(value),
|
||||
),
|
||||
decoration: textBoxDecoration(radios: 20)?.copyWith(
|
||||
fillColor: Colors.white,
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: SvgPicture.asset(
|
||||
Assets.textFieldSearch,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
hintStyle: context.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: CustomSearchBar(
|
||||
onSearchChanged: (query) {
|
||||
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
CustomSearchBar(
|
||||
onSearchChanged: (query) => context.read<SpaceTreeBloc>().add(
|
||||
SearchQueryEvent(query),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
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: 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(),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
: SidebarCommunitiesList(
|
||||
onScrollToEnd: () {
|
||||
if (!state.paginationIsLoading) {
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
PaginationEvent(
|
||||
state.paginationModel,
|
||||
state.communityList,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
scrollController: _scrollController,
|
||||
communities: communities,
|
||||
itemBuilder: (context, index) {
|
||||
return CustomExpansionTileSpaceTree(
|
||||
title: communities[index].name,
|
||||
isSelected:
|
||||
state.selectedCommunities.contains(communities[index].uuid),
|
||||
isSoldCheck:
|
||||
state.selectedCommunities.contains(communities[index].uuid),
|
||||
onExpansionChanged: () => context.read<SpaceTreeBloc>().add(
|
||||
OnCommunityExpanded(
|
||||
communities[index].uuid,
|
||||
),
|
||||
),
|
||||
isExpanded: state.expandedCommunities.contains(
|
||||
communities[index].uuid,
|
||||
),
|
||||
onItemSelected: () {
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnCommunitySelected(
|
||||
communities[index].uuid,
|
||||
communities[index].spaces,
|
||||
),
|
||||
);
|
||||
widget.onSelect();
|
||||
},
|
||||
children: communities[index].spaces.map(
|
||||
(space) {
|
||||
return CustomExpansionTileSpaceTree(
|
||||
title: space.name,
|
||||
isExpanded: state.expandedSpaces.contains(space.uuid),
|
||||
onItemSelected: () {
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnSpaceSelected(
|
||||
communities[index],
|
||||
space.uuid ?? '',
|
||||
space.children,
|
||||
),
|
||||
);
|
||||
widget.onSelect();
|
||||
},
|
||||
onExpansionChanged: () => context.read<SpaceTreeBloc>().add(
|
||||
OnSpaceExpanded(
|
||||
communities[index].uuid,
|
||||
space.uuid ?? '',
|
||||
),
|
||||
),
|
||||
isSelected: state.selectedSpaces.contains(space.uuid) ||
|
||||
state.soldCheck.contains(space.uuid),
|
||||
isSoldCheck: state.soldCheck.contains(space.uuid),
|
||||
children: _buildNestedSpaces(
|
||||
context,
|
||||
state,
|
||||
space,
|
||||
communities[index],
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (state.paginationIsLoading) const CircularProgressIndicator(),
|
||||
// Expanded(
|
||||
// child: Padding(
|
||||
// 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, // Gray when not selected
|
||||
// fontWeight: FontWeight.w400,
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// : ListView(
|
||||
// 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));
|
||||
|
||||
// onSelect();
|
||||
// },
|
||||
// children: community.spaces.map((space) {
|
||||
// return CustomExpansionTileSpaceTree(
|
||||
// title: space.name,
|
||||
// isExpanded: state.expandedSpaces.contains(space.uuid),
|
||||
// onItemSelected: () {
|
||||
// context.read<SpaceTreeBloc>().add(OnSpaceSelected(
|
||||
// community.uuid, space.uuid ?? '', space.children));
|
||||
// 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.uuid),
|
||||
// );
|
||||
// }).toList(),
|
||||
// ),
|
||||
// )
|
||||
// .toList(),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -260,7 +197,11 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
}
|
||||
|
||||
List<Widget> _buildNestedSpaces(
|
||||
BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) {
|
||||
BuildContext context,
|
||||
SpaceTreeState state,
|
||||
SpaceModel space,
|
||||
CommunityModel community,
|
||||
) {
|
||||
return space.children.map((child) {
|
||||
return CustomExpansionTileSpaceTree(
|
||||
isSelected:
|
||||
@ -269,13 +210,15 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
title: child.name,
|
||||
isExpanded: state.expandedSpaces.contains(child.uuid),
|
||||
onItemSelected: () {
|
||||
context
|
||||
.read<SpaceTreeBloc>()
|
||||
.add(OnSpaceSelected(community, child.uuid ?? '', child.children));
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnSpaceSelected(community, child.uuid ?? '', child.children),
|
||||
);
|
||||
widget.onSelect();
|
||||
},
|
||||
onExpansionChanged: () {
|
||||
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(community.uuid, child.uuid ?? ''));
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnSpaceExpanded(community.uuid, child.uuid ?? ''),
|
||||
);
|
||||
},
|
||||
children: _buildNestedSpaces(context, state, child, community),
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ 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_bloc.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/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/product_model.dart';
|
||||
@ -246,7 +247,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
final previousState = state;
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
|
||||
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||
await fetchSpaceModels();
|
||||
await fetchTags();
|
||||
|
||||
@ -277,11 +280,13 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
LoadCommunityAndSpacesEvent event,
|
||||
Emitter<SpaceManagementState> emit,
|
||||
) async {
|
||||
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||
|
||||
_onloadProducts();
|
||||
await fetchTags();
|
||||
// Wait until `communityList` is loaded
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||
|
||||
// Fetch space models after communities are available
|
||||
final prevSpaceModels = await fetchSpaceModels();
|
||||
@ -292,23 +297,38 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
allTags: _cachedTags ?? []));
|
||||
}
|
||||
|
||||
Future<List<CommunityModel>> _waitForCommunityList(SpaceTreeBloc spaceBloc) async {
|
||||
Future<List<CommunityModel>> _waitForCommunityList(
|
||||
SpaceTreeBloc spaceBloc, SpaceTreeState spaceTreeState) async {
|
||||
// Check if communityList is already populated
|
||||
if (spaceBloc.state.communityList.isNotEmpty) {
|
||||
return spaceBloc.state.communityList;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
if (filteredCommunities.isNotEmpty) {
|
||||
return filteredCommunities;
|
||||
}
|
||||
|
||||
final completer = Completer<List<CommunityModel>>();
|
||||
final subscription = spaceBloc.stream.listen((state) {
|
||||
if (state.communityList.isNotEmpty) {
|
||||
completer.complete(state.communityList);
|
||||
if (!completer.isCompleted && state.communityList.isNotEmpty) {
|
||||
completer
|
||||
.complete(state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList);
|
||||
}
|
||||
});
|
||||
try {
|
||||
final communities = await completer.future.timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete([]);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
);
|
||||
|
||||
// Return the list once available, then cancel the listener
|
||||
final communities = await completer.future;
|
||||
await subscription.cancel();
|
||||
return communities;
|
||||
return communities;
|
||||
} finally {
|
||||
await subscription.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void _onCommunityDelete(
|
||||
@ -437,15 +457,17 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
emit(SpaceManagementLoading());
|
||||
|
||||
try {
|
||||
final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
|
||||
final updatedSpaces =
|
||||
await saveSpacesHierarchically(event.context, event.spaces, event.communityUuid);
|
||||
|
||||
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
|
||||
|
||||
emit(SpaceCreationSuccess(spaces: updatedSpaces));
|
||||
|
||||
if (previousState is SpaceManagementLoaded) {
|
||||
await _updateLoadedState(
|
||||
spaceTreeState,
|
||||
previousState,
|
||||
allSpaces,
|
||||
event.communityUuid,
|
||||
@ -462,41 +484,60 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
}
|
||||
|
||||
Future<void> _updateLoadedState(
|
||||
SpaceTreeState spaceTreeState,
|
||||
SpaceManagementLoaded previousState,
|
||||
List<SpaceModel> allSpaces,
|
||||
String communityUuid,
|
||||
Emitter<SpaceManagementState> emit,
|
||||
) async {
|
||||
var prevSpaceModels = await fetchSpaceModels();
|
||||
await fetchTags();
|
||||
final communities = List<CommunityModel>.from(previousState.communities);
|
||||
try {
|
||||
var prevSpaceModels = await fetchSpaceModels();
|
||||
|
||||
for (var community in communities) {
|
||||
if (community.uuid == communityUuid) {
|
||||
community.spaces = allSpaces;
|
||||
_spaceTreeBloc.add(InitialEvent());
|
||||
await fetchTags();
|
||||
|
||||
emit(SpaceManagementLoaded(
|
||||
final communities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
for (var community in communities) {
|
||||
if (community.uuid == communityUuid) {
|
||||
community.spaces = allSpaces;
|
||||
_spaceTreeBloc.add(InitialEvent());
|
||||
|
||||
emit(SpaceManagementLoaded(
|
||||
communities: communities,
|
||||
products: _cachedProducts ?? [],
|
||||
selectedCommunity: community,
|
||||
selectedSpace: null,
|
||||
spaceModels: prevSpaceModels,
|
||||
allTags: _cachedTags ?? []));
|
||||
return;
|
||||
allTags: _cachedTags ?? [],
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<SpaceModel>> saveSpacesHierarchically(
|
||||
BuildContext context, List<SpaceModel> spaces, String communityUuid) async {
|
||||
final orderedSpaces = flattenHierarchy(spaces);
|
||||
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||
List<CommunityModel> communities = spaceBloc.state.communityList;
|
||||
CommunityModel? selectedCommunity = communities.firstWhere(
|
||||
(community) => community.uuid == communityUuid,
|
||||
);
|
||||
CommunityModel? selectedCommunity;
|
||||
try {
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
selectedCommunity = filteredCommunities.firstWhere(
|
||||
(community) => community.uuid == communityUuid,
|
||||
);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final parentsToDelete = orderedSpaces.where((space) =>
|
||||
space.status == SpaceStatus.deleted &&
|
||||
@ -605,11 +646,11 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
projectId: projectUuid);
|
||||
} else {
|
||||
// Call create if the space does not have a UUID
|
||||
final List<CreateTagBodyModel> tagBodyModels = space.tags != null
|
||||
List<CreateTagBodyModel> tagBodyModels = space.tags != null
|
||||
? space.tags!.map((tag) => tag.toCreateTagBodyModel()).toList()
|
||||
: [];
|
||||
|
||||
final createSubspaceBodyModels = space.subspaces?.map((subspace) {
|
||||
var createSubspaceBodyModels = space.subspaces?.map((subspace) {
|
||||
final tagBodyModels =
|
||||
subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? [];
|
||||
return CreateSubspaceModel()
|
||||
@ -618,6 +659,11 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
}).toList() ??
|
||||
[];
|
||||
|
||||
if (space.spaceModel?.uuid != null) {
|
||||
tagBodyModels = [];
|
||||
createSubspaceBodyModels = [];
|
||||
}
|
||||
|
||||
final response = await _api.createSpace(
|
||||
communityId: communityUuid,
|
||||
name: space.name,
|
||||
@ -669,9 +715,12 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
|
||||
try {
|
||||
await fetchTags();
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||
List<CommunityModel> communities = spaceBloc.state.communityList;
|
||||
final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
List<CommunityModel> communities = filteredCommunities;
|
||||
|
||||
var prevSpaceModels = await fetchSpaceModels();
|
||||
|
||||
|
@ -53,6 +53,9 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
builder: (context, state) {
|
||||
if (state is SpaceManagementLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (state is SpaceManagementInitial) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is BlankState) {
|
||||
return LoadedSpaceView(
|
||||
communities: state.communities,
|
||||
|
@ -1,4 +1,6 @@
|
||||
// Flutter imports
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -70,6 +72,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
_nameController = TextEditingController(
|
||||
text: widget.selectedCommunity?.name ?? '',
|
||||
);
|
||||
realignTree();
|
||||
|
||||
_transformationController = TransformationController();
|
||||
if (widget.selectedSpace != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@ -94,6 +98,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
|
||||
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
|
||||
_adjustCanvasSizeForSpaces();
|
||||
realignTree();
|
||||
});
|
||||
}
|
||||
|
||||
@ -336,6 +341,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
spaces.add(newSpace);
|
||||
_updateNodePosition(newSpace, newSpace.position);
|
||||
realignTree();
|
||||
});
|
||||
},
|
||||
);
|
||||
@ -401,12 +407,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
|
||||
List<SpaceModel> flattenSpaces(List<SpaceModel> spaces) {
|
||||
List<SpaceModel> result = [];
|
||||
Map<String, SpaceModel> idToSpace = {};
|
||||
|
||||
void flatten(SpaceModel space) {
|
||||
if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
|
||||
return;
|
||||
}
|
||||
result.add(space);
|
||||
idToSpace[space.internalId] = space;
|
||||
|
||||
for (var child in space.children) {
|
||||
flatten(child);
|
||||
}
|
||||
@ -416,6 +425,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
flatten(space);
|
||||
}
|
||||
|
||||
for (var space in result) {
|
||||
if (space.parent != null) {
|
||||
space.parent = idToSpace[space.parent!.internalId];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -450,7 +465,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
|
||||
void _saveSpaces() {
|
||||
if (widget.selectedCommunity == null) {
|
||||
debugPrint("No community selected for saving spaces.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -465,7 +479,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
|
||||
String communityUuid = widget.selectedCommunity!.uuid;
|
||||
|
||||
context.read<SpaceManagementBloc>().add(SaveSpacesEvent(
|
||||
context,
|
||||
spaces: spacesToSave,
|
||||
@ -530,35 +543,83 @@ 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);
|
||||
const double nodeWidth = 200;
|
||||
const double verticalGap = 180;
|
||||
|
||||
Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180);
|
||||
if (parent.children.isEmpty) {
|
||||
// First child → exactly center
|
||||
return Offset(parent.position.dx, parent.position.dy + verticalGap);
|
||||
} else {
|
||||
// More children → arrange them spaced horizontally
|
||||
double totalWidth = (parent.children.length) * (nodeWidth + 60);
|
||||
double startX = parent.position.dx - (totalWidth / 2);
|
||||
|
||||
// Check for overlaps & adjust
|
||||
while (spaces.any((s) => (s.position - position).distance < 250)) {
|
||||
position = Offset(position.dx + 250, position.dy);
|
||||
double childX = startX + (parent.children.length * (nodeWidth + 60));
|
||||
return Offset(childX, parent.position.dy + verticalGap);
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
void realignTree() {
|
||||
void updatePositions(SpaceModel node, double x, double y) {
|
||||
node.position = Offset(x, y);
|
||||
const double nodeWidth = 200;
|
||||
const double nodeHeight = 100;
|
||||
const double horizontalGap = 60;
|
||||
const double verticalGap = 180;
|
||||
const double rootGap = 400; // extra space between different roots
|
||||
|
||||
int numChildren = node.children.length;
|
||||
double childStartX = x - ((numChildren - 1) * 250) / 2;
|
||||
double canvasRightEdge = 1000;
|
||||
double canvasBottomEdge = 1000;
|
||||
|
||||
for (int i = 0; i < numChildren; i++) {
|
||||
updatePositions(node.children[i], childStartX + (i * 250), y + 180);
|
||||
double calculateSubtreeWidth(SpaceModel node) {
|
||||
if (node.children.isEmpty) return nodeWidth;
|
||||
double totalWidth = 0;
|
||||
for (var child in node.children) {
|
||||
totalWidth += calculateSubtreeWidth(child) + horizontalGap;
|
||||
}
|
||||
return totalWidth - horizontalGap;
|
||||
}
|
||||
|
||||
void layoutSubtree(SpaceModel node, double startX, double y) {
|
||||
double subtreeWidth = calculateSubtreeWidth(node);
|
||||
double centerX = startX + subtreeWidth / 2 - nodeWidth / 2;
|
||||
node.position = Offset(centerX, y);
|
||||
|
||||
canvasRightEdge = max(canvasRightEdge, centerX + nodeWidth);
|
||||
canvasBottomEdge = max(canvasBottomEdge, y + nodeHeight);
|
||||
|
||||
if (node.children.length == 1) {
|
||||
final child = node.children.first;
|
||||
layoutSubtree(child, centerX, y + verticalGap);
|
||||
} else {
|
||||
double childX = startX;
|
||||
for (var child in node.children) {
|
||||
double childWidth = calculateSubtreeWidth(child);
|
||||
layoutSubtree(child, childX, y + verticalGap);
|
||||
childX += childWidth + horizontalGap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spaces.isNotEmpty) {
|
||||
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
|
||||
// ⚡ New: layout each root separately
|
||||
final List<SpaceModel> roots = spaces
|
||||
.where((s) =>
|
||||
s.parent == null &&
|
||||
s.status != SpaceStatus.deleted &&
|
||||
s.status != SpaceStatus.parentDeleted)
|
||||
.toList();
|
||||
|
||||
double currentX = 100; // start some margin from left
|
||||
double currentY = 100; // top margin
|
||||
|
||||
for (var root in roots) {
|
||||
layoutSubtree(root, currentX, currentY);
|
||||
double rootWidth = calculateSubtreeWidth(root);
|
||||
currentX += rootWidth + rootGap;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
canvasWidth = canvasRightEdge + 400;
|
||||
canvasHeight = canvasBottomEdge + 400;
|
||||
});
|
||||
}
|
||||
|
||||
void _onDuplicate(BuildContext parentContext) {
|
||||
@ -642,116 +703,72 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
|
||||
void _duplicateSpace(SpaceModel space) {
|
||||
final Map<SpaceModel, SpaceModel> originalToDuplicate = {};
|
||||
double horizontalGap = 250.0; // Increased spacing
|
||||
double verticalGap = 180.0; // Adjusted for better visualization
|
||||
setState(() {
|
||||
SpaceModel? parent = space.parent;
|
||||
|
||||
print("🟢 Duplicating: ${space.name}");
|
||||
SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent);
|
||||
|
||||
/// **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);
|
||||
duplicated.position = Offset(space.position.dx + 300, space.position.dy + 100);
|
||||
List<SpaceModel> duplicatedSubtree = [];
|
||||
void collectSubtree(SpaceModel node) {
|
||||
duplicatedSubtree.add(node);
|
||||
for (var child in node.children) {
|
||||
collectSubtree(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (spaces.isNotEmpty) {
|
||||
print("🔄 Realigning tree...");
|
||||
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
|
||||
collectSubtree(duplicated);
|
||||
spaces.addAll(duplicatedSubtree);
|
||||
|
||||
if (parent != null) {
|
||||
parent.children.add(duplicated);
|
||||
|
||||
final newConnection = Connection(
|
||||
startSpace: parent,
|
||||
endSpace: duplicated,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
duplicated.incomingConnection = newConnection;
|
||||
parent.addOutgoingConnection(newConnection);
|
||||
}
|
||||
}
|
||||
|
||||
/// **Recursive duplication logic**
|
||||
SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) {
|
||||
Offset newPosition = duplicatedParent == null
|
||||
? Offset(original.position.dx + horizontalGap, original.position.dy)
|
||||
: getBalancedChildPosition(duplicatedParent);
|
||||
realignTree();
|
||||
connections = createConnections(spaces);
|
||||
});
|
||||
}
|
||||
|
||||
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
||||
print(
|
||||
"🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})");
|
||||
SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) {
|
||||
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
||||
|
||||
final duplicated = SpaceModel(
|
||||
name: duplicatedName,
|
||||
icon: original.icon,
|
||||
position: newPosition,
|
||||
isPrivate: original.isPrivate,
|
||||
children: [],
|
||||
status: SpaceStatus.newSpace,
|
||||
parent: duplicatedParent,
|
||||
spaceModel: original.spaceModel,
|
||||
subspaces: original.subspaces,
|
||||
tags: original.tags,
|
||||
final newSpace = SpaceModel(
|
||||
name: duplicatedName,
|
||||
icon: original.icon,
|
||||
position: original.position,
|
||||
isPrivate: original.isPrivate,
|
||||
children: [],
|
||||
status: SpaceStatus.newSpace,
|
||||
spaceModel: original.spaceModel,
|
||||
subspaces: original.subspaces,
|
||||
tags: original.tags,
|
||||
parent: parent,
|
||||
);
|
||||
|
||||
for (var child in original.children) {
|
||||
final duplicatedChild = _deepCloneSpaceTree(child, parent: newSpace);
|
||||
newSpace.children.add(duplicatedChild);
|
||||
|
||||
final newConnection = Connection(
|
||||
startSpace: newSpace,
|
||||
endSpace: duplicatedChild,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
|
||||
setState(() {
|
||||
spaces.add(duplicated);
|
||||
_updateNodePosition(duplicated, duplicated.position);
|
||||
|
||||
if (duplicatedParent != null) {
|
||||
final newConnection = Connection(
|
||||
startSpace: duplicatedParent,
|
||||
endSpace: duplicated,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
duplicated.incomingConnection = newConnection;
|
||||
duplicatedParent.addOutgoingConnection(newConnection);
|
||||
duplicatedParent.children.add(duplicated);
|
||||
print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}");
|
||||
}
|
||||
|
||||
// **Recalculate the whole tree to avoid overlaps**
|
||||
realignTree();
|
||||
});
|
||||
|
||||
// Recursively duplicate children
|
||||
for (var child in original.children) {
|
||||
duplicateRecursive(child, duplicated);
|
||||
}
|
||||
|
||||
return duplicated;
|
||||
duplicatedChild.incomingConnection = newConnection;
|
||||
newSpace.addOutgoingConnection(newConnection);
|
||||
}
|
||||
|
||||
/// **Handle root duplication**
|
||||
if (space.parent == null) {
|
||||
print("🟠 Duplicating root node: ${space.name}");
|
||||
SpaceModel duplicatedRoot = duplicateRecursive(space, null);
|
||||
|
||||
setState(() {
|
||||
spaces.add(duplicatedRoot);
|
||||
realignTree();
|
||||
});
|
||||
|
||||
print("✅ Root duplication successful: ${duplicatedRoot.name}");
|
||||
} else {
|
||||
duplicateRecursive(space, space.parent);
|
||||
}
|
||||
|
||||
print("🟢 Finished duplication process for: ${space.name}");
|
||||
return newSpace;
|
||||
}
|
||||
}
|
||||
|
@ -526,6 +526,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
isNameFieldInvalid = true;
|
||||
});
|
||||
return;
|
||||
} else if (isNameFieldExist) {
|
||||
return;
|
||||
} else {
|
||||
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
|
||||
if (newName.isNotEmpty) {
|
||||
@ -592,8 +594,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
return CreateSubSpaceDialog(
|
||||
spaceName: name,
|
||||
dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space',
|
||||
spaceTags: spaceTags,
|
||||
isEdit: isEdit,
|
||||
products: products,
|
||||
existingSubSpaces: existingSubSpaces,
|
||||
onSave: (slectedSubspaces) {
|
||||
|
@ -45,7 +45,6 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_spaceModels = List.from(widget.spaceModels ?? []);
|
||||
}
|
||||
|
||||
@ -106,9 +105,8 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
|
||||
children: [
|
||||
SidebarWidget(
|
||||
communities: widget.communities,
|
||||
selectedSpaceUuid: widget.selectedSpace?.uuid ??
|
||||
widget.selectedCommunity?.uuid ??
|
||||
'',
|
||||
selectedSpaceUuid:
|
||||
widget.selectedSpace?.uuid ?? widget.selectedCommunity?.uuid ?? '',
|
||||
onCreateCommunity: (name, description) {
|
||||
context.read<SpaceManagementBloc>().add(
|
||||
CreateCommunityEvent(name, description, context),
|
||||
|
@ -1,6 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/common/widgets/search_bar.dart';
|
||||
import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||
@ -13,6 +18,8 @@ import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/cent
|
||||
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
import '../../../space_tree/bloc/space_tree_event.dart';
|
||||
|
||||
class SidebarWidget extends StatefulWidget {
|
||||
final List<CommunityModel> communities;
|
||||
final String? selectedSpaceUuid;
|
||||
@ -30,14 +37,55 @@ class SidebarWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
late final ScrollController _scrollController;
|
||||
Timer? _debounce;
|
||||
|
||||
String _searchQuery = '';
|
||||
String? _selectedSpaceUuid;
|
||||
String? _selectedId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_selectedId = widget.selectedSpaceUuid;
|
||||
super.initState();
|
||||
_scrollController = ScrollController();
|
||||
_scrollController.addListener(_onScroll);
|
||||
_selectedId = widget.selectedSpaceUuid;
|
||||
_searchQuery = '';
|
||||
context.read<SpaceTreeBloc>().add(ClearCachedData());
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) {
|
||||
// Trigger pagination event
|
||||
final bloc = context.read<SpaceTreeBloc>();
|
||||
if (!bloc.state.paginationIsLoading && bloc.state.paginationModel?.hasNext == true) {
|
||||
bloc.add(
|
||||
PaginationEvent(
|
||||
bloc.state.paginationModel!,
|
||||
bloc.state.communityList,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.removeListener(_onScroll);
|
||||
_scrollController.dispose();
|
||||
_debounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onSearchChanged(String query) {
|
||||
if (_debounce?.isActive ?? false) _debounce?.cancel();
|
||||
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||
setState(() {
|
||||
_searchQuery = query;
|
||||
});
|
||||
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -48,73 +96,81 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
List<CommunityModel> _filteredCommunities() {
|
||||
if (_searchQuery.isEmpty) {
|
||||
_selectedSpaceUuid = null;
|
||||
return widget.communities;
|
||||
}
|
||||
|
||||
return widget.communities.where((community) {
|
||||
final containsQueryInCommunity =
|
||||
community.name.toLowerCase().contains(_searchQuery.toLowerCase());
|
||||
final containsQueryInSpaces = community.spaces.any((space) =>
|
||||
_containsQuery(space: space, query: _searchQuery.toLowerCase()));
|
||||
|
||||
return containsQueryInCommunity || containsQueryInSpaces;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
bool _containsQuery({
|
||||
required SpaceModel space,
|
||||
required String query,
|
||||
}) {
|
||||
final matchesSpace = space.name.toLowerCase().contains(query);
|
||||
final matchesChildren = space.children.any(
|
||||
(child) => _containsQuery(space: child, query: query),
|
||||
);
|
||||
|
||||
if (matchesSpace || matchesChildren) {
|
||||
_selectedSpaceUuid = space.uuid;
|
||||
}
|
||||
|
||||
return matchesSpace || matchesChildren;
|
||||
}
|
||||
|
||||
bool _isSpaceOrChildSelected(SpaceModel space) {
|
||||
final isSpaceSelected = _selectedSpaceUuid == space.uuid;
|
||||
final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected);
|
||||
return isSpaceSelected || anySubSpaceIsSelected;
|
||||
}
|
||||
|
||||
static const _width = 300.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteredCommunities = _filteredCommunities();
|
||||
final spaceTreeState = context.watch<SpaceTreeBloc>().state;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
return Container(
|
||||
width: 300,
|
||||
width: _width,
|
||||
decoration: subSectionContainerDecoration,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SidebarHeader(onAddCommunity: _onAddCommunity),
|
||||
CustomSearchBar(
|
||||
onSearchChanged: (query) => setState(() => _searchQuery = query),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: filteredCommunities
|
||||
.map((community) => _buildCommunityTile(context, community))
|
||||
.toList(),
|
||||
child: spaceTreeState is SpaceTreeLoadingState
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SidebarHeader(onAddCommunity: _onAddCommunity),
|
||||
CustomSearchBar(
|
||||
onSearchChanged: _onSearchChanged,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (_) {
|
||||
return SidebarCommunitiesList(
|
||||
scrollController: _scrollController,
|
||||
onScrollToEnd: () {},
|
||||
communities: filteredCommunities,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == filteredCommunities.length) {
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
if (spaceTreeState.paginationIsLoading) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
return _buildCommunityTile(context, filteredCommunities[index]);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (spaceTreeState.paginationIsLoading || spaceTreeState.isSearching)
|
||||
Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCommunityTile(BuildContext context, CommunityModel community) {
|
||||
final spaces = community.spaces
|
||||
.where(
|
||||
(space) =>
|
||||
{
|
||||
SpaceStatus.deleted,
|
||||
SpaceStatus.parentDeleted,
|
||||
}.contains(space.status) ==
|
||||
false,
|
||||
)
|
||||
.map((space) => _buildSpaceTile(space: space, community: community))
|
||||
.toList();
|
||||
return CommunityTile(
|
||||
title: community.name,
|
||||
key: ValueKey(community.uuid),
|
||||
@ -133,14 +189,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
);
|
||||
},
|
||||
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(),
|
||||
children: spaces,
|
||||
);
|
||||
}
|
||||
|
||||
@ -180,9 +229,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
void _onAddCommunity() => _selectedId?.isNotEmpty ?? true
|
||||
? _clearSelection()
|
||||
: _showCreateCommunityDialog();
|
||||
void _onAddCommunity() =>
|
||||
_selectedId?.isNotEmpty ?? true ? _clearSelection() : _showCreateCommunityDialog();
|
||||
|
||||
void _clearSelection() {
|
||||
setState(() => _selectedId = '');
|
||||
|
@ -4,222 +4,204 @@ import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class CreateSubSpaceDialog extends StatelessWidget {
|
||||
final bool isEdit;
|
||||
class CreateSubSpaceDialog extends StatefulWidget {
|
||||
final String dialogTitle;
|
||||
final List<SubspaceModel>? existingSubSpaces;
|
||||
final String? spaceName;
|
||||
final List<Tag>? spaceTags;
|
||||
final List<ProductModel>? products;
|
||||
final Function(List<SubspaceModel>?)? onSave;
|
||||
final void Function(List<SubspaceModel>?)? onSave;
|
||||
|
||||
const CreateSubSpaceDialog(
|
||||
{Key? key,
|
||||
required this.isEdit,
|
||||
required this.dialogTitle,
|
||||
this.existingSubSpaces,
|
||||
required this.spaceName,
|
||||
required this.spaceTags,
|
||||
required this.products,
|
||||
required this.onSave})
|
||||
: super(key: key);
|
||||
const CreateSubSpaceDialog({
|
||||
required this.dialogTitle,
|
||||
required this.spaceName,
|
||||
required this.products,
|
||||
required this.onSave,
|
||||
this.existingSubSpaces,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CreateSubSpaceDialog> createState() => _CreateSubSpaceDialogState();
|
||||
}
|
||||
|
||||
class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
|
||||
late final TextEditingController _subspaceNameController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_subspaceNameController = TextEditingController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subspaceNameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final textController = TextEditingController();
|
||||
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: BlocProvider(
|
||||
create: (_) {
|
||||
final bloc = SubSpaceBloc();
|
||||
if (existingSubSpaces != null) {
|
||||
for (var subSpace in existingSubSpaces!) {
|
||||
bloc.add(AddSubSpace(subSpace));
|
||||
}
|
||||
return BlocProvider(
|
||||
create: (_) {
|
||||
final bloc = SubSpaceBloc();
|
||||
if (widget.existingSubSpaces != null) {
|
||||
for (final subSpace in widget.existingSubSpaces ?? []) {
|
||||
bloc.add(AddSubSpace(subSpace));
|
||||
}
|
||||
return bloc;
|
||||
},
|
||||
}
|
||||
return bloc;
|
||||
},
|
||||
child: Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: BlocBuilder<SubSpaceBloc, SubSpaceState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
color: ColorsManager.whiteColors,
|
||||
child: SizedBox(
|
||||
width: screenWidth * 0.35,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
width: context.screenWidth * 0.35,
|
||||
color: ColorsManager.whiteColors,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
widget.dialogTitle,
|
||||
style: context.textTheme.headlineLarge?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
width: context.screenWidth * 0.35,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 16,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
dialogTitle,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineLarge
|
||||
?.copyWith(color: ColorsManager.blackColor),
|
||||
...state.subSpaces.asMap().entries.map(
|
||||
(entry) {
|
||||
final index = entry.key;
|
||||
final subSpace = entry.value;
|
||||
|
||||
final lowerName = subSpace.subspaceName.toLowerCase();
|
||||
|
||||
final duplicateIndices = state.subSpaces
|
||||
.asMap()
|
||||
.entries
|
||||
.where((e) =>
|
||||
e.value.subspaceName.toLowerCase() == lowerName)
|
||||
.map((e) => e.key)
|
||||
.toList();
|
||||
final isDuplicate = duplicateIndices.length > 1 &&
|
||||
duplicateIndices.indexOf(index) != 0;
|
||||
return SubspaceChip(
|
||||
subSpace: SubspaceTemplateModel(
|
||||
subspaceName: entry.value.subspaceName,
|
||||
disabled: entry.value.disabled,
|
||||
),
|
||||
isDuplicate: isDuplicate,
|
||||
onDeleted: () => context.read<SubSpaceBloc>().add(
|
||||
RemoveSubSpace(subSpace),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
width: screenWidth * 0.35,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0, horizontal: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
...state.subSpaces.asMap().entries.map(
|
||||
(entry) {
|
||||
final index = entry.key;
|
||||
final subSpace = entry.value;
|
||||
|
||||
final lowerName =
|
||||
subSpace.subspaceName.toLowerCase();
|
||||
|
||||
final duplicateIndices = state.subSpaces
|
||||
.asMap()
|
||||
.entries
|
||||
.where((e) =>
|
||||
e.value.subspaceName.toLowerCase() ==
|
||||
lowerName)
|
||||
.map((e) => e.key)
|
||||
.toList();
|
||||
final isDuplicate =
|
||||
duplicateIndices.length > 1 &&
|
||||
duplicateIndices.indexOf(index) != 0;
|
||||
|
||||
return Chip(
|
||||
label: Text(subSpace.subspaceName,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(
|
||||
color:
|
||||
ColorsManager.spaceColor)),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
side: BorderSide(
|
||||
color: isDuplicate
|
||||
? ColorsManager.red
|
||||
: ColorsManager.transparentColor,
|
||||
width: 0,
|
||||
),
|
||||
),
|
||||
deleteIcon: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1.5,
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: TextField(
|
||||
controller: _subspaceNameController,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: state.subSpaces.isEmpty
|
||||
? 'Please enter the name'
|
||||
: null,
|
||||
hintStyle: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
onSubmitted: (value) {
|
||||
final trimmedValue = value.trim();
|
||||
if (trimmedValue.isNotEmpty) {
|
||||
context.read<SubSpaceBloc>().add(
|
||||
AddSubSpace(
|
||||
SubspaceModel(
|
||||
subspaceName: trimmedValue,
|
||||
disabled: false,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 16,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
onDeleted: () => context
|
||||
.read<SubSpaceBloc>()
|
||||
.add(RemoveSubSpace(subSpace)),
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: TextField(
|
||||
controller: textController,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: state.subSpaces.isEmpty
|
||||
? 'Please enter the name'
|
||||
: null,
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(
|
||||
color: ColorsManager
|
||||
.lightGrayColor)),
|
||||
onSubmitted: (value) {
|
||||
if (value.trim().isNotEmpty) {
|
||||
context.read<SubSpaceBloc>().add(
|
||||
AddSubSpace(SubspaceModel(
|
||||
subspaceName: value.trim(),
|
||||
disabled: false)));
|
||||
textController.clear();
|
||||
}
|
||||
},
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium),
|
||||
),
|
||||
],
|
||||
);
|
||||
_subspaceNameController.clear();
|
||||
}
|
||||
},
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
if (state.errorMessage.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(state.errorMessage,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(
|
||||
color: ColorsManager.warningRed,
|
||||
)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CancelButton(
|
||||
label: 'Cancel',
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
onPressed: (state.errorMessage.isNotEmpty)
|
||||
? null
|
||||
: () async {
|
||||
final subSpaces = context
|
||||
.read<SubSpaceBloc>()
|
||||
.state
|
||||
.subSpaces;
|
||||
onSave!(subSpaces);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
borderRadius: 10,
|
||||
foregroundColor: state.errorMessage.isNotEmpty
|
||||
? ColorsManager.whiteColorsWithOpacity
|
||||
: ColorsManager.whiteColors,
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
if (state.errorMessage.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
state.errorMessage,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.warningRed,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CancelButton(
|
||||
label: 'Cancel',
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
onPressed: state.errorMessage.isEmpty
|
||||
? () {
|
||||
final subSpacesBloc = context.read<SubSpaceBloc>();
|
||||
final subSpaces = subSpacesBloc.state.subSpaces;
|
||||
widget.onSave?.call(subSpaces);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
: null,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
borderRadius: 10,
|
||||
foregroundColor: state.errorMessage.isNotEmpty
|
||||
? ColorsManager.whiteColorsWithOpacity
|
||||
: ColorsManager.whiteColors,
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,4 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
@ -51,6 +54,9 @@ class CreateSubspaceModelChipsBox extends StatelessWidget {
|
||||
return SubspaceChip(
|
||||
subSpace: subSpace,
|
||||
isDuplicate: isDuplicate,
|
||||
onDeleted: () => context.read<SubSpaceModelBloc>().add(
|
||||
RemoveSubSpaceModel(subSpace),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -1,7 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
@ -11,10 +8,12 @@ class SubspaceChip extends StatelessWidget {
|
||||
required this.subSpace,
|
||||
required this.isDuplicate,
|
||||
super.key,
|
||||
required this.onDeleted,
|
||||
});
|
||||
|
||||
final SubspaceTemplateModel subSpace;
|
||||
final bool isDuplicate;
|
||||
final void Function() onDeleted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -50,9 +49,7 @@ class SubspaceChip extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
onDeleted: () => context.read<SubSpaceModelBloc>().add(
|
||||
RemoveSubSpaceModel(subSpace),
|
||||
),
|
||||
onDeleted: onDeleted,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
|
||||
@ -27,6 +29,8 @@ class SpaceModelPage extends StatelessWidget {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is SpaceModelLoaded) {
|
||||
final spaceModels = state.spaceModels;
|
||||
context.read<SpaceTreeBloc>().add(ClearCachedData());
|
||||
|
||||
final allTagValues = _getAllTagValues(spaceModels);
|
||||
final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
|
||||
|
||||
|
@ -145,13 +145,11 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
TagChipDisplay(
|
||||
context,
|
||||
screenWidth: screenWidth,
|
||||
spaceModel: updatedSpaceModel,
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
allTags: allTags,
|
||||
spaceNameController: spaceNameController,
|
||||
spaceName: spaceNameController.text,
|
||||
pageContext: pageContext,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
allSpaceModels: allSpaceModels,
|
||||
|
@ -10,139 +10,152 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_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';
|
||||
|
||||
class TagChipDisplay extends StatelessWidget {
|
||||
final double screenWidth;
|
||||
const TagChipDisplay({
|
||||
required this.spaceModel,
|
||||
required this.products,
|
||||
required this.subspaces,
|
||||
required this.allTags,
|
||||
required this.spaceName,
|
||||
required this.projectTags,
|
||||
this.pageContext,
|
||||
this.otherSpaceModels,
|
||||
this.allSpaceModels,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final SpaceTemplateModel? spaceModel;
|
||||
final List<ProductModel>? products;
|
||||
final List<SubspaceTemplateModel>? subspaces;
|
||||
final List<String>? allTags;
|
||||
final TextEditingController spaceNameController;
|
||||
final String spaceName;
|
||||
final BuildContext? pageContext;
|
||||
final List<String>? otherSpaceModels;
|
||||
final List<SpaceTemplateModel>? allSpaceModels;
|
||||
final List<Tag> projectTags;
|
||||
|
||||
const TagChipDisplay(BuildContext context,
|
||||
{Key? key,
|
||||
required this.screenWidth,
|
||||
required this.spaceModel,
|
||||
required this.products,
|
||||
required this.subspaces,
|
||||
required this.allTags,
|
||||
required this.spaceNameController,
|
||||
this.pageContext,
|
||||
this.otherSpaceModels,
|
||||
this.allSpaceModels,
|
||||
required this.projectTags})
|
||||
: super(key: key);
|
||||
Map<ProductModel, int> get _groupedTags {
|
||||
final spaceTags = spaceModel?.tags ?? <Tag>[];
|
||||
|
||||
final subspaces = spaceModel?.subspaceModels ?? [];
|
||||
final subspaceTags = subspaces.expand((e) => e.tags ?? <Tag>[]).toList();
|
||||
|
||||
return TagHelper.groupTags([...spaceTags, ...subspaceTags]);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return (spaceModel?.tags?.isNotEmpty == true ||
|
||||
spaceModel?.subspaceModels?.any((subspace) => subspace.tags?.isNotEmpty == true) ==
|
||||
true)
|
||||
? SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3.0, // Border width
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
// Combine tags from spaceModel and subspaces
|
||||
...TagHelper.groupTags([
|
||||
...?spaceModel?.tags,
|
||||
...?spaceModel?.subspaceModels?.expand((subspace) => subspace.tags ?? [])
|
||||
]).entries.map(
|
||||
(entry) => Chip(
|
||||
avatar: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: SvgPicture.asset(
|
||||
entry.key.icon ?? 'assets/icons/gateway.svg',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
'x${entry.value}', // Show count
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
EditChip(onTap: () async {
|
||||
// Use the Navigator's context for showDialog
|
||||
Navigator.of(context).pop();
|
||||
final hasTags = spaceModel?.tags?.isNotEmpty ?? false;
|
||||
final hasSubspaceTags =
|
||||
spaceModel?.subspaceModels?.any((e) => e.tags?.isNotEmpty ?? false) ?? false;
|
||||
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => AssignTagModelsDialog(
|
||||
products: products,
|
||||
allSpaceModels: allSpaceModels,
|
||||
subspaces: subspaces,
|
||||
pageContext: pageContext,
|
||||
allTags: allTags,
|
||||
spaceModel: spaceModel,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
initialTags: TagHelper.generateInitialTags(
|
||||
subspaces: subspaces, spaceTagModels: spaceModel?.tags ?? []),
|
||||
title: 'Edit Device',
|
||||
addedProducts: TagHelper.createInitialSelectedProducts(
|
||||
spaceModel?.tags ?? [], subspaces),
|
||||
spaceName: spaceModel?.modelName ?? '',
|
||||
projectTags: projectTags,
|
||||
));
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
if (hasTags || hasSubspaceTags) {
|
||||
return Container(
|
||||
width: context.screenWidth * 0.25,
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3,
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
..._groupedTags.entries.map((entry) => _buildChip(context, entry)),
|
||||
_buildEditChip(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => AddDeviceTypeModelWidget(
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
allTags: allTags,
|
||||
spaceName: spaceNameController.text,
|
||||
pageContext: pageContext,
|
||||
isCreate: true,
|
||||
spaceModel: spaceModel,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
projectTags: projectTags,
|
||||
),
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
child: const ButtonContentWidget(
|
||||
icon: Icons.add,
|
||||
label: 'Add Devices',
|
||||
),
|
||||
);
|
||||
return _buildAddDevicesButton(context);
|
||||
}
|
||||
|
||||
Widget _buildEditChip(BuildContext context) {
|
||||
return EditChip(
|
||||
onTap: () => showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AssignTagModelsDialog(
|
||||
products: products,
|
||||
allSpaceModels: allSpaceModels,
|
||||
subspaces: subspaces,
|
||||
pageContext: pageContext,
|
||||
allTags: allTags,
|
||||
spaceModel: spaceModel,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
initialTags: TagHelper.generateInitialTags(
|
||||
subspaces: subspaces,
|
||||
spaceTagModels: spaceModel?.tags ?? [],
|
||||
),
|
||||
title: 'Edit Device',
|
||||
addedProducts: TagHelper.createInitialSelectedProducts(
|
||||
spaceModel?.tags ?? [],
|
||||
subspaces,
|
||||
),
|
||||
spaceName: spaceModel?.modelName ?? '',
|
||||
projectTags: projectTags,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChip(
|
||||
BuildContext context,
|
||||
MapEntry<ProductModel, int> entry,
|
||||
) {
|
||||
return Chip(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
avatar: SvgPicture.asset(
|
||||
entry.key.icon ?? Assets.gateway,
|
||||
fit: BoxFit.contain,
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
label: Text(
|
||||
'${entry.value}',
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAddDevicesButton(BuildContext context) {
|
||||
return TextButton(
|
||||
onPressed: () => showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AddDeviceTypeModelWidget(
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
pageContext: pageContext,
|
||||
isCreate: true,
|
||||
spaceModel: spaceModel,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
projectTags: projectTags,
|
||||
),
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
child: const ButtonContentWidget(
|
||||
icon: Icons.add,
|
||||
label: 'Add Devices',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
95
lib/services/batch_control_devices_service.dart
Normal file
95
lib/services/batch_control_devices_service.dart
Normal file
@ -0,0 +1,95 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
abstract interface class BatchControlDevicesService {
|
||||
Future<bool> batchControlDevices({
|
||||
required List<String> uuids,
|
||||
required String code,
|
||||
required Object value,
|
||||
});
|
||||
}
|
||||
|
||||
final class RemoteBatchControlDevicesService implements BatchControlDevicesService {
|
||||
@override
|
||||
Future<bool> batchControlDevices({
|
||||
required List<String> uuids,
|
||||
required String code,
|
||||
required Object value,
|
||||
}) async {
|
||||
try {
|
||||
final body = {
|
||||
'devicesUuid': uuids,
|
||||
'code': code,
|
||||
'value': value,
|
||||
'operationType': 'COMMAND',
|
||||
};
|
||||
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.deviceBatchControl,
|
||||
body: body,
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) => (json['success'] as bool?) ?? false,
|
||||
);
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
log('Error fetching $e', name: 'BatchControlDevicesService');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class DebouncedBatchControlDevicesService
|
||||
implements BatchControlDevicesService {
|
||||
final BatchControlDevicesService decoratee;
|
||||
final Duration debounceDuration;
|
||||
|
||||
DebouncedBatchControlDevicesService({
|
||||
required this.decoratee,
|
||||
this.debounceDuration = const Duration(milliseconds: 1500),
|
||||
});
|
||||
|
||||
final _pendingRequests = <(List<String> uuids, String code, Object value)>[];
|
||||
var _isProcessing = false;
|
||||
|
||||
@override
|
||||
Future<bool> batchControlDevices({
|
||||
required List<String> uuids,
|
||||
required String code,
|
||||
required Object value,
|
||||
}) async {
|
||||
_pendingRequests.add((uuids, code, value));
|
||||
|
||||
if (_isProcessing) return false;
|
||||
|
||||
_isProcessing = true;
|
||||
|
||||
await Future.delayed(debounceDuration);
|
||||
|
||||
final groupedRequests =
|
||||
<String, (List<String> uuids, String code, Object value)>{};
|
||||
for (final request in _pendingRequests) {
|
||||
final (_, requestCode, requestValue) = request;
|
||||
groupedRequests[requestCode] = request;
|
||||
}
|
||||
_pendingRequests.clear();
|
||||
|
||||
try {
|
||||
var allSuccessful = true;
|
||||
for (final request in groupedRequests.values) {
|
||||
final (lastRequestUuids, lastRequestCode, lastRequestValue) = request;
|
||||
final success = await decoratee.batchControlDevices(
|
||||
uuids: lastRequestUuids,
|
||||
code: lastRequestCode,
|
||||
value: lastRequestValue,
|
||||
);
|
||||
if (!success) allSuccessful = false;
|
||||
}
|
||||
return allSuccessful;
|
||||
} finally {
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
}
|
84
lib/services/control_device_service.dart
Normal file
84
lib/services/control_device_service.dart
Normal file
@ -0,0 +1,84 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
abstract interface class ControlDeviceService {
|
||||
Future<bool> controlDevice({
|
||||
required String deviceUuid,
|
||||
required Status status,
|
||||
});
|
||||
}
|
||||
|
||||
final class RemoteControlDeviceService implements ControlDeviceService {
|
||||
@override
|
||||
Future<bool> controlDevice({
|
||||
required String deviceUuid,
|
||||
required Status status,
|
||||
}) async {
|
||||
try {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.deviceControl.replaceAll('{uuid}', deviceUuid),
|
||||
body: status.toMap(),
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
return (json['success'] as bool?) ?? false;
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
log('Error fetching $e', name: 'ControlDeviceService');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class DebouncedControlDeviceService implements ControlDeviceService {
|
||||
final ControlDeviceService decoratee;
|
||||
final Duration debounceDuration;
|
||||
|
||||
DebouncedControlDeviceService({
|
||||
required this.decoratee,
|
||||
this.debounceDuration = const Duration(milliseconds: 1500),
|
||||
});
|
||||
|
||||
final _pendingRequests = <(String deviceUuid, Status status)>[];
|
||||
var _isProcessing = false;
|
||||
|
||||
@override
|
||||
Future<bool> controlDevice({
|
||||
required String deviceUuid,
|
||||
required Status status,
|
||||
}) async {
|
||||
_pendingRequests.add((deviceUuid, status));
|
||||
|
||||
if (_isProcessing) return false;
|
||||
|
||||
_isProcessing = true;
|
||||
|
||||
await Future.delayed(debounceDuration);
|
||||
|
||||
final groupedRequests = <String, (String deviceUuid, Status status)>{};
|
||||
for (final request in _pendingRequests) {
|
||||
final (_, requestStatus) = request;
|
||||
groupedRequests[requestStatus.code] = request;
|
||||
}
|
||||
_pendingRequests.clear();
|
||||
|
||||
try {
|
||||
var allSuccessful = true;
|
||||
for (final request in groupedRequests.values) {
|
||||
final (lastRequestDeviceUuid, lastRequestStatus) = request;
|
||||
final success = await decoratee.controlDevice(
|
||||
deviceUuid: lastRequestDeviceUuid,
|
||||
status: lastRequestStatus,
|
||||
);
|
||||
if (!success) allSuccessful = false;
|
||||
}
|
||||
return allSuccessful;
|
||||
} finally {
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -281,6 +281,7 @@ class CommunitySpaceManagementApi {
|
||||
return json['success'] ?? false;
|
||||
},
|
||||
);
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error updating space: $e');
|
||||
|
@ -13,7 +13,8 @@ class Assets {
|
||||
static const String rightLine = "assets/images/right_line.png";
|
||||
static const String google = "assets/images/google.svg";
|
||||
static const String facebook = "assets/images/facebook.svg";
|
||||
static const String invisiblePassword = "assets/images/Password_invisible.svg";
|
||||
static const String invisiblePassword =
|
||||
"assets/images/Password_invisible.svg";
|
||||
static const String visiblePassword = "assets/images/password_visible.svg";
|
||||
static const String accessIcon = "assets/images/access_icon.svg";
|
||||
static const String spaseManagementIcon =
|
||||
@ -30,7 +31,8 @@ class Assets {
|
||||
static const String emptyTable = "assets/images/empty_table.svg";
|
||||
|
||||
// General assets
|
||||
static const String motionlessDetection = "assets/icons/motionless_detection.svg";
|
||||
static const String motionlessDetection =
|
||||
"assets/icons/motionless_detection.svg";
|
||||
static const String acHeating = "assets/icons/ac_heating.svg";
|
||||
static const String acPowerOff = "assets/icons/ac_power_off.svg";
|
||||
static const String acFanMiddle = "assets/icons/ac_fan_middle.svg";
|
||||
@ -67,19 +69,22 @@ class Assets {
|
||||
"assets/icons/automation_functions/temp_password_unlock.svg";
|
||||
static const String doorlockNormalOpen =
|
||||
"assets/icons/automation_functions/doorlock_normal_open.svg";
|
||||
static const String doorbell = "assets/icons/automation_functions/doorbell.svg";
|
||||
static const String doorbell =
|
||||
"assets/icons/automation_functions/doorbell.svg";
|
||||
static const String remoteUnlockViaApp =
|
||||
"assets/icons/automation_functions/remote_unlock_via_app.svg";
|
||||
static const String doubleLock =
|
||||
"assets/icons/automation_functions/double_lock.svg";
|
||||
static const String selfTestResult =
|
||||
"assets/icons/automation_functions/self_test_result.svg";
|
||||
static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg";
|
||||
static const String lockAlarm =
|
||||
"assets/icons/automation_functions/lock_alarm.svg";
|
||||
static const String presenceState =
|
||||
"assets/icons/automation_functions/presence_state.svg";
|
||||
static const String currentTemp =
|
||||
"assets/icons/automation_functions/current_temp.svg";
|
||||
static const String presence = "assets/icons/automation_functions/presence.svg";
|
||||
static const String presence =
|
||||
"assets/icons/automation_functions/presence.svg";
|
||||
static const String residualElectricity =
|
||||
"assets/icons/automation_functions/residual_electricity.svg";
|
||||
static const String hijackAlarm =
|
||||
@ -96,12 +101,15 @@ class Assets {
|
||||
|
||||
// Presence Sensor Assets
|
||||
static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg";
|
||||
static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg";
|
||||
static const String sensorPresenceIcon =
|
||||
"assets/icons/sensor_presence_ic.svg";
|
||||
static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg";
|
||||
static const String illuminanceRecordIcon =
|
||||
"assets/icons/illuminance_record_ic.svg";
|
||||
static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg";
|
||||
static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg";
|
||||
static const String presenceRecordIcon =
|
||||
"assets/icons/presence_record_ic.svg";
|
||||
static const String helpDescriptionIcon =
|
||||
"assets/icons/help_description_ic.svg";
|
||||
|
||||
static const String lightPulp = "assets/icons/light_pulb.svg";
|
||||
static const String acDevice = "assets/icons/ac_device.svg";
|
||||
@ -151,10 +159,12 @@ class Assets {
|
||||
static const String unit = 'assets/icons/unit_icon.svg';
|
||||
static const String villa = 'assets/icons/villa_icon.svg';
|
||||
static const String iconEdit = 'assets/icons/icon_edit_icon.svg';
|
||||
static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg';
|
||||
static const String textFieldSearch =
|
||||
'assets/icons/textfield_search_icon.svg';
|
||||
static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg';
|
||||
static const String addIcon = 'assets/icons/add_icon.svg';
|
||||
static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg';
|
||||
static const String smartThermostatIcon =
|
||||
'assets/icons/smart_thermostat_icon.svg';
|
||||
static const String smartLightIcon = 'assets/icons/smart_light_icon.svg';
|
||||
static const String presenceSensor = 'assets/icons/presence_sensor.svg';
|
||||
static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg';
|
||||
@ -202,7 +212,8 @@ class Assets {
|
||||
//assets/icons/water_leak_normal.svg
|
||||
static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg';
|
||||
//assets/icons/water_leak_detected.svg
|
||||
static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg';
|
||||
static const String waterLeakDetected =
|
||||
'assets/icons/water_leak_detected.svg';
|
||||
|
||||
//assets/icons/automation_records.svg
|
||||
static const String automationRecords = 'assets/icons/automation_records.svg';
|
||||
@ -273,13 +284,16 @@ class Assets {
|
||||
"assets/icons/functions_icons/sensitivity.svg";
|
||||
static const String assetsSensitivityOperationIcon =
|
||||
"assets/icons/functions_icons/sesitivity_operation_icon.svg";
|
||||
static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg";
|
||||
static const String assetsAcPower =
|
||||
"assets/icons/functions_icons/ac_power.svg";
|
||||
static const String assetsAcPowerOFF =
|
||||
"assets/icons/functions_icons/ac_power_off.svg";
|
||||
static const String assetsChildLock =
|
||||
"assets/icons/functions_icons/child_lock.svg";
|
||||
static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg";
|
||||
static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg";
|
||||
static const String assetsFreezing =
|
||||
"assets/icons/functions_icons/freezing.svg";
|
||||
static const String assetsFanSpeed =
|
||||
"assets/icons/functions_icons/fan_speed.svg";
|
||||
static const String assetsAcCooling =
|
||||
"assets/icons/functions_icons/ac_cooling.svg";
|
||||
static const String assetsAcHeating =
|
||||
@ -288,7 +302,8 @@ class Assets {
|
||||
"assets/icons/functions_icons/celsius_degrees.svg";
|
||||
static const String assetsTempreture =
|
||||
"assets/icons/functions_icons/tempreture.svg";
|
||||
static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg";
|
||||
static const String assetsAcFanLow =
|
||||
"assets/icons/functions_icons/ac_fan_low.svg";
|
||||
static const String assetsAcFanMiddle =
|
||||
"assets/icons/functions_icons/ac_fan_middle.svg";
|
||||
static const String assetsAcFanHigh =
|
||||
@ -307,7 +322,8 @@ class Assets {
|
||||
"assets/icons/functions_icons/far_detection.svg";
|
||||
static const String assetsFarDetectionFunction =
|
||||
"assets/icons/functions_icons/far_detection_function.svg";
|
||||
static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg";
|
||||
static const String assetsIndicator =
|
||||
"assets/icons/functions_icons/indicator.svg";
|
||||
static const String assetsMotionDetection =
|
||||
"assets/icons/functions_icons/motion_detection.svg";
|
||||
static const String assetsMotionlessDetection =
|
||||
@ -320,7 +336,8 @@ class Assets {
|
||||
"assets/icons/functions_icons/master_state.svg";
|
||||
static const String assetsSwitchAlarmSound =
|
||||
"assets/icons/functions_icons/switch_alarm_sound.svg";
|
||||
static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg";
|
||||
static const String assetsResetOff =
|
||||
"assets/icons/functions_icons/reset_off.svg";
|
||||
|
||||
// Assets for automation_functions
|
||||
static const String assetsCardUnlock =
|
||||
@ -364,12 +381,14 @@ class Assets {
|
||||
static const String activeUser = 'assets/icons/active_user.svg';
|
||||
static const String deActiveUser = 'assets/icons/deactive_user.svg';
|
||||
static const String invitedIcon = 'assets/icons/invited_icon.svg';
|
||||
static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png';
|
||||
static const String rectangleCheckBox =
|
||||
'assets/icons/rectangle_check_box.png';
|
||||
static const String CheckBoxChecked = 'assets/icons/box_checked.png';
|
||||
static const String emptyBox = 'assets/icons/empty_box.png';
|
||||
static const String completeProcessIcon =
|
||||
'assets/icons/compleate_process_icon.svg';
|
||||
static const String currentProcessIcon = 'assets/icons/current_process_icon.svg';
|
||||
static const String currentProcessIcon =
|
||||
'assets/icons/current_process_icon.svg';
|
||||
static const String uncomplete_ProcessIcon =
|
||||
'assets/icons/uncompleate_process_icon.svg';
|
||||
static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg';
|
||||
@ -390,9 +409,11 @@ class Assets {
|
||||
static const String successIcon = 'assets/icons/success_icon.svg';
|
||||
static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
|
||||
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png';
|
||||
static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png';
|
||||
static const String scenesPlayIconCheck =
|
||||
'assets/icons/scenesPlayIconCheck.png';
|
||||
static const String presenceStateIcon = 'assets/icons/presence_state.svg';
|
||||
static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg';
|
||||
static const String currentDistanceIcon =
|
||||
'assets/icons/current_distance_icon.svg';
|
||||
|
||||
static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg';
|
||||
static const String motionDetectionSensitivityIcon =
|
||||
@ -415,28 +436,48 @@ class Assets {
|
||||
static const String cpsMode4 = 'assets/icons/cps_mode4.svg';
|
||||
static const String closeToMotion = 'assets/icons/close_to_motion.svg';
|
||||
static const String farAwayMotion = 'assets/icons/far_away_motion.svg';
|
||||
static const String communicationFault = 'assets/icons/communication_fault.svg';
|
||||
static const String communicationFault =
|
||||
'assets/icons/communication_fault.svg';
|
||||
static const String radarFault = 'assets/icons/radar_fault.svg';
|
||||
static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg';
|
||||
static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg';
|
||||
static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg';
|
||||
static const String selfTestingSuccess =
|
||||
'assets/icons/self_testing_success.svg';
|
||||
static const String selfTestingFailure =
|
||||
'assets/icons/self_testing_failure.svg';
|
||||
static const String selfTestingTimeout =
|
||||
'assets/icons/self_testing_timeout.svg';
|
||||
static const String movingSpeed = 'assets/icons/moving_speed.svg';
|
||||
static const String boundary = 'assets/icons/boundary.svg';
|
||||
static const String motionMeter = 'assets/icons/motion_meter.svg';
|
||||
static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg';
|
||||
static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg';
|
||||
static const String spatialStaticValue =
|
||||
'assets/icons/spatial_static_value.svg';
|
||||
static const String spatialMotionValue =
|
||||
'assets/icons/spatial_motion_value.svg';
|
||||
static const String presenceJudgementThrshold =
|
||||
'assets/icons/presence_judgement_threshold.svg';
|
||||
static const String spaceType = 'assets/icons/space_type.svg';
|
||||
static const String sportsPara = 'assets/icons/sports_para.svg';
|
||||
static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg';
|
||||
static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg';
|
||||
static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg';
|
||||
static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg';
|
||||
static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg';
|
||||
static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg';
|
||||
static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg';
|
||||
static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg';
|
||||
static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg';
|
||||
static const String sensitivityFeature1 =
|
||||
'assets/icons/sensitivity_feature_1.svg';
|
||||
static const String sensitivityFeature2 =
|
||||
'assets/icons/sensitivity_feature_2.svg';
|
||||
static const String sensitivityFeature3 =
|
||||
'assets/icons/sensitivity_feature_3.svg';
|
||||
static const String sensitivityFeature4 =
|
||||
'assets/icons/sensitivity_feature_4.svg';
|
||||
static const String sensitivityFeature5 =
|
||||
'assets/icons/sensitivity_feature_5.svg';
|
||||
static const String sensitivityFeature6 =
|
||||
'assets/icons/sensitivity_feature_6.svg';
|
||||
static const String sensitivityFeature7 =
|
||||
'assets/icons/sensitivity_feature_7.svg';
|
||||
static const String sensitivityFeature8 =
|
||||
'assets/icons/sensitivity_feature_8.svg';
|
||||
static const String sensitivityFeature9 =
|
||||
'assets/icons/sensitivity_feature_9.svg';
|
||||
static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg';
|
||||
static const String targetConfirmTimeIcon =
|
||||
'assets/icons/target_confirm_time_icon.svg';
|
||||
static const String DisappeDelayIcon = 'assets/icons/disappe_delay_icon.svg';
|
||||
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
|
||||
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ enum DeviceType {
|
||||
ThreeTouch,
|
||||
GarageDoor,
|
||||
WaterLeak,
|
||||
NCPS,
|
||||
DoorSensor,
|
||||
Other,
|
||||
}
|
||||
@ -56,5 +57,6 @@ Map<String, DeviceType> devicesTypesMap = {
|
||||
"2GT": DeviceType.TwoGang,
|
||||
"3GT": DeviceType.ThreeGang,
|
||||
'GD': DeviceType.GarageDoor,
|
||||
'WL': DeviceType.WaterLeak
|
||||
'WL': DeviceType.WaterLeak,
|
||||
'NCPS': DeviceType.NCPS,
|
||||
};
|
||||
|
@ -26,7 +26,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
bloc:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: bloc
|
||||
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
|
||||
|
@ -60,6 +60,7 @@ dependencies:
|
||||
firebase_core: ^3.11.0
|
||||
firebase_crashlytics: ^4.3.2
|
||||
firebase_database: ^11.3.2
|
||||
bloc: ^8.1.4
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|
Reference in New Issue
Block a user