@ -1,10 +1,4 @@
|
||||
<svg width="17" height="22" viewBox="0 0 17 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.771 11.6328H8.13623L7.81885 11L8.13623 10.3672H8.771V11.6328Z" fill="#292827"/>
|
||||
<path d="M7.50146 10.3672H8.13623V11.6328H7.50146V10.3672Z" fill="#464443"/>
|
||||
<path d="M4.8042 10.3672H6.07373V11.6328H4.8042V10.3672Z" fill="#464443"/>
|
||||
<path d="M10.1987 10.3672H11.4683V11.6328H10.1987V10.3672Z" fill="#292827"/>
|
||||
<path d="M8.13626 2.88879C3.64275 2.88879 0 6.52029 0 11C0 13.0387 0.754651 14.9015 2.00036 16.3265L3.9143 14.4184C3.15343 13.4856 2.69708 12.296 2.69708 11C2.69708 8.00525 5.13225 5.57757 8.13621 5.57757H8.8105V2.88879H8.13626Z" fill="#30BBEC"/>
|
||||
<path d="M14.2725 5.67358L12.3586 7.58168C13.1195 8.51449 13.5758 9.70409 13.5758 11.0001C13.5758 13.9949 11.1407 16.4225 8.13669 16.4225H7.4624V19.1113H8.13665C12.6302 19.1113 16.2729 15.4798 16.2729 11.0001C16.2729 8.96142 15.5183 7.09859 14.2725 5.67358Z" fill="#1F8DCD"/>
|
||||
<path d="M8.13623 0.199951V8.26637L12.1819 4.23316L8.13623 0.199951Z" fill="#16A5D9"/>
|
||||
<path d="M8.13648 21.8V13.7336L4.09082 17.7668L8.13648 21.8Z" fill="#16A5D9"/>
|
||||
<svg width="35" height="30" viewBox="0 0 35 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M27.4702 3.86642L24.351 7.51614C22.56 5.82834 20.1489 4.79201 17.5 4.79201C11.9882 4.79201 7.50309 9.27708 7.50309 14.7889C7.50309 15.0111 7.5113 15.2319 7.52565 15.4506H9.73845C10.4774 15.4506 10.8725 16.3209 10.3872 16.8773L8.47038 19.0757L5.30055 22.7125L4.41734 21.6994L0.213911 16.8773C-0.272125 16.3209 0.123677 15.4506 0.862644 15.4506H2.72613C2.71656 15.2312 2.71109 15.0104 2.71109 14.7889C2.71109 10.8384 4.24918 7.12444 7.04235 4.33126C9.83552 1.53809 13.5495 0 17.5 0C21.2249 0 24.7393 1.36856 27.4702 3.86642Z" fill="#619CED"/>
|
||||
<path d="M34.1379 15.491H32.273C32.1021 19.1776 30.5852 22.6188 27.9582 25.2466C25.165 28.0397 21.451 29.5778 17.5005 29.5778C14.1003 29.5778 10.8758 28.4376 8.26172 26.3383L11.3817 22.6886C13.0742 24.0024 15.1975 24.7858 17.5005 24.7858C22.7765 24.7858 27.1105 20.6767 27.4721 15.491H25.2621C24.5231 15.491 24.128 14.6214 24.6133 14.0643L26.9143 11.425L29.7 8.22914L31.6516 10.4679L34.7866 14.0643C35.2726 14.6214 34.8768 15.491 34.1379 15.491Z" fill="#619CED"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
8
assets/icons/firmware.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg width="32" height="35" viewBox="0 0 32 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.20859 17.5333C2.20859 22.0298 4.44255 26.0045 7.86104 28.408C8.46346 28.8318 8.4955 29.7117 7.91685 30.1672L7.90056 30.18C7.52272 30.4775 6.99293 30.4973 6.59933 30.2209C2.6086 27.4182 0 22.7802 0 17.5333C0 9.02288 7.04447 2.11406 15.5378 2.04143V4.25055C8.26345 4.32265 2.20859 10.2421 2.20859 17.5333Z" fill="#1ED688"/>
|
||||
<path d="M15.9433 0.065948L20.0021 2.84224C20.2195 2.99097 20.2195 3.31167 20.0021 3.46041L15.9433 6.2367C15.6947 6.4068 15.3574 6.22869 15.3574 5.92775V0.375166C15.3574 0.0739588 15.6947 -0.103882 15.9433 0.065948Z" fill="#1AB975"/>
|
||||
<path d="M29.0795 17.4666C29.0795 12.9701 26.8453 8.99537 23.4268 6.59185C22.8243 6.16808 22.7923 5.28822 23.371 4.83267L23.3872 4.81985C23.7651 4.52238 24.2949 4.50262 24.6885 4.779C28.6795 7.58172 31.2878 12.2197 31.2878 17.4666C31.2878 25.977 24.2553 32.8858 15.7617 32.9585V30.7493C23.0361 30.6772 29.0795 24.758 29.0795 17.4666Z" fill="#1ED688"/>
|
||||
<path d="M15.3449 34.9339L11.2861 32.1576C11.0687 32.0089 11.0687 31.6882 11.2861 31.5395L15.3449 28.7632C15.5932 28.5931 15.9308 28.7712 15.9308 29.0721V34.6247C15.9308 34.9259 15.5932 35.104 15.3449 34.9339Z" fill="#1AB975"/>
|
||||
<path d="M22.5325 15.7787L15.884 21.2923L14.2079 22.6824C13.813 23.0095 13.3131 23.1871 12.8047 23.1871C12.72 23.1871 12.6354 23.1823 12.5499 23.1724C11.9568 23.1027 11.411 22.7882 11.053 22.3099L8.46759 18.8637C7.88387 18.0853 8.04168 16.9803 8.82034 16.3955C9.59953 15.8118 10.7045 15.9696 11.2885 16.7483L13.0338 19.0754L13.0479 19.0639L20.2822 13.0643C21.0318 12.4432 22.1432 12.5468 22.7645 13.2964C23.3859 14.0459 23.282 15.1573 22.5325 15.7787Z" fill="#1ED688"/>
|
||||
<path d="M22.5334 15.7787L15.8849 21.2923C14.7976 20.7494 13.8414 19.9991 13.0488 19.0639L20.2832 13.0643C21.0327 12.4432 22.1441 12.5468 22.7654 13.2964C23.3868 14.0459 23.2829 15.1573 22.5334 15.7787Z" fill="#35E298"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
23
assets/icons/main_door.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<svg width="29" height="38" viewBox="0 0 29 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_i_2351_2766)">
|
||||
<path d="M0 5.18168C0 2.69345 1.82822 0.560776 4.30407 0.312864C8.58098 -0.115392 11.9929 -0.0966095 16.2213 0.32393C18.7091 0.57135 20.5548 2.70894 20.5548 5.20899V32.7835C20.5548 35.246 18.7637 37.3665 16.3165 37.6408C12.0557 38.1183 8.5855 38.1222 4.24256 37.6376C1.79332 37.3643 0 35.2435 0 32.7791V5.18168Z" fill="#EAF6FF"/>
|
||||
</g>
|
||||
<path d="M17.5761 23.3577C17.7051 23.3577 17.8341 23.3086 17.9325 23.2102C18.1293 23.0133 18.1293 22.6943 17.9325 22.4975C16.2479 20.813 16.2479 18.0721 17.9325 16.3875C18.1293 16.1907 18.1293 15.8717 17.9325 15.6749C17.7356 15.4782 17.4166 15.4782 17.2198 15.6749C15.1423 17.7525 15.1423 21.1327 17.2198 23.2103C17.3182 23.3086 17.4472 23.3577 17.5761 23.3577Z" fill="#8AC9FE"/>
|
||||
<path d="M18.6358 21.4001C18.744 21.4001 18.8522 21.3588 18.9348 21.2762C19.0999 21.1111 19.0999 20.8434 18.9347 20.6783C18.6046 20.3483 18.4228 19.9093 18.4228 19.4424C18.4228 18.9756 18.6046 18.5367 18.9347 18.2066C19.0999 18.0415 19.0999 17.7738 18.9348 17.6087C18.7696 17.4436 18.5019 17.4436 18.3369 17.6087C17.847 18.0985 17.5772 18.7497 17.5772 19.4424C17.5772 20.1352 17.847 20.7864 18.3369 21.2763C18.4193 21.3587 18.5276 21.4001 18.6358 21.4001Z" fill="#8AC9FE"/>
|
||||
<path d="M11.1713 30.5347L11.1713 32.4539C11.1713 32.8359 10.8617 33.1455 10.4797 33.1455C10.0976 33.1455 9.78809 32.8359 9.78809 32.4539L9.78809 30.5347C9.78809 30.1527 10.0976 29.8431 10.4797 29.8431C10.8617 29.8431 11.1713 30.1527 11.1713 30.5347Z" fill="#B3DAFE"/>
|
||||
<path d="M21.5342 8.79642C21.5342 7.91189 22.1118 7.11379 22.9777 6.93351C24.3924 6.63901 25.5238 6.65107 26.9209 6.93884C27.7961 7.1191 28.3858 7.92191 28.3858 8.81547V31.0421C28.3858 31.9071 27.8339 32.6929 26.9918 32.8912C25.5575 33.2289 24.3936 33.2319 22.9318 32.8895C22.0881 32.6918 21.5342 31.9055 21.5342 31.039V8.79642Z" fill="#EAF6FF"/>
|
||||
<path d="M23.9952 23.3577C23.8662 23.3577 23.7372 23.3086 23.6388 23.2102C23.442 23.0133 23.442 22.6943 23.6388 22.4975C25.3234 20.813 25.3234 18.0721 23.6388 16.3875C23.442 16.1907 23.442 15.8717 23.6388 15.6749C23.8357 15.4782 24.1547 15.4782 24.3515 15.6749C26.429 17.7525 26.429 21.1327 24.3515 23.2103C24.2531 23.3086 24.1241 23.3577 23.9952 23.3577Z" fill="#8AC9FE"/>
|
||||
<path d="M22.9355 21.4001C22.8273 21.4001 22.7191 21.3588 22.6365 21.2762C22.4714 21.1111 22.4714 20.8434 22.6366 20.6783C22.9666 20.3483 23.1485 19.9093 23.1485 19.4424C23.1485 18.9756 22.9666 18.5367 22.6366 18.2066C22.4714 18.0415 22.4714 17.7738 22.6365 17.6087C22.8017 17.4436 23.0694 17.4436 23.2344 17.6087C23.7243 18.0985 23.9941 18.7497 23.9941 19.4424C23.9941 20.1352 23.7243 20.7864 23.2344 21.2763C23.1519 21.3587 23.0437 21.4001 22.9355 21.4001Z" fill="#8AC9FE"/>
|
||||
<defs>
|
||||
<filter id="filter0_i_2351_2766" x="-1" y="0" width="21.5547" height="38" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="-1"/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.538295 0 0 0 0 0.538295 0 0 0 0 0.538295 0 0 0 0.3 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_2351_2766"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
10
assets/icons/main_door_notifi.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="31" height="35" viewBox="0 0 31 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.4316 6.37335C16.8654 6.37335 16.4062 5.91425 16.4062 5.34796V3.07617C16.4062 2.51077 15.9463 2.05078 15.3809 2.05078C14.8155 2.05078 14.3555 2.51077 14.3555 3.07617V5.34796C14.3555 5.91425 13.8964 6.37335 13.3301 6.37335C12.7638 6.37335 12.3047 5.91425 12.3047 5.34796V3.07617C12.3047 1.37997 13.6847 0 15.3809 0C17.0771 0 18.457 1.37997 18.457 3.07617V5.34796C18.457 5.91425 17.9979 6.37335 17.4316 6.37335Z" fill="#FFB454"/>
|
||||
<path d="M16.4062 3.07617V5.34796C16.4062 5.91425 16.8654 6.37335 17.4316 6.37335C17.9979 6.37335 18.457 5.91425 18.457 5.34796V3.07617C18.457 1.37997 17.0771 0 15.3809 0V2.05078C15.9463 2.05078 16.4062 2.51077 16.4062 3.07617Z" fill="#FF8E00"/>
|
||||
<path d="M15.3809 35C12.5539 35 10.2539 32.7 10.2539 29.873C10.2539 29.3068 10.713 28.8477 11.2793 28.8477H19.4824C20.0487 28.8477 20.5078 29.3068 20.5078 29.873C20.5078 32.7 18.2079 35 15.3809 35Z" fill="#FFB454"/>
|
||||
<path d="M19.4824 28.8477H15.3809V35C18.2079 35 20.5078 32.7 20.5078 29.873C20.5078 29.3068 20.0487 28.8477 19.4824 28.8477Z" fill="#FF8E00"/>
|
||||
<path d="M29.7363 30.8984H1.0254C0.614562 30.8984 0.243439 30.6532 0.0822478 30.2753C-0.0789436 29.8975 0.000968566 29.4599 0.285344 29.1633C3.18706 26.1372 4.78517 22.1616 4.78517 17.9691V14.6973C4.78517 8.85479 9.5384 4.10156 15.3809 4.10156C21.2233 4.10156 25.9766 8.85479 25.9766 14.6973V17.9691C25.9766 22.1617 27.5747 26.1372 30.4765 29.1633C30.7608 29.4599 30.8407 29.8975 30.6795 30.2753C30.5184 30.6532 30.1472 30.8984 29.7363 30.8984Z" fill="#FFE278"/>
|
||||
<path d="M29.7363 30.8984C30.1472 30.8984 30.5183 30.6532 30.6795 30.2753C30.8407 29.8975 30.7608 29.4599 30.4765 29.1633C27.5747 26.1372 25.9766 22.1616 25.9766 17.9691V14.6973C25.9766 8.85479 21.2233 4.10156 15.3809 4.10156V30.8984H29.7363Z" fill="#FFB454"/>
|
||||
<path d="M29.7359 15.7226C29.1696 15.7226 28.7105 15.2635 28.7105 14.6972C28.7105 11.1366 27.3239 7.78912 24.8062 5.27144C24.4057 4.87099 24.4057 4.22178 24.8062 3.82133C25.2066 3.42088 25.8559 3.42088 26.2563 3.82133C29.1614 6.72633 30.7613 10.5888 30.7613 14.6972C30.7613 15.2635 30.3022 15.7226 29.7359 15.7226Z" fill="#08475E"/>
|
||||
<path d="M1.02539 15.7226C0.459102 15.7226 0 15.2635 0 14.6972C0 10.5888 1.59988 6.7264 4.50495 3.82133C4.90533 3.42088 5.55461 3.42088 5.95506 3.82133C6.35551 4.22178 6.35551 4.87099 5.95506 5.27144C3.43738 7.78918 2.05078 11.1367 2.05078 14.6972C2.05078 15.2635 1.59168 15.7226 1.02539 15.7226Z" fill="#0A789B"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
18
assets/icons/main_door_reports.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<svg width="35" height="35" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.62771 22.7848L0.0206433 9.97422C-0.0455284 9.78766 0.0527038 9.58101 0.239256 9.51286C6.29643 7.33432 13.1218 5.30816 18.9563 2.81865C19.2171 2.65418 19.4217 2.81667 19.534 3.12757L26.7725 21.7858L28.7381 27.2523C28.8043 27.4388 28.7081 27.6475 28.5215 27.7137L14.6702 32.6947L9.27087 34.6366C9.08432 34.7048 8.87774 34.6065 8.80958 34.4199L4.62771 22.7848Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.46894 18.5338L6.16844 5.11738C6.13433 4.92078 6.26674 4.73423 6.46129 4.70012L22.3042 1.98393L25.9325 1.38613C26.1311 1.29986 26.454 1.40015 26.5302 1.84352L30.4493 21.4146L31.4301 27.1398C31.4642 27.3345 31.3318 27.523 31.1372 27.5571L16.6301 30.0446L10.974 31.0155C10.7795 31.0477 10.5909 30.9152 10.5568 30.7206L8.46894 18.5338Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.9063 14.5779V27.4768C12.9063 27.6835 13.0748 27.854 13.2814 27.854H19.2684H28.1315H29.4051H34.624C34.8305 27.854 34.999 27.6835 34.999 27.4768V21.4166V4.67611V4.50159H31.1882C30.8212 4.50159 30.059 4.55375 30.059 3.72127L30.0509 0H13.2813C13.0748 0 12.9062 0.168505 12.9062 0.375155V3.59487V4.957V14.5779H12.9063Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.54915 7.34229C4.40308 8.0645 2.27704 8.78063 0.239256 9.51283C0.0527037 9.58105 -0.0455284 9.78763 0.0206433 9.97418L4.62771 22.7848L8.80958 34.4199C8.87781 34.6065 9.08439 34.7048 9.27087 34.6365L14.6702 32.6947L26.9732 28.2713L6.54915 7.34229Z" fill="#B5C4CF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.9076 3.59473L6.46129 4.70003C6.26674 4.73414 6.13433 4.92069 6.16844 5.11729L6.54955 7.34197L8.469 18.5337L10.5569 30.7205C10.591 30.9151 10.7795 31.0474 10.9741 31.0154L16.6301 30.0445L26.9735 28.2711L29.4064 27.8538H28.1328L12.9076 3.59473Z" fill="#D7E7EC"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.999 21.4166V4.67611V4.50159V4.44342C34.999 4.33507 34.9528 4.31702 34.7864 4.15453L30.6727 0.230712C30.4782 0.0461424 30.4461 0 30.3318 0H30.0509H13.2813C13.0748 0 12.9062 0.168505 12.9062 0.375155V27.4768C12.9062 27.6835 13.0748 27.854 13.2813 27.854H34.6239C34.8305 27.854 34.999 27.6835 34.999 27.4768V21.4166Z" fill="#EDF3F4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.9077 3.59473L11.3975 3.85353V28.8629C11.3975 29.0716 11.566 29.2401 11.7725 29.2401H17.7595H21.3216L26.9736 28.2712L29.4065 27.8539H28.1329H26.5665H19.2698H13.2828C13.0762 27.8539 12.9077 27.6834 12.9077 27.4768C12.9077 19.5167 12.9077 11.5547 12.9077 3.59473Z" fill="#B5C4CF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.46896 18.5336L6.54951 7.3418C6.03005 7.51632 5.51257 7.69084 4.99707 7.86536L5.23578 9.25558L7.15524 20.4473L9.24518 32.6341C9.27724 32.8286 9.46578 32.961 9.66033 32.929L15.3164 31.9581L18 31.4987L26.0829 28.59L25.9867 28.4395L21.3214 29.2399L16.6302 30.0444L10.9741 31.0153C10.7796 31.0474 10.591 30.915 10.5569 30.7204L8.46896 18.5336Z" fill="#9AAFB7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.9998 21.4166V4.67611V4.50159C34.9998 4.29897 34.9337 4.29699 34.7873 4.15453L30.6736 0.230712C30.4791 0.0461424 30.447 0 30.3326 0H30.0518H25.8779C30.9784 6.8286 31.2692 19.1377 27.4845 26.8209C27.31 27.178 27.1275 27.521 26.937 27.854H28.1323H29.4059H34.6248C34.8313 27.854 34.9998 27.6835 34.9998 27.4768V21.4166Z" fill="#D7E7EC"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.0607 3.72127C30.0607 4.55375 30.8229 4.50159 31.19 4.50159H35.0008V4.44342C35.0008 4.33507 34.9546 4.31702 34.7882 4.15453L30.6745 0.230712C30.48 0.0461424 30.4479 0 30.3336 0H30.0527L30.0607 3.72127Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.7882 4.15453L30.6745 0.230712C30.48 0.0461424 30.4479 0 30.3336 0H30.0527L30.0607 3.7212C30.0607 4.55368 30.8229 4.50152 31.19 4.50152H35.0008V4.44335C35.0008 4.33507 34.9546 4.31702 34.7882 4.15453Z" fill="#B5C4CF"/>
|
||||
<path d="M31.6094 8.20367H16.5086C16.2169 8.20367 15.9805 7.96715 15.9805 7.67532C15.9805 7.3835 16.2169 7.14697 16.5086 7.14697H31.6094C31.9011 7.14697 32.1376 7.3835 32.1376 7.67532C32.1376 7.96715 31.9012 8.20367 31.6094 8.20367Z" fill="#9AAFB7"/>
|
||||
<path d="M31.6094 11.9566H16.5086C16.2169 11.9566 15.9805 11.7201 15.9805 11.4283C15.9805 11.1364 16.2169 10.8999 16.5086 10.8999H31.6094C31.9011 10.8999 32.1376 11.1364 32.1376 11.4283C32.1376 11.7201 31.9012 11.9566 31.6094 11.9566Z" fill="#9AAFB7"/>
|
||||
<path d="M31.6094 15.7105H16.5086C16.2169 15.7105 15.9805 15.474 15.9805 15.1822C15.9805 14.8903 16.2169 14.6538 16.5086 14.6538H31.6094C31.9011 14.6538 32.1376 14.8903 32.1376 15.1822C32.1376 15.474 31.9012 15.7105 31.6094 15.7105Z" fill="#9AAFB7"/>
|
||||
<path d="M31.6094 19.4634H16.5086C16.2169 19.4634 15.9805 19.2269 15.9805 18.9351C15.9805 18.6433 16.2169 18.4067 16.5086 18.4067H31.6094C31.9011 18.4067 32.1376 18.6433 32.1376 18.9351C32.1376 19.2269 31.9012 19.4634 31.6094 19.4634Z" fill="#9AAFB7"/>
|
||||
<path d="M31.6094 23.2169H16.5086C16.2169 23.2169 15.9805 22.9803 15.9805 22.6885C15.9805 22.3967 16.2169 22.1602 16.5086 22.1602H31.6094C31.9011 22.1602 32.1376 22.3967 32.1376 22.6885C32.1376 22.9803 31.9012 23.2169 31.6094 23.2169Z" fill="#9AAFB7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 5.1 KiB |
28
assets/images/scheduling.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<svg width="35" height="35" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6724 33.3447C25.8803 33.3447 33.3447 25.8803 33.3447 16.6724C33.3447 7.46448 25.8803 0 16.6724 0C7.46448 0 0 7.46448 0 16.6724C0 25.8803 7.46448 33.3447 16.6724 33.3447Z" fill="#F07281"/>
|
||||
<path d="M17.3767 33.3303C17.1436 33.3401 16.9084 33.345 16.6725 33.345C7.46478 33.345 0 25.8802 0 16.6725C0 7.46478 7.46478 0 16.6725 0C16.9084 0 17.1436 0.00492188 17.3767 0.0147656C8.4957 0.38377 1.40841 7.70068 1.40841 16.6725C1.40841 25.6443 8.4957 32.9613 17.3767 33.3303Z" fill="#EB5569"/>
|
||||
<path d="M16.6726 31.1577C24.6725 31.1577 31.1577 24.6725 31.1577 16.6726C31.1577 8.67269 24.6725 2.1875 16.6726 2.1875C8.67269 2.1875 2.1875 8.67269 2.1875 16.6726C2.1875 24.6725 8.67269 31.1577 16.6726 31.1577Z" fill="#EAF6FF"/>
|
||||
<path d="M17.7291 31.1199C17.3805 31.1453 17.0276 31.1579 16.6727 31.1579C8.67269 31.1579 2.1875 24.6727 2.1875 16.6727C2.1875 8.67269 8.67275 2.1875 16.6728 2.1875C17.0277 2.1875 17.3805 2.20015 17.7291 2.22551C10.2221 2.76644 4.30022 9.02768 4.30022 16.6727C4.30022 24.3178 10.2221 30.5791 17.7291 31.1199Z" fill="#D8ECFE"/>
|
||||
<path d="M15.7672 8.23957L16.4138 7.82326C16.5715 7.72167 16.7741 7.72167 16.9319 7.82326L17.5785 8.23957C17.6957 8.31503 17.7666 8.44498 17.7666 8.58444V16.8663H15.5791V8.58444C15.579 8.44498 15.6499 8.3151 15.7672 8.23957Z" fill="#5680A6"/>
|
||||
<path d="M17.377 8.10996L16.9876 8.36063V16.8662H15.5791V8.62163C15.5791 8.45893 15.6617 8.30738 15.7985 8.21933L16.4136 7.82326C16.5714 7.72167 16.774 7.72167 16.9318 7.82326L17.377 8.10996Z" fill="#497090"/>
|
||||
<path d="M23.5067 16.9318L23.1106 17.547C23.0225 17.6837 22.871 17.7664 22.7083 17.7664H16.6729V15.5791H22.7083C22.871 15.5791 23.0225 15.6617 23.1106 15.7985L23.5067 16.4137C23.6083 16.5715 23.6083 16.774 23.5067 16.9318Z" fill="#5680A6"/>
|
||||
<path d="M19.6131 16.6727C19.6131 17.0594 19.5384 17.4284 19.4018 17.7664H16.6729V15.5791H19.4018C19.5384 15.9171 19.6131 16.2861 19.6131 16.6727Z" fill="#497090"/>
|
||||
<path d="M16.6723 18.2039C17.5182 18.2039 18.2039 17.5182 18.2039 16.6723C18.2039 15.8264 17.5182 15.1406 16.6723 15.1406C15.8264 15.1406 15.1406 15.8264 15.1406 16.6723C15.1406 17.5182 15.8264 18.2039 16.6723 18.2039Z" fill="#F07281"/>
|
||||
<path d="M17.3765 18.0326C17.166 18.1424 16.9265 18.2044 16.6723 18.2044C15.8265 18.2044 15.1406 17.5185 15.1406 16.6728C15.1406 15.827 15.8265 15.1411 16.6723 15.1411C16.9265 15.1411 17.166 15.2031 17.3765 15.313C16.885 15.5672 16.549 16.0813 16.549 16.6728C16.549 17.2644 16.885 17.7783 17.3765 18.0326Z" fill="#EB5569"/>
|
||||
<path d="M34.5329 28.7151V27.6225C34.5329 27.4043 34.3853 27.2137 34.174 27.1592L32.9876 26.853C32.8555 26.3207 32.6457 25.8193 32.37 25.3617L32.9923 24.3064C33.1032 24.1184 33.0728 23.8793 32.9185 23.7249L32.1459 22.9523C31.9916 22.798 31.7524 22.7676 31.5644 22.8785L30.5091 23.5008C30.0516 23.2251 29.5501 23.0153 29.0178 22.8832L28.7116 21.6968C28.6571 21.4855 28.4665 21.3379 28.2483 21.3379H27.1557C26.9375 21.3379 26.7469 21.4855 26.6924 21.6968L26.3862 22.8832C25.8539 23.0153 25.3525 23.2252 24.8949 23.5008L23.8396 22.8785C23.6516 22.7676 23.4125 22.798 23.2581 22.9523L22.4855 23.7249C22.3313 23.8792 22.3009 24.1184 22.4117 24.3064L23.0341 25.3617C22.7583 25.8193 22.5485 26.3207 22.4164 26.853L21.23 27.1592C21.0188 27.2137 20.8711 27.4043 20.8711 27.6225V28.7151C20.8711 28.9333 21.0188 29.1239 21.23 29.1784L22.4164 29.4846C22.5485 30.0169 22.7584 30.5183 23.0341 30.9759L22.4117 32.0312C22.3008 32.2192 22.3313 32.4583 22.4855 32.6127L23.2581 33.3853C23.4124 33.5396 23.6516 33.5699 23.8396 33.4591L24.8949 32.8368C25.3525 33.1125 25.8539 33.3223 26.3862 33.4544L26.6924 34.6408C26.7469 34.8521 26.9375 34.9997 27.1557 34.9997H28.2483C28.4665 34.9997 28.6571 34.8521 28.7116 34.6408L29.0178 33.4544C29.5501 33.3223 30.0516 33.1124 30.5091 32.8368L31.5644 33.4591C31.7524 33.57 31.9915 33.5396 32.1459 33.3853L32.9185 32.6127C33.0728 32.4584 33.1031 32.2192 32.9923 32.0312L32.37 30.9759C32.6457 30.5183 32.8555 30.0169 32.9876 29.4846L34.174 29.1784C34.3853 29.1239 34.5329 28.9333 34.5329 28.7151Z" fill="#88B4F5"/>
|
||||
<path d="M22.2796 27.252V29.0858L23.8246 29.4844C23.9563 30.0168 24.1662 30.5182 24.4423 30.976L23.6317 32.3506L24.4057 33.1246L23.8391 33.4588C23.6511 33.5696 23.4119 33.5393 23.2576 33.3849L22.4853 32.6127C22.331 32.4584 22.3007 32.2192 22.4115 32.0313L23.0338 30.976C22.7577 30.5182 22.5479 30.0168 22.4162 29.4844L21.23 29.1784C21.0187 29.124 20.8711 28.9333 20.8711 28.7151V27.6226C20.8711 27.4044 21.0187 27.2139 21.2299 27.1593L22.4162 26.8527C22.5479 26.321 22.7577 25.8189 23.0338 25.3619L22.4115 24.3064C22.3007 24.1185 22.331 23.8794 22.4853 23.7251L23.2577 22.9523C23.4119 22.7978 23.6512 22.7675 23.8392 22.8784L24.4057 23.2125L23.6317 23.9872L24.4423 25.3619C24.1662 25.8189 23.9564 26.321 23.8246 26.8527L22.2796 27.252Z" fill="#6EA2F2"/>
|
||||
<path d="M27.7946 33.454L28.1932 34.9997H27.1555C26.9372 34.9997 26.7466 34.8521 26.6921 34.6407L26.3861 33.454C26.1361 33.3927 25.8924 33.3132 25.6572 33.2174L26.303 32.8364C26.7608 33.1124 27.2622 33.3223 27.7946 33.454Z" fill="#6EA2F2"/>
|
||||
<path d="M27.1555 21.3379H28.1932L27.7946 22.883C27.2622 23.0154 26.7608 23.2252 26.303 23.5006L25.6572 23.1196C25.8925 23.0245 26.1361 22.945 26.3861 22.883L26.6921 21.6969C26.7466 21.4855 26.9373 21.3379 27.1555 21.3379Z" fill="#6EA2F2"/>
|
||||
<path d="M27.7023 31.4612C29.5205 31.4612 30.9944 29.9873 30.9944 28.1691C30.9944 26.3509 29.5205 24.877 27.7023 24.877C25.8841 24.877 24.4102 26.3509 24.4102 28.1691C24.4102 29.9873 25.8841 31.4612 27.7023 31.4612Z" fill="#5680A6"/>
|
||||
<path d="M28.7581 31.2882C28.4264 31.4009 28.0707 31.4615 27.7017 31.4615C25.8841 31.4615 24.4102 29.9875 24.4102 28.1692C24.4102 26.3509 25.8841 24.877 27.7017 24.877C28.0707 24.877 28.4263 24.9375 28.7581 25.0502C27.4588 25.4903 26.5229 26.7206 26.5229 28.1692C26.5229 29.6178 27.4588 30.8481 28.7581 31.2882Z" fill="#497090"/>
|
||||
<path d="M16.6727 6.19265C16.381 6.19265 16.1445 5.95619 16.1445 5.6645V4.30012C16.1445 4.00843 16.381 3.77197 16.6727 3.77197C16.9644 3.77197 17.2008 4.00843 17.2008 4.30012V5.66443C17.2008 5.95612 16.9644 6.19265 16.6727 6.19265Z" fill="#88B4F5"/>
|
||||
<path d="M16.6727 29.5729C16.381 29.5729 16.1445 29.3365 16.1445 29.0448V27.6805C16.1445 27.3888 16.381 27.1523 16.6727 27.1523C16.9644 27.1523 17.2008 27.3888 17.2008 27.6805V29.0448C17.2008 29.3364 16.9644 29.5729 16.6727 29.5729Z" fill="#88B4F5"/>
|
||||
<path d="M5.66492 17.2003H4.30061C4.00892 17.2003 3.77246 16.9639 3.77246 16.6722C3.77246 16.3805 4.00892 16.144 4.30061 16.144H5.66492C5.95661 16.144 6.19307 16.3805 6.19307 16.6722C6.19307 16.9639 5.95661 17.2003 5.66492 17.2003Z" fill="#88B4F5"/>
|
||||
<path d="M29.0448 17.2003H27.6805C27.3887 17.2003 27.1523 16.9639 27.1523 16.6722C27.1523 16.3805 27.3888 16.144 27.6805 16.144H29.0448C29.3366 16.144 29.5729 16.3805 29.5729 16.6722C29.5729 16.9639 29.3365 17.2003 29.0448 17.2003Z" fill="#88B4F5"/>
|
||||
<path d="M5.95843 23.3866C5.77591 23.3866 5.59838 23.2918 5.50056 23.1224C5.35468 22.8698 5.44122 22.5467 5.69388 22.4009L6.8754 21.7188C7.12785 21.573 7.45099 21.6594 7.59687 21.9121C7.74275 22.1647 7.6562 22.4877 7.40355 22.6335L6.22202 23.3157C6.1389 23.3638 6.04805 23.3866 5.95843 23.3866Z" fill="#88B4F5"/>
|
||||
<path d="M26.2065 11.6967C26.0239 11.6967 25.8464 11.6019 25.7486 11.4325C25.6027 11.1799 25.6893 10.8568 25.9419 10.711L27.1234 10.0288C27.376 9.88304 27.699 9.96951 27.8449 10.2222C27.9908 10.4748 27.9042 10.7978 27.6516 10.9436L26.4701 11.6258C26.3869 11.6738 26.296 11.6967 26.2065 11.6967Z" fill="#88B4F5"/>
|
||||
<path d="M7.139 11.6967C7.04938 11.6967 6.9586 11.6739 6.8754 11.6259L5.69388 10.9437C5.44122 10.7978 5.35468 10.4748 5.50056 10.2222C5.64637 9.96957 5.96937 9.88296 6.22202 10.0289L7.40355 10.7111C7.6562 10.8569 7.74275 11.1799 7.59687 11.4325C7.49904 11.602 7.32152 11.6967 7.139 11.6967Z" fill="#88B4F5"/>
|
||||
<path d="M10.4858 27.9149C10.3962 27.9149 10.3054 27.892 10.2222 27.844C9.96954 27.6981 9.883 27.3751 10.0289 27.1225L10.711 25.941C10.8568 25.6883 11.1799 25.6017 11.4325 25.7477C11.6852 25.8935 11.7717 26.2165 11.6258 26.4691L10.9437 27.6506C10.8458 27.8201 10.6683 27.9149 10.4858 27.9149Z" fill="#88B4F5"/>
|
||||
<path d="M22.1752 7.66727C22.0856 7.66727 21.9948 7.64444 21.9116 7.59638C21.659 7.45057 21.5724 7.1275 21.7183 6.87491L22.4005 5.69339C22.5463 5.44073 22.8692 5.35419 23.1219 5.50007C23.3745 5.64588 23.4611 5.96895 23.3153 6.22154L22.6331 7.40306C22.5353 7.57252 22.3578 7.66727 22.1752 7.66727Z" fill="#88B4F5"/>
|
||||
<path d="M11.1689 7.66727C10.9864 7.66727 10.8089 7.57252 10.711 7.40306L10.0289 6.22154C9.883 5.96895 9.96954 5.64588 10.2222 5.50007C10.4747 5.35419 10.7979 5.44073 10.9437 5.69339L11.6258 6.87491C11.7717 7.1275 11.6852 7.45057 11.4325 7.59638C11.3494 7.64437 11.2585 7.66727 11.1689 7.66727Z" fill="#88B4F5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 8.6 KiB |
@ -1,9 +1,6 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
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/curtain/bloc/curtain_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
@ -12,6 +9,7 @@ class CurtainToggle extends StatelessWidget {
|
||||
final String code;
|
||||
final String deviceId;
|
||||
final String label;
|
||||
final Null Function(dynamic value) onChanged;
|
||||
|
||||
const CurtainToggle({
|
||||
super.key,
|
||||
@ -19,6 +17,7 @@ class CurtainToggle extends StatelessWidget {
|
||||
required this.code,
|
||||
required this.deviceId,
|
||||
required this.label,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -54,15 +53,7 @@ class CurtainToggle extends StatelessWidget {
|
||||
child: CupertinoSwitch(
|
||||
value: value,
|
||||
activeColor: ColorsManager.dialogBlueTitle,
|
||||
onChanged: (newValue) {
|
||||
context.read<CurtainBloc>().add(
|
||||
CurtainControl(
|
||||
deviceId: deviceId,
|
||||
code: code,
|
||||
value: newValue,
|
||||
),
|
||||
);
|
||||
},
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -86,8 +86,10 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
setState(() {
|
||||
_selectAll = value ?? false;
|
||||
_selected = List<bool>.filled(widget.data.length, _selectAll);
|
||||
if (widget.selectAll != null) {
|
||||
widget.selectAll!(_selectAll);
|
||||
for (int i = 0; i < widget.data.length; i++) {
|
||||
if (widget.onRowSelected != null) {
|
||||
widget.onRowSelected!(i, _selectAll, widget.data[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -185,8 +187,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
),
|
||||
),
|
||||
child: Checkbox(
|
||||
value: widget.data.isNotEmpty &&
|
||||
_selected.every((element) => element == true),
|
||||
value: _selectAll,
|
||||
onChanged: widget.withSelectAll && widget.data.isNotEmpty
|
||||
? _toggleSelectAll
|
||||
: null,
|
||||
|
@ -14,12 +14,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
Timer? _timer;
|
||||
|
||||
AcBloc({required this.deviceId}) : super(AcsInitialState()) {
|
||||
on<AcFetchDeviceStatus>(_onFetchAcStatus);
|
||||
on<AcControl>(_onAcControl);
|
||||
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
|
||||
on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus);
|
||||
on<AcControlEvent>(_onAcControl);
|
||||
on<AcBatchControlEvent>(_onAcBatchControl);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchAcStatus(
|
||||
AcFetchDeviceStatus event, Emitter<AcsState> emit) async {
|
||||
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async {
|
||||
emit(AcsLoadingState());
|
||||
try {
|
||||
final status =
|
||||
@ -31,7 +33,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onAcControl(AcControl event, Emitter<AcsState> emit) async {
|
||||
FutureOr<void> _onAcControl(
|
||||
AcControlEvent event, Emitter<AcsState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value, emit);
|
||||
@ -39,6 +42,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
emit(ACStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
isBatch: false,
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
@ -48,27 +52,43 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required String deviceId,
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required dynamic oldValue,
|
||||
required Emitter<AcsState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
final response = await DevicesManagementApi()
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is DioException && e.response != null) {
|
||||
debugPrint('Error response: ${e.response?.data}');
|
||||
}
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -77,7 +97,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
|
||||
_updateLocalValue(code, oldValue, emit);
|
||||
emit(ACStatusLoaded(deviceStatus));
|
||||
emit(const AcsFailedState(error: 'Failed to control the device.'));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) {
|
||||
@ -133,4 +152,36 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchAcBatchStatus(
|
||||
AcFetchBatchStatusEvent event, Emitter<AcsState> emit) async {
|
||||
emit(AcsLoadingState());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus =
|
||||
AcStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||
emit(ACStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(AcsFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onAcBatchControl(
|
||||
AcBatchControlEvent event, Emitter<AcsState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value, emit);
|
||||
|
||||
emit(ACStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
isBatch: true,
|
||||
deviceId: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,21 +7,30 @@ sealed class AcsEvent extends Equatable {
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class AcFetchDeviceStatus extends AcsEvent {
|
||||
class AcFetchDeviceStatusEvent extends AcsEvent {
|
||||
final String deviceId;
|
||||
|
||||
const AcFetchDeviceStatus(this.deviceId);
|
||||
const AcFetchDeviceStatusEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class AcControl extends AcsEvent {
|
||||
class AcFetchBatchStatusEvent extends AcsEvent {
|
||||
final List<String> devicesIds;
|
||||
|
||||
const AcFetchBatchStatusEvent(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class AcControlEvent extends AcsEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final dynamic value;
|
||||
|
||||
const AcControl({
|
||||
const AcControlEvent({
|
||||
required this.deviceId,
|
||||
required this.code,
|
||||
required this.value,
|
||||
@ -30,3 +39,18 @@ class AcControl extends AcsEvent {
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class AcBatchControlEvent extends AcsEvent {
|
||||
final List<String> devicesIds;
|
||||
final String code;
|
||||
final dynamic value;
|
||||
|
||||
const AcBatchControlEvent({
|
||||
required this.devicesIds,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds, code, value];
|
||||
}
|
||||
|
@ -22,6 +22,16 @@ class ACStatusLoaded extends AcsState {
|
||||
List<Object> get props => [status, timestamp];
|
||||
}
|
||||
|
||||
class AcBatchStatusLoaded extends AcsState {
|
||||
final AcStatusModel status;
|
||||
final DateTime timestamp;
|
||||
|
||||
AcBatchStatusLoaded(this.status) : timestamp = DateTime.now();
|
||||
|
||||
@override
|
||||
List<Object> get props => [status, timestamp];
|
||||
}
|
||||
|
||||
class AcsFailedState extends AcsState {
|
||||
final String error;
|
||||
|
||||
|
@ -40,16 +40,16 @@ class AcStatusModel {
|
||||
acSwitch = status.value ?? false;
|
||||
break;
|
||||
case 'mode':
|
||||
mode = status.value ?? 'cold'; // default to 'cold' if null
|
||||
mode = status.value ?? 'cold';
|
||||
break;
|
||||
case 'temp_set':
|
||||
tempSet = status.value ?? 210; // default value if null
|
||||
tempSet = status.value ?? 210;
|
||||
break;
|
||||
case 'temp_current':
|
||||
currentTemp = status.value ?? 210; // default value if null
|
||||
currentTemp = status.value ?? 210;
|
||||
break;
|
||||
case 'level':
|
||||
fanSpeeds = status.value ?? 'low'; // default value if null
|
||||
fanSpeeds = status.value ?? 'low';
|
||||
break;
|
||||
case 'child_lock':
|
||||
childLock = status.value ?? false;
|
||||
|
104
lib/pages/device_managment/ac/view/ac_device_batch_control.dart
Normal file
@ -0,0 +1,104 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class AcDeviceBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const AcDeviceBatchControlView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => AcBloc(deviceId: devicesIds.first)
|
||||
..add(AcFetchBatchStatusEvent(devicesIds)),
|
||||
child: BlocBuilder<AcBloc, AcsState>(
|
||||
builder: (context, state) {
|
||||
if (state is ACStatusLoaded) {
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
ToggleWidget(
|
||||
deviceId: devicesIds.first,
|
||||
code: 'switch',
|
||||
value: state.status.acSwitch,
|
||||
label: 'ThermoState',
|
||||
onChange: (value) {
|
||||
context.read<AcBloc>().add(AcBatchControlEvent(
|
||||
devicesIds: devicesIds,
|
||||
code: 'switch',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
BatchCurrentTemp(
|
||||
currentTemp: state.status.currentTemp,
|
||||
tempSet: state.status.tempSet,
|
||||
code: 'temp_set',
|
||||
devicesIds: devicesIds,
|
||||
),
|
||||
BatchAcMode(
|
||||
value: state.status.acMode,
|
||||
code: 'mode',
|
||||
devicesIds: devicesIds,
|
||||
),
|
||||
BatchFanSpeedControl(
|
||||
value: state.status.acFanSpeed,
|
||||
code: 'level',
|
||||
devicesIds: devicesIds,
|
||||
),
|
||||
ToggleWidget(
|
||||
deviceId: devicesIds.first,
|
||||
code: 'child_lock',
|
||||
value: state.status.childLock,
|
||||
label: 'Child Lock',
|
||||
icon:
|
||||
state.status.childLock ? Assets.childLock : Assets.unlock,
|
||||
onChange: (value) {
|
||||
context.read<AcBloc>().add(AcBatchControlEvent(
|
||||
devicesIds: devicesIds,
|
||||
code: 'child_lock',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5),
|
||||
FactoryResetWidget(deviceId: devicesIds.first),
|
||||
],
|
||||
);
|
||||
} else if (state is AcsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -11,8 +11,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||
const AcDeviceControl({super.key, required this.device});
|
||||
class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
|
||||
const AcDeviceControlsView({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@ -23,7 +23,7 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => AcBloc(deviceId: device.uuid!)
|
||||
..add(AcFetchDeviceStatus(device.uuid!)),
|
||||
..add(AcFetchDeviceStatusEvent(device.uuid!)),
|
||||
child: BlocBuilder<AcBloc, AcsState>(
|
||||
builder: (context, state) {
|
||||
if (state is ACStatusLoaded) {
|
||||
|
@ -0,0 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
|
||||
class BatchAcMode extends StatelessWidget {
|
||||
const BatchAcMode({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.code,
|
||||
required this.devicesIds,
|
||||
});
|
||||
|
||||
final TempModes value;
|
||||
final String code;
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: ColorsManager.greyColor.withOpacity(0.2),
|
||||
border: Border.all(color: ColorsManager.boxDivider),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildIconContainer(context, TempModes.cold, Assets.freezing,
|
||||
value == TempModes.cold),
|
||||
_buildIconContainer(
|
||||
context, TempModes.hot, Assets.acSun, value == TempModes.hot),
|
||||
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner,
|
||||
value == TempModes.wind),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIconContainer(
|
||||
BuildContext context, TempModes mode, String assetPath, bool isSelected) {
|
||||
return Flexible(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.read<AcBloc>().add(
|
||||
AcBatchControlEvent(
|
||||
devicesIds: devicesIds,
|
||||
code: code,
|
||||
value: mode.name,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.whiteColors,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue : Colors.transparent,
|
||||
width: 2.0,
|
||||
),
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ClipOval(
|
||||
child: SvgPicture.asset(
|
||||
assetPath,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/celciuse_symbol.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
|
||||
class BatchCurrentTemp extends StatefulWidget {
|
||||
const BatchCurrentTemp({
|
||||
super.key,
|
||||
required this.code,
|
||||
required this.devicesIds,
|
||||
required this.currentTemp,
|
||||
required this.tempSet,
|
||||
});
|
||||
|
||||
final String code;
|
||||
final List<String> devicesIds;
|
||||
final int currentTemp;
|
||||
final int tempSet;
|
||||
|
||||
@override
|
||||
State<BatchCurrentTemp> createState() => _CurrentTempState();
|
||||
}
|
||||
|
||||
class _CurrentTempState extends State<BatchCurrentTemp> {
|
||||
late double _adjustedValue;
|
||||
Timer? _debounce;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_adjustedValue = _initialAdjustedValue(widget.tempSet);
|
||||
}
|
||||
|
||||
double _initialAdjustedValue(dynamic value) {
|
||||
if (value is int || value is double) {
|
||||
double doubleValue = value.toDouble();
|
||||
return doubleValue > 99 ? doubleValue / 10 : doubleValue;
|
||||
} else {
|
||||
throw ArgumentError('Invalid value type: Expected int or double');
|
||||
}
|
||||
}
|
||||
|
||||
void _onValueChanged(double newValue) {
|
||||
if (_debounce?.isActive ?? false) {
|
||||
_debounce?.cancel();
|
||||
}
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||
context.read<AcBloc>().add(
|
||||
AcBatchControlEvent(
|
||||
devicesIds: widget.devicesIds,
|
||||
code: widget.code,
|
||||
value: (newValue * 10).toInt(),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: ColorsManager.greyColor.withOpacity(0.2),
|
||||
border: Border.all(color: ColorsManager.boxDivider),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Current Temperature',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.grey),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
(widget.currentTemp > 99
|
||||
? widget.currentTemp / 10
|
||||
: widget.currentTemp)
|
||||
.toString(),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.grey),
|
||||
),
|
||||
const CelsiusSymbol(
|
||||
color: Colors.grey,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
IncrementDecrementWidget(
|
||||
value: _adjustedValue.toString(),
|
||||
description: '°C',
|
||||
descriptionColor: ColorsManager.dialogBlueTitle,
|
||||
onIncrement: () {
|
||||
if (_adjustedValue < 30) {
|
||||
setState(() {
|
||||
_adjustedValue++;
|
||||
});
|
||||
_onValueChanged(_adjustedValue);
|
||||
}
|
||||
},
|
||||
onDecrement: () {
|
||||
if (_adjustedValue > 20) {
|
||||
setState(() {
|
||||
_adjustedValue--;
|
||||
});
|
||||
_onValueChanged(_adjustedValue);
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
|
||||
class BatchFanSpeedControl extends StatelessWidget {
|
||||
const BatchFanSpeedControl({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.code,
|
||||
required this.devicesIds,
|
||||
});
|
||||
|
||||
final FanSpeeds value;
|
||||
final String code;
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: ColorsManager.greyColor.withOpacity(0.2),
|
||||
border: Border.all(color: ColorsManager.boxDivider),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
children: [
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto,
|
||||
value == FanSpeeds.auto),
|
||||
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow,
|
||||
value == FanSpeeds.low),
|
||||
],
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle,
|
||||
value == FanSpeeds.middle),
|
||||
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh,
|
||||
value == FanSpeeds.high),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIconContainer(BuildContext context, FanSpeeds speed,
|
||||
String assetPath, bool isSelected) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context.read<AcBloc>().add(
|
||||
AcBatchControlEvent(
|
||||
devicesIds: devicesIds,
|
||||
code: code,
|
||||
value: speed.name,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.whiteColors,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue : Colors.transparent,
|
||||
width: 2.0,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ClipOval(
|
||||
child: SvgPicture.asset(
|
||||
assetPath,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ class AcMode extends StatelessWidget {
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.read<AcBloc>().add(
|
||||
AcControl(
|
||||
AcControlEvent(
|
||||
deviceId: deviceId,
|
||||
code: code,
|
||||
value: mode.name,
|
||||
|
@ -57,7 +57,7 @@ class AcToggle extends StatelessWidget {
|
||||
value: value,
|
||||
onChanged: (newValue) {
|
||||
context.read<AcBloc>().add(
|
||||
AcControl(
|
||||
AcControlEvent(
|
||||
deviceId: deviceId,
|
||||
code: code,
|
||||
value: newValue,
|
||||
|
@ -50,7 +50,7 @@ class _CurrentTempState extends State<CurrentTemp> {
|
||||
}
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||
context.read<AcBloc>().add(
|
||||
AcControl(
|
||||
AcControlEvent(
|
||||
deviceId: widget.deviceId,
|
||||
code: widget.code,
|
||||
value: (newValue * 10).toInt(),
|
||||
|
@ -60,7 +60,7 @@ class FanSpeedControl extends StatelessWidget {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context.read<AcBloc>().add(
|
||||
AcControl(
|
||||
AcControlEvent(
|
||||
deviceId: deviceId,
|
||||
code: code,
|
||||
value: speed.name,
|
||||
|
@ -2,6 +2,7 @@ import 'package:equatable/equatable.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/services/devices_mang_api.dart';
|
||||
|
||||
part 'device_managment_event.dart';
|
||||
part 'device_managment_state.dart';
|
||||
|
||||
@ -23,6 +24,7 @@ class DeviceManagementBloc
|
||||
on<SearchDevices>(_onSearchDevices);
|
||||
on<SelectDevice>(_onSelectDevice);
|
||||
on<ResetFilters>(_onResetFilters);
|
||||
on<ResetSelectedDevices>(_onResetSelectedDevices);
|
||||
}
|
||||
|
||||
Future<void> _onFetchDevices(
|
||||
@ -41,6 +43,7 @@ class DeviceManagementBloc
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: null,
|
||||
isControlButtonEnabled: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(DeviceManagementInitial());
|
||||
@ -69,8 +72,8 @@ class DeviceManagementBloc
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice:
|
||||
_selectedDevices.isNotEmpty ? _selectedDevices.first : null,
|
||||
selectedDevice: _selectedDevices.isNotEmpty ? _selectedDevices : null,
|
||||
isControlButtonEnabled: _selectedDevices.isNotEmpty,
|
||||
));
|
||||
|
||||
if (productName.isNotEmpty) {
|
||||
@ -92,9 +95,37 @@ class DeviceManagementBloc
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: null,
|
||||
isControlButtonEnabled: false,
|
||||
));
|
||||
}
|
||||
|
||||
void _onResetSelectedDevices(
|
||||
ResetSelectedDevices event, Emitter<DeviceManagementState> emit) {
|
||||
_selectedDevices.clear();
|
||||
|
||||
if (state is DeviceManagementLoaded) {
|
||||
emit(DeviceManagementLoaded(
|
||||
devices: _devices,
|
||||
selectedIndex: _selectedIndex,
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: null,
|
||||
isControlButtonEnabled: false,
|
||||
));
|
||||
} else if (state is DeviceManagementFiltered) {
|
||||
emit(DeviceManagementFiltered(
|
||||
filteredDevices: (state as DeviceManagementFiltered).filteredDevices,
|
||||
selectedIndex: _selectedIndex,
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: null,
|
||||
isControlButtonEnabled: false,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectedFilterChanged(
|
||||
SelectedFilterChanged event, Emitter<DeviceManagementState> emit) {
|
||||
_selectedIndex = event.selectedIndex;
|
||||
@ -111,7 +142,10 @@ class DeviceManagementBloc
|
||||
_selectedDevices.add(event.selectedDevice);
|
||||
}
|
||||
|
||||
bool isControlButtonEnabled = _selectedDevices.length == 1;
|
||||
List<AllDevicesModel> clonedSelectedDevices = List.from(_selectedDevices);
|
||||
|
||||
bool isControlButtonEnabled =
|
||||
_checkIfControlButtonEnabled(clonedSelectedDevices);
|
||||
|
||||
if (state is DeviceManagementLoaded) {
|
||||
emit(DeviceManagementLoaded(
|
||||
@ -120,7 +154,9 @@ class DeviceManagementBloc
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null,
|
||||
selectedDevice:
|
||||
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
|
||||
isControlButtonEnabled: isControlButtonEnabled,
|
||||
));
|
||||
} else if (state is DeviceManagementFiltered) {
|
||||
emit(DeviceManagementFiltered(
|
||||
@ -129,11 +165,24 @@ class DeviceManagementBloc
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null,
|
||||
selectedDevice:
|
||||
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
|
||||
isControlButtonEnabled: isControlButtonEnabled,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
bool _checkIfControlButtonEnabled(List<AllDevicesModel> selectedDevices) {
|
||||
if (selectedDevices.length > 1) {
|
||||
final productTypes =
|
||||
selectedDevices.map((device) => device.productType).toSet();
|
||||
return productTypes.length == 1;
|
||||
} else if (selectedDevices.length == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _calculateDeviceCounts() {
|
||||
_onlineCount = _devices.where((device) => device.online == true).length;
|
||||
_offlineCount = _devices.where((device) => device.online == false).length;
|
||||
@ -158,12 +207,10 @@ class DeviceManagementBloc
|
||||
|
||||
void _onSearchDevices(
|
||||
SearchDevices event, Emitter<DeviceManagementState> emit) {
|
||||
// If the search fields are all empty, restore the last filtered devices
|
||||
if ((event.community == null || event.community!.isEmpty) &&
|
||||
(event.unitName == null || event.unitName!.isEmpty) &&
|
||||
(event.productName == null || event.productName!.isEmpty)) {
|
||||
productName = '';
|
||||
// If the current state is filtered, re-emit the filtered state
|
||||
if (state is DeviceManagementFiltered) {
|
||||
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
|
||||
}
|
||||
@ -214,6 +261,7 @@ class DeviceManagementBloc
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: null,
|
||||
isControlButtonEnabled: false,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -52,3 +52,5 @@ class SelectDevice extends DeviceManagementEvent {
|
||||
}
|
||||
|
||||
class ResetFilters extends DeviceManagementEvent {}
|
||||
|
||||
class ResetSelectedDevices extends DeviceManagementEvent {}
|
||||
|
@ -17,7 +17,8 @@ class DeviceManagementLoaded extends DeviceManagementState {
|
||||
final int onlineCount;
|
||||
final int offlineCount;
|
||||
final int lowBatteryCount;
|
||||
final AllDevicesModel? selectedDevice;
|
||||
final List<AllDevicesModel>? selectedDevice;
|
||||
final bool isControlButtonEnabled;
|
||||
|
||||
const DeviceManagementLoaded({
|
||||
required this.devices,
|
||||
@ -26,6 +27,7 @@ class DeviceManagementLoaded extends DeviceManagementState {
|
||||
required this.offlineCount,
|
||||
required this.lowBatteryCount,
|
||||
this.selectedDevice,
|
||||
required this.isControlButtonEnabled,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -35,7 +37,8 @@ class DeviceManagementLoaded extends DeviceManagementState {
|
||||
onlineCount,
|
||||
offlineCount,
|
||||
lowBatteryCount,
|
||||
selectedDevice
|
||||
selectedDevice,
|
||||
isControlButtonEnabled
|
||||
];
|
||||
}
|
||||
|
||||
@ -45,7 +48,8 @@ class DeviceManagementFiltered extends DeviceManagementState {
|
||||
final int onlineCount;
|
||||
final int offlineCount;
|
||||
final int lowBatteryCount;
|
||||
final AllDevicesModel? selectedDevice;
|
||||
final List<AllDevicesModel>? selectedDevice;
|
||||
final bool isControlButtonEnabled;
|
||||
|
||||
const DeviceManagementFiltered({
|
||||
required this.filteredDevices,
|
||||
@ -54,6 +58,7 @@ class DeviceManagementFiltered extends DeviceManagementState {
|
||||
required this.offlineCount,
|
||||
required this.lowBatteryCount,
|
||||
this.selectedDevice,
|
||||
required this.isControlButtonEnabled,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -63,7 +68,8 @@ class DeviceManagementFiltered extends DeviceManagementState {
|
||||
onlineCount,
|
||||
offlineCount,
|
||||
lowBatteryCount,
|
||||
selectedDevice
|
||||
selectedDevice,
|
||||
isControlButtonEnabled
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -1,39 +1,145 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_status_view.dart';
|
||||
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_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/gateway/view/gateway_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_device_control.dart';
|
||||
|
||||
mixin RouteControlsBasedCode {
|
||||
Widget routeControlsWidgets({required AllDevicesModel device}) {
|
||||
switch (device.productType) {
|
||||
case '1G':
|
||||
return WallLightDeviceControl(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case '2G':
|
||||
return TwoGangDeviceControlView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case '3G':
|
||||
return LivingRoomDeviceControl(
|
||||
return LivingRoomDeviceControlsView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case 'GW':
|
||||
return GateWayControls(
|
||||
return GateWayControlsView(
|
||||
gatewayId: device.uuid!,
|
||||
);
|
||||
case 'DL':
|
||||
return DoorLockView(device: device);
|
||||
return DoorLockControlsView(device: device);
|
||||
case 'WPS':
|
||||
return WallSensorControls(device: device);
|
||||
return WallSensorControlsView(device: device);
|
||||
case 'CPS':
|
||||
return CeilingSensorControls(
|
||||
return CeilingSensorControlsView(
|
||||
device: device,
|
||||
);
|
||||
case 'CUR':
|
||||
return CurtainStatusView(
|
||||
return CurtainStatusControlsView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case 'AC':
|
||||
return AcDeviceControl(device: device);
|
||||
return AcDeviceControlsView(device: device);
|
||||
case 'WH':
|
||||
return WaterHeaterDeviceControl(
|
||||
device: device,
|
||||
);
|
||||
case 'DS':
|
||||
return MainDoorSensorControlView(device: device);
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
3G:
|
||||
1G:
|
||||
2G:
|
||||
GW:
|
||||
DL:
|
||||
WPS:
|
||||
CPS:
|
||||
AC:
|
||||
CUR:
|
||||
*/
|
||||
|
||||
Widget routeBatchControlsWidgets({required List<AllDevicesModel> devices}) {
|
||||
switch (devices.first.productType) {
|
||||
case '1G':
|
||||
return WallLightBatchControlView(
|
||||
deviceIds: devices
|
||||
.where((e) => (e.productType == '1G'))
|
||||
.map((e) => e.uuid!)
|
||||
.toList(),
|
||||
);
|
||||
case '2G':
|
||||
return TwoGangBatchControlView(
|
||||
deviceIds: devices
|
||||
.where((e) => (e.productType == '2G'))
|
||||
.map((e) => e.uuid!)
|
||||
.toList(),
|
||||
);
|
||||
case '3G':
|
||||
return LivingRoomBatchControlsView(
|
||||
deviceIds: devices
|
||||
.where((e) => (e.productType == '3G'))
|
||||
.map((e) => e.uuid!)
|
||||
.toList(),
|
||||
);
|
||||
case 'GW':
|
||||
return GatewayBatchControlView(
|
||||
gatewayIds: devices
|
||||
.where((e) => (e.productType == 'GW'))
|
||||
.map((e) => e.uuid!)
|
||||
.toList(),
|
||||
);
|
||||
case 'DL':
|
||||
return DoorLockBatchControlView(
|
||||
devicesIds: devices
|
||||
.where((e) => (e.productType == 'DL'))
|
||||
.map((e) => e.uuid!)
|
||||
.toList());
|
||||
case 'WPS':
|
||||
return WallSensorBatchControlView(
|
||||
devicesIds: devices
|
||||
.where((e) => (e.productType == 'WPS'))
|
||||
.map((e) => e.uuid!)
|
||||
.toList());
|
||||
case 'CPS':
|
||||
return CeilingSensorBatchControlView(
|
||||
devicesIds: devices
|
||||
.where((e) => (e.productType == 'CPS'))
|
||||
.map((e) => e.uuid!)
|
||||
.toList(),
|
||||
);
|
||||
case 'CUR':
|
||||
return CurtainBatchStatusView(
|
||||
devicesIds: devices
|
||||
.where((e) => (e.productType == 'CUR'))
|
||||
.map((e) => e.uuid!)
|
||||
.toList(),
|
||||
);
|
||||
case 'AC':
|
||||
return AcDeviceBatchControlView(
|
||||
devicesIds: devices
|
||||
.where((e) => (e.productType == 'AC'))
|
||||
.map((e) => e.uuid!)
|
||||
.toList());
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
|
@ -13,11 +13,15 @@ class DeviceReport {
|
||||
|
||||
DeviceReport.fromJson(Map<String, dynamic> json)
|
||||
: deviceUuid = json['deviceUuid'] as String?,
|
||||
startTime = json['startTime'] as int?,
|
||||
endTime = json['endTime'] as int?,
|
||||
data = (json['data'] as List<dynamic>?)
|
||||
startTime = int.tryParse(json['startTime'].toString()) ??
|
||||
json['startTime'] as int?,
|
||||
endTime =
|
||||
int.tryParse(json['endTime'].toString()) ?? json['endTime'] as int?,
|
||||
data = json['data'] != null
|
||||
? (json['data'] as List<dynamic>?)
|
||||
?.map((e) => DeviceEvent.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
.toList()
|
||||
: [];
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'deviceUuid': deviceUuid,
|
||||
|
@ -1,5 +1,6 @@
|
||||
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/utils/constants/assets.dart';
|
||||
|
||||
class AllDevicesModel {
|
||||
/*
|
||||
@ -105,7 +106,7 @@ class AllDevicesModel {
|
||||
categoryName = json['categoryName']?.toString();
|
||||
createTime = int.tryParse(json['createTime']?.toString() ?? '');
|
||||
gatewayId = json['gatewayId']?.toString();
|
||||
icon = json['icon']?.toString();
|
||||
icon = json['icon'] ?? _getDefaultIcon(productType);
|
||||
ip = json['ip']?.toString();
|
||||
lat = json['lat']?.toString();
|
||||
localKey = json['localKey']?.toString();
|
||||
@ -121,6 +122,35 @@ class AllDevicesModel {
|
||||
uuid = json['uuid']?.toString();
|
||||
batteryLevel = int.tryParse(json['battery']?.toString() ?? '');
|
||||
}
|
||||
|
||||
String _getDefaultIcon(String? productType) {
|
||||
switch (productType) {
|
||||
case 'LightBulb':
|
||||
return Assets.lightBulb;
|
||||
case 'CeilingSensor':
|
||||
case 'WallSensor':
|
||||
return Assets.sensors;
|
||||
case 'AC':
|
||||
return Assets.ac;
|
||||
case 'DoorLock':
|
||||
return Assets.doorLock;
|
||||
case 'Curtain':
|
||||
return Assets.curtain;
|
||||
case '3G':
|
||||
case '2G':
|
||||
case '1G':
|
||||
return Assets.gangSwitch;
|
||||
case 'Gateway':
|
||||
return Assets.gateway;
|
||||
case 'WH':
|
||||
return Assets.blackLogo;
|
||||
case 'DS':
|
||||
return Assets.sensors;
|
||||
default:
|
||||
return Assets.logo;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (room != null) {
|
||||
@ -154,4 +184,65 @@ class AllDevicesModel {
|
||||
data['battery'] = batteryLevel;
|
||||
return data;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is AllDevicesModel &&
|
||||
other.room == room &&
|
||||
other.unit == unit &&
|
||||
other.productUuid == productUuid &&
|
||||
other.productType == productType &&
|
||||
other.permissionType == permissionType &&
|
||||
other.activeTime == activeTime &&
|
||||
other.category == category &&
|
||||
other.categoryName == categoryName &&
|
||||
other.createTime == createTime &&
|
||||
other.gatewayId == gatewayId &&
|
||||
other.icon == icon &&
|
||||
other.ip == ip &&
|
||||
other.lat == lat &&
|
||||
other.localKey == localKey &&
|
||||
other.lon == lon &&
|
||||
other.model == model &&
|
||||
other.name == name &&
|
||||
other.nodeId == nodeId &&
|
||||
other.online == online &&
|
||||
other.ownerId == ownerId &&
|
||||
other.sub == sub &&
|
||||
other.timeZone == timeZone &&
|
||||
other.updateTime == updateTime &&
|
||||
other.uuid == uuid &&
|
||||
other.batteryLevel == batteryLevel;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return room.hashCode ^
|
||||
unit.hashCode ^
|
||||
productUuid.hashCode ^
|
||||
productType.hashCode ^
|
||||
permissionType.hashCode ^
|
||||
activeTime.hashCode ^
|
||||
category.hashCode ^
|
||||
categoryName.hashCode ^
|
||||
createTime.hashCode ^
|
||||
gatewayId.hashCode ^
|
||||
icon.hashCode ^
|
||||
ip.hashCode ^
|
||||
lat.hashCode ^
|
||||
localKey.hashCode ^
|
||||
lon.hashCode ^
|
||||
model.hashCode ^
|
||||
name.hashCode ^
|
||||
nodeId.hashCode ^
|
||||
online.hashCode ^
|
||||
ownerId.hashCode ^
|
||||
sub.hashCode ^
|
||||
timeZone.hashCode ^
|
||||
updateTime.hashCode ^
|
||||
uuid.hashCode ^
|
||||
batteryLevel.hashCode;
|
||||
}
|
||||
}
|
||||
|
@ -40,3 +40,6 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/common/custom_table.dart';
|
||||
@ -27,6 +28,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
int offlineCount = 0;
|
||||
int lowBatteryCount = 0;
|
||||
bool isControlButtonEnabled = false;
|
||||
List<AllDevicesModel> selectedDevices = [];
|
||||
|
||||
if (state is DeviceManagementLoaded) {
|
||||
devicesToShow = state.devices;
|
||||
@ -34,14 +36,18 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
onlineCount = state.onlineCount;
|
||||
offlineCount = state.offlineCount;
|
||||
lowBatteryCount = state.lowBatteryCount;
|
||||
isControlButtonEnabled = state.selectedDevice != null;
|
||||
isControlButtonEnabled = state.isControlButtonEnabled;
|
||||
selectedDevices = state.selectedDevice ??
|
||||
context.read<DeviceManagementBloc>().selectedDevices;
|
||||
} else if (state is DeviceManagementFiltered) {
|
||||
devicesToShow = state.filteredDevices;
|
||||
selectedIndex = state.selectedIndex;
|
||||
onlineCount = state.onlineCount;
|
||||
offlineCount = state.offlineCount;
|
||||
lowBatteryCount = state.lowBatteryCount;
|
||||
isControlButtonEnabled = state.selectedDevice != null;
|
||||
isControlButtonEnabled = state.isControlButtonEnabled;
|
||||
selectedDevices = state.selectedDevice ??
|
||||
context.read<DeviceManagementBloc>().selectedDevices;
|
||||
} else if (state is DeviceManagementInitial) {
|
||||
devicesToShow = [];
|
||||
selectedIndex = 0;
|
||||
@ -55,6 +61,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
'Low Battery ($lowBatteryCount)',
|
||||
];
|
||||
|
||||
final buttonLabel =
|
||||
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
@ -69,7 +78,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
tabs: tabs,
|
||||
selectedIndex: selectedIndex,
|
||||
onTabChanged: (index) {
|
||||
context.read<DeviceManagementBloc>()
|
||||
context
|
||||
.read<DeviceManagementBloc>()
|
||||
.add(SelectedFilterChanged(index));
|
||||
},
|
||||
),
|
||||
@ -84,19 +94,32 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
child: DefaultButton(
|
||||
onPressed: isControlButtonEnabled
|
||||
? () {
|
||||
final selectedDevice = context
|
||||
.read<DeviceManagementBloc>()
|
||||
.selectedDevices.first;
|
||||
if (selectedDevices.length == 1) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => DeviceControlDialog(
|
||||
device: selectedDevice),
|
||||
device: selectedDevices.first,
|
||||
),
|
||||
);
|
||||
} else if (selectedDevices.length > 1) {
|
||||
final productTypes = selectedDevices
|
||||
.map((device) => device.productType)
|
||||
.toSet();
|
||||
if (productTypes.length == 1) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
DeviceBatchControlDialog(
|
||||
devices: selectedDevices,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
: null,
|
||||
borderRadius: 9,
|
||||
child: Text(
|
||||
'Control',
|
||||
buttonLabel,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: isControlButtonEnabled
|
||||
@ -116,7 +139,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
? const EdgeInsets.all(30)
|
||||
: const EdgeInsets.all(15),
|
||||
child: DynamicTable(
|
||||
withSelectAll: false,
|
||||
withSelectAll: true,
|
||||
cellDecoration: containerDecoration,
|
||||
onRowSelected: (index, isSelected, row) {
|
||||
final selectedDevice = devicesToShow[index];
|
||||
|
@ -14,7 +14,9 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
||||
|
||||
CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) {
|
||||
on<CeilingInitialEvent>(_fetchCeilingSensorStatus);
|
||||
on<CeilingFetchDeviceStatusEvent>(_fetchCeilingSensorBatchControl);
|
||||
on<CeilingChangeValueEvent>(_changeValue);
|
||||
on<CeilingBatchControlEvent>(_onBatchControl);
|
||||
on<GetCeilingDeviceReportsEvent>(_getDeviceReports);
|
||||
on<ShowCeilingDescriptionEvent>(_showDescription);
|
||||
on<BackToCeilingGridViewEvent>(_backToGridView);
|
||||
@ -24,7 +26,8 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
||||
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
emit(CeilingLoadingInitialState());
|
||||
try {
|
||||
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||
var response = await DevicesManagementApi()
|
||||
.getDeviceStatus(event.deviceId);
|
||||
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
// _listenToChanges();
|
||||
@ -67,34 +70,76 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
||||
}
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
await _runDeBouncer(
|
||||
deviceId: deviceId, code: event.code, value: event.value, emit: emit);
|
||||
deviceId: deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(
|
||||
CeilingBatchControlEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||
if (event.code == 'sensitivity') {
|
||||
deviceStatus.sensitivity = event.value;
|
||||
} else if (event.code == 'none_body_time') {
|
||||
deviceStatus.noBodyTime = event.value;
|
||||
} else if (event.code == 'moving_max_dis') {
|
||||
deviceStatus.maxDistance = event.value;
|
||||
} else if (event.code == 'scene') {
|
||||
deviceStatus.spaceType = getSpaceType(event.value);
|
||||
}
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
await _runDeBouncer(
|
||||
deviceId: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
|
||||
_runDeBouncer({
|
||||
required String deviceId,
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required Emitter<CeilingSensorState> emit,
|
||||
required bool isBatch,
|
||||
}) {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
final response = await DevicesManagementApi()
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
add(CeilingInitialEvent());
|
||||
add(CeilingInitialEvent(id));
|
||||
}
|
||||
if (response == true && code == 'scene') {
|
||||
emit(CeilingLoadingInitialState());
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
add(CeilingInitialEvent());
|
||||
add(CeilingInitialEvent(id));
|
||||
}
|
||||
} catch (_) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
add(CeilingInitialEvent());
|
||||
add(CeilingInitialEvent(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -128,4 +173,19 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
||||
BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
}
|
||||
|
||||
FutureOr<void> _fetchCeilingSensorBatchControl(
|
||||
CeilingFetchDeviceStatusEvent event,
|
||||
Emitter<CeilingSensorState> emit) async {
|
||||
emit(CeilingLoadingInitialState());
|
||||
try {
|
||||
var response =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
} catch (e) {
|
||||
emit(CeilingFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,37 @@ abstract class CeilingSensorEvent extends Equatable {
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class CeilingInitialEvent extends CeilingSensorEvent {}
|
||||
class CeilingInitialEvent extends CeilingSensorEvent {
|
||||
final String deviceId;
|
||||
const CeilingInitialEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class CeilingFetchDeviceStatusEvent extends CeilingSensorEvent {
|
||||
final List<String> devicesIds;
|
||||
|
||||
const CeilingFetchDeviceStatusEvent(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class CeilingBatchControlEvent extends CeilingSensorEvent {
|
||||
final List<String> deviceIds;
|
||||
final String code;
|
||||
final dynamic value;
|
||||
|
||||
const CeilingBatchControlEvent({
|
||||
required this.deviceIds,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceIds, code, value];
|
||||
}
|
||||
|
||||
class CeilingChangeValueEvent extends CeilingSensorEvent {
|
||||
final dynamic value;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class CeilingSensorModel {
|
||||
@ -91,6 +92,30 @@ class CeilingSensorModel {
|
||||
spaceType: _spaceType,
|
||||
);
|
||||
}
|
||||
|
||||
CeilingSensorModel copyWith({
|
||||
String? presenceState,
|
||||
int? sensitivity,
|
||||
String? checkingResult,
|
||||
int? presenceRange,
|
||||
int? sportsPara,
|
||||
String? bodyMovement,
|
||||
String? noBodyTime,
|
||||
int? maxDistance,
|
||||
SpaceTypes? spaceType,
|
||||
}) {
|
||||
return CeilingSensorModel(
|
||||
presenceState: presenceState ?? this.presenceState,
|
||||
sensitivity: sensitivity ?? this.sensitivity,
|
||||
checkingResult: checkingResult ?? this.checkingResult,
|
||||
presenceRange: presenceRange ?? this.presenceRange,
|
||||
sportsPara: sportsPara ?? this.sportsPara,
|
||||
bodyMovement: bodyMovement ?? this.bodyMovement,
|
||||
noBodyTime: noBodyTime ?? this.noBodyTime,
|
||||
maxDistance: maxDistance ?? this.maxDistance,
|
||||
spaceType: spaceType ?? this.spaceType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum SpaceTypes {
|
||||
|
@ -0,0 +1,119 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class CeilingSensorBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const CeilingSensorBatchControlView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => CeilingSensorBloc(deviceId: devicesIds.first)
|
||||
..add(CeilingFetchDeviceStatusEvent(devicesIds)),
|
||||
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
||||
builder: (context, state) {
|
||||
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 const Center(child: Text('Error fetching status'));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView(BuildContext context, CeilingSensorModel model,
|
||||
bool isExtraLarge, bool isLarge, bool isMedium) {
|
||||
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: [
|
||||
PresenceSpaceType(
|
||||
description: 'Space Type',
|
||||
value: model.spaceType,
|
||||
action: (String value) => context.read<CeilingSensorBloc>().add(
|
||||
CeilingBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'scene',
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.sensitivity.toDouble(),
|
||||
title: 'Sensitivity:',
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
steps: 1,
|
||||
action: (int value) {
|
||||
context.read<CeilingSensorBloc>().add(
|
||||
CeilingBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'sensitivity',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.maxDistance.toDouble(),
|
||||
title: 'Maximum Distance:',
|
||||
minValue: 0,
|
||||
maxValue: 500,
|
||||
steps: 50,
|
||||
description: 'm',
|
||||
action: (int value) => context.read<CeilingSensorBloc>().add(
|
||||
CeilingBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'moving_max_dis',
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceNoBodyTime(
|
||||
value: model.noBodyTime,
|
||||
title: 'Nobody Time:',
|
||||
description: '',
|
||||
action: (String value) => context.read<CeilingSensorBloc>().add(
|
||||
CeilingBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'nobody_time',
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4),
|
||||
FactoryResetWidget(deviceId: devicesIds.first),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -16,9 +16,9 @@ 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 CeilingSensorControls extends StatelessWidget
|
||||
class CeilingSensorControlsView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const CeilingSensorControls({super.key, required this.device});
|
||||
const CeilingSensorControlsView({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@ -29,7 +29,7 @@ class CeilingSensorControls extends StatelessWidget
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => CeilingSensorBloc(deviceId: device.uuid ?? '')
|
||||
..add(CeilingInitialEvent()),
|
||||
..add(CeilingInitialEvent(device.uuid ?? '')),
|
||||
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is CeilingLoadingInitialState ||
|
||||
|
@ -12,7 +12,9 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||
|
||||
CurtainBloc({required this.deviceId}) : super(CurtainInitial()) {
|
||||
on<CurtainFetchDeviceStatus>(_onFetchDeviceStatus);
|
||||
on<CurtainFetchBatchStatus>(_onFetchBatchStatus);
|
||||
on<CurtainControl>(_onCurtainControl);
|
||||
on<CurtainBatchControl>(_onCurtainBatchControl);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(
|
||||
@ -44,16 +46,26 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required String deviceId,
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<CurtainState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
@ -61,14 +73,20 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||
try {
|
||||
final controlValue = value ? 'open' : 'close';
|
||||
|
||||
final response = await DevicesManagementApi()
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, controlValue);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: controlValue));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(deviceId, oldValue, emit);
|
||||
_revertValueAndEmit(id, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(deviceId, oldValue, emit);
|
||||
_revertValueAndEmit(id, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -88,4 +106,37 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||
bool _checkStatus(String command) {
|
||||
return command.toLowerCase() == 'open';
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchBatchStatus(
|
||||
CurtainFetchBatchStatus event, Emitter<CurtainState> emit) async {
|
||||
emit(CurtainStatusLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
|
||||
deviceStatus = _checkStatus(status.status[0].value);
|
||||
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(CurtainError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onCurtainBatchControl(
|
||||
CurtainBatchControl event, Emitter<CurtainState> emit) async {
|
||||
final oldValue = deviceStatus;
|
||||
|
||||
_updateLocalValue(event.value, emit);
|
||||
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
|
||||
sealed class CurtainEvent extends Equatable {
|
||||
const CurtainEvent();
|
||||
|
||||
@ -18,6 +16,15 @@ class CurtainFetchDeviceStatus extends CurtainEvent {
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class CurtainFetchBatchStatus extends CurtainEvent {
|
||||
final List<String> devicesIds;
|
||||
|
||||
const CurtainFetchBatchStatus(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class CurtainControl extends CurtainEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
@ -29,3 +36,15 @@ class CurtainControl extends CurtainEvent {
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class CurtainBatchControl extends CurtainEvent {
|
||||
final List<String> devicesIds;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
const CurtainBatchControl(
|
||||
{required this.devicesIds, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds, code, value];
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/curtain_toggle.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class CurtainBatchStatusView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const CurtainBatchStatusView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => CurtainBloc(deviceId: devicesIds.first)
|
||||
..add(CurtainFetchBatchStatus(devicesIds)),
|
||||
child: BlocBuilder<CurtainBloc, CurtainState>(
|
||||
builder: (context, state) {
|
||||
if (state is CurtainStatusLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is CurtainStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is CurtainError || state is CurtainControlError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(BuildContext context, bool status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
CurtainToggle(
|
||||
value: status,
|
||||
code: 'control',
|
||||
deviceId: devicesIds.first,
|
||||
label: 'Curtains',
|
||||
onChanged: (value) {
|
||||
context.read<CurtainBloc>().add(CurtainBatchControl(
|
||||
devicesIds: devicesIds,
|
||||
code: 'control',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5),
|
||||
FactoryResetWidget(deviceId: devicesIds.first),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -6,10 +6,11 @@ import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.da
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class CurtainStatusView extends StatelessWidget with HelperResponsiveLayout {
|
||||
class CurtainStatusControlsView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const CurtainStatusView({super.key, required this.deviceId});
|
||||
const CurtainStatusControlsView({super.key, required this.deviceId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -57,6 +58,15 @@ class CurtainStatusView extends StatelessWidget with HelperResponsiveLayout {
|
||||
code: 'control',
|
||||
deviceId: deviceId,
|
||||
label: 'Curtains',
|
||||
onChanged: (value) {
|
||||
context.read<CurtainBloc>().add(
|
||||
CurtainControl(
|
||||
deviceId: deviceId,
|
||||
code: 'control',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
|
@ -37,3 +37,5 @@ class UpdateLockEvent extends DoorLockEvent {
|
||||
@override
|
||||
List<Object> get props => [value];
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class DoorLockBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const DoorLockBatchControlView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return SizedBox(
|
||||
child: GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge || isMedium ? 2 : 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
FirmwareUpdateWidget(
|
||||
deviceId: devicesIds.first,
|
||||
version: 12,
|
||||
),
|
||||
FactoryResetWidget(deviceId: devicesIds.first),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -7,10 +7,10 @@ import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_stat
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/widget/door_button.dart';
|
||||
|
||||
class DoorLockView extends StatelessWidget {
|
||||
class DoorLockControlsView extends StatelessWidget {
|
||||
final AllDevicesModel device;
|
||||
|
||||
const DoorLockView({super.key, required this.device});
|
||||
const DoorLockControlsView({super.key, required this.device});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/gateway/bloc/gate_way_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class GatewayBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const GatewayBatchControlView({super.key, required this.gatewayIds});
|
||||
|
||||
final List<String> gatewayIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => GateWayBloc()..add(GatWayById(gatewayIds.first)),
|
||||
child: BlocBuilder<GateWayBloc, GateWayState>(
|
||||
builder: (context, state) {
|
||||
if (state is GatewayLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is UpdateGatewayState) {
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge || isMedium ? 2 : 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
FirmwareUpdateWidget(deviceId: gatewayIds.first, version: 2),
|
||||
FactoryResetWidget(deviceId: gatewayIds.first),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class GateWayControls extends StatelessWidget with HelperResponsiveLayout {
|
||||
const GateWayControls({super.key, required this.gatewayId});
|
||||
class GateWayControlsView extends StatelessWidget with HelperResponsiveLayout {
|
||||
const GateWayControlsView({super.key, required this.gatewayId});
|
||||
|
||||
final String gatewayId;
|
||||
|
||||
@ -86,7 +86,7 @@ class _DeviceItem extends StatelessWidget {
|
||||
const Spacer(),
|
||||
Text(
|
||||
device.name ?? 'Unknown Device',
|
||||
textAlign: TextAlign.center,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
|
@ -0,0 +1,140 @@
|
||||
import 'dart:async';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class MainDoorSensorBloc
|
||||
extends Bloc<MainDoorSensorEvent, MainDoorSensorState> {
|
||||
MainDoorSensorBloc() : super(MainDoorSensorInitial()) {
|
||||
on<MainDoorSensorFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<MainDoorSensorControl>(_onControl);
|
||||
on<MainDoorSensorFetchBatchEvent>(_onFetchBatchStatus);
|
||||
on<MainDoorSensorReportsEvent>(_fetchReports);
|
||||
}
|
||||
|
||||
late MainDoorSensorStatusModel deviceStatus;
|
||||
Timer? _timer;
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(MainDoorSensorFetchDeviceEvent event,
|
||||
Emitter<MainDoorSensorState> emit) async {
|
||||
emit(MainDoorSensorLoadingState());
|
||||
try {
|
||||
final status = await DevicesManagementApi()
|
||||
.getDeviceStatus(event.deviceId)
|
||||
.then((value) => value.status);
|
||||
|
||||
deviceStatus = MainDoorSensorStatusModel.fromJson(event.deviceId, status);
|
||||
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(MainDoorSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onControl(
|
||||
MainDoorSensorControl event, Emitter<MainDoorSensorState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required String deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<MainDoorSensorState> emit,
|
||||
}) async {
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
final response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is DioException && e.response != null) {
|
||||
debugPrint('Error response: ${e.response?.data}');
|
||||
}
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
||||
Emitter<MainDoorSensorState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
switch (code) {
|
||||
case 'doorcontact_state':
|
||||
deviceStatus = deviceStatus.copyWith(doorContactState: value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'doorcontact_state':
|
||||
return deviceStatus.doorContactState;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch batch status for multiple devices (if needed)
|
||||
FutureOr<void> _onFetchBatchStatus(MainDoorSensorFetchBatchEvent event,
|
||||
Emitter<MainDoorSensorState> emit) async {
|
||||
emit(MainDoorSensorLoadingState());
|
||||
try {
|
||||
// final batchStatus =
|
||||
// await DevicesManagementApi().getBatchDeviceStatus(event.deviceIds);
|
||||
// Assuming you need to update multiple devices status here
|
||||
// You might need a list or map of MainDoorSensorStatusModel for batch processing
|
||||
// emit(MainDoorSensorBatchStatusLoaded(batchStatus));
|
||||
} catch (e) {
|
||||
emit(MainDoorSensorBatchFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _fetchReports(MainDoorSensorReportsEvent event,
|
||||
Emitter<MainDoorSensorState> emit) async {
|
||||
emit(MainDoorSensorLoadingState());
|
||||
try {
|
||||
final reports = await DevicesManagementApi.getDeviceReportsByDate(
|
||||
event.deviceId, event.code, event.from, event.to);
|
||||
emit(MainDoorSensorReportLoaded(reports));
|
||||
} catch (e) {
|
||||
emit(MainDoorSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class MainDoorSensorEvent extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class MainDoorSensorFetchDeviceEvent extends MainDoorSensorEvent {
|
||||
final String deviceId;
|
||||
|
||||
MainDoorSensorFetchDeviceEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class MainDoorSensorFetchBatchEvent extends MainDoorSensorEvent {
|
||||
final String deviceId;
|
||||
|
||||
MainDoorSensorFetchBatchEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class MainDoorSensorControl extends MainDoorSensorEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
MainDoorSensorControl(
|
||||
{required this.deviceId, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class MainDoorSensorBatchControl extends MainDoorSensorEvent {
|
||||
final List<String> deviceId;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
MainDoorSensorBatchControl(
|
||||
{required this.deviceId, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class MainDoorSensorReportsEvent extends MainDoorSensorEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final String from;
|
||||
final String to;
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, from, to];
|
||||
|
||||
MainDoorSensorReportsEvent(
|
||||
{required this.deviceId,
|
||||
required this.code,
|
||||
required this.from,
|
||||
required this.to});
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart';
|
||||
|
||||
class MainDoorSensorState extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class MainDoorSensorInitial extends MainDoorSensorState {}
|
||||
|
||||
class MainDoorSensorLoadingState extends MainDoorSensorState {}
|
||||
|
||||
class MainDoorSensorDeviceStatusLoaded extends MainDoorSensorState {
|
||||
final MainDoorSensorStatusModel status;
|
||||
|
||||
MainDoorSensorDeviceStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status];
|
||||
}
|
||||
|
||||
class MainDoorSensorFailedState extends MainDoorSensorState {
|
||||
final String error;
|
||||
|
||||
MainDoorSensorFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [error];
|
||||
}
|
||||
|
||||
class MainDoorSensorBatchFailedState extends MainDoorSensorState {
|
||||
final String error;
|
||||
|
||||
MainDoorSensorBatchFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [error];
|
||||
}
|
||||
|
||||
class MainDoorSensorBatchStatusLoaded extends MainDoorSensorState {
|
||||
final List<MainDoorSensorStatusModel> status;
|
||||
|
||||
MainDoorSensorBatchStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status];
|
||||
}
|
||||
|
||||
class MainDoorSensorReportLoaded extends MainDoorSensorState {
|
||||
final DeviceReport deviceReport;
|
||||
|
||||
MainDoorSensorReportLoaded(this.deviceReport);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceReport];
|
||||
}
|
||||
|
||||
class MainDoorSensorReportsLoadingState extends MainDoorSensorState {}
|
||||
|
||||
class MainDoorSensorReportsFailedState extends MainDoorSensorState {
|
||||
final String error;
|
||||
|
||||
MainDoorSensorReportsFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [error];
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class MainDoorSensorStatusModel {
|
||||
final String uuid;
|
||||
final bool doorContactState;
|
||||
final int batteryPercentage;
|
||||
|
||||
MainDoorSensorStatusModel({
|
||||
required this.uuid,
|
||||
required this.doorContactState,
|
||||
required this.batteryPercentage,
|
||||
});
|
||||
|
||||
factory MainDoorSensorStatusModel.fromJson(String id, List<Status> jsonList) {
|
||||
late bool doorContactState = false;
|
||||
late int batteryPercentage = 0;
|
||||
|
||||
for (var status in jsonList) {
|
||||
switch (status.code) {
|
||||
case 'doorcontact_state':
|
||||
doorContactState = status.value ?? false;
|
||||
break;
|
||||
case 'battery_percentage':
|
||||
batteryPercentage = status.value ?? 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return MainDoorSensorStatusModel(
|
||||
uuid: id,
|
||||
doorContactState: doorContactState,
|
||||
batteryPercentage: batteryPercentage,
|
||||
);
|
||||
}
|
||||
|
||||
MainDoorSensorStatusModel copyWith({
|
||||
String? uuid,
|
||||
bool? doorContactState,
|
||||
int? batteryPercentage,
|
||||
}) {
|
||||
return MainDoorSensorStatusModel(
|
||||
uuid: uuid ?? this.uuid,
|
||||
doorContactState: doorContactState ?? this.doorContactState,
|
||||
batteryPercentage: batteryPercentage ?? this.batteryPercentage,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class MainDoorSensorControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const MainDoorSensorControlView({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => MainDoorSensorBloc()
|
||||
..add(MainDoorSensorFetchDeviceEvent(device.uuid!)),
|
||||
child: BlocBuilder<MainDoorSensorBloc, MainDoorSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is MainDoorSensorLoadingState ||
|
||||
state is MainDoorSensorReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is MainDoorSensorDeviceStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is MainDoorSensorReportLoaded) {
|
||||
return ReportsTable(
|
||||
report: state.deviceReport,
|
||||
onRowTap: (index) {},
|
||||
onClose: () {
|
||||
context
|
||||
.read<MainDoorSensorBloc>()
|
||||
.add(MainDoorSensorFetchDeviceEvent(device.uuid!));
|
||||
},
|
||||
hideValueShowDescription: true,
|
||||
mainDoorSensor: true,
|
||||
);
|
||||
} else if (state is MainDoorSensorFailedState ||
|
||||
state is MainDoorSensorBatchFailedState) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(
|
||||
BuildContext context, MainDoorSensorStatusModel status) {
|
||||
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: [
|
||||
IconNameStatusContainer(
|
||||
name: status.doorContactState ? 'Open' : 'Close',
|
||||
icon: Assets.mainDoor,
|
||||
onTap: () {},
|
||||
status: status.doorContactState,
|
||||
textColor: ColorsManager.red,
|
||||
paddingAmount: 8,
|
||||
),
|
||||
IconNameStatusContainer(
|
||||
name: 'Open/Close\nRecord',
|
||||
icon: Assets.mainDoorReports,
|
||||
onTap: () {
|
||||
final from = DateTime.now()
|
||||
.subtract(const Duration(days: 30))
|
||||
.millisecondsSinceEpoch;
|
||||
final to = DateTime.now().millisecondsSinceEpoch;
|
||||
context.read<MainDoorSensorBloc>().add(
|
||||
MainDoorSensorReportsEvent(
|
||||
deviceId: device.uuid!,
|
||||
code: 'doorcontact_state',
|
||||
from: from.toString(),
|
||||
to: to.toString(),
|
||||
),
|
||||
);
|
||||
},
|
||||
status: false,
|
||||
textColor: ColorsManager.blackColor,
|
||||
),
|
||||
IconNameStatusContainer(
|
||||
name: 'Notifications\nSettings',
|
||||
icon: Assets.mainDoorNotifi,
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => const NotificationDialog(),
|
||||
);
|
||||
},
|
||||
status: false,
|
||||
textColor: ColorsManager.blackColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IconNameStatusContainer extends StatelessWidget {
|
||||
const IconNameStatusContainer({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
required this.status,
|
||||
required this.textColor,
|
||||
this.paddingAmount = 12,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String icon;
|
||||
final GestureTapCallback onTap;
|
||||
final bool status;
|
||||
final Color textColor;
|
||||
final double? paddingAmount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: DeviceControlsContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.whiteColors,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: EdgeInsets.all(paddingAmount ?? 12),
|
||||
child: ClipOval(
|
||||
child: SvgPicture.asset(
|
||||
icon,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Text(
|
||||
name,
|
||||
textAlign: TextAlign.start,
|
||||
style: context.textTheme.titleMedium!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class NotificationDialog extends StatelessWidget {
|
||||
const NotificationDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
insetPadding: const EdgeInsets.all(20),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 798,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
Text(
|
||||
'Notification Settings',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22,
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 25,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.grey,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.all(1),
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.grey,
|
||||
size: 18,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ToggleWidget(
|
||||
value: true,
|
||||
code: 'notification',
|
||||
deviceId: '',
|
||||
label: 'Low Battery',
|
||||
onChange: (v) {},
|
||||
icon: '-1',
|
||||
),
|
||||
ToggleWidget(
|
||||
value: true,
|
||||
code: 'notification',
|
||||
deviceId: '',
|
||||
label: 'Closing\nReminders',
|
||||
onChange: (v) {},
|
||||
icon: '-1',
|
||||
),
|
||||
ToggleWidget(
|
||||
value: true,
|
||||
code: 'notification',
|
||||
deviceId: '',
|
||||
label: 'Door Alarm',
|
||||
onChange: (v) {},
|
||||
icon: '-1',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class WallLightSwitchBloc
|
||||
extends Bloc<WallLightSwitchEvent, WallLightSwitchState> {
|
||||
WallLightSwitchBloc({required this.deviceId})
|
||||
: super(WallLightSwitchInitial()) {
|
||||
on<WallLightSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<WallLightSwitchControl>(_onControl);
|
||||
on<WallLightSwitchFetchBatchEvent>(_onFetchBatchStatus);
|
||||
on<WallLightSwitchBatchControl>(_onBatchControl);
|
||||
}
|
||||
|
||||
late WallLightStatusModel deviceStatus;
|
||||
final String deviceId;
|
||||
Timer? _timer;
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(WallLightSwitchFetchDeviceEvent event,
|
||||
Emitter<WallLightSwitchState> emit) async {
|
||||
emit(WallLightSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
|
||||
deviceStatus =
|
||||
WallLightStatusModel.fromJson(event.deviceId, status.status);
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(WallLightSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onControl(
|
||||
WallLightSwitchControl event, Emitter<WallLightSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<WallLightSwitchState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
late bool response;
|
||||
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
||||
Emitter<WallLightSwitchState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
if (code == 'switch_1') {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
}
|
||||
}
|
||||
|
||||
bool _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
return deviceStatus.switch1;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(WallLightSwitchFetchBatchEvent event,
|
||||
Emitter<WallLightSwitchState> emit) async {
|
||||
emit(WallLightSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus =
|
||||
WallLightStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(WallLightSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
FutureOr<void> _onBatchControl(WallLightSwitchBatchControl event,
|
||||
Emitter<WallLightSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class WallLightSwitchEvent extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class WallLightSwitchFetchDeviceEvent extends WallLightSwitchEvent {
|
||||
final String deviceId;
|
||||
|
||||
WallLightSwitchFetchDeviceEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class WallLightSwitchControl extends WallLightSwitchEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
WallLightSwitchControl(
|
||||
{required this.deviceId, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class WallLightSwitchFetchBatchEvent extends WallLightSwitchEvent {
|
||||
final List<String> devicesIds;
|
||||
|
||||
WallLightSwitchFetchBatchEvent(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class WallLightSwitchBatchControl extends WallLightSwitchEvent {
|
||||
final List<String> devicesIds;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
WallLightSwitchBatchControl(
|
||||
{required this.devicesIds, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds, code, value];
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
||||
|
||||
class WallLightSwitchState extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class WallLightSwitchInitial extends WallLightSwitchState {}
|
||||
|
||||
class WallLightSwitchLoading extends WallLightSwitchState {}
|
||||
|
||||
class WallLightSwitchStatusLoaded extends WallLightSwitchState {
|
||||
final WallLightStatusModel status;
|
||||
|
||||
WallLightSwitchStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object> get props => [status];
|
||||
}
|
||||
|
||||
class WallLightSwitchError extends WallLightSwitchState {
|
||||
final String message;
|
||||
|
||||
WallLightSwitchError(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
||||
class WallLightSwitchControlError extends WallLightSwitchState {
|
||||
final String message;
|
||||
|
||||
WallLightSwitchControlError(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
||||
class WallLightSwitchBatchControlError extends WallLightSwitchState {
|
||||
final String message;
|
||||
|
||||
WallLightSwitchBatchControlError(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
||||
class WallLightSwitchBatchStatusLoaded extends WallLightSwitchState {
|
||||
final List<String> status;
|
||||
|
||||
WallLightSwitchBatchStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object> get props => [status];
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class WallLightStatusModel {
|
||||
final String uuid;
|
||||
final bool switch1;
|
||||
final int countDown;
|
||||
|
||||
WallLightStatusModel({
|
||||
required this.uuid,
|
||||
required this.switch1,
|
||||
required this.countDown,
|
||||
});
|
||||
|
||||
factory WallLightStatusModel.fromJson(String id, List<Status> jsonList) {
|
||||
late bool switch1;
|
||||
late int countDown;
|
||||
|
||||
for (var status in jsonList) {
|
||||
switch (status.code) {
|
||||
case 'switch_1':
|
||||
switch1 = status.value ?? false;
|
||||
break;
|
||||
case 'countdown_1':
|
||||
countDown = status.value ?? 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return WallLightStatusModel(
|
||||
uuid: id,
|
||||
switch1: switch1,
|
||||
countDown: countDown,
|
||||
);
|
||||
}
|
||||
|
||||
WallLightStatusModel copyWith({
|
||||
String? uuid,
|
||||
bool? switch1,
|
||||
int? countDown,
|
||||
}) {
|
||||
return WallLightStatusModel(
|
||||
uuid: uuid ?? this.uuid,
|
||||
switch1: switch1 ?? this.switch1,
|
||||
countDown: countDown ?? this.countDown,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class WallLightBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const WallLightBatchControlView({super.key, required this.deviceIds});
|
||||
|
||||
final List<String> deviceIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => WallLightSwitchBloc(deviceId: deviceIds.first)
|
||||
..add(WallLightSwitchFetchBatchEvent(deviceIds)),
|
||||
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
|
||||
builder: (context, state) {
|
||||
if (state is WallLightSwitchLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is WallLightSwitchStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is WallLightSwitchError ||
|
||||
state is WallLightSwitchControlError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(
|
||||
BuildContext context, WallLightStatusModel status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return SizedBox(
|
||||
child: 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: [
|
||||
ToggleWidget(
|
||||
value: status.switch1,
|
||||
code: 'switch_1',
|
||||
deviceId: deviceIds.first,
|
||||
label: 'Wall Light',
|
||||
onChange: (value) {
|
||||
context.read<WallLightSwitchBloc>().add(
|
||||
WallLightSwitchBatchControl(
|
||||
devicesIds: deviceIds,
|
||||
code: 'switch_1',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
FirmwareUpdateWidget(
|
||||
deviceId: deviceIds.first,
|
||||
version: 12,
|
||||
),
|
||||
FactoryResetWidget(deviceId: deviceIds.first),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class WallLightDeviceControl extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const WallLightDeviceControl({super.key, required this.deviceId});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => WallLightSwitchBloc(deviceId: deviceId)
|
||||
..add(WallLightSwitchFetchDeviceEvent(deviceId)),
|
||||
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
|
||||
builder: (context, state) {
|
||||
if (state is WallLightSwitchLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is WallLightSwitchStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is WallLightSwitchError ||
|
||||
state is WallLightSwitchControlError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(
|
||||
BuildContext context, WallLightStatusModel status) {
|
||||
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: [
|
||||
const SizedBox(),
|
||||
ToggleWidget(
|
||||
value: status.switch1,
|
||||
code: 'switch_1',
|
||||
deviceId: deviceId,
|
||||
label: 'Wall Light',
|
||||
onChange: (value) {
|
||||
context.read<WallLightSwitchBloc>().add(WallLightSwitchControl(
|
||||
deviceId: deviceId,
|
||||
code: 'switch_1',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
const SizedBox(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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 FactoryResetWidget extends StatelessWidget {
|
||||
const FactoryResetWidget({super.key, required String deviceId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DeviceControlsContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ClipOval(
|
||||
child: Container(
|
||||
color: ColorsManager.whiteColors,
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: SvgPicture.asset(
|
||||
Assets.factoryReset,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)),
|
||||
Text(
|
||||
'Factory Reset',
|
||||
style: context.textTheme.titleMedium!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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 FirmwareUpdateWidget extends StatelessWidget {
|
||||
const FirmwareUpdateWidget(
|
||||
{super.key, required String deviceId, required int version});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DeviceControlsContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ClipOval(
|
||||
child: Container(
|
||||
color: ColorsManager.whiteColors,
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: SvgPicture.asset(
|
||||
Assets.firmware,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)),
|
||||
Text(
|
||||
'Firmware Update',
|
||||
style: context.textTheme.titleMedium!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart';
|
||||
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class DeviceBatchControlDialog extends StatelessWidget
|
||||
with RouteControlsBasedCode {
|
||||
final List<AllDevicesModel> devices;
|
||||
|
||||
const DeviceBatchControlDialog({super.key, required this.devices});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
insetPadding: const EdgeInsets.all(20),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: devices.length < 2 ? 500 : 800,
|
||||
// height: context.screenHeight * 0.7,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
devices.first.categoryName ?? 'Device Control',
|
||||
style: context.textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Text(
|
||||
"Batch Control",
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
width: 25,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.grey,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.all(1),
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.grey,
|
||||
size: 18,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
//// BUILD DEVICE CONTROLS
|
||||
///
|
||||
//// ROUTE TO SPECIFIC CONTROL VIEW BASED ON DEVICE CATEGORY
|
||||
routeBatchControlsWidgets(devices: devices),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart';
|
||||
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
@ -22,7 +21,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 798,
|
||||
height: context.screenHeight * 0.7,
|
||||
// height: context.screenHeight * 0.7,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
|
@ -4,20 +4,25 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_rep
|
||||
import 'package:syncrow_web/pages/device_managment/shared/table/table_cell_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/table/table_header.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ReportsTable extends StatelessWidget {
|
||||
final DeviceReport report;
|
||||
final String? thirdColumnTitle;
|
||||
final String? thirdColumnDescription;
|
||||
final Function(int index) onRowTap;
|
||||
final VoidCallback onClose;
|
||||
bool? hideValueShowDescription;
|
||||
bool? mainDoorSensor;
|
||||
|
||||
const ReportsTable({
|
||||
ReportsTable({
|
||||
super.key,
|
||||
required this.report,
|
||||
required this.onRowTap,
|
||||
required this.onClose,
|
||||
this.thirdColumnTitle,
|
||||
this.thirdColumnDescription,
|
||||
this.hideValueShowDescription,
|
||||
this.mainDoorSensor,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -57,8 +62,19 @@ class ReportsTable extends StatelessWidget {
|
||||
children: [
|
||||
TableCellWidget(value: date),
|
||||
TableCellWidget(value: time),
|
||||
TableCellWidget(
|
||||
value: '${data.value!} ${thirdColumnDescription ?? ''}',
|
||||
hideValueShowDescription == true
|
||||
? TableCellWidget(
|
||||
value: (mainDoorSensor != null &&
|
||||
mainDoorSensor == true)
|
||||
? data.value == 'true'
|
||||
? 'Open'
|
||||
: 'Close'
|
||||
: thirdColumnDescription ?? '',
|
||||
onTap: () => onRowTap(index),
|
||||
)
|
||||
: TableCellWidget(
|
||||
value:
|
||||
'${data.value!} ${thirdColumnDescription ?? ''}',
|
||||
onTap: () => onRowTap(index),
|
||||
),
|
||||
],
|
||||
|
81
lib/pages/device_managment/shared/toggle_widget.dart
Normal file
@ -0,0 +1,81 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.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 ToggleWidget extends StatelessWidget {
|
||||
final bool value;
|
||||
final String code;
|
||||
final String deviceId;
|
||||
final String label;
|
||||
final String? icon;
|
||||
final Function(dynamic value) onChange;
|
||||
|
||||
const ToggleWidget({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.code,
|
||||
required this.deviceId,
|
||||
required this.label,
|
||||
required this.onChange,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: ColorsManager.greyColor.withOpacity(0.2),
|
||||
border: Border.all(color: ColorsManager.boxDivider),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
icon == '-1'
|
||||
? const SizedBox(
|
||||
height: 60,
|
||||
width: 60,
|
||||
)
|
||||
: ClipOval(
|
||||
child: Container(
|
||||
color: ColorsManager.whiteColors,
|
||||
child: SvgPicture.asset(
|
||||
icon ?? Assets.lightPulp,
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)),
|
||||
Text(
|
||||
label,
|
||||
style: context.textTheme.titleMedium!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
height: 20,
|
||||
width: 35,
|
||||
padding: const EdgeInsets.only(right: 16, top: 10),
|
||||
child: CupertinoSwitch(
|
||||
value: value,
|
||||
activeColor: ColorsManager.dialogBlueTitle,
|
||||
onChanged: onChange,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -16,23 +16,28 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
|
||||
Timer? _timer;
|
||||
|
||||
LivingRoomBloc({required this.deviceId}) : super(LivingRoomInitial()) {
|
||||
on<LivingRoomFetchDeviceStatus>(_onFetchDeviceStatus);
|
||||
on<LivingRoomFetchDeviceStatusEvent>(_onFetchDeviceStatus);
|
||||
on<LivingRoomControl>(_livingRoomControl);
|
||||
on<LivingRoomBatchControl>(_livingRoomBatchControl);
|
||||
on<LivingRoomFetchBatchEvent>(_livingRoomFetchBatchControl);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(
|
||||
LivingRoomFetchDeviceStatus event, Emitter<LivingRoomState> emit) async {
|
||||
FutureOr<void> _onFetchDeviceStatus(LivingRoomFetchDeviceStatusEvent event,
|
||||
Emitter<LivingRoomState> emit) async {
|
||||
emit(LivingRoomDeviceStatusLoading());
|
||||
try {
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = LivingRoomStatusModel.fromJson(event.deviceId, status.status);
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus =
|
||||
LivingRoomStatusModel.fromJson(event.deviceId, status.status);
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _livingRoomControl(LivingRoomControl event, Emitter<LivingRoomState> emit) async {
|
||||
FutureOr<void> _livingRoomControl(
|
||||
LivingRoomControl event, Emitter<LivingRoomState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
@ -45,37 +50,52 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required String deviceId,
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required dynamic oldValue,
|
||||
required Emitter<LivingRoomState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
final response =
|
||||
await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value));
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
if (!response) {
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(
|
||||
String deviceId, String code, dynamic oldValue, Emitter<LivingRoomState> emit) {
|
||||
void _revertValueAndEmit(String deviceId, String code, dynamic oldValue,
|
||||
Emitter<LivingRoomState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
emit(const LivingRoomControlError('Failed to control the device.'));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, dynamic value) {
|
||||
@ -113,4 +133,36 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _livingRoomFetchBatchControl(
|
||||
LivingRoomFetchBatchEvent event, Emitter<LivingRoomState> emit) async {
|
||||
emit(LivingRoomDeviceStatusLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus =
|
||||
LivingRoomStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _livingRoomBatchControl(
|
||||
LivingRoomBatchControl event, Emitter<LivingRoomState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,15 +7,25 @@ sealed class LivingRoomEvent extends Equatable {
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LivingRoomFetchDeviceStatus extends LivingRoomEvent {
|
||||
class LivingRoomFetchDeviceStatusEvent extends LivingRoomEvent {
|
||||
final String deviceId;
|
||||
|
||||
const LivingRoomFetchDeviceStatus(this.deviceId);
|
||||
const LivingRoomFetchDeviceStatusEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
//LivingRoomFetchBatchStatus
|
||||
class LivingRoomFetchBatchEvent extends LivingRoomEvent {
|
||||
final List<String> devicesIds;
|
||||
|
||||
const LivingRoomFetchBatchEvent(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class LivingRoomControl extends LivingRoomEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
@ -27,3 +37,15 @@ class LivingRoomControl extends LivingRoomEvent {
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class LivingRoomBatchControl extends LivingRoomEvent {
|
||||
final List<String> devicesIds;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
const LivingRoomBatchControl(
|
||||
{required this.devicesIds, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds, code, value];
|
||||
}
|
||||
|
@ -0,0 +1,113 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class LivingRoomBatchControlsView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const LivingRoomBatchControlsView({super.key, required this.deviceIds});
|
||||
|
||||
final List<String> deviceIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LivingRoomBloc(deviceId: deviceIds.first)
|
||||
..add(LivingRoomFetchBatchEvent(deviceIds)),
|
||||
child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
|
||||
builder: (context, state) {
|
||||
if (state is LivingRoomDeviceStatusLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is LivingRoomDeviceStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is LivingRoomDeviceManagementError ||
|
||||
state is LivingRoomControlError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(
|
||||
BuildContext context, LivingRoomStatusModel status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return SizedBox(
|
||||
child: 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: [
|
||||
ToggleWidget(
|
||||
value: status.switch1,
|
||||
code: 'switch_1',
|
||||
deviceId: deviceIds.first,
|
||||
label: 'Wall Light',
|
||||
onChange: (value) {
|
||||
context.read<LivingRoomBloc>().add(
|
||||
LivingRoomBatchControl(
|
||||
devicesIds: deviceIds,
|
||||
code: 'switch_1',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ToggleWidget(
|
||||
value: status.switch2,
|
||||
code: 'switch_2',
|
||||
deviceId: deviceIds.first,
|
||||
label: 'Ceiling Light',
|
||||
onChange: (value) {
|
||||
context.read<LivingRoomBloc>().add(
|
||||
LivingRoomBatchControl(
|
||||
devicesIds: deviceIds,
|
||||
code: 'switch_2',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ToggleWidget(
|
||||
value: status.switch3,
|
||||
code: 'switch_2',
|
||||
deviceId: deviceIds.first,
|
||||
label: 'Spotlight',
|
||||
onChange: (value) {
|
||||
context.read<LivingRoomBloc>().add(
|
||||
LivingRoomBatchControl(
|
||||
devicesIds: deviceIds,
|
||||
code: 'switch_3',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
FirmwareUpdateWidget(
|
||||
deviceId: deviceIds.first,
|
||||
version: 12,
|
||||
),
|
||||
FactoryResetWidget(deviceId: deviceIds.first),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -2,26 +2,28 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class LivingRoomDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||
class LivingRoomDeviceControlsView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const LivingRoomDeviceControl({super.key, required this.deviceId});
|
||||
const LivingRoomDeviceControlsView({super.key, required this.deviceId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
LivingRoomBloc(deviceId: deviceId)..add(LivingRoomFetchDeviceStatus(deviceId)),
|
||||
create: (context) => LivingRoomBloc(deviceId: deviceId)
|
||||
..add(LivingRoomFetchDeviceStatusEvent(deviceId)),
|
||||
child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
|
||||
builder: (context, state) {
|
||||
if (state is LivingRoomDeviceStatusLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is LivingRoomDeviceStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is LivingRoomDeviceManagementError || state is LivingRoomControlError) {
|
||||
} else if (state is LivingRoomDeviceManagementError ||
|
||||
state is LivingRoomControlError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@ -31,12 +33,12 @@ class LivingRoomDeviceControl extends StatelessWidget with HelperResponsiveLayou
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(BuildContext context, LivingRoomStatusModel status) {
|
||||
Widget _buildStatusControls(
|
||||
BuildContext context, LivingRoomStatusModel status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return Container(
|
||||
child: GridView(
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
@ -56,21 +58,38 @@ class LivingRoomDeviceControl extends StatelessWidget with HelperResponsiveLayou
|
||||
code: 'switch_1',
|
||||
deviceId: deviceId,
|
||||
label: 'Wall Light',
|
||||
onChange: (value) {
|
||||
context.read<LivingRoomBloc>().add(
|
||||
LivingRoomControl(
|
||||
deviceId: deviceId, code: 'switch_1', value: value),
|
||||
);
|
||||
},
|
||||
),
|
||||
ToggleWidget(
|
||||
value: status.switch2,
|
||||
code: 'switch_2',
|
||||
deviceId: deviceId,
|
||||
label: 'Ceiling Light',
|
||||
onChange: (value) {
|
||||
context.read<LivingRoomBloc>().add(
|
||||
LivingRoomControl(
|
||||
deviceId: deviceId, code: 'switch_2', value: value),
|
||||
);
|
||||
},
|
||||
),
|
||||
ToggleWidget(
|
||||
value: status.switch3,
|
||||
code: 'switch_3',
|
||||
deviceId: deviceId,
|
||||
label: 'Spotlight',
|
||||
onChange: (value) {
|
||||
context.read<LivingRoomBloc>().add(
|
||||
LivingRoomControl(
|
||||
deviceId: deviceId, code: 'switch_3', value: value),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
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/three_gang_switch/bloc/living_room_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class ToggleWidget extends StatelessWidget {
|
||||
final bool value;
|
||||
final String code;
|
||||
final String deviceId;
|
||||
final String label;
|
||||
|
||||
const ToggleWidget({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.code,
|
||||
required this.deviceId,
|
||||
required this.label,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: ColorsManager.greyColor.withOpacity(0.2),
|
||||
border: Border.all(color: ColorsManager.boxDivider),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ClipOval(
|
||||
child: Container(
|
||||
color: ColorsManager.whiteColors,
|
||||
child: SvgPicture.asset(
|
||||
Assets.lightPulp,
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 35,
|
||||
child: CupertinoSwitch(
|
||||
value: value,
|
||||
activeColor: ColorsManager.dialogBlueTitle,
|
||||
onChanged: (newValue) {
|
||||
context.read<LivingRoomBloc>().add(
|
||||
LivingRoomControl(
|
||||
deviceId: deviceId,
|
||||
code: code,
|
||||
value: newValue,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
|
||||
TwoGangSwitchBloc({required this.deviceId}) : super(TwoGangSwitchInitial()) {
|
||||
on<TwoGangSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<TwoGangSwitchControl>(_onControl);
|
||||
on<TwoGangSwitchFetchBatchEvent>(_onFetchBatchStatus);
|
||||
on<TwoGangSwitchBatchControl>(_onBatchControl);
|
||||
}
|
||||
|
||||
late TwoGangStatusModel deviceStatus;
|
||||
final String deviceId;
|
||||
Timer? _timer;
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(TwoGangSwitchFetchDeviceEvent event,
|
||||
Emitter<TwoGangSwitchState> emit) async {
|
||||
emit(TwoGangSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
|
||||
deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status);
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(TwoGangSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onControl(
|
||||
TwoGangSwitchControl event, Emitter<TwoGangSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<TwoGangSwitchState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
||||
Emitter<TwoGangSwitchState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
if (code == 'switch_1') {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
}
|
||||
|
||||
if (code == 'switch_2') {
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
}
|
||||
}
|
||||
|
||||
bool _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
return deviceStatus.switch1;
|
||||
case 'switch_2':
|
||||
return deviceStatus.switch2;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(TwoGangSwitchFetchBatchEvent event,
|
||||
Emitter<TwoGangSwitchState> emit) async {
|
||||
emit(TwoGangSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus =
|
||||
TwoGangStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(TwoGangSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
FutureOr<void> _onBatchControl(
|
||||
TwoGangSwitchBatchControl event, Emitter<TwoGangSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class TwoGangSwitchEvent extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class TwoGangSwitchFetchDeviceEvent extends TwoGangSwitchEvent {
|
||||
final String deviceId;
|
||||
|
||||
TwoGangSwitchFetchDeviceEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class TwoGangSwitchControl extends TwoGangSwitchEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
TwoGangSwitchControl(
|
||||
{required this.deviceId, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class TwoGangSwitchFetchBatchEvent extends TwoGangSwitchEvent {
|
||||
final List<String> devicesIds;
|
||||
|
||||
TwoGangSwitchFetchBatchEvent(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class TwoGangSwitchBatchControl extends TwoGangSwitchEvent {
|
||||
final List<String> deviceId;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
TwoGangSwitchBatchControl(
|
||||
{required this.deviceId, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
|
||||
|
||||
class TwoGangSwitchState extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class TwoGangSwitchInitial extends TwoGangSwitchState {}
|
||||
|
||||
class TwoGangSwitchLoading extends TwoGangSwitchState {}
|
||||
|
||||
class TwoGangSwitchStatusLoaded extends TwoGangSwitchState {
|
||||
final TwoGangStatusModel status;
|
||||
|
||||
TwoGangSwitchStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object> get props => [status];
|
||||
}
|
||||
|
||||
class TwoGangSwitchError extends TwoGangSwitchState {
|
||||
final String message;
|
||||
|
||||
TwoGangSwitchError(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
||||
class TwoGangSwitchControlError extends TwoGangSwitchState {
|
||||
final String message;
|
||||
|
||||
TwoGangSwitchControlError(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
||||
class TwoGangSwitchBatchControlError extends TwoGangSwitchState {
|
||||
final String message;
|
||||
|
||||
TwoGangSwitchBatchControlError(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
||||
class TwoGangSwitchBatchStatusLoaded extends TwoGangSwitchState {
|
||||
final List<String> status;
|
||||
|
||||
TwoGangSwitchBatchStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object> get props => [status];
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class TwoGangStatusModel {
|
||||
final String uuid;
|
||||
final bool switch1;
|
||||
final bool switch2;
|
||||
final int countDown;
|
||||
final int countDown2;
|
||||
|
||||
TwoGangStatusModel({
|
||||
required this.uuid,
|
||||
required this.switch1,
|
||||
required this.switch2,
|
||||
required this.countDown,
|
||||
required this.countDown2,
|
||||
});
|
||||
|
||||
factory TwoGangStatusModel.fromJson(String id, List<Status> jsonList) {
|
||||
late bool switch1;
|
||||
late bool switch2;
|
||||
late int countDown;
|
||||
late int countDown2;
|
||||
|
||||
for (var status in jsonList) {
|
||||
switch (status.code) {
|
||||
case 'switch_1':
|
||||
switch1 = status.value ?? false;
|
||||
break;
|
||||
case 'countdown_1':
|
||||
countDown = status.value ?? 0;
|
||||
break;
|
||||
case 'switch_2':
|
||||
switch2 = status.value ?? false;
|
||||
break;
|
||||
case 'countdown_2':
|
||||
countDown2 = status.value ?? 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return TwoGangStatusModel(
|
||||
uuid: id,
|
||||
switch1: switch1,
|
||||
countDown: countDown,
|
||||
switch2: switch2,
|
||||
countDown2: countDown2,
|
||||
);
|
||||
}
|
||||
|
||||
TwoGangStatusModel copyWith({
|
||||
String? uuid,
|
||||
bool? switch1,
|
||||
int? countDown,
|
||||
bool? switch2,
|
||||
int? countDown2,
|
||||
}) {
|
||||
return TwoGangStatusModel(
|
||||
uuid: uuid ?? this.uuid,
|
||||
switch1: switch1 ?? this.switch1,
|
||||
countDown: countDown ?? this.countDown,
|
||||
switch2: switch2 ?? this.switch2,
|
||||
countDown2: countDown2 ?? this.countDown2,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class TwoGangBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const TwoGangBatchControlView({super.key, required this.deviceIds});
|
||||
|
||||
final List<String> deviceIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => TwoGangSwitchBloc(deviceId: deviceIds.first)
|
||||
..add(TwoGangSwitchFetchBatchEvent(deviceIds)),
|
||||
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
|
||||
builder: (context, state) {
|
||||
if (state is TwoGangSwitchLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is TwoGangSwitchStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is TwoGangSwitchError ||
|
||||
state is TwoGangSwitchControlError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(BuildContext context, TwoGangStatusModel status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return SizedBox(
|
||||
child: 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: [
|
||||
ToggleWidget(
|
||||
value: status.switch1,
|
||||
code: 'switch_1',
|
||||
deviceId: deviceIds.first,
|
||||
label: 'Wall Light',
|
||||
onChange: (value) {
|
||||
context.read<TwoGangSwitchBloc>().add(TwoGangSwitchBatchControl(
|
||||
deviceId: deviceIds,
|
||||
code: 'switch_1',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
ToggleWidget(
|
||||
value: status.switch2,
|
||||
code: 'switch_2',
|
||||
deviceId: deviceIds.first,
|
||||
label: 'Ceiling Light',
|
||||
onChange: (value) {
|
||||
context.read<TwoGangSwitchBloc>().add(TwoGangSwitchBatchControl(
|
||||
deviceId: deviceIds,
|
||||
code: 'switch_2',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
FirmwareUpdateWidget(
|
||||
deviceId: deviceIds.first,
|
||||
version: 12,
|
||||
),
|
||||
FactoryResetWidget(deviceId: deviceIds.first),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class TwoGangDeviceControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const TwoGangDeviceControlView({super.key, required this.deviceId});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => TwoGangSwitchBloc(deviceId: deviceId)
|
||||
..add(TwoGangSwitchFetchDeviceEvent(deviceId)),
|
||||
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
|
||||
builder: (context, state) {
|
||||
if (state is TwoGangSwitchLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is TwoGangSwitchStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is TwoGangSwitchError ||
|
||||
state is TwoGangSwitchControlError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(BuildContext context, TwoGangStatusModel status) {
|
||||
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: [
|
||||
ToggleWidget(
|
||||
value: status.switch1,
|
||||
code: 'switch_1',
|
||||
deviceId: deviceId,
|
||||
label: 'Wall Light',
|
||||
onChange: (value) {
|
||||
context.read<TwoGangSwitchBloc>().add(TwoGangSwitchControl(
|
||||
deviceId: deviceId,
|
||||
code: 'switch_1',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
ToggleWidget(
|
||||
value: status.switch2,
|
||||
code: 'switch_2',
|
||||
deviceId: deviceId,
|
||||
label: 'Ceiling Light',
|
||||
onChange: (value) {
|
||||
context.read<TwoGangSwitchBloc>().add(TwoGangSwitchControl(
|
||||
deviceId: deviceId,
|
||||
code: 'switch_2',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -12,15 +12,17 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
Timer? _timer;
|
||||
|
||||
WallSensorBloc({required this.deviceId}) : super(WallSensorInitialState()) {
|
||||
on<WallSensorInitialEvent>(_fetchWallSensorStatus);
|
||||
on<WallSensorFetchStatusEvent>(_fetchWallSensorStatus);
|
||||
on<WallSensorFetchBatchStatusEvent>(_fetchWallSensorBatchControl);
|
||||
on<WallSensorChangeValueEvent>(_changeValue);
|
||||
on<WallSensorBatchControlEvent>(_onBatchControl);
|
||||
on<GetDeviceReportsEvent>(_getDeviceReports);
|
||||
on<ShowDescriptionEvent>(_showDescription);
|
||||
on<BackToGridViewEvent>(_backToGridView);
|
||||
}
|
||||
|
||||
void _fetchWallSensorStatus(
|
||||
WallSensorInitialEvent event, Emitter<WallSensorState> emit) async {
|
||||
WallSensorFetchStatusEvent event, Emitter<WallSensorState> emit) async {
|
||||
emit(WallSensorLoadingInitialState());
|
||||
try {
|
||||
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||
@ -33,6 +35,21 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch batch status
|
||||
FutureOr<void> _fetchWallSensorBatchControl(
|
||||
WallSensorFetchBatchStatusEvent event,
|
||||
Emitter<WallSensorState> emit) async {
|
||||
emit(WallSensorLoadingInitialState());
|
||||
try {
|
||||
var response =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus = WallSensorModel.fromJson(response.status);
|
||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||
} catch (e) {
|
||||
emit(WallSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
// _listenToChanges() {
|
||||
// try {
|
||||
// DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
@ -66,28 +83,63 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
}
|
||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||
await _runDeBouncer(
|
||||
deviceId: deviceId, code: event.code, value: event.value);
|
||||
deviceId: deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
isBatch: false,
|
||||
emit: emit,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(
|
||||
WallSensorBatchControlEvent event, Emitter<WallSensorState> emit) async {
|
||||
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
||||
if (event.code == 'far_detection') {
|
||||
deviceStatus.farDetection = event.value;
|
||||
} else if (event.code == 'motionless_sensitivity') {
|
||||
deviceStatus.motionlessSensitivity = event.value;
|
||||
} else if (event.code == 'motion_sensitivity_value') {
|
||||
deviceStatus.motionSensitivity = event.value;
|
||||
} else if (event.code == 'no_one_time') {
|
||||
deviceStatus.noBodyTime = event.value;
|
||||
}
|
||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||
await _runDeBouncer(
|
||||
deviceId: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
|
||||
_runDeBouncer({
|
||||
required String deviceId,
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required Emitter<WallSensorState> emit,
|
||||
required bool isBatch,
|
||||
}) {
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
final response = await DevicesManagementApi()
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
add(WallSensorInitialEvent());
|
||||
add(WallSensorFetchStatusEvent());
|
||||
}
|
||||
} catch (_) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
add(WallSensorInitialEvent());
|
||||
add(WallSensorFetchStatusEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ abstract class WallSensorEvent extends Equatable {
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class WallSensorInitialEvent extends WallSensorEvent {}
|
||||
class WallSensorFetchStatusEvent extends WallSensorEvent {}
|
||||
|
||||
class WallSensorChangeValueEvent extends WallSensorEvent {
|
||||
final int value;
|
||||
@ -18,6 +18,14 @@ class WallSensorChangeValueEvent extends WallSensorEvent {
|
||||
List<Object> get props => [value, code];
|
||||
}
|
||||
|
||||
class WallSensorFetchBatchStatusEvent extends WallSensorEvent {
|
||||
final List<String> devicesIds;
|
||||
const WallSensorFetchBatchStatusEvent(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class GetDeviceReportsEvent extends WallSensorEvent {
|
||||
final String deviceUuid;
|
||||
final String code;
|
||||
@ -36,3 +44,18 @@ class ShowDescriptionEvent extends WallSensorEvent {
|
||||
}
|
||||
|
||||
class BackToGridViewEvent extends WallSensorEvent {}
|
||||
|
||||
class WallSensorBatchControlEvent extends WallSensorEvent {
|
||||
final List<String> deviceIds;
|
||||
final String code;
|
||||
final dynamic value;
|
||||
|
||||
const WallSensorBatchControlEvent({
|
||||
required this.deviceIds,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceIds, code, value];
|
||||
}
|
||||
|
@ -0,0 +1,125 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class WallSensorBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const WallSensorBatchControlView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => WallSensorBloc(deviceId: devicesIds.first)
|
||||
..add(WallSensorFetchBatchStatusEvent(devicesIds)),
|
||||
child: BlocBuilder<WallSensorBloc, WallSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is WallSensorLoadingInitialState ||
|
||||
state is DeviceReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is WallSensorUpdateState) {
|
||||
return _buildGridView(context, state.wallSensorModel, isExtraLarge,
|
||||
isLarge, isMedium);
|
||||
} else if (state is DeviceReportsFailedState) {
|
||||
final model = context.read<WallSensorBloc>().deviceStatus;
|
||||
return _buildGridView(
|
||||
context, model, isExtraLarge, isLarge, isMedium);
|
||||
}
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView(BuildContext context, WallSensorModel model,
|
||||
bool isExtraLarge, bool isLarge, bool isMedium) {
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
PresenceUpdateData(
|
||||
value: model.motionSensitivity.toDouble(),
|
||||
title: 'Motion Detection Sensitivity:',
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
steps: 1,
|
||||
action: (int value) {
|
||||
context.read<WallSensorBloc>().add(
|
||||
WallSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'motion_sensitivity_value',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.motionlessSensitivity.toDouble(),
|
||||
title: 'Motionless Detection Sensitivity:',
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<WallSensorBloc>().add(
|
||||
WallSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'motionless_sensitivity',
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.noBodyTime.toDouble(),
|
||||
title: 'Nobody Time:',
|
||||
minValue: 10,
|
||||
maxValue: 10000,
|
||||
steps: 1,
|
||||
description: 'hr',
|
||||
action: (int value) =>
|
||||
context.read<WallSensorBloc>().add(WallSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'no_one_time',
|
||||
value: value,
|
||||
))),
|
||||
PresenceUpdateData(
|
||||
value: model.farDetection.toDouble(),
|
||||
title: 'Far Detection:',
|
||||
minValue: 75,
|
||||
maxValue: 600,
|
||||
steps: 75,
|
||||
description: 'cm',
|
||||
action: (int value) => context.read<WallSensorBloc>().add(
|
||||
WallSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'far_detection',
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 2),
|
||||
FactoryResetWidget(deviceId: devicesIds.first),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -14,8 +14,9 @@ import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
|
||||
const WallSensorControls({super.key, required this.device});
|
||||
class WallSensorControlsView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const WallSensorControlsView({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@ -25,8 +26,8 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
WallSensorBloc(deviceId: device.uuid!)..add(WallSensorInitialEvent()),
|
||||
create: (context) => WallSensorBloc(deviceId: device.uuid!)
|
||||
..add(WallSensorFetchStatusEvent()),
|
||||
child: BlocBuilder<WallSensorBloc, WallSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is WallSensorLoadingInitialState ||
|
||||
|
@ -0,0 +1,226 @@
|
||||
import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
part 'water_heater_event.dart';
|
||||
part 'water_heater_state.dart';
|
||||
|
||||
class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
||||
WaterHeaterBloc() : super(WaterHeaterInitial()) {
|
||||
on<WaterHeaterFetchStatusEvent>(_fetchWaterHeaterStatus);
|
||||
on<ToggleWaterHeaterEvent>(_controlWaterHeater);
|
||||
on<UpdateScheduleEvent>(_updateScheduleEvent);
|
||||
on<StopScheduleEvent>(_stopScheduleEvent);
|
||||
}
|
||||
|
||||
late WaterHeaterStatusModel deviceStatus;
|
||||
Timer? _timer;
|
||||
|
||||
FutureOr<void> _updateScheduleEvent(
|
||||
UpdateScheduleEvent event,
|
||||
Emitter<WaterHeaterState> emit,
|
||||
) async {
|
||||
final currentState = state as WaterHeaterScheduleViewState;
|
||||
|
||||
final countdownRemaining = currentState.isActive
|
||||
? currentState.countdownRemaining
|
||||
: Duration(hours: event.hours, minutes: event.minutes);
|
||||
|
||||
emit(WaterHeaterScheduleViewState(
|
||||
scheduleMode: event.scheduleMode,
|
||||
hours: countdownRemaining!.inHours,
|
||||
minutes: countdownRemaining.inMinutes % 60,
|
||||
isActive: currentState.isActive,
|
||||
countdownRemaining: countdownRemaining,
|
||||
));
|
||||
|
||||
if (currentState.isActive) {
|
||||
_startCountdown(countdownRemaining, emit);
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _controlWaterHeater(
|
||||
ToggleWaterHeaterEvent event, Emitter<WaterHeaterState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value, emit);
|
||||
|
||||
emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
|
||||
|
||||
final success = await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
);
|
||||
|
||||
if (success && (event.code == "countdown_1" || event.code == "switch_inching")) {
|
||||
final countdownDuration = Duration(seconds: event.value);
|
||||
|
||||
emit(WaterHeaterScheduleViewState(
|
||||
scheduleMode: deviceStatus.scheduleMode,
|
||||
hours: countdownDuration.inHours,
|
||||
minutes: (countdownDuration.inMinutes % 60),
|
||||
isActive: true,
|
||||
countdownRemaining: countdownDuration,
|
||||
));
|
||||
|
||||
_startCountdown(countdownDuration, emit);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _runDebounce({
|
||||
required String deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required dynamic oldValue,
|
||||
required Emitter<WaterHeaterState> emit,
|
||||
}) async {
|
||||
final completer = Completer<bool>();
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
final status = await DevicesManagementApi().deviceControl(
|
||||
deviceId,
|
||||
Status(code: code, value: value),
|
||||
);
|
||||
|
||||
if (!status) {
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
completer.complete(false);
|
||||
} else {
|
||||
completer.complete(true);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
completer.complete(false);
|
||||
}
|
||||
});
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(
|
||||
String deviceId, String code, dynamic oldValue, Emitter<WaterHeaterState> emit) {
|
||||
_updateLocalValue(code, oldValue, emit);
|
||||
emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, dynamic value, Emitter<WaterHeaterState> emit) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(heaterSwitch: value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
return deviceStatus.heaterSwitch;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _fetchWaterHeaterStatus(
|
||||
WaterHeaterFetchStatusEvent event, Emitter<WaterHeaterState> emit) async {
|
||||
emit(WaterHeaterLoadingState());
|
||||
|
||||
try {
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
|
||||
|
||||
if (deviceStatus.countdownHours > 0 || deviceStatus.countdownMinutes > 0) {
|
||||
final remainingDuration = Duration(
|
||||
hours: deviceStatus.countdownHours,
|
||||
minutes: deviceStatus.countdownMinutes,
|
||||
);
|
||||
|
||||
emit(WaterHeaterScheduleViewState(
|
||||
scheduleMode: deviceStatus.scheduleMode,
|
||||
hours: deviceStatus.countdownHours,
|
||||
minutes: deviceStatus.countdownMinutes,
|
||||
isActive: true,
|
||||
countdownRemaining: remainingDuration,
|
||||
));
|
||||
|
||||
// _startCountdown(remainingDuration, emit);
|
||||
} else {
|
||||
emit(WaterHeaterScheduleViewState(
|
||||
scheduleMode: deviceStatus.scheduleMode,
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
isActive: false,
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(WaterHeaterFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _startCountdown(Duration duration, Emitter<WaterHeaterState> emit) {
|
||||
_timer?.cancel();
|
||||
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
final state = this.state as WaterHeaterScheduleViewState;
|
||||
final remaining = state.countdownRemaining! - const Duration(seconds: 1);
|
||||
|
||||
if (remaining.isNegative || remaining == Duration.zero) {
|
||||
_timer?.cancel();
|
||||
emit(WaterHeaterScheduleViewState(
|
||||
scheduleMode: state.scheduleMode,
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
isActive: false,
|
||||
countdownRemaining: Duration.zero,
|
||||
));
|
||||
} else {
|
||||
emit(WaterHeaterScheduleViewState(
|
||||
scheduleMode: state.scheduleMode,
|
||||
hours: remaining.inHours,
|
||||
minutes: remaining.inMinutes % 60,
|
||||
isActive: true,
|
||||
countdownRemaining: remaining,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FutureOr<void> _stopScheduleEvent(
|
||||
StopScheduleEvent event,
|
||||
Emitter<WaterHeaterState> emit,
|
||||
) {
|
||||
_timer?.cancel();
|
||||
deviceStatus = deviceStatus.copyWith(
|
||||
countdownHours: 0,
|
||||
countdownMinutes: 0,
|
||||
scheduleMode: ScheduleModes.countdown,
|
||||
);
|
||||
emit(const WaterHeaterScheduleViewState(
|
||||
scheduleMode: ScheduleModes.countdown,
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
isActive: false,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
part of 'water_heater_bloc.dart';
|
||||
|
||||
sealed class WaterHeaterEvent extends Equatable {
|
||||
const WaterHeaterEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
final class ToggleWaterHeaterEvent extends WaterHeaterEvent {
|
||||
final dynamic value;
|
||||
final String deviceId;
|
||||
final String code;
|
||||
|
||||
const ToggleWaterHeaterEvent(
|
||||
{required this.value, required this.deviceId, required this.code});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [value];
|
||||
}
|
||||
|
||||
final class UpdateScheduleEvent extends WaterHeaterEvent {
|
||||
final ScheduleModes scheduleMode;
|
||||
final int hours;
|
||||
final int minutes;
|
||||
|
||||
const UpdateScheduleEvent(
|
||||
{required this.scheduleMode, required this.hours, required this.minutes});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [scheduleMode, hours, minutes];
|
||||
}
|
||||
|
||||
final class StopScheduleEvent extends WaterHeaterEvent {}
|
||||
|
||||
class WaterHeaterFetchStatusEvent extends WaterHeaterEvent {
|
||||
final String deviceId;
|
||||
|
||||
const WaterHeaterFetchStatusEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceId];
|
||||
}
|
||||
|
||||
class WaterHeaterFetchBatchStatusEvent extends WaterHeaterEvent {
|
||||
final String deviceId;
|
||||
|
||||
const WaterHeaterFetchBatchStatusEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceId];
|
||||
}
|
||||
|
||||
// class ShowScheduleViewEvent extends WaterHeaterEvent {
|
||||
// const ShowScheduleViewEvent();
|
||||
// }
|
@ -0,0 +1,61 @@
|
||||
part of 'water_heater_bloc.dart';
|
||||
|
||||
enum ScheduleType { countdown, schedule, circulate, inching }
|
||||
|
||||
sealed class WaterHeaterState extends Equatable {
|
||||
const WaterHeaterState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
final class WaterHeaterInitial extends WaterHeaterState {}
|
||||
|
||||
final class WaterHeaterLoadingState extends WaterHeaterState {}
|
||||
|
||||
final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState {
|
||||
final WaterHeaterStatusModel status;
|
||||
|
||||
const WaterHeaterDeviceStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status];
|
||||
}
|
||||
|
||||
final class WaterHeaterFailedState extends WaterHeaterState {
|
||||
final String error;
|
||||
|
||||
const WaterHeaterFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [error];
|
||||
}
|
||||
|
||||
final class WaterHeaterBatchFailedState extends WaterHeaterState {
|
||||
final String error;
|
||||
|
||||
const WaterHeaterBatchFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [error];
|
||||
}
|
||||
|
||||
class WaterHeaterScheduleViewState extends WaterHeaterState {
|
||||
final ScheduleModes scheduleMode;
|
||||
final int hours;
|
||||
final int minutes;
|
||||
final bool isActive;
|
||||
final Duration? countdownRemaining;
|
||||
|
||||
const WaterHeaterScheduleViewState({
|
||||
required this.scheduleMode,
|
||||
required this.hours,
|
||||
required this.minutes,
|
||||
required this.isActive,
|
||||
this.countdownRemaining,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props =>
|
||||
[scheduleMode, hours, minutes, isActive, countdownRemaining];
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
enum ScheduleModes { countdown, schedule, circulate, inching }
|
||||
|
||||
class WaterHeaterStatusModel {
|
||||
final String uuid;
|
||||
final bool heaterSwitch;
|
||||
final int countdownHours;
|
||||
final int countdownMinutes;
|
||||
final ScheduleModes scheduleMode;
|
||||
final String relayStatus;
|
||||
final String cycleTiming;
|
||||
|
||||
WaterHeaterStatusModel({
|
||||
required this.uuid,
|
||||
required this.heaterSwitch,
|
||||
required this.countdownHours,
|
||||
required this.countdownMinutes,
|
||||
required this.relayStatus,
|
||||
required this.cycleTiming,
|
||||
required this.scheduleMode,
|
||||
});
|
||||
|
||||
factory WaterHeaterStatusModel.fromJson(String id, List<Status> jsonList) {
|
||||
late bool heaterSwitch = false;
|
||||
late int countdownInSeconds = 0;
|
||||
late String relayStatus = '';
|
||||
late String cycleTiming = '';
|
||||
late ScheduleModes scheduleMode = ScheduleModes.countdown;
|
||||
|
||||
for (var status in jsonList) {
|
||||
switch (status.code) {
|
||||
case 'switch_1':
|
||||
heaterSwitch = status.value ?? false;
|
||||
break;
|
||||
case 'countdown_1':
|
||||
countdownInSeconds = status.value ?? 0;
|
||||
break;
|
||||
case 'relay_status':
|
||||
relayStatus = status.value ?? 'memory';
|
||||
break;
|
||||
case 'cycle_timing':
|
||||
cycleTiming = status.value ?? '';
|
||||
break;
|
||||
case 'switch_inching':
|
||||
scheduleMode = getScheduleMode(status.value ?? 'countdown');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final countdownHours = countdownInSeconds ~/ 3600;
|
||||
final countdownMinutes =
|
||||
(countdownInSeconds % 3600) ~/ 60;
|
||||
|
||||
return WaterHeaterStatusModel(
|
||||
uuid: id,
|
||||
heaterSwitch: heaterSwitch,
|
||||
countdownHours: countdownHours,
|
||||
countdownMinutes: countdownMinutes,
|
||||
relayStatus: relayStatus,
|
||||
cycleTiming: cycleTiming,
|
||||
scheduleMode: scheduleMode,
|
||||
);
|
||||
}
|
||||
|
||||
WaterHeaterStatusModel copyWith({
|
||||
String? uuid,
|
||||
bool? heaterSwitch,
|
||||
int? countdownHours,
|
||||
int? countdownMinutes,
|
||||
String? relayStatus,
|
||||
String? cycleTiming,
|
||||
ScheduleModes? scheduleMode,
|
||||
}) {
|
||||
return WaterHeaterStatusModel(
|
||||
uuid: uuid ?? this.uuid,
|
||||
heaterSwitch: heaterSwitch ?? this.heaterSwitch,
|
||||
countdownHours: countdownHours ?? this.countdownHours,
|
||||
countdownMinutes: countdownMinutes ?? this.countdownMinutes,
|
||||
relayStatus: relayStatus ?? this.relayStatus,
|
||||
cycleTiming: cycleTiming ?? this.cycleTiming,
|
||||
scheduleMode: scheduleMode ?? this.scheduleMode,
|
||||
);
|
||||
}
|
||||
|
||||
static ScheduleModes getScheduleMode(String value) {
|
||||
switch (value) {
|
||||
case 'countdown':
|
||||
return ScheduleModes.countdown;
|
||||
case 'schedule':
|
||||
return ScheduleModes.schedule;
|
||||
case 'circulate':
|
||||
return ScheduleModes.circulate;
|
||||
case 'inching':
|
||||
return ScheduleModes.inching;
|
||||
default:
|
||||
return ScheduleModes.countdown;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedual_view.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class WaterHeaterDeviceControl extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const WaterHeaterDeviceControl({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
WaterHeaterBloc()..add(WaterHeaterFetchStatusEvent(device.uuid!)),
|
||||
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
|
||||
builder: (context, state) {
|
||||
if (state is WaterHeaterLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is WaterHeaterDeviceStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is WaterHeaterScheduleViewState) {
|
||||
final status = context.read<WaterHeaterBloc>().deviceStatus;
|
||||
return _buildStatusControls(context, status);
|
||||
} else if (state is WaterHeaterFailedState ||
|
||||
state is WaterHeaterBatchFailedState) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(
|
||||
BuildContext context,
|
||||
WaterHeaterStatusModel status,
|
||||
) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 150),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge || isMedium ? 2 : 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
ToggleWidget(
|
||||
deviceId: device.uuid!,
|
||||
code: 'switch_1',
|
||||
value: status.heaterSwitch,
|
||||
label: 'Water Heater',
|
||||
onChange: (value) {
|
||||
context.read<WaterHeaterBloc>().add(ToggleWaterHeaterEvent(
|
||||
deviceId: device.uuid!,
|
||||
code: 'switch_1',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// context.read<WaterHeaterBloc>().add(const ShowScheduleViewEvent());
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => BlocProvider.value(
|
||||
value: BlocProvider.of<WaterHeaterBloc>(context),
|
||||
child: BuildScheduleView(status: status),
|
||||
));
|
||||
},
|
||||
child: DeviceControlsContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.whiteColors,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ClipOval(
|
||||
child: SvgPicture.asset(
|
||||
Assets.scheduling,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'Scheduling',
|
||||
textAlign: TextAlign.center,
|
||||
style: context.textTheme.titleMedium!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,412 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class BuildScheduleView extends StatelessWidget {
|
||||
const BuildScheduleView({super.key, required this.status});
|
||||
|
||||
final WaterHeaterStatusModel status;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
insetPadding: const EdgeInsets.all(20),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 700,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20),
|
||||
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
|
||||
builder: (context, state) {
|
||||
if (state is WaterHeaterScheduleViewState) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
Text(
|
||||
'Scheduling',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22,
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 25,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.grey,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.all(1),
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.grey,
|
||||
size: 18,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'Type:',
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SizedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Flexible(
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
'Countdown',
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
leading: Radio<ScheduleModes>(
|
||||
value: ScheduleModes.countdown,
|
||||
groupValue: state.scheduleMode,
|
||||
onChanged: (ScheduleModes? value) {
|
||||
if (value != null) {
|
||||
context
|
||||
.read<WaterHeaterBloc>()
|
||||
.add(UpdateScheduleEvent(
|
||||
scheduleMode: value,
|
||||
hours: state.hours,
|
||||
minutes: state.minutes,
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
'Schedule',
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
leading: Radio<ScheduleModes>(
|
||||
value: ScheduleModes.schedule,
|
||||
groupValue: state.scheduleMode,
|
||||
onChanged: (ScheduleModes? value) {
|
||||
if (value != null) {
|
||||
context
|
||||
.read<WaterHeaterBloc>()
|
||||
.add(UpdateScheduleEvent(
|
||||
scheduleMode: value,
|
||||
hours: state.hours,
|
||||
minutes: state.minutes,
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'Circulate',
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
leading: Radio<ScheduleModes>(
|
||||
value: ScheduleModes.circulate,
|
||||
groupValue: state.scheduleMode,
|
||||
onChanged: (ScheduleModes? value) {
|
||||
if (value != null) {
|
||||
context
|
||||
.read<WaterHeaterBloc>()
|
||||
.add(UpdateScheduleEvent(
|
||||
scheduleMode: value,
|
||||
hours: state.hours,
|
||||
minutes: state.minutes,
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'Inching',
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
leading: Radio<ScheduleModes>(
|
||||
value: ScheduleModes.inching,
|
||||
groupValue: state.scheduleMode,
|
||||
onChanged: (ScheduleModes? value) {
|
||||
if (value != null) {
|
||||
context
|
||||
.read<WaterHeaterBloc>()
|
||||
.add(UpdateScheduleEvent(
|
||||
scheduleMode: value,
|
||||
hours: state.hours,
|
||||
minutes: state.minutes,
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (state.scheduleMode == ScheduleModes.countdown ||
|
||||
state.scheduleMode == ScheduleModes.inching) ...[
|
||||
Text(
|
||||
'Countdown:',
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
_hourMinutesWheel(state, context)
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
child: SizedBox(
|
||||
width: 400,
|
||||
height: 50,
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
height: 40,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
backgroundColor: ColorsManager.boxColor,
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: (state.countdownRemaining != null &&
|
||||
state.isActive)
|
||||
? DefaultButton(
|
||||
height: 40,
|
||||
onPressed: () {
|
||||
late String code;
|
||||
if (state.scheduleMode ==
|
||||
ScheduleModes.countdown) {
|
||||
code = 'countdown_1';
|
||||
} else if (state.scheduleMode ==
|
||||
ScheduleModes.inching) {
|
||||
code = 'switch_inching';
|
||||
}
|
||||
context
|
||||
.read<WaterHeaterBloc>()
|
||||
.add(StopScheduleEvent());
|
||||
context.read<WaterHeaterBloc>().add(
|
||||
ToggleWaterHeaterEvent(
|
||||
deviceId: status.uuid,
|
||||
code: code,
|
||||
value: 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
backgroundColor: Colors.red,
|
||||
child: const Text('Stop'),
|
||||
)
|
||||
: DefaultButton(
|
||||
height: 40,
|
||||
onPressed: () {
|
||||
late String code;
|
||||
if (state.scheduleMode ==
|
||||
ScheduleModes.countdown) {
|
||||
code = 'countdown_1';
|
||||
} else if (state.scheduleMode ==
|
||||
ScheduleModes.inching) {
|
||||
code = 'switch_inching';
|
||||
}
|
||||
context.read<WaterHeaterBloc>().add(
|
||||
ToggleWaterHeaterEvent(
|
||||
deviceId: status.uuid,
|
||||
code: code,
|
||||
// value is time in seconds
|
||||
value: Duration(
|
||||
hours: state.hours,
|
||||
minutes:
|
||||
state.minutes)
|
||||
.inSeconds,
|
||||
),
|
||||
);
|
||||
},
|
||||
backgroundColor:
|
||||
ColorsManager.primaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Row _hourMinutesWheel(
|
||||
WaterHeaterScheduleViewState state, BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
// Hours Picker
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
height: 50,
|
||||
width: 80,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: ListWheelScrollView.useDelegate(
|
||||
controller:
|
||||
FixedExtentScrollController(initialItem: state.hours),
|
||||
itemExtent: 40.0,
|
||||
physics: FixedExtentScrollPhysics(),
|
||||
onSelectedItemChanged: (int value) {
|
||||
context.read<WaterHeaterBloc>().add(
|
||||
UpdateScheduleEvent(
|
||||
scheduleMode: state.scheduleMode,
|
||||
hours: value,
|
||||
minutes: state.minutes,
|
||||
),
|
||||
);
|
||||
},
|
||||
childDelegate: ListWheelChildBuilderDelegate(
|
||||
builder: (context, index) {
|
||||
return Center(
|
||||
child: Text(
|
||||
index.toString().padLeft(2, '0'),
|
||||
style: const TextStyle(fontSize: 24),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'h',
|
||||
style: TextStyle(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// Minutes Picker
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
height: 50,
|
||||
width: 80,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: ListWheelScrollView.useDelegate(
|
||||
controller:
|
||||
FixedExtentScrollController(initialItem: state.minutes),
|
||||
itemExtent: 40.0,
|
||||
physics: FixedExtentScrollPhysics(),
|
||||
onSelectedItemChanged: (int value) {
|
||||
context.read<WaterHeaterBloc>().add(
|
||||
UpdateScheduleEvent(
|
||||
scheduleMode: state.scheduleMode,
|
||||
hours: state.hours,
|
||||
minutes: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
childDelegate: ListWheelChildBuilderDelegate(
|
||||
builder: (context, index) {
|
||||
return Center(
|
||||
child: Text(
|
||||
index.toString().padLeft(2, '0'),
|
||||
style: const TextStyle(fontSize: 24),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: 60,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'm',
|
||||
style: TextStyle(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -41,7 +41,8 @@ class HomeMobilePage extends StatelessWidget {
|
||||
SizedBox(height: size.height * 0.05),
|
||||
const Text(
|
||||
'ACCESS YOUR APPS',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
|
||||
style:
|
||||
TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Expanded(
|
||||
@ -51,7 +52,8 @@ class HomeMobilePage extends StatelessWidget {
|
||||
width: size.width * 0.68,
|
||||
child: GridView.builder(
|
||||
itemCount: 8,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 20.0,
|
||||
mainAxisSpacing: 20.0,
|
||||
@ -63,7 +65,8 @@ class HomeMobilePage extends StatelessWidget {
|
||||
active: homeItems[index]['active'],
|
||||
name: homeItems[index]['title'],
|
||||
img: homeItems[index]['icon'],
|
||||
onTap: () => homeBloc.homeItems[index].onPress(context),
|
||||
onTap: () =>
|
||||
homeBloc.homeItems[index].onPress(context),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -65,7 +65,33 @@ class DevicesManagementApi {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<DeviceModel>> getDevicesByGatewayId(String gatewayId) async {
|
||||
Future<bool> deviceBatchControl(
|
||||
List<String> uuids, String code, dynamic value) async {
|
||||
try {
|
||||
final body = {
|
||||
'devicesUuid': uuids,
|
||||
'code': code,
|
||||
'value': value,
|
||||
};
|
||||
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.deviceBatchControl,
|
||||
body: body,
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
return (json['successResults'] as List).isNotEmpty ;
|
||||
},
|
||||
);
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error fetching $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<DeviceModel>> getDevicesByGatewayId(
|
||||
String gatewayId) async {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId),
|
||||
showServerMessage: false,
|
||||
@ -94,9 +120,14 @@ class DevicesManagementApi {
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future<DeviceReport> getDeviceReports(String uuid, String code) async {
|
||||
static Future<DeviceReport> getDeviceReports(
|
||||
String uuid,
|
||||
String code,
|
||||
) async {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code),
|
||||
path: ApiEndpoints.getDeviceLogs
|
||||
.replaceAll('{uuid}', uuid)
|
||||
.replaceAll('{code}', code),
|
||||
showServerMessage: false,
|
||||
expectedResponseModel: (json) {
|
||||
return DeviceReport.fromJson(json);
|
||||
@ -104,4 +135,44 @@ class DevicesManagementApi {
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future<DeviceReport> getDeviceReportsByDate(String uuid, String code,
|
||||
[String? from, String? to]) async {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getDeviceLogsByDate
|
||||
.replaceAll('{uuid}', uuid)
|
||||
.replaceAll('{code}', code)
|
||||
.replaceAll('{startTime}', from ?? '')
|
||||
.replaceAll('{endTime}', to ?? ''),
|
||||
showServerMessage: false,
|
||||
expectedResponseModel: (json) {
|
||||
return DeviceReport.fromJson(json);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<DeviceStatus> getBatchStatus(List<String> uuids) async {
|
||||
try {
|
||||
final queryParameters = {
|
||||
'devicesUuid': uuids.join(','),
|
||||
};
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getBatchStatus,
|
||||
queryParameters: queryParameters,
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
return DeviceStatus.fromJson(json['status']);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error fetching $e');
|
||||
return DeviceStatus(
|
||||
productUuid: '',
|
||||
productType: '',
|
||||
status: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,12 +11,14 @@ abstract class ApiEndpoints {
|
||||
static const String visitorPassword = '/visitor-password';
|
||||
static const String getDevices = '/visitor-password/devices';
|
||||
|
||||
static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time';
|
||||
static const String sendOnlineOneTime =
|
||||
'/visitor-password/temporary-password/online/one-time';
|
||||
static const String sendOnlineMultipleTime =
|
||||
'/visitor-password/temporary-password/online/multiple-time';
|
||||
|
||||
//offline Password
|
||||
static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time';
|
||||
static const String sendOffLineOneTime =
|
||||
'/visitor-password/temporary-password/offline/one-time';
|
||||
static const String sendOffLineMultipleTime =
|
||||
'/visitor-password/temporary-password/offline/multiple-time';
|
||||
|
||||
@ -26,10 +28,14 @@ abstract class ApiEndpoints {
|
||||
|
||||
static const String getAllDevices = '/device';
|
||||
static const String getDeviceStatus = '/device/{uuid}/functions/status';
|
||||
static const String getBatchStatus = '/device/status/batch';
|
||||
|
||||
static const String deviceControl = '/device/{uuid}/control';
|
||||
static const String deviceBatchControl = '/device/control/batch';
|
||||
static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices';
|
||||
static const String openDoorLock = '/door-lock/open/{doorLockUuid}';
|
||||
|
||||
static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}';
|
||||
static const String getDeviceLogsByDate =
|
||||
'/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}';
|
||||
}
|
||||
|
@ -13,10 +13,12 @@ class Assets {
|
||||
static const String rightLine = "assets/images/right_line.png";
|
||||
static const String google = "assets/images/google.svg";
|
||||
static const String facebook = "assets/images/facebook.svg";
|
||||
static const String invisiblePassword = "assets/images/Password_invisible.svg";
|
||||
static const String invisiblePassword =
|
||||
"assets/images/Password_invisible.svg";
|
||||
static const String visiblePassword = "assets/images/password_visible.svg";
|
||||
static const String accessIcon = "assets/images/access_icon.svg";
|
||||
static const String spaseManagementIcon = "assets/images/spase_management_icon.svg";
|
||||
static const String spaseManagementIcon =
|
||||
"assets/images/spase_management_icon.svg";
|
||||
static const String devicesIcon = "assets/images/devices_icon.svg";
|
||||
static const String moveinIcon = "assets/images/movein_icon.svg";
|
||||
static const String constructionIcon = "assets/images/construction_icon.svg";
|
||||
@ -29,13 +31,15 @@ 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";
|
||||
static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg";
|
||||
static const String resetOff = "assets/icons/reset_off.svg";
|
||||
static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg";
|
||||
static const String sensitivityOperationIcon =
|
||||
"assets/icons/sesitivity_operation_icon.svg";
|
||||
static const String motionDetection = "assets/icons/motion_detection.svg";
|
||||
static const String freezing = "assets/icons/freezing.svg";
|
||||
static const String indicator = "assets/icons/indicator.svg";
|
||||
@ -56,7 +60,8 @@ class Assets {
|
||||
static const String celsiusDegrees = "assets/icons/celsius_degrees.svg";
|
||||
static const String masterState = "assets/icons/master_state.svg";
|
||||
static const String acPower = "assets/icons/ac_power.svg";
|
||||
static const String farDetectionFunction = "assets/icons/far_detection_function.svg";
|
||||
static const String farDetectionFunction =
|
||||
"assets/icons/far_detection_function.svg";
|
||||
static const String nobodyTime = "assets/icons/nobody_time.svg";
|
||||
|
||||
// Automation functions
|
||||
@ -64,33 +69,47 @@ 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 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 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 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 residualElectricity =
|
||||
"assets/icons/automation_functions/residual_electricity.svg";
|
||||
static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg";
|
||||
static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg";
|
||||
static const String hijackAlarm =
|
||||
"assets/icons/automation_functions/hijack_alarm.svg";
|
||||
static const String passwordUnlock =
|
||||
"assets/icons/automation_functions/password_unlock.svg";
|
||||
static const String remoteUnlockRequest =
|
||||
"assets/icons/automation_functions/remote_unlock_req.svg";
|
||||
static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg";
|
||||
static const String cardUnlock =
|
||||
"assets/icons/automation_functions/card_unlock.svg";
|
||||
static const String motion = "assets/icons/automation_functions/motion.svg";
|
||||
static const String fingerprintUnlock =
|
||||
"assets/icons/automation_functions/fingerprint_unlock.svg";
|
||||
|
||||
// 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 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 lightPulp = "assets/icons/light_pulb.svg";
|
||||
static const String acDevice = "assets/icons/ac_device.svg";
|
||||
@ -126,4 +145,13 @@ class Assets {
|
||||
static const String grid = "assets/images/grid.svg";
|
||||
static const String curtainIcon = "assets/images/curtain.svg";
|
||||
static const String unlock = 'assets/icons/unlock_ic.svg';
|
||||
static const String firmware = 'assets/icons/firmware.svg';
|
||||
//assets/images/scheduling.svg
|
||||
static const String scheduling = 'assets/images/scheduling.svg';
|
||||
//assets/icons/main_door_notifi.svg
|
||||
static const String mainDoorNotifi = 'assets/icons/main_door_notifi.svg';
|
||||
//assets/icons/main_door_reports.svg
|
||||
static const String mainDoorReports = 'assets/icons/main_door_reports.svg';
|
||||
//assets/icons/main_door.svg
|
||||
static const String mainDoor = 'assets/icons/main_door.svg';
|
||||
}
|
||||
|
24
pubspec.lock
@ -292,18 +292,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.4"
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -340,18 +340,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.0"
|
||||
version: "1.15.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -561,10 +561,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
version: "0.7.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -609,10 +609,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.1"
|
||||
version: "14.2.5"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
|