Redesign. Improve dashboard page loading.
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.4 KiB |
9
assets/images/thingsboard.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="320" height="320" version="1.1" viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke-width="28"></g>
|
||||
<g shape-rendering="auto">
|
||||
<path d="m151.13 0.00104c-28.363 0-54.915 7.9153-77.613 21.537-6.4723-5.2623-14.605-8.1915-23.067-8.1937a8.7661 8.7661 0 0 0-0.0035 0c-20.154 7.3e-4 -36.679 16.528-36.678 36.682a8.7661 8.7661 0 0 0 0 0.0106c0.0099 8.4147 2.9267 16.483 8.1033 22.927-13.825 22.819-21.865 49.572-21.865 78.161a8.7661 8.7661 0 1 0 17.531 0c0-24.702 6.7193-47.748 18.378-67.574 4.5663 1.9846 9.4727 3.1496 14.519 3.1573a8.7661 8.7661 0 0 0 0.01241 0c20.155 6.2e-4 36.683-16.527 36.682-36.682a8.7661 8.7661 0 0 0 0-4e-3c-0.0016-4.9994-1.1387-9.8628-3.0828-14.397 19.717-11.484 42.585-18.095 67.085-18.095a8.7661 8.7661 0 1 0 0-17.531zm-100.69 30.874c5.9129 2e-3 11.191 2.5121 14.836 7.0769a8.7661 8.7661 0 0 0 0.1826 0.21451c2.6715 3.3798 4.1326 7.5531 4.1341 11.863-0.0015 10.677-8.468 19.144-19.144 19.148-4.37-8e-3 -8.6011-1.5088-12-4.2546a8.7661 8.7661 0 0 0-0.01241-9e-3c-4.514-3.6331-7.1358-9.0979-7.1442-14.893 0.0025-10.677 8.4701-19.144 19.148-19.146z" color="#000000" color-rendering="auto" image-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
<path d="m66.992 102.83c-1.492 1.5583-2.3663 3.7103-2.2576 6.0713 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223l11.844-11.83s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-2.3583-0.0112-4.4673 0.95389-5.9592 2.5122zm32.147 19.983-36.639 36.639c-3.9751 3.976-3.9751 10.421 0 14.397l80.387 80.387c3.9758 3.9759 10.422 3.9763 14.398 0l99.345-99.345c3.9754-3.9764 3.975-10.422-6.7e-4 -14.398l-18.63-18.628-61.758-61.758c-3.976-3.9751-10.421-3.9751-14.397 0l-24.791 24.791zm37.914-37.914s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-4.7166-0.0226-8.4341 3.8616-8.2169 8.5834 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223zm69.193 5.2132s12.961-12.973 20.171-20.176c1.6325-1.604 2.3141-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91322-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223zm31.753 31.753s12.961-12.973 20.171-20.176c1.6325-1.604 2.314-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91323-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223zm-18.009 69.667s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3243 4.7166 0.0225 8.4342-3.8615 8.2169-8.5834l-2e-3 2e-3c-0.0962-2.0447-0.91415-4.0214-2.3382-5.5186-6.8051-6.8559-20.222-20.222-20.222-20.222l-11.844 11.83zm-37.914 37.914s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3242 4.7166 0.023 8.4342-3.8615 8.2169-8.5834h-2e-3c-0.0962-2.0447-0.91323-4.0205-2.3372-5.5177-6.8051-6.8559-20.223-20.223-20.223-20.223l-11.844 11.83zm-69.667-5.6871s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7166 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.096 4.0204-0.9132 5.5177-2.3373 6.8561-6.8048 20.223-20.223 20.223-20.223zm-31.753-31.753s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7167 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.0962 4.0204-0.91322 5.5177-2.3372 6.8561-6.8047 20.223-20.223 20.223-20.223zm87.237-90.554c1.6794-1.7064 3.9669-2.6599 6.2971-2.626 1.6628 0.0237 3.253 0.55012 4.5625 1.5097l16.499 12.1c3.2009 2.297 4.1445 6.6589 2.2308 10.312-1.9137 3.6528-6.1234 5.5243-9.9506 4.4227l6.124 23.948c1.1128 4.3517-1.564 8.9677-5.9833 10.317l-44.642 13.631 8.2456 31.883c1.1728 4.3696-1.5017 9.0445-5.9546 10.407-4.4529 1.3625-8.9756-1.1106-10.068-5.5047l-10.282-39.769c-1.1265-4.3556 1.5493-8.9847 5.9759-10.338l44.661-13.637-4.1219-16.118c-2.7634 3.0643-7.2335 3.8084-10.586 1.7615-3.3531-2.0469-4.6144-6.2896-2.9861-10.047l8.1169-19.454c0.43322-1.0376 1.0673-1.9888 1.8624-2.7973z" color="#000000" color-rendering="auto" fill-rule="evenodd" image-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
<path d="m168.87 320c28.363 0 54.915-7.9155 77.614-21.538 6.4724 5.2624 14.605 8.1917 23.067 8.1939a8.7662 8.7662 0 0 0 4e-3 0c20.155-7e-4 36.68-16.528 36.679-36.682a8.7662 8.7662 0 0 0 0-0.011c-0.01-8.4149-2.9267-16.484-8.1034-22.927 13.825-22.82 21.866-49.572 21.866-78.162a8.7662 8.7662 0 1 0-17.531 0c0 24.703-6.7194 47.749-18.378 67.575-4.5664-1.9846-9.4728-3.1496-14.519-3.1573a8.7662 8.7662 0 0 0-0.0124 0c-20.155-6.2e-4 -36.683 16.527-36.682 36.682 2e-3 4.9994 1.1387 9.8628 3.0829 14.397-19.717 11.484-42.586 18.095-67.086 18.095a8.7662 8.7662 0 1 0 0 17.531zm100.69-30.875c-5.913 0-11.191-2.5122-14.836-7.0769a8.7662 8.7662 0 0 0-0.18261-0.2146c-2.6715-3.3799-4.1327-7.5531-4.1341-11.863 2e-3 -10.677 8.4681-19.144 19.144-19.148 4.37 8e-3 8.6012 1.5088 12 4.2547a8.7662 8.7662 0 0 0 0.0124 9e-3c4.5141 3.6332 7.1359 9.098 7.1443 14.893-3e-3 10.677-8.4702 19.145-19.148 19.146z" color="#000000" color-rendering="auto" image-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
7
assets/images/thingsboard_center.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="320" height="320" version="1.1" viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke-width="28"></g>
|
||||
<g shape-rendering="auto">
|
||||
<path d="m66.992 102.83c-1.492 1.5583-2.3663 3.7103-2.2576 6.0713 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223l11.844-11.83s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-2.3583-0.0112-4.4673 0.95389-5.9592 2.5122zm32.147 19.983-36.639 36.639c-3.9751 3.976-3.9751 10.421 0 14.397l80.387 80.387c3.9758 3.9759 10.422 3.9763 14.398 0l99.345-99.345c3.9754-3.9764 3.975-10.422-6.7e-4 -14.398l-18.63-18.628-61.758-61.758c-3.976-3.9751-10.421-3.9751-14.397 0l-24.791 24.791zm37.914-37.914s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-4.7166-0.0226-8.4341 3.8616-8.2169 8.5834 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223zm69.193 5.2132s12.961-12.973 20.171-20.176c1.6325-1.604 2.3141-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91322-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223zm31.753 31.753s12.961-12.973 20.171-20.176c1.6325-1.604 2.314-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91323-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223zm-18.009 69.667s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3243 4.7166 0.0225 8.4342-3.8615 8.2169-8.5834l-2e-3 2e-3c-0.0962-2.0447-0.91415-4.0214-2.3382-5.5186-6.8051-6.8559-20.222-20.222-20.222-20.222l-11.844 11.83zm-37.914 37.914s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3242 4.7166 0.023 8.4342-3.8615 8.2169-8.5834h-2e-3c-0.0962-2.0447-0.91323-4.0205-2.3372-5.5177-6.8051-6.8559-20.223-20.223-20.223-20.223l-11.844 11.83zm-69.667-5.6871s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7166 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.096 4.0204-0.9132 5.5177-2.3373 6.8561-6.8048 20.223-20.223 20.223-20.223zm-31.753-31.753s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7167 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.0962 4.0204-0.91322 5.5177-2.3372 6.8561-6.8047 20.223-20.223 20.223-20.223zm87.237-90.554c1.6794-1.7064 3.9669-2.6599 6.2971-2.626 1.6628 0.0237 3.253 0.55012 4.5625 1.5097l16.499 12.1c3.2009 2.297 4.1445 6.6589 2.2308 10.312-1.9137 3.6528-6.1234 5.5243-9.9506 4.4227l6.124 23.948c1.1128 4.3517-1.564 8.9677-5.9833 10.317l-44.642 13.631 8.2456 31.883c1.1728 4.3696-1.5017 9.0445-5.9546 10.407-4.4529 1.3625-8.9756-1.1106-10.068-5.5047l-10.282-39.769c-1.1265-4.3556 1.5493-8.9847 5.9759-10.338l44.661-13.637-4.1219-16.118c-2.7634 3.0643-7.2335 3.8084-10.586 1.7615-3.3531-2.0469-4.6144-6.2896-2.9861-10.047l8.1169-19.454c0.43322-1.0376 1.0673-1.9888 1.8624-2.7973z" color="#000000" color-rendering="auto" fill-rule="evenodd" image-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -1,33 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="320" width="1543.4" version="1.1" viewBox="0 0 1543.4268 320.00026">
|
||||
<g transform="translate(0 -732.36)">
|
||||
<g stroke-width="28">
|
||||
<g transform="matrix(-.66287 .69913 -.66371 -.70001 863.46 1410.6)">
|
||||
<g transform="translate(2.5254 3.0305)"></g>
|
||||
<g transform="translate(41.25 -30.543)" stroke-width="28"></g>
|
||||
</g>
|
||||
<g transform="matrix(.57657 .52719 -.57729 .52786 584.63 346.6)"></g>
|
||||
<g transform="matrix(.66287 -.69913 .66371 .70001 -543.46 380.47)">
|
||||
<g transform="translate(2.5254 3.0305)"></g>
|
||||
<g transform="translate(41.25 -30.543)" stroke-width="28"></g>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0 732.36)" fill="#305680">
|
||||
<g transform="translate(-.000061238 -732.36)" fill="#305680">
|
||||
<path style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m151.13 732.36c-28.363 0-54.915 7.9153-77.613 21.537-6.4723-5.2623-14.605-8.1915-23.067-8.1937a8.7661 8.7661 0 0 0 -0.0035 0c-20.154 0.00073-36.679 16.528-36.678 36.682a8.7661 8.7661 0 0 0 0 0.0106c0.0099 8.4147 2.9267 16.483 8.1033 22.927-13.83 22.83-21.87 49.58-21.87 78.17a8.7661 8.7661 0 1 0 17.531 0c0-24.702 6.7193-47.748 18.378-67.574 4.5663 1.9846 9.4727 3.1496 14.519 3.1573a8.7661 8.7661 0 0 0 0.01241 0c20.155 0.00062 36.683-16.527 36.682-36.682a8.7661 8.7661 0 0 0 0 -0.004c-0.0016-4.9994-1.1387-9.8628-3.0828-14.397 19.717-11.484 42.585-18.095 67.085-18.095a8.7661 8.7661 0 1 0 0 -17.531zm-100.69 30.88c5.9129 0.002 11.191 2.5121 14.836 7.0769a8.7661 8.7661 0 0 0 0.1826 0.21451c2.6715 3.3798 4.1326 7.5531 4.1341 11.863-0.0015 10.677-8.468 19.144-19.144 19.148-4.37-0.008-8.6011-1.5088-12-4.2546a8.7661 8.7661 0 0 0 -0.01241 -0.009c-4.514-3.6331-7.1358-9.0979-7.1442-14.893 0.0025-10.677 8.4701-19.144 19.148-19.146z"/>
|
||||
<path style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m66.992 835.19c-1.492 1.5583-2.3663 3.7103-2.2576 6.0713 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223l11.844-11.83s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-2.3583-0.0112-4.4673 0.95389-5.9592 2.5122zm32.147 19.983-36.639 36.639c-3.9751 3.976-3.9751 10.421 0 14.397l18.156 18.156 31.753 31.753 30.478 30.478c3.9758 3.9759 10.422 3.9763 14.398 0l24.791-24.791 37.914-37.914 36.639-36.639c3.9754-3.9764 3.975-10.422-0.00067-14.398l-18.63-18.63-31.75-31.76-30.01-30c-3.976-3.9751-10.421-3.9751-14.397 0l-24.79 24.79-37.91 37.91zm37.911-37.91s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-4.7166-0.0226-8.4341 3.8616-8.2169 8.5834 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223l11.844-11.83zm69.193 5.2132s12.961-12.973 20.171-20.176c1.6325-1.604 2.3141-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91322-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223l11.83 11.844zm31.753 31.753s12.961-12.973 20.171-20.176c1.6325-1.604 2.314-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91323-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223l11.83 11.844zm-18.009 69.667s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3243 4.7166 0.0225 8.4342-3.8615 8.2169-8.5834l-0.002 0.002c-0.0962-2.0447-0.91415-4.0214-2.3382-5.5186-6.8051-6.8559-20.222-20.222-20.222-20.222l-11.844 11.83zm-37.914 37.914s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3242 4.7166 0.023 8.4342-3.8615 8.2169-8.5834h-0.002c-0.0962-2.0447-0.91323-4.0205-2.3372-5.5177-6.8051-6.8559-20.223-20.223-20.223-20.223l-11.844 11.83zm-69.667-5.6871s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7166 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.096 4.0204-0.9132 5.5177-2.3373 6.8561-6.8048 20.223-20.223 20.223-20.223l-11.82-11.84zm-31.743-31.74s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7167 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.0962 4.0204-0.91322 5.5177-2.3372 6.8561-6.8047 20.223-20.223 20.223-20.223l-11.829-11.85zm87.237-90.554c1.6794-1.7064 3.9669-2.6599 6.2971-2.626 1.6628 0.0237 3.253 0.55012 4.5625 1.5097l16.499 12.1c3.2009 2.297 4.1445 6.6589 2.2308 10.312-1.9137 3.6528-6.1234 5.5243-9.9506 4.4227l6.124 23.948c1.1128 4.3517-1.564 8.9677-5.9833 10.317l-44.642 13.631 8.2456 31.883c1.1728 4.3696-1.5017 9.0445-5.9546 10.407s-8.9756-1.1106-10.068-5.5047l-10.282-39.769c-1.1265-4.3556 1.5493-8.9847 5.9759-10.338l44.661-13.637-4.1219-16.118c-2.7634 3.0643-7.2335 3.8084-10.586 1.7615-3.3531-2.0469-4.6144-6.2896-2.9861-10.047l8.1169-19.454c0.43322-1.0376 1.0673-1.9888 1.8624-2.7973z" fill-rule="evenodd"/>
|
||||
<path style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m168.87 1052.4c28.363 0 54.915-7.9155 77.614-21.538 6.4724 5.2624 14.605 8.1917 23.067 8.1939a8.7662 8.7662 0 0 0 0.004 0c20.155-0.0007 36.68-16.528 36.679-36.682a8.7662 8.7662 0 0 0 0 -0.011c-0.01-8.4149-2.9267-16.484-8.1034-22.927 13.825-22.82 21.866-49.572 21.866-78.162a8.7662 8.7662 0 1 0 -17.531 0c0 24.703-6.7194 47.749-18.378 67.575-4.5664-1.9846-9.4728-3.1496-14.519-3.1573a8.7662 8.7662 0 0 0 -0.0124 0c-20.155-0.00062-36.683 16.527-36.682 36.682 0.002 4.9994 1.1387 9.8628 3.0829 14.397-19.717 11.484-42.586 18.095-67.086 18.095a8.7662 8.7662 0 1 0 0 17.531zm100.69-30.875c-5.913 0-11.191-2.5122-14.836-7.0769a8.7662 8.7662 0 0 0 -0.18261 -0.2146c-2.6715-3.3799-4.1327-7.5531-4.1341-11.863 0.002-10.677 8.4681-19.144 19.144-19.148 4.37 0.008 8.6012 1.5088 12 4.2547a8.7662 8.7662 0 0 0 0.0124 0.009c4.5141 3.6332 7.1359 9.098 7.1443 14.893-0.003 10.677-8.4702 19.145-19.148 19.146z"/>
|
||||
</g>
|
||||
<path d="m477.92 93.229h-48.56v134.68h-19.818v-134.68h-48.456v-16.394h116.83v16.394z"/>
|
||||
<path d="m516.72 129.23q12.762-15.668 33.203-15.668 35.59 0 35.901 40.155v74.188h-19.196v-74.292q-0.10376-12.14-5.603-17.95-5.3955-5.8105-16.913-5.8105-9.3384 0-16.394 4.9805-7.0557 4.9805-10.999 13.074v79.999h-19.196v-159.38h19.196v60.699z"/>
|
||||
<path d="m635.43 227.91h-19.196v-112.27h19.196v112.27zm-20.76-142.05q0-4.6692 2.8015-7.8857 2.9053-3.2166 8.5083-3.2166t8.5083 3.2166 2.9053 7.8857q0 4.6692-2.9053 7.782t-8.5083 3.1128-8.5083-3.1128q-2.8015-3.1128-2.8015-7.782z"/>
|
||||
<path d="m684.19 115.64 0.62256 14.111q12.866-16.187 33.618-16.187 35.59 0 35.901 40.155v74.188h-19.196v-74.292q-0.10376-12.14-5.603-17.95-5.3955-5.8105-16.913-5.8105-9.3384 0-16.394 4.9805-7.0557 4.9805-10.999 13.074v79.999h-19.196v-112.27h18.158z"/>
|
||||
<path d="m778.92 170.84q0-26.251 12.14-41.711 12.14-15.564 32.166-15.564 20.544 0 32.062 14.526l0.93384-12.451h17.535v109.57q0 21.79-12.97 34.344-12.866 12.555-34.656 12.555-12.14 0-23.761-5.188t-17.743-14.215l9.9609-11.517q12.347 15.253 30.194 15.253 14.008 0 21.79-7.8857 7.8857-7.8858 7.8857-22.205v-9.6497q-11.517 13.281-31.439 13.281-19.714 0-31.958-15.875-12.14-15.875-12.14-43.268zm19.299 2.179q0 18.988 7.782 29.883 7.782 10.791 21.79 10.791 18.158 0 26.666-16.498v-51.257q-8.8196-16.083-26.459-16.083-14.008 0-21.893 10.895-7.8858 10.895-7.8858 32.269z"/>
|
||||
<path d="m967.98 198.13q0-7.782-5.9143-12.036-5.8106-4.3579-20.441-7.4707-14.526-3.1128-23.138-7.4707-8.5083-4.3579-12.659-10.376-4.0466-6.0181-4.0466-14.319 0-13.8 11.621-23.346 11.725-9.5459 29.883-9.5459 19.092 0 30.92 9.8572 11.932 9.8572 11.932 25.214h-19.299q0-7.8858-6.7444-13.593-6.6406-5.7068-16.809-5.7068-10.48 0-16.394 4.5654-5.9143 4.5654-5.9143 11.932 0 6.9519 5.4993 10.48 5.4993 3.5278 19.818 6.7444 14.423 3.2166 23.346 7.6782 8.9233 4.4617 13.177 10.791 4.3579 6.2256 4.3579 15.253 0 15.045-12.036 24.176-12.036 9.0271-31.232 9.0271-13.489 0-23.865-4.773t-16.29-13.281q-5.8105-8.6121-5.8105-18.573h19.196q0.5188 9.6497 7.6782 15.356 7.2632 5.603 19.092 5.603 10.895 0 17.432-4.3579 6.6406-4.4617 6.6406-11.829z"/>
|
||||
<path d="m1015.2 227.91v-151.07h49.39q24.591 0 36.938 10.168 12.451 10.168 12.451 30.09 0 10.583-6.0181 18.781-6.018 8.0933-16.394 12.555 12.244 3.4241 19.299 13.074 7.1594 9.5459 7.1594 22.827 0 20.337-13.178 31.958t-37.25 11.621h-52.399zm19.922-70.66v54.37h32.892q13.904 0 21.893-7.1594 8.0933-7.2632 8.0933-19.922 0-27.289-29.675-27.289h-33.203zm0-15.979h30.09q13.074 0 20.856-6.5369 7.8858-6.5369 7.8858-17.743 0-12.451-7.2632-18.054-7.2632-5.7068-22.101-5.7068h-29.468v48.041z"/>
|
||||
<path d="m1139.5 170.74q0-16.498 6.4331-29.675 6.5369-13.177 18.054-20.337 11.621-7.1594 26.459-7.1594 22.931 0 37.042 15.875 14.215 15.875 14.215 42.23v1.3489q0 16.394-6.3294 29.468-6.2256 12.97-17.95 20.233-11.621 7.2632-26.77 7.2632-22.827 0-37.042-15.875-14.1-15.88-14.1-42.02v-1.3489zm19.299 2.2827q0 18.677 8.6121 29.987 8.7158 11.31 23.242 11.31 14.63 0 23.242-11.414 8.6121-11.517 8.6121-32.166 0-18.469-8.8196-29.883-8.7158-11.517-23.242-11.517-14.215 0-22.931 11.31-8.7158 11.31-8.7158 32.373z"/>
|
||||
<path d="m1335.1 227.91q-1.6602-3.3203-2.6978-11.829-13.385 13.904-31.958 13.904-16.602 0-27.289-9.3384-10.584-9.4421-10.584-23.865 0-17.535 13.281-27.185 13.385-9.7534 37.561-9.7534h18.677v-8.8196q0-10.065-6.018-15.979-6.0181-6.0181-17.743-6.0181-10.272 0-17.224 5.188-6.9519 5.188-6.9519 12.555h-19.299q0-8.4045 5.9143-16.187 6.0181-7.8858 16.186-12.451 10.272-4.5654 22.516-4.5654 19.403 0 30.402 9.7534 10.998 9.6497 11.414 26.666v51.672q0 15.46 3.9429 24.591v1.6602h-20.129zm-31.854-14.63q9.0271 0 17.12-4.6692 8.0932-4.6692 11.725-12.14v-23.035h-15.045q-35.278 0-35.278 20.648 0 9.0271 6.0181 14.111 6.0181 5.0842 15.46 5.0842z"/>
|
||||
<path d="m1435.8 132.87q-4.3579-0.72632-9.4422-0.72632-18.884 0-25.629 16.083v79.688h-19.196v-112.27h18.677l0.3113 12.97q9.4421-15.045 26.77-15.045 5.603 0 8.5083 1.4526v17.847z"/>
|
||||
<path d="m1448.7 170.84q0-25.836 12.244-41.504 12.244-15.771 32.062-15.771 19.714 0 31.232 13.489v-58.521h19.196v159.38h-17.639l-0.9339-12.036q-11.517 14.111-32.062 14.111-19.507 0-31.854-15.979-12.244-15.979-12.244-41.711v-1.4526zm19.196 2.179q0 19.092 7.8858 29.883 7.8857 10.791 21.79 10.791 18.262 0 26.666-16.394v-51.569q-8.6121-15.875-26.459-15.875-14.111 0-21.997 10.895-7.8858 10.895-7.8858 32.269z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 10 KiB |
8
assets/images/thingsboard_outer.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="320" height="320" version="1.1" viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke-width="28"></g>
|
||||
<g shape-rendering="auto">
|
||||
<path d="m151.13 0.00104c-28.363 0-54.915 7.9153-77.613 21.537-6.4723-5.2623-14.605-8.1915-23.067-8.1937a8.7661 8.7661 0 0 0-0.0035 0c-20.154 7.3e-4 -36.679 16.528-36.678 36.682a8.7661 8.7661 0 0 0 0 0.0106c0.0099 8.4147 2.9267 16.483 8.1033 22.927-13.825 22.819-21.865 49.572-21.865 78.161a8.7661 8.7661 0 1 0 17.531 0c0-24.702 6.7193-47.748 18.378-67.574 4.5663 1.9846 9.4727 3.1496 14.519 3.1573a8.7661 8.7661 0 0 0 0.01241 0c20.155 6.2e-4 36.683-16.527 36.682-36.682a8.7661 8.7661 0 0 0 0-4e-3c-0.0016-4.9994-1.1387-9.8628-3.0828-14.397 19.717-11.484 42.585-18.095 67.085-18.095a8.7661 8.7661 0 1 0 0-17.531zm-100.69 30.874c5.9129 2e-3 11.191 2.5121 14.836 7.0769a8.7661 8.7661 0 0 0 0.1826 0.21451c2.6715 3.3798 4.1326 7.5531 4.1341 11.863-0.0015 10.677-8.468 19.144-19.144 19.148-4.37-8e-3 -8.6011-1.5088-12-4.2546a8.7661 8.7661 0 0 0-0.01241-9e-3c-4.514-3.6331-7.1358-9.0979-7.1442-14.893 0.0025-10.677 8.4701-19.144 19.148-19.146z" color="#000000" color-rendering="auto" image-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
<path d="m168.87 320c28.363 0 54.915-7.9155 77.614-21.538 6.4724 5.2624 14.605 8.1917 23.067 8.1939a8.7662 8.7662 0 0 0 4e-3 0c20.155-7e-4 36.68-16.528 36.679-36.682a8.7662 8.7662 0 0 0 0-0.011c-0.01-8.4149-2.9267-16.484-8.1034-22.927 13.825-22.82 21.866-49.572 21.866-78.162a8.7662 8.7662 0 1 0-17.531 0c0 24.703-6.7194 47.749-18.378 67.575-4.5664-1.9846-9.4728-3.1496-14.519-3.1573a8.7662 8.7662 0 0 0-0.0124 0c-20.155-6.2e-4 -36.683 16.527-36.682 36.682 2e-3 4.9994 1.1387 9.8628 3.0829 14.397-19.717 11.484-42.586 18.095-67.086 18.095a8.7662 8.7662 0 1 0 0 17.531zm100.69-30.875c-5.913 0-11.191-2.5122-14.836-7.0769a8.7662 8.7662 0 0 0-0.18261-0.2146c-2.6715-3.3799-4.1327-7.5531-4.1341-11.863 2e-3 -10.677 8.4681-19.144 19.144-19.148 4.37 8e-3 8.6012 1.5088 12 4.2547a8.7662 8.7662 0 0 0 0.0124 9e-3c4.5141 3.6332 7.1359 9.098 7.1443 14.893-3e-3 10.677-8.4702 19.145-19.148 19.146z" color="#000000" color-rendering="auto" image-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
36
assets/images/thingsboard_with_title.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1543.4" height="320" version="1.1" viewBox="0 0 1543.4 320" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(0 -732.36)">
|
||||
<g stroke-width="28">
|
||||
<g transform="matrix(-.66287 .69913 -.66371 -.70001 863.46 1410.6)">
|
||||
<g transform="translate(2.5254 3.0305)"></g>
|
||||
<g transform="translate(41.25 -30.543)" stroke-width="28"></g>
|
||||
</g>
|
||||
<g transform="matrix(.57657 .52719 -.57729 .52786 584.63 346.6)"></g>
|
||||
<g transform="matrix(.66287 -.69913 .66371 .70001 -543.46 380.47)">
|
||||
<g transform="translate(2.5254 3.0305)"></g>
|
||||
<g transform="translate(41.25 -30.543)" stroke-width="28"></g>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0 732.36)" fill="#305680" stroke-width="1px">
|
||||
<g fill="#000">
|
||||
<g transform="translate(-6.1238e-5 -732.36)" shape-rendering="auto">
|
||||
<path d="m151.13 732.36c-28.363 0-54.915 7.9153-77.613 21.537-6.4723-5.2623-14.605-8.1915-23.067-8.1937a8.7661 8.7661 0 0 0-0.0035 0c-20.154 7.3e-4 -36.679 16.528-36.678 36.682a8.7661 8.7661 0 0 0 0 0.0106c0.0099 8.4147 2.9267 16.483 8.1033 22.927-13.825 22.819-21.865 49.572-21.865 78.161a8.7661 8.7661 0 1 0 17.531 0c0-24.702 6.7193-47.748 18.378-67.574 4.5663 1.9846 9.4727 3.1496 14.519 3.1573a8.7661 8.7661 0 0 0 0.01241 0c20.155 6.2e-4 36.683-16.527 36.682-36.682a8.7661 8.7661 0 0 0 0-4e-3c-0.0016-4.9994-1.1387-9.8628-3.0828-14.397 19.717-11.484 42.585-18.095 67.085-18.095a8.7661 8.7661 0 1 0 0-17.531zm-100.69 30.874c5.9129 2e-3 11.191 2.5121 14.836 7.0769a8.7661 8.7661 0 0 0 0.1826 0.21451c2.6715 3.3798 4.1326 7.5531 4.1341 11.863-0.0015 10.677-8.468 19.144-19.144 19.148-4.37-8e-3 -8.6011-1.5088-12-4.2546a8.7661 8.7661 0 0 0-0.01241-9e-3c-4.514-3.6331-7.1358-9.0979-7.1442-14.893 0.0025-10.677 8.4701-19.144 19.148-19.146z" color="#000000" color-rendering="auto" image-rendering="auto" solid-color="#000000" style="block-progression:tb;isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
<path d="m66.992 835.19c-1.492 1.5583-2.3663 3.7103-2.2576 6.0713 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223l11.844-11.83s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-2.3583-0.0112-4.4673 0.95389-5.9592 2.5122zm32.147 19.983-36.639 36.639c-3.9751 3.976-3.9751 10.421 0 14.397l80.387 80.387c3.9758 3.9759 10.422 3.9763 14.398 0l99.345-99.345c3.9754-3.9764 3.975-10.422-6.7e-4 -14.398l-18.63-18.628-61.758-61.758c-3.976-3.9751-10.421-3.9751-14.397 0l-62.705 62.705zm37.914-37.914s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-4.7166-0.0226-8.4341 3.8616-8.2169 8.5834 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223l11.844-11.83zm69.193 5.2132s12.961-12.973 20.171-20.176c1.6325-1.604 2.3141-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91322-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223l11.83 11.844zm31.753 31.753s12.961-12.973 20.171-20.176c1.6325-1.604 2.314-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91323-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223l11.83 11.844zm-18.009 69.667s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3243 4.7166 0.0225 8.4342-3.8615 8.2169-8.5834l-2e-3 2e-3c-0.0962-2.0447-0.91415-4.0214-2.3382-5.5186-6.8051-6.8559-20.222-20.222-20.222-20.222l-11.844 11.83zm-37.914 37.914s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3242 4.7166 0.023 8.4342-3.8615 8.2169-8.5834h-2e-3c-0.0962-2.0447-0.91323-4.0205-2.3372-5.5177-6.8051-6.8559-20.223-20.223-20.223-20.223l-11.844 11.83zm-69.667-5.6871s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7166 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.096 4.0204-0.9132 5.5177-2.3373 6.8561-6.8048 20.223-20.223 20.223-20.223l-11.83-11.846zm-31.753-31.753s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7167 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.0962 4.0204-0.91322 5.5177-2.3372 6.8561-6.8047 20.223-20.223 20.223-20.223l-11.83-11.846zm87.237-90.554c1.6794-1.7064 3.9669-2.6599 6.2971-2.626 1.6628 0.0237 3.253 0.55012 4.5625 1.5097l16.499 12.1c3.2009 2.297 4.1445 6.6589 2.2308 10.312-1.9137 3.6528-6.1234 5.5243-9.9506 4.4227l6.124 23.948c1.1128 4.3517-1.564 8.9677-5.9833 10.317l-44.642 13.631 8.2456 31.883c1.1728 4.3696-1.5017 9.0445-5.9546 10.407-4.4529 1.3625-8.9756-1.1106-10.068-5.5047l-10.282-39.769c-1.1265-4.3556 1.5493-8.9847 5.9759-10.338l44.661-13.637-4.1219-16.118c-2.7634 3.0643-7.2335 3.8084-10.586 1.7615-3.3531-2.0469-4.6144-6.2896-2.9861-10.047l8.1169-19.454c0.43322-1.0376 1.0673-1.9888 1.8624-2.7973z" color="#000000" color-rendering="auto" fill-rule="evenodd" image-rendering="auto" solid-color="#000000" style="block-progression:tb;isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
<path d="m168.87 1052.4c28.363 0 54.915-7.9155 77.614-21.538 6.4724 5.2624 14.605 8.1917 23.067 8.1939a8.7662 8.7662 0 0 0 4e-3 0c20.155-7e-4 36.68-16.528 36.679-36.682a8.7662 8.7662 0 0 0 0-0.011c-0.01-8.4149-2.9267-16.484-8.1034-22.927 13.825-22.82 21.866-49.572 21.866-78.162a8.7662 8.7662 0 1 0-17.531 0c0 24.703-6.7194 47.749-18.378 67.575-4.5664-1.9846-9.4728-3.1496-14.519-3.1573a8.7662 8.7662 0 0 0-0.0124 0c-20.155-6.2e-4 -36.683 16.527-36.682 36.682 2e-3 4.9994 1.1387 9.8628 3.0829 14.397-19.717 11.484-42.586 18.095-67.086 18.095a8.7662 8.7662 0 1 0 0 17.531zm100.69-30.875c-5.913 0-11.191-2.5122-14.836-7.0769a8.7662 8.7662 0 0 0-0.18261-0.2146c-2.6715-3.3799-4.1327-7.5531-4.1341-11.863 2e-3 -10.677 8.4681-19.144 19.144-19.148 4.37 8e-3 8.6012 1.5088 12 4.2547a8.7662 8.7662 0 0 0 0.0124 9e-3c4.5141 3.6332 7.1359 9.098 7.1443 14.893-3e-3 10.677-8.4702 19.145-19.148 19.146z" color="#000000" color-rendering="auto" image-rendering="auto" solid-color="#000000" style="block-progression:tb;isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
</g>
|
||||
<path d="m477.92 93.229h-48.56v134.68h-19.818v-134.68h-48.456v-16.394h116.83v16.394z"/>
|
||||
<path d="m516.72 129.23q12.762-15.668 33.203-15.668 35.59 0 35.901 40.155v74.188h-19.196v-74.292q-0.10376-12.14-5.603-17.95-5.3955-5.8105-16.913-5.8105-9.3384 0-16.394 4.9805-7.0557 4.9805-10.999 13.074v79.999h-19.196v-159.38h19.196v60.699z"/>
|
||||
<path d="m635.43 227.91h-19.196v-112.27h19.196v112.27zm-20.752-142.05q0-4.6692 2.8015-7.8857 2.9053-3.2166 8.5083-3.2166 5.603 0 8.5083 3.2166 2.9053 3.2166 2.9053 7.8857 0 4.6692-2.9053 7.782-2.9053 3.1128-8.5083 3.1128-5.603 0-8.5083-3.1128-2.8015-3.1128-2.8015-7.782z"/>
|
||||
<path d="m684.19 115.64 0.62256 14.111q12.866-16.187 33.618-16.187 35.59 0 35.901 40.155v74.188h-19.196v-74.292q-0.10376-12.14-5.603-17.95-5.3955-5.8105-16.913-5.8105-9.3384 0-16.394 4.9805-7.0557 4.9805-10.999 13.074v79.999h-19.196v-112.27h18.158z"/>
|
||||
<path d="m778.92 170.84q0-26.251 12.14-41.711 12.14-15.564 32.166-15.564 20.544 0 32.062 14.526l0.93384-12.451h17.535v109.57q0 21.79-12.97 34.344-12.866 12.555-34.656 12.555-12.14 0-23.761-5.188-11.621-5.188-17.743-14.215l9.9609-11.517q12.347 15.253 30.194 15.253 14.008 0 21.79-7.8857 7.8857-7.8858 7.8857-22.205v-9.6497q-11.517 13.281-31.439 13.281-19.714 0-31.958-15.875-12.14-15.875-12.14-43.268zm19.299 2.179q0 18.988 7.782 29.883 7.782 10.791 21.79 10.791 18.158 0 26.666-16.498v-51.257q-8.8196-16.083-26.459-16.083-14.008 0-21.893 10.895-7.8858 10.895-7.8858 32.269z"/>
|
||||
<path d="m967.98 198.13q0-7.782-5.9143-12.036-5.8106-4.3579-20.441-7.4707-14.526-3.1128-23.138-7.4707-8.5083-4.3579-12.659-10.376-4.0466-6.0181-4.0466-14.319 0-13.8 11.621-23.346 11.725-9.5459 29.883-9.5459 19.092 0 30.92 9.8572 11.932 9.8572 11.932 25.214h-19.299q0-7.8858-6.7444-13.593-6.6406-5.7068-16.809-5.7068-10.48 0-16.394 4.5654-5.9143 4.5654-5.9143 11.932 0 6.9519 5.4993 10.48 5.4993 3.5278 19.818 6.7444 14.423 3.2166 23.346 7.6782t13.177 10.791q4.3579 6.2256 4.3579 15.253 0 15.045-12.036 24.176-12.036 9.0271-31.232 9.0271-13.489 0-23.865-4.773t-16.29-13.281q-5.8105-8.6121-5.8105-18.573h19.196q0.5188 9.6497 7.6782 15.356 7.2632 5.603 19.092 5.603 10.895 0 17.432-4.3579 6.6406-4.4617 6.6406-11.829z"/>
|
||||
<path d="m1015.2 227.91v-151.07h49.39q24.591 0 36.938 10.168 12.451 10.168 12.451 30.09 0 10.583-6.0181 18.781-6.018 8.0933-16.394 12.555 12.244 3.4241 19.299 13.074 7.1594 9.5459 7.1594 22.827 0 20.337-13.178 31.958-13.178 11.621-37.25 11.621h-52.399zm19.922-70.66v54.37h32.892q13.904 0 21.893-7.1594 8.0933-7.2632 8.0933-19.922 0-27.289-29.675-27.289h-33.203zm0-15.979h30.09q13.074 0 20.856-6.5369 7.8858-6.5369 7.8858-17.743 0-12.451-7.2632-18.054-7.2632-5.7068-22.101-5.7068h-29.468v48.041z"/>
|
||||
<path d="m1139.5 170.74q0-16.498 6.4331-29.675 6.5369-13.177 18.054-20.337 11.621-7.1594 26.459-7.1594 22.931 0 37.042 15.875 14.215 15.875 14.215 42.23v1.3489q0 16.394-6.3294 29.468-6.2256 12.97-17.95 20.233-11.621 7.2632-26.77 7.2632-22.827 0-37.042-15.875-14.111-15.875-14.111-42.023v-1.3489zm19.299 2.2827q0 18.677 8.6121 29.987 8.7158 11.31 23.242 11.31 14.63 0 23.242-11.414 8.6121-11.517 8.6121-32.166 0-18.469-8.8196-29.883-8.7158-11.517-23.242-11.517-14.215 0-22.931 11.31-8.7158 11.31-8.7158 32.373z"/>
|
||||
<path d="m1335.1 227.91q-1.6602-3.3203-2.6978-11.829-13.385 13.904-31.958 13.904-16.602 0-27.289-9.3384-10.584-9.4421-10.584-23.865 0-17.535 13.281-27.185 13.385-9.7534 37.561-9.7534h18.677v-8.8196q0-10.065-6.018-15.979-6.0181-6.0181-17.743-6.0181-10.272 0-17.224 5.188t-6.9519 12.555h-19.299q0-8.4045 5.9143-16.187 6.0181-7.8858 16.186-12.451 10.272-4.5654 22.516-4.5654 19.403 0 30.402 9.7534 10.998 9.6497 11.414 26.666v51.672q0 15.46 3.9429 24.591v1.6602h-20.129zm-31.854-14.63q9.0271 0 17.12-4.6692 8.0932-4.6692 11.725-12.14v-23.035h-15.045q-35.278 0-35.278 20.648 0 9.0271 6.0181 14.111 6.0181 5.0842 15.46 5.0842z"/>
|
||||
<path d="m1435.8 132.87q-4.3579-0.72632-9.4422-0.72632-18.884 0-25.629 16.083v79.688h-19.196v-112.27h18.677l0.3113 12.97q9.4421-15.045 26.77-15.045 5.603 0 8.5083 1.4526v17.847z"/>
|
||||
<path d="m1448.7 170.84q0-25.836 12.244-41.504 12.244-15.771 32.062-15.771 19.714 0 31.232 13.489v-58.521h19.196v159.38h-17.639l-0.9339-12.036q-11.517 14.111-32.062 14.111-19.507 0-31.854-15.979-12.244-15.979-12.244-41.711v-1.4526zm19.196 2.179q0 19.092 7.8858 29.883 7.8857 10.791 21.79 10.791 18.262 0 26.666-16.394v-51.569q-8.6121-15.875-26.459-15.875-14.111 0-21.997 10.895-7.8858 10.895-7.8858 32.269z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -1,43 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const int _tbPrimaryColor = 0xFF305680;
|
||||
const int _tbSecondaryColor = 0xFF527dad;
|
||||
const int _tbDarkPrimaryColor = 0xFF9fa8da;
|
||||
const int _tbPrimaryColorValue = 0xFF305680;
|
||||
const Color _tbPrimaryColor = Color(_tbPrimaryColorValue);
|
||||
const Color _tbSecondaryColor = Color(0xFF527dad);
|
||||
const Color _tbDarkPrimaryColor = Color(0xFF9fa8da);
|
||||
|
||||
const int _tbTextColorValue = 0xFF282828;
|
||||
const Color _tbTextColor = Color(_tbTextColorValue);
|
||||
|
||||
var tbTypography = Typography.material2018();
|
||||
|
||||
const tbMatIndigo = MaterialColor(
|
||||
_tbPrimaryColor,
|
||||
_tbPrimaryColorValue,
|
||||
<int, Color>{
|
||||
50: Color(0xFFE8EAF6),
|
||||
100: Color(0xFFC5CAE9),
|
||||
200: Color(0xFF9FA8DA),
|
||||
300: Color(0xFF7986CB),
|
||||
400: Color(0xFF5C6BC0),
|
||||
500: Color(_tbPrimaryColor),
|
||||
600: Color(_tbSecondaryColor),
|
||||
500: _tbPrimaryColor,
|
||||
600: _tbSecondaryColor,
|
||||
700: Color(0xFF303F9F),
|
||||
800: Color(0xFF283593),
|
||||
900: Color(0xFF1A237E),
|
||||
},);
|
||||
|
||||
const tbDarkMatIndigo = MaterialColor(
|
||||
_tbPrimaryColor,
|
||||
_tbPrimaryColorValue,
|
||||
<int, Color>{
|
||||
50: Color(0xFFE8EAF6),
|
||||
100: Color(0xFFC5CAE9),
|
||||
200: Color(0xFF9FA8DA),
|
||||
300: Color(0xFF7986CB),
|
||||
400: Color(0xFF5C6BC0),
|
||||
500: Color(_tbDarkPrimaryColor),
|
||||
600: Color(_tbSecondaryColor),
|
||||
500: _tbDarkPrimaryColor,
|
||||
600: _tbSecondaryColor,
|
||||
700: Color(0xFF303F9F),
|
||||
800: Color(_tbPrimaryColor),
|
||||
800: _tbPrimaryColor,
|
||||
900: Color(0xFF1A237E),
|
||||
},);
|
||||
|
||||
ThemeData tbTheme = ThemeData(
|
||||
primarySwatch: tbMatIndigo,
|
||||
accentColor: Colors.deepOrange,
|
||||
scaffoldBackgroundColor: Color(0xFFF0F4F9)
|
||||
scaffoldBackgroundColor: Color(0xFFFAFAFA),
|
||||
textTheme: tbTypography.black,
|
||||
primaryTextTheme: tbTypography.black,
|
||||
typography: tbTypography,
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: _tbTextColor,
|
||||
/* titleTextStyle: TextStyle(
|
||||
color: _tbTextColor
|
||||
),
|
||||
toolbarTextStyle: TextStyle(
|
||||
color: _tbTextColor
|
||||
), */
|
||||
iconTheme: IconThemeData(
|
||||
color: _tbTextColor
|
||||
)
|
||||
|
||||
),
|
||||
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||
backgroundColor: Colors.white,
|
||||
selectedItemColor: _tbPrimaryColor,
|
||||
unselectedItemColor: _tbPrimaryColor.withAlpha((255 * 0.38).ceil()),
|
||||
showSelectedLabels: true,
|
||||
showUnselectedLabels: true
|
||||
)
|
||||
);
|
||||
|
||||
ThemeData tbDarkTheme = ThemeData(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
abstract class ThingsboardImage {
|
||||
static final thingsBoardLogoBlue = 'assets/images/thingsboard_logo_blue.svg';
|
||||
static final thingsboard = 'assets/images/thingsboard.png';
|
||||
static final thingsBoardWithTitle = 'assets/images/thingsboard_with_title.svg';
|
||||
static final thingsboard = 'assets/images/thingsboard.svg';
|
||||
static final thingsboardOuter = 'assets/images/thingsboard_outer.svg';
|
||||
static final thingsboardCenter = 'assets/images/thingsboard_center.svg';
|
||||
static final dashboardPlaceholder = 'assets/images/dashboard-placeholder.png';
|
||||
static final deviceProfilePlaceholder = 'assets/images/device-profile-placeholder.png';
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
@@ -12,12 +12,7 @@ import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
|
||||
class LoginPage extends TbPageWidget<LoginPage, _LoginPageState> {
|
||||
|
||||
LoginPage(TbContext tbContext) : super(tbContext) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Colors.white,
|
||||
systemNavigationBarIconBrightness: Brightness.light
|
||||
));
|
||||
}
|
||||
LoginPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_LoginPageState createState() => _LoginPageState();
|
||||
@@ -26,6 +21,8 @@ class LoginPage extends TbPageWidget<LoginPage, _LoginPageState> {
|
||||
|
||||
class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
|
||||
|
||||
final _isLoginNotifier = ValueNotifier<bool>(false);
|
||||
|
||||
final usernameController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
|
||||
@@ -49,7 +46,7 @@ class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
|
||||
title: const Text('Login to ThingsBoard'),
|
||||
),
|
||||
body: ValueListenableBuilder(
|
||||
valueListenable: loadingNotifier,
|
||||
valueListenable: _isLoginNotifier,
|
||||
builder: (BuildContext context, bool loading, child) {
|
||||
List<Widget> children = [
|
||||
SingleChildScrollView(
|
||||
@@ -61,7 +58,8 @@ class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
|
||||
child: Container(
|
||||
width: 300,
|
||||
height: 150,
|
||||
child: SvgPicture.asset(ThingsboardImage.thingsBoardLogoBlue,
|
||||
child: SvgPicture.asset(ThingsboardImage.thingsBoardWithTitle,
|
||||
color: Theme.of(context).primaryColor,
|
||||
semanticsLabel: 'ThingsBoard Logo')
|
||||
)
|
||||
)
|
||||
@@ -107,9 +105,15 @@ class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
|
||||
decoration: BoxDecoration(
|
||||
color: loading ? Colors.black12 : Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(4)),
|
||||
child: TextButton(
|
||||
onPressed: loading ? null : () {
|
||||
tbClient.login(
|
||||
LoginRequest(usernameController.text, passwordController.text));
|
||||
onPressed: loading ? null : () async {
|
||||
_isLoginNotifier.value = true;
|
||||
try {
|
||||
await tbClient.login(
|
||||
LoginRequest(usernameController.text,
|
||||
passwordController.text));
|
||||
} catch (e) {
|
||||
_isLoginNotifier.value = false;
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
'Login',
|
||||
@@ -126,6 +130,9 @@ class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
|
||||
)
|
||||
];
|
||||
if (loading) {
|
||||
var data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
|
||||
var bottomPadding = data.padding.top;
|
||||
bottomPadding += kToolbarHeight;
|
||||
children.add(
|
||||
SizedBox.expand(
|
||||
child: ClipRect(
|
||||
@@ -135,15 +142,16 @@ class _LoginPageState extends TbPageState<LoginPage, _LoginPageState> {
|
||||
decoration: new BoxDecoration(
|
||||
color: Colors.grey.shade200.withOpacity(0.2)
|
||||
),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(bottom: bottomPadding),
|
||||
alignment: Alignment.center,
|
||||
child: TbProgressIndicator(size: 50.0),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
//children.add(Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
return Stack(
|
||||
children: children,
|
||||
|
||||
@@ -81,12 +81,31 @@ class TbLogger {
|
||||
}
|
||||
}
|
||||
|
||||
typedef OpenDashboardCallback = void Function(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar});
|
||||
|
||||
abstract class TbMainDashboardHolder {
|
||||
|
||||
Future<void> navigateToDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar, bool animate = true});
|
||||
|
||||
Future<bool> openMain({bool animate});
|
||||
|
||||
Future<bool> closeMain({bool animate});
|
||||
|
||||
Future<bool> openDashboard({bool animate});
|
||||
|
||||
Future<bool> closeDashboard({bool animate});
|
||||
|
||||
bool isDashboardOpen();
|
||||
|
||||
Future<bool> dashboardGoBack();
|
||||
|
||||
}
|
||||
|
||||
class TbContext {
|
||||
static final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||
bool _initialized = false;
|
||||
bool isUserLoaded = false;
|
||||
bool isAuthenticated = false;
|
||||
final ValueNotifier<bool> _isAuthenticated = ValueNotifier(false);
|
||||
User? userDetails;
|
||||
HomeDashboardInfo? homeDashboard;
|
||||
final _isLoadingNotifier = ValueNotifier<bool>(false);
|
||||
@@ -94,6 +113,7 @@ class TbContext {
|
||||
late final _widgetActionHandler;
|
||||
late final AndroidDeviceInfo? _androidInfo;
|
||||
late final IosDeviceInfo? _iosInfo;
|
||||
TbMainDashboardHolder? _mainDashboardHolder;
|
||||
|
||||
GlobalKey<ScaffoldMessengerState> messengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
late ThingsboardClient tbClient;
|
||||
@@ -137,6 +157,10 @@ class TbContext {
|
||||
}
|
||||
}
|
||||
|
||||
void setMainDashboardHolder(TbMainDashboardHolder holder) {
|
||||
_mainDashboardHolder = holder;
|
||||
}
|
||||
|
||||
void onError(ThingsboardError tbError) {
|
||||
log.error('onError', tbError, tbError.getStackTrace());
|
||||
showErrorNotification(tbError.message!);
|
||||
@@ -214,8 +238,8 @@ class TbContext {
|
||||
try {
|
||||
log.debug('onUserLoaded: isAuthenticated=${tbClient.isAuthenticated()}');
|
||||
isUserLoaded = true;
|
||||
isAuthenticated = tbClient.isAuthenticated();
|
||||
if (tbClient.isAuthenticated()) {
|
||||
_isAuthenticated.value = tbClient.isAuthenticated();
|
||||
if (isAuthenticated) {
|
||||
log.debug('authUser: ${tbClient.getAuthUser()}');
|
||||
if (tbClient.getAuthUser()!.userId != null) {
|
||||
try {
|
||||
@@ -230,20 +254,33 @@ class TbContext {
|
||||
userDetails = null;
|
||||
homeDashboard = null;
|
||||
}
|
||||
updateRouteState();
|
||||
await updateRouteState();
|
||||
|
||||
} catch (e, s) {
|
||||
log.error('Error: $e', e, s);
|
||||
}
|
||||
}
|
||||
|
||||
void updateRouteState() {
|
||||
Listenable get isAuthenticatedListenable => _isAuthenticated;
|
||||
|
||||
bool get isAuthenticated => _isAuthenticated.value;
|
||||
|
||||
Future<void> updateRouteState() async {
|
||||
if (currentState != null) {
|
||||
if (tbClient.isAuthenticated()) {
|
||||
var defaultDashboardId = _defaultDashboardId();
|
||||
if (defaultDashboardId != null) {
|
||||
bool fullscreen = _userForceFullscreen();
|
||||
navigateTo('/dashboard/$defaultDashboardId?fullscreen=$fullscreen', replace: true, transition: TransitionType.fadeIn, transitionDuration: Duration(milliseconds: 750));
|
||||
if (!fullscreen) {
|
||||
await navigateToDashboard(defaultDashboardId, animate: false);
|
||||
navigateTo('/home',
|
||||
replace: true,
|
||||
transition: TransitionType.none);
|
||||
} else {
|
||||
navigateTo('/fullscreenDashboard/$defaultDashboardId',
|
||||
replace: true,
|
||||
transition: TransitionType.fadeIn);
|
||||
}
|
||||
} else {
|
||||
navigateTo('/home', replace: true, transition: TransitionType.fadeIn, transitionDuration: Duration(milliseconds: 750));
|
||||
}
|
||||
@@ -276,9 +313,24 @@ class TbContext {
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> navigateTo(String path, {bool replace = false, bool clearStack = false, TransitionType? transition, Duration? transitionDuration}) async {
|
||||
bool isHomePage() {
|
||||
if (currentState != null) {
|
||||
if (currentState is TbMainState) {
|
||||
var mainState = currentState as TbMainState;
|
||||
return mainState.isHomePage();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<dynamic> navigateTo(String path, {bool replace = false, bool clearStack = false,
|
||||
TransitionType? transition, Duration? transitionDuration, bool restoreDashboard = true}) async {
|
||||
if (currentState != null) {
|
||||
hideNotification();
|
||||
bool isOpenedDashboard = _mainDashboardHolder?.isDashboardOpen() == true;
|
||||
if (isOpenedDashboard) {
|
||||
_mainDashboardHolder?.openMain();
|
||||
}
|
||||
if (currentState is TbMainState) {
|
||||
var mainState = currentState as TbMainState;
|
||||
if (mainState.canNavigate(path) && !replace) {
|
||||
@@ -290,23 +342,48 @@ class TbContext {
|
||||
replace = true;
|
||||
clearStack = true;
|
||||
}
|
||||
if (transition == null) {
|
||||
if (isOpenedDashboard) {
|
||||
transition = TransitionType.none;
|
||||
} else if (transition == null) {
|
||||
if (replace) {
|
||||
transition = TransitionType.fadeIn;
|
||||
} else {
|
||||
transition = TransitionType.inFromRight;
|
||||
}
|
||||
}
|
||||
return await router.navigateTo(currentState!.context, path, transition: transition, transitionDuration: transitionDuration, replace: replace, clearStack: clearStack);
|
||||
var res = await router.navigateTo(currentState!.context, path, transition: transition, transitionDuration: transitionDuration, replace: replace, clearStack: clearStack);
|
||||
if (isOpenedDashboard) {
|
||||
await _mainDashboardHolder?.closeMain();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> navigateToDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar, bool animate = true}) async {
|
||||
await _mainDashboardHolder?.navigateToDashboard(dashboardId, dashboardTitle: dashboardTitle, state: state, hideToolbar: hideToolbar, animate: animate);
|
||||
}
|
||||
|
||||
void pop<T>([T? result]) {
|
||||
if (currentState != null) {
|
||||
router.pop<T>(currentState!.context, result);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> maybePop<T extends Object?>([ T? result ]) async {
|
||||
if (currentState != null) {
|
||||
return Navigator.of(currentState!.context).maybePop(result);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> willPop() async {
|
||||
if (_mainDashboardHolder != null) {
|
||||
return await _mainDashboardHolder!.dashboardGoBack();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool?> confirm({required String title, required String message, String cancel = 'Cancel', String ok = 'Ok'}) {
|
||||
return showDialog<bool>(context: currentState!.context,
|
||||
builder: (context) => AlertDialog(
|
||||
@@ -330,7 +407,13 @@ mixin HasTbContext {
|
||||
}
|
||||
|
||||
void setupCurrentState(TbContextState currentState) {
|
||||
if (_tbContext.currentState != null) {
|
||||
ModalRoute.of(_tbContext.currentState!.context)?.removeScopedWillPopCallback(_tbContext.willPop);
|
||||
}
|
||||
_tbContext.currentState = currentState;
|
||||
if (_tbContext.currentState != null) {
|
||||
ModalRoute.of(_tbContext.currentState!.context)?.addScopedWillPopCallback(_tbContext.willPop);
|
||||
}
|
||||
}
|
||||
|
||||
void setupTbContext(TbContextState currentState) {
|
||||
@@ -357,6 +440,11 @@ mixin HasTbContext {
|
||||
|
||||
void pop<T>([T? result]) => _tbContext.pop<T>(result);
|
||||
|
||||
Future<bool> maybePop<T extends Object?>([ T? result ]) => _tbContext.maybePop<T>(result);
|
||||
|
||||
Future<void> navigateToDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar, bool animate = true}) =>
|
||||
_tbContext.navigateToDashboard(dashboardId, dashboardTitle: dashboardTitle, state: state, hideToolbar: hideToolbar, animate: animate);
|
||||
|
||||
Future<bool?> confirm({required String title, required String message, String cancel = 'Cancel', String ok = 'Ok'}) => _tbContext.confirm(title: title, message: message, cancel: cancel, ok: ok);
|
||||
|
||||
void hideNotification() => _tbContext.hideNotification();
|
||||
|
||||
@@ -43,6 +43,8 @@ mixin TbMainState {
|
||||
|
||||
navigateToPath(String path);
|
||||
|
||||
bool isHomePage();
|
||||
|
||||
}
|
||||
|
||||
abstract class TbPageWidget<W extends TbPageWidget<W,S>, S extends TbPageState<W,S>> extends TbContextWidget<W,S> {
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
typedef EntityTapFunction<T> = Function(T entity);
|
||||
@@ -44,6 +45,8 @@ mixin EntitiesBase<T, P> on HasTbContext {
|
||||
return Text('Not implemented!');
|
||||
}
|
||||
|
||||
double? gridChildAspectRatio() => null;
|
||||
|
||||
EntityCardSettings entityListCardSettings(T entity) => EntityCardSettings();
|
||||
|
||||
EntityCardSettings entityGridCardSettings(T entity) => EntityCardSettings();
|
||||
|
||||
@@ -19,6 +19,7 @@ class _EntitiesGridState<T, P> extends BaseEntitiesState<T, P> {
|
||||
@override
|
||||
Widget pagedViewBuilder(BuildContext context) {
|
||||
var heading = widget.buildHeading(context);
|
||||
var gridChildAspectRatio = widget.gridChildAspectRatio() ?? 156 / 150;
|
||||
List<Widget> slivers = [];
|
||||
if (heading != null) {
|
||||
slivers.add(SliverPadding(
|
||||
@@ -35,8 +36,8 @@ class _EntitiesGridState<T, P> extends BaseEntitiesState<T, P> {
|
||||
showNoMoreItemsIndicatorAsGridChild: false,
|
||||
pagingController: pagingController,
|
||||
// padding: EdgeInsets.all(16),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
childAspectRatio: 156 / 150,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
childAspectRatio: gridChildAspectRatio,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
crossAxisCount: 2,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
abstract class EntityDetailsPage<T extends BaseData> extends TbPageWidget<EntityDetailsPage<T>, _EntityDetailsPageState<T>> {
|
||||
@@ -87,7 +88,9 @@ class _EntityDetailsPageState<T extends BaseData> extends TbPageState<EntityDeta
|
||||
var entity = snapshot.data!;
|
||||
return widget.buildEntityDetails(context, entity);
|
||||
} else {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
return Center(child: TbProgressIndicator(
|
||||
size: 50.0,
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -30,26 +30,18 @@ class EntityGridCard<T> extends StatelessWidget {
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
elevation: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: _entityCardWidgetBuilder(context, _entity)
|
||||
)
|
||||
child: _entityCardWidgetBuilder(context, _entity)
|
||||
),
|
||||
decoration: _settings.dropShadow ? BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(25),
|
||||
blurRadius: 10.0,
|
||||
color: Colors.black.withAlpha((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4)
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(18),
|
||||
blurRadius: 30.0,
|
||||
offset: Offset(0, 10)
|
||||
),
|
||||
)
|
||||
],
|
||||
) : null,
|
||||
),
|
||||
|
||||
@@ -33,13 +33,10 @@ class EntityListCard<T> extends StatelessWidget {
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(_listWidgetCard ? 4 : 6),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
elevation: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: _entityCardWidgetBuilder(context, _entity)
|
||||
)
|
||||
child: _entityCardWidgetBuilder(context, _entity)
|
||||
),
|
||||
decoration: _listWidgetCard ? BoxDecoration(
|
||||
border: Border.all(
|
||||
@@ -51,15 +48,10 @@ class EntityListCard<T> extends StatelessWidget {
|
||||
) : BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(25),
|
||||
blurRadius: 10.0,
|
||||
color: Colors.black.withAlpha((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4)
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(18),
|
||||
blurRadius: 30.0,
|
||||
offset: Offset(0, 10)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,50 +1,24 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
|
||||
class ThingsboardInitApp extends TbPageWidget<ThingsboardInitApp, _ThingsboardInitAppState> {
|
||||
|
||||
ThingsboardInitApp(TbContext tbContext, {Key? key}) : super(tbContext, key: key) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Colors.white,
|
||||
systemNavigationBarIconBrightness: Brightness.light
|
||||
));
|
||||
}
|
||||
ThingsboardInitApp(TbContext tbContext, {Key? key}) : super(tbContext, key: key);
|
||||
|
||||
@override
|
||||
_ThingsboardInitAppState createState() => _ThingsboardInitAppState();
|
||||
|
||||
}
|
||||
|
||||
class _ThingsboardInitAppState extends TbPageState<ThingsboardInitApp, _ThingsboardInitAppState> with TickerProviderStateMixin {
|
||||
|
||||
late final AnimationController rotationController;
|
||||
late final CurvedAnimation animation;
|
||||
class _ThingsboardInitAppState extends TbPageState<ThingsboardInitApp, _ThingsboardInitAppState> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
rotationController = AnimationController(duration: Duration(milliseconds: 2000),
|
||||
vsync: this, upperBound: 1, animationBehavior: AnimationBehavior.preserve);
|
||||
animation = CurvedAnimation(parent: rotationController, curve: Curves.easeInOutCirc);
|
||||
super.initState();
|
||||
initTbContext();
|
||||
rotationController.forward(from: 0.0);
|
||||
rotationController.addListener(() {
|
||||
if (rotationController.status == AnimationStatus.completed) {
|
||||
rotationController.repeat();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
rotationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -52,20 +26,10 @@ class _ThingsboardInitAppState extends TbPageState<ThingsboardInitApp, _Thingsbo
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
color: Colors.white,
|
||||
child: AnimatedBuilder(
|
||||
animation: animation,
|
||||
child: Container(
|
||||
height: 50.0,
|
||||
width: 50.0,
|
||||
child: Image.asset(ThingsboardImage.thingsboard),
|
||||
),
|
||||
builder: (BuildContext context, Widget? _widget) {
|
||||
return Transform.rotate(
|
||||
angle: animation.value * pi * 2,
|
||||
child: _widget,
|
||||
);
|
||||
},
|
||||
child: TbProgressIndicator(
|
||||
size: 50.0
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
104
lib/main.dart
@@ -2,8 +2,13 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:thingsboard_app/config/routes/router.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/main_dashboard_page.dart';
|
||||
import 'package:thingsboard_app/widgets/transition_indexed_stack.dart';
|
||||
|
||||
import 'config/themes/tb_theme.dart';
|
||||
|
||||
final appRouter = ThingsboardAppRouter();
|
||||
@@ -30,21 +35,108 @@ class ThingsboardApp extends StatefulWidget {
|
||||
|
||||
}
|
||||
|
||||
class ThingsboardAppState extends State<ThingsboardApp> {
|
||||
class ThingsboardAppState extends State<ThingsboardApp> with TickerProviderStateMixin implements TbMainDashboardHolder {
|
||||
|
||||
final TransitionIndexedStackController _mainStackController = TransitionIndexedStackController();
|
||||
final MainDashboardPageController _mainDashboardPageController = MainDashboardPageController();
|
||||
|
||||
final GlobalKey mainAppKey = GlobalKey();
|
||||
final GlobalKey dashboardKey = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
appRouter.tbContext.setMainDashboardHolder(this);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> navigateToDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar, bool animate = true}) async {
|
||||
await _mainDashboardPageController.openDashboard(dashboardId, dashboardTitle: dashboardTitle, state: state, hideToolbar: hideToolbar);
|
||||
await _openDashboard(animate: animate);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> dashboardGoBack() async {
|
||||
if (_mainStackController.index == 1) {
|
||||
var canGoBack = await _mainDashboardPageController.dashboardGoBack();
|
||||
if (canGoBack) {
|
||||
closeDashboard();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> openMain({bool animate = true}) async {
|
||||
return _openMain(animate: animate);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> closeMain({bool animate = true}) async {
|
||||
return _closeMain(animate: animate);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> openDashboard({bool animate = true}) async {
|
||||
return _openDashboard(animate: animate);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> closeDashboard({bool animate = true}) {
|
||||
return _closeDashboard(animate: animate);
|
||||
}
|
||||
|
||||
bool isDashboardOpen() {
|
||||
return _mainStackController.index == 1;
|
||||
}
|
||||
|
||||
Future<bool> _openMain({bool animate: true}) async {
|
||||
return _mainStackController.open(0, animate: animate);
|
||||
}
|
||||
|
||||
Future<bool> _closeMain({bool animate: true}) async {
|
||||
return _mainStackController.close(0, animate: animate);
|
||||
}
|
||||
|
||||
Future<bool> _openDashboard({bool animate: true}) async {
|
||||
return _mainStackController.open(1, animate: animate);
|
||||
}
|
||||
|
||||
Future<bool> _closeDashboard({bool animate: true}) async {
|
||||
return _mainStackController.close(1, animate: animate);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Colors.white,
|
||||
systemNavigationBarIconBrightness: Brightness.light
|
||||
));
|
||||
return MaterialApp(
|
||||
scaffoldMessengerKey: appRouter.tbContext.messengerKey,
|
||||
title: 'ThingsBoard',
|
||||
theme: tbTheme,
|
||||
darkTheme: tbDarkTheme,
|
||||
onGenerateRoute: appRouter.router.generator,
|
||||
navigatorObservers: [appRouter.tbContext.routeObserver],
|
||||
home: TransitionIndexedStack(
|
||||
controller: _mainStackController,
|
||||
first: MaterialApp(
|
||||
key: mainAppKey,
|
||||
scaffoldMessengerKey: appRouter.tbContext.messengerKey,
|
||||
title: 'ThingsBoard',
|
||||
theme: tbTheme,
|
||||
darkTheme: tbDarkTheme,
|
||||
onGenerateRoute: appRouter.router.generator,
|
||||
navigatorObservers: [appRouter.tbContext.routeObserver],
|
||||
),
|
||||
second: MaterialApp(
|
||||
key: dashboardKey,
|
||||
// scaffoldMessengerKey: appRouter.tbContext.messengerKey,
|
||||
title: 'ThingsBoard',
|
||||
theme: tbTheme,
|
||||
darkTheme: tbDarkTheme,
|
||||
home: MainDashboardPage(appRouter.tbContext, controller: _mainDashboardPageController),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -114,109 +114,126 @@ class _AlarmCardState extends TbContextState<AlarmCard, _AlarmCardState> {
|
||||
if (this.loading) {
|
||||
return Container( height: 134, alignment: Alignment.center, child: RefreshProgressIndicator());
|
||||
} else {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child:
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: AutoSizeText(alarm.type,
|
||||
maxLines: 2,
|
||||
minFontSize: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
Text(alarmSeverityTranslations[alarm.severity]!,
|
||||
style: TextStyle(
|
||||
color: alarmSeverityColors[alarm.severity]!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(alarm.originatorName != null ? alarm.originatorName! : '',
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: alarmSeverityColors[alarm.severity]!,
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4))
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child:
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: AutoSizeText(alarm.type,
|
||||
maxLines: 2,
|
||||
minFontSize: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(alarm.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(alarm.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 22),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(alarmStatusTranslations[alarm.status]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
if ([AlarmStatus.CLEARED_UNACK, AlarmStatus.ACTIVE_UNACK].contains(alarm.status))
|
||||
CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.done), padding: EdgeInsets.all(6.0), onPressed: () => _ackAlarm(alarm))
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(alarm.originatorName != null ? alarm.originatorName! : '',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
),
|
||||
if ([AlarmStatus.ACTIVE_UNACK, AlarmStatus.ACTIVE_ACK].contains(alarm.status))
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.clear), padding: EdgeInsets.all(6.0), onPressed: () => _clearAlarm(alarm))
|
||||
)
|
||||
]
|
||||
Text(alarmSeverityTranslations[alarm.severity]!,
|
||||
style: TextStyle(
|
||||
color: alarmSeverityColors[alarm.severity]!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 22),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(alarmStatusTranslations[alarm.status]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if ([AlarmStatus.CLEARED_UNACK, AlarmStatus.ACTIVE_UNACK].contains(alarm.status))
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.done, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _ackAlarm(alarm))
|
||||
),
|
||||
if ([AlarmStatus.ACTIVE_UNACK, AlarmStatus.ACTIVE_ACK].contains(alarm.status))
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.clear, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _clearAlarm(alarm))
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,18 @@ class AlarmsPage extends TbContextWidget<AlarmsPage, _AlarmsPageState> {
|
||||
|
||||
}
|
||||
|
||||
class _AlarmsPageState extends TbContextState<AlarmsPage, _AlarmsPageState> {
|
||||
class _AlarmsPageState extends TbContextState<AlarmsPage, _AlarmsPageState> with AutomaticKeepAliveClientMixin<AlarmsPage> {
|
||||
|
||||
final AlarmQueryController _alarmQueryController = AlarmQueryController();
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
var alarmsList = AlarmsList(tbContext, _alarmQueryController, searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -7,29 +8,47 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:thingsboard_app/constants/api_path.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class DashboardController {
|
||||
|
||||
final ValueNotifier<bool> canGoBack = ValueNotifier(false);
|
||||
final _DashboardState dashboardState;
|
||||
DashboardController(this.dashboardState);
|
||||
|
||||
Future<void> openDashboard(String dashboardId, {String? state, bool? hideToolbar, bool fullscreen = false}) async {
|
||||
return await dashboardState._openDashboard(dashboardId, state: state, hideToolbar: hideToolbar, fullscreen: fullscreen);
|
||||
}
|
||||
|
||||
Future<bool> goBack() async {
|
||||
return dashboardState._goBack();
|
||||
}
|
||||
|
||||
onHistoryUpdated(Future<bool> canGoBackFuture) async {
|
||||
canGoBack.value = await canGoBackFuture;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
canGoBack.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
typedef DashboardTitleCallback = void Function(String title);
|
||||
|
||||
typedef DashboardControllerCallback = void Function(DashboardController controller);
|
||||
|
||||
class Dashboard extends TbContextWidget<Dashboard, _DashboardState> {
|
||||
|
||||
final String _dashboardId;
|
||||
final String? _state;
|
||||
final bool? _home;
|
||||
final bool? _hideToolbar;
|
||||
final bool _fullscreen;
|
||||
final DashboardTitleCallback? _titleCallback;
|
||||
final DashboardControllerCallback? _controllerCallback;
|
||||
|
||||
Dashboard(TbContext tbContext, {required String dashboardId, required bool fullscreen,
|
||||
DashboardTitleCallback? titleCallback, String? state, bool? home,
|
||||
bool? hideToolbar}):
|
||||
this._dashboardId = dashboardId,
|
||||
this._fullscreen = fullscreen,
|
||||
this._titleCallback = titleCallback,
|
||||
this._state = state,
|
||||
Dashboard(TbContext tbContext, {Key? key, bool? home, DashboardTitleCallback? titleCallback, DashboardControllerCallback? controllerCallback}):
|
||||
this._home = home,
|
||||
this._hideToolbar = hideToolbar,
|
||||
this._titleCallback = titleCallback,
|
||||
this._controllerCallback = controllerCallback,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
@@ -41,15 +60,23 @@ class _DashboardState extends TbContextState<Dashboard, _DashboardState> {
|
||||
|
||||
final Completer<InAppWebViewController> _controller = Completer<InAppWebViewController>();
|
||||
|
||||
final ValueNotifier<bool> webViewLoading = ValueNotifier(true);
|
||||
bool webViewLoading = true;
|
||||
final ValueNotifier<bool> dashboardLoading = ValueNotifier(true);
|
||||
final ValueNotifier<bool> readyState = ValueNotifier(false);
|
||||
|
||||
final GlobalKey webViewKey = GlobalKey();
|
||||
|
||||
late final DashboardController _dashboardController;
|
||||
|
||||
bool _fullscreen = false;
|
||||
|
||||
InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
|
||||
crossPlatform: InAppWebViewOptions(
|
||||
useShouldOverrideUrlLoading: true,
|
||||
mediaPlaybackRequiresUserGesture: false,
|
||||
javaScriptEnabled: true,
|
||||
cacheEnabled: true,
|
||||
supportZoom: false,
|
||||
// useOnDownloadStart: true
|
||||
),
|
||||
android: AndroidInAppWebViewOptions(
|
||||
@@ -60,37 +87,88 @@ class _DashboardState extends TbContextState<Dashboard, _DashboardState> {
|
||||
allowsInlineMediaPlayback: true,
|
||||
));
|
||||
|
||||
late String _dashboardUrl;
|
||||
late String _currentDashboardId;
|
||||
late String? _currentDashboardState;
|
||||
late Uri _initialUrl;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_dashboardUrl = thingsBoardApiEndpoint + '/dashboard/' + widget._dashboardId;
|
||||
List<String> params = [];
|
||||
params.add("accessToken=${tbClient.getJwtToken()!}");
|
||||
params.add("refreshToken=${tbClient.getRefreshToken()!}");
|
||||
if (widget._state != null) {
|
||||
params.add('state=${widget._state}');
|
||||
_dashboardController = DashboardController(this);
|
||||
if (widget._controllerCallback != null) {
|
||||
widget._controllerCallback!(_dashboardController);
|
||||
}
|
||||
tbContext.isAuthenticatedListenable.addListener(_onAuthenticated);
|
||||
if (tbContext.isAuthenticated) {
|
||||
_onAuthenticated();
|
||||
}
|
||||
}
|
||||
|
||||
void _onAuthenticated() async {
|
||||
if (tbContext.isAuthenticated) {
|
||||
if (!readyState.value) {
|
||||
_initialUrl = Uri.parse(thingsBoardApiEndpoint + '?accessToken=${tbClient.getJwtToken()!}&refreshToken=${tbClient.getRefreshToken()!}');
|
||||
readyState.value = true;
|
||||
} else {
|
||||
var windowMessage = <String, dynamic>{
|
||||
'type': 'reloadUserMessage',
|
||||
'data': <String, dynamic>{
|
||||
'accessToken': tbClient.getJwtToken()!,
|
||||
'refreshToken': tbClient.getRefreshToken()!
|
||||
}
|
||||
};
|
||||
var controller = await _controller.future;
|
||||
await controller.postWebMessage(message: WebMessage(data: jsonEncode(windowMessage)), targetOrigin: Uri.parse('*'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _goBack() async {
|
||||
var controller = await _controller.future;
|
||||
if (await controller.canGoBack()) {
|
||||
await controller.goBack();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
tbContext.isAuthenticatedListenable.removeListener(_onAuthenticated);
|
||||
readyState.dispose();
|
||||
dashboardLoading.dispose();
|
||||
_dashboardController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _openDashboard(String dashboardId, {String? state, bool? hideToolbar, bool fullscreen = false}) async {
|
||||
_fullscreen = fullscreen;
|
||||
dashboardLoading.value = true;
|
||||
var controller = await _controller.future;
|
||||
var windowMessage = <String, dynamic>{
|
||||
'type': 'openDashboardMessage',
|
||||
'data': <String, dynamic>{
|
||||
'dashboardId': dashboardId
|
||||
}
|
||||
};
|
||||
if (state != null) {
|
||||
windowMessage['data']['state'] = state;
|
||||
}
|
||||
if (widget._home == true) {
|
||||
params.add('embedded=true');
|
||||
windowMessage['data']['embedded'] = true;
|
||||
}
|
||||
if (widget._hideToolbar == true) {
|
||||
params.add('hideToolbar=true');
|
||||
if (hideToolbar == true) {
|
||||
windowMessage['data']['hideToolbar'] = true;
|
||||
}
|
||||
if (params.isNotEmpty) {
|
||||
_dashboardUrl += '?${params.join('&')}';
|
||||
}
|
||||
_currentDashboardId = widget._dashboardId;
|
||||
_currentDashboardState = widget._state;
|
||||
var webMessage = WebMessage(data: jsonEncode(windowMessage));
|
||||
await controller.postWebMessage(message: webMessage, targetOrigin: Uri.parse('*'));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (widget._home == true && !tbContext.isHomePage()) {
|
||||
return true;
|
||||
}
|
||||
var controller = await _controller.future;
|
||||
if (await controller.canGoBack()) {
|
||||
await controller.goBack();
|
||||
@@ -98,191 +176,147 @@ class _DashboardState extends TbContextState<Dashboard, _DashboardState> {
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
key: webViewKey,
|
||||
initialUrlRequest: URLRequest(url: Uri.parse(_dashboardUrl)),
|
||||
initialOptions: options,
|
||||
onWebViewCreated: (webViewController) {
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardStateNameHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileDashboardStateNameHandler: $args");
|
||||
webViewLoading.value = false;
|
||||
if (args.isNotEmpty && args[0] is String) {
|
||||
if (widget._titleCallback != null) {
|
||||
widget._titleCallback!(args[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileHandler: $args");
|
||||
return await widgetActionHandler.handleWidgetMobileAction(args, webViewController);
|
||||
});
|
||||
_controller.complete(webViewController);
|
||||
},
|
||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||
var uri = navigationAction.request.url!;
|
||||
var uriString = uri.toString();
|
||||
log.debug('shouldOverrideUrlLoading $uriString');
|
||||
if (![
|
||||
"http",
|
||||
"https",
|
||||
"file",
|
||||
"chrome",
|
||||
"data",
|
||||
"javascript",
|
||||
"about"
|
||||
].contains(uri.scheme)) {
|
||||
if (await canLaunch(uriString)) {
|
||||
// Launch the App
|
||||
await launch(
|
||||
uriString,
|
||||
);
|
||||
// and cancel the request
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
}
|
||||
}
|
||||
child:
|
||||
ValueListenableBuilder(
|
||||
valueListenable: readyState,
|
||||
builder: (BuildContext context, bool ready, child) {
|
||||
if (!ready) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: Colors.white),
|
||||
child: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
key: webViewKey,
|
||||
initialUrlRequest: URLRequest(url: _initialUrl),
|
||||
initialOptions: options,
|
||||
onWebViewCreated: (webViewController) {
|
||||
log.debug("onWebViewCreated");
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardLoadedHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileDashboardLoadedHandler");
|
||||
dashboardLoading.value = false;
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardStateNameHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileDashboardStateNameHandler: $args");
|
||||
if (args.isNotEmpty && args[0] is String) {
|
||||
if (widget._titleCallback != null) {
|
||||
widget._titleCallback!(args[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileNavigationHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileNavigationHandler: $args");
|
||||
if (args.length > 0) {
|
||||
String? path = args[0];
|
||||
Map<String, dynamic>? params;
|
||||
if (args.length > 1) {
|
||||
params = args[1];
|
||||
}
|
||||
log.debug("path: $path");
|
||||
log.debug("params: $params");
|
||||
if (path != null) {
|
||||
if ([
|
||||
'profile',
|
||||
'devices',
|
||||
'assets',
|
||||
'dashboards',
|
||||
'customers',
|
||||
'auditLogs'
|
||||
].contains(path)) {
|
||||
var targetPath = '/$path';
|
||||
if (path == 'devices' && widget._home != true) {
|
||||
targetPath = '/devicesPage';
|
||||
}
|
||||
navigateTo(targetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
webViewController.addJavaScriptHandler(handlerName: "tbMobileHandler", callback: (args) async {
|
||||
log.debug("Invoked tbMobileHandler: $args");
|
||||
return await widgetActionHandler.handleWidgetMobileAction(args, webViewController);
|
||||
});
|
||||
},
|
||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||
var uri = navigationAction.request.url!;
|
||||
var uriString = uri.toString();
|
||||
log.debug('shouldOverrideUrlLoading $uriString');
|
||||
if (![
|
||||
"http",
|
||||
"https",
|
||||
"file",
|
||||
"chrome",
|
||||
"data",
|
||||
"javascript",
|
||||
"about"
|
||||
].contains(uri.scheme)) {
|
||||
if (await canLaunch(uriString)) {
|
||||
// Launch the App
|
||||
await launch(
|
||||
uriString,
|
||||
);
|
||||
// and cancel the request
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
}
|
||||
}
|
||||
|
||||
return Platform.isIOS ? NavigationActionPolicy.ALLOW : NavigationActionPolicy.CANCEL;
|
||||
},
|
||||
onUpdateVisitedHistory: (controller, url, androidIsReload) async {
|
||||
if (url != null) {
|
||||
String newStateId = url.pathSegments.last;
|
||||
log.debug('onUpdateVisitedHistory: $newStateId');
|
||||
if (newStateId == 'profile') {
|
||||
webViewLoading.value = true;
|
||||
await controller.goBack();
|
||||
await navigateTo('/profile');
|
||||
webViewLoading.value = false;
|
||||
return;
|
||||
} else if (newStateId == 'login') {
|
||||
webViewLoading.value = true;
|
||||
await controller.pauseTimers();
|
||||
await controller.stopLoading();
|
||||
await tbClient.logout();
|
||||
return;
|
||||
} else if (['devices', 'assets', 'dashboards'].contains(newStateId)) {
|
||||
var controller = await _controller.future;
|
||||
await controller.goBack();
|
||||
navigateTo('/$newStateId');
|
||||
return;
|
||||
} else {
|
||||
if (url.pathSegments.length > 1) {
|
||||
var segmentName = url.pathSegments[url.pathSegments.length-2];
|
||||
if (segmentName == 'dashboards' && widget._home != true) {
|
||||
webViewLoading.value = true;
|
||||
var targetPath = _createDashboardNavigationPath(newStateId, fullscreen: widget._fullscreen);
|
||||
await navigateTo(targetPath, replace: true);
|
||||
return;
|
||||
} else if (segmentName == 'dashboard') {
|
||||
_currentDashboardId = newStateId;
|
||||
_currentDashboardState = url.queryParameters['state'];
|
||||
return;
|
||||
}
|
||||
}
|
||||
webViewLoading.value = true;
|
||||
if (widget._home == true) {
|
||||
await navigateTo('/home', replace: true);
|
||||
} else {
|
||||
var targetPath = _createDashboardNavigationPath(_currentDashboardId, state: _currentDashboardState, fullscreen: widget._fullscreen);
|
||||
await navigateTo(targetPath, replace: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onConsoleMessage: (controller, consoleMessage) {
|
||||
log.debug('[JavaScript console] ${consoleMessage.messageLevel}: ${consoleMessage.message}');
|
||||
},
|
||||
onLoadStart: (controller, url) async {
|
||||
log.debug('onLoadStart: $url');
|
||||
// await _setTokens(controller.webStorage.localStorage);
|
||||
},
|
||||
onLoadStop: (controller, url) async {
|
||||
log.debug('onLoadStop: $url');
|
||||
// await _setTokens(controller.webStorage.localStorage);
|
||||
},
|
||||
androidOnPermissionRequest: (controller, origin, resources) async {
|
||||
log.debug('androidOnPermissionRequest origin: $origin, resources: $resources');
|
||||
return PermissionRequestResponse(
|
||||
resources: resources,
|
||||
action: PermissionRequestResponseAction.GRANT);
|
||||
},
|
||||
/* onDownloadStart: (controller, url) async {
|
||||
log.debug("onDownloadStart $url");
|
||||
final taskId = await FlutterDownloader.enqueue(
|
||||
url: url.toString(),
|
||||
savedDir: (await getExternalStorageDirectory())!.path,
|
||||
showNotification: true,
|
||||
openFileFromNotification: true,
|
||||
);
|
||||
} */
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: webViewLoading,
|
||||
builder: (BuildContext context, bool loading, child) {
|
||||
if (!loading) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: Colors.white),
|
||||
child: Center(
|
||||
child: RefreshProgressIndicator()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
return Platform.isIOS ? NavigationActionPolicy.ALLOW : NavigationActionPolicy.CANCEL;
|
||||
},
|
||||
onUpdateVisitedHistory: (controller, url, androidIsReload) async {
|
||||
log.debug('onUpdateVisitedHistory: url');
|
||||
_dashboardController.onHistoryUpdated(controller.canGoBack());
|
||||
},
|
||||
onConsoleMessage: (controller, consoleMessage) {
|
||||
log.debug('[JavaScript console] ${consoleMessage.messageLevel}: ${consoleMessage.message}');
|
||||
},
|
||||
onLoadStart: (controller, url) async {
|
||||
log.debug('onLoadStart: $url');
|
||||
},
|
||||
onLoadStop: (controller, url) async {
|
||||
log.debug('onLoadStop: $url');
|
||||
if (webViewLoading) {
|
||||
webViewLoading = false;
|
||||
_controller.complete(controller);
|
||||
}
|
||||
},
|
||||
androidOnPermissionRequest: (controller, origin, resources) async {
|
||||
log.debug('androidOnPermissionRequest origin: $origin, resources: $resources');
|
||||
return PermissionRequestResponse(
|
||||
resources: resources,
|
||||
action: PermissionRequestResponseAction.GRANT);
|
||||
},
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: dashboardLoading,
|
||||
builder: (BuildContext context, bool loading, child) {
|
||||
if (!loading) {
|
||||
return SizedBox.shrink();
|
||||
} else {
|
||||
var data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
|
||||
var bottomPadding = data.padding.top;
|
||||
if (widget._home != true) {
|
||||
bottomPadding += kToolbarHeight;
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(bottom: bottomPadding),
|
||||
alignment: Alignment.center,
|
||||
color: Colors.white,
|
||||
child: TbProgressIndicator(
|
||||
size: 50.0
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
String _createDashboardNavigationPath(String dashboardId, {bool? fullscreen, String? state}) {
|
||||
var targetPath = '/dashboard/$dashboardId';
|
||||
List<String> params = [];
|
||||
if (state != null) {
|
||||
params.add('state=$state');
|
||||
}
|
||||
if (fullscreen != null) {
|
||||
params.add('fullscreen=$fullscreen');
|
||||
}
|
||||
if (params.isNotEmpty) {
|
||||
targetPath += '?${params.join('&')}';
|
||||
}
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
Future<void> _setTokens(Storage storage) async {
|
||||
String jwtToken = tbClient.getJwtToken()!;
|
||||
int jwtTokenExpiration = _getClientExpiration(jwtToken);
|
||||
String refreshToken = tbClient.getRefreshToken()!;
|
||||
int refreshTokenExpiration = _getClientExpiration(refreshToken);
|
||||
await storage.setItem(key: 'jwt_token', value: jwtToken);
|
||||
await storage.setItem(key: 'jwt_token_expiration', value: jwtTokenExpiration);
|
||||
await storage.setItem(key: 'refresh_token', value: refreshToken);
|
||||
await storage.setItem(key: 'refresh_token_expiration', value: refreshTokenExpiration);
|
||||
}
|
||||
|
||||
/* String _setTokensJavaScript() {
|
||||
String jwtToken = tbClient.getJwtToken()!;
|
||||
int jwtTokenExpiration = _getClientExpiration(jwtToken);
|
||||
String refreshToken = tbClient.getRefreshToken()!;
|
||||
int refreshTokenExpiration = _getClientExpiration(refreshToken);
|
||||
return "window.localStorage.setItem('jwt_token','$jwtToken');\n"+
|
||||
"window.localStorage.setItem('jwt_token_expiration','$jwtTokenExpiration');\n"+
|
||||
"window.localStorage.setItem('refresh_token','$refreshToken');\n"+
|
||||
"window.localStorage.setItem('refresh_token_expiration','$refreshTokenExpiration');";
|
||||
} */
|
||||
|
||||
int _getClientExpiration(String token) {
|
||||
var decodedToken = JwtDecoder.decode(tbClient.getJwtToken()!);
|
||||
int issuedAt = decodedToken['iat'];
|
||||
int expTime = decodedToken['exp'];
|
||||
int ttl = expTime - issuedAt;
|
||||
int clientExpiration = DateTime.now().millisecondsSinceEpoch + ttl * 1000;
|
||||
return clientExpiration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
class DashboardPage extends TbPageWidget<DashboardPage, _DashboardPageState> {
|
||||
|
||||
final String? _dashboardTitle;
|
||||
final String _dashboardId;
|
||||
final String? _dashboardId;
|
||||
final String? _state;
|
||||
final bool _fullscreen;
|
||||
final bool? _fullscreen;
|
||||
|
||||
DashboardPage(TbContext tbContext, {required String dashboardId, required bool fullscreen, String? dashboardTitle, String? state}):
|
||||
DashboardPage(TbContext tbContext, {String? dashboardId, bool? fullscreen, String? dashboardTitle, String? state}):
|
||||
_dashboardId = dashboardId,
|
||||
_fullscreen = fullscreen,
|
||||
_dashboardTitle = dashboardTitle,
|
||||
@@ -52,10 +52,11 @@ class _DashboardPageState extends TbPageState<DashboardPage, _DashboardPageState
|
||||
},
|
||||
),
|
||||
),
|
||||
body: Dashboard(tbContext, dashboardId: widget._dashboardId, state: widget._state,
|
||||
fullscreen: widget._fullscreen, titleCallback: (title) {
|
||||
dashboardTitleValue.value = title;
|
||||
}),
|
||||
body: Text('Deprecated') //Dashboard(tbContext, dashboardId: widget._dashboardId, state: widget._state,
|
||||
//fullscreen: widget._fullscreen, titleCallback: (title) {
|
||||
//dashboardTitleValue.value = title;
|
||||
//}
|
||||
//),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/config/routes/router.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboards_page.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/fullscreen_dashboard_page.dart';
|
||||
|
||||
import 'dashboard_page.dart';
|
||||
|
||||
@@ -20,12 +21,17 @@ class DashboardRoutes extends TbRoutes {
|
||||
dashboardTitle: dashboardTitle, state: state);
|
||||
});
|
||||
|
||||
late var fullscreenDashboardHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return FullscreenDashboardPage(tbContext, params["id"]![0]);
|
||||
});
|
||||
|
||||
DashboardRoutes(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/dashboards", handler: dashboardsHandler);
|
||||
router.define("/dashboard/:id", handler: dashboardDetailsHandler);
|
||||
router.define("/fullscreenDashboard/:id", handler: fullscreenDashboardHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ mixin DashboardsBase on EntitiesBase<DashboardInfo, PageLink> {
|
||||
|
||||
@override
|
||||
void onEntityTap(DashboardInfo dashboard) {
|
||||
navigateTo('/dashboard/${dashboard.id!.id}?title=${dashboard.title}');
|
||||
navigateToDashboard(dashboard.id!.id!, dashboardTitle: dashboard.title);
|
||||
// navigateTo('/dashboard/${dashboard.id!.id}?title=${dashboard.title}');
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -152,57 +153,50 @@ class _DashboardGridCardState extends TbContextState<DashboardGridCard, _Dashboa
|
||||
Widget build(BuildContext context) {
|
||||
var hasImage = widget.dashboard.image != null;
|
||||
Widget image;
|
||||
BoxFit imageFit;
|
||||
if (hasImage) {
|
||||
var uriData = UriData.parse(widget.dashboard.image!);
|
||||
image = Image.memory(uriData.contentAsBytes());
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
image = Image.asset(ThingsboardImage.dashboardPlaceholder);
|
||||
imageFit = BoxFit.cover;
|
||||
}
|
||||
return
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Stack(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Column(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: imageFit,
|
||||
child: image,
|
||||
Expanded(
|
||||
child: Stack (
|
||||
children: [
|
||||
SizedBox.expand(
|
||||
child: FittedBox(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: BoxFit.cover,
|
||||
child: image
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
hasImage ? Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x00000000),
|
||||
Color(0xb7000000)
|
||||
],
|
||||
stops: [0.4219, 1]
|
||||
Divider(height: 1),
|
||||
Container(
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child:
|
||||
Center(
|
||||
child: AutoSizeText(widget.dashboard.title,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
minFontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
) : Container(),
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
child: AutoSizeText(widget.dashboard.title,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
minFontSize: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: hasImage ? Colors.white : Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
import 'dashboards_list.dart';
|
||||
import 'dashboards_grid.dart';
|
||||
|
||||
class DashboardsPage extends TbPageWidget<DashboardsPage, _DashboardsPageState> {
|
||||
|
||||
@@ -17,23 +16,19 @@ class DashboardsPage extends TbPageWidget<DashboardsPage, _DashboardsPageState>
|
||||
|
||||
class _DashboardsPageState extends TbPageState<DashboardsPage, _DashboardsPageState> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var dashboardsList = DashboardsList(tbContext, _pageLinkController);
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Text(dashboardsList.title)
|
||||
title: Text('Dashboards')
|
||||
),
|
||||
body: dashboardsList
|
||||
body: DashboardsGridWidget(tbContext)
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
90
lib/modules/dashboard/fullscreen_dashboard_page.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class FullscreenDashboardPage extends TbPageWidget<FullscreenDashboardPage, _FullscreenDashboardPageState> {
|
||||
|
||||
final String fullscreenDashboardId;
|
||||
final String? _dashboardTitle;
|
||||
|
||||
FullscreenDashboardPage(TbContext tbContext, this.fullscreenDashboardId, {String? dashboardTitle}):
|
||||
_dashboardTitle = dashboardTitle,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_FullscreenDashboardPageState createState() => _FullscreenDashboardPageState();
|
||||
|
||||
}
|
||||
|
||||
class _FullscreenDashboardPageState extends TbPageState<FullscreenDashboardPage, _FullscreenDashboardPageState> {
|
||||
|
||||
late ValueNotifier<String> dashboardTitleValue;
|
||||
final ValueNotifier<bool> showBackValue = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
dashboardTitleValue = ValueNotifier(widget._dashboardTitle ?? 'Dashboard');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onCanGoBack(bool canGoBack) {
|
||||
showBackValue.value = canGoBack;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(kToolbarHeight),
|
||||
child: ValueListenableBuilder<bool>(
|
||||
valueListenable: showBackValue,
|
||||
builder: (context, canGoBack, widget) {
|
||||
return TbAppBar(
|
||||
tbContext,
|
||||
leading: canGoBack ? BackButton(
|
||||
onPressed: () {
|
||||
maybePop();
|
||||
}
|
||||
) : null,
|
||||
showLoadingIndicator: false,
|
||||
elevation: 0,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: dashboardTitleValue,
|
||||
builder: (context, title, widget) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title)
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(icon: Icon(Icons.settings), onPressed: () => navigateTo('/profile?fullscreen=true'))
|
||||
]
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
body: Dashboard(
|
||||
tbContext,
|
||||
titleCallback: (title) {
|
||||
dashboardTitleValue.value = title;
|
||||
},
|
||||
controllerCallback: (controller) {
|
||||
controller.canGoBack.addListener(() {
|
||||
_onCanGoBack(controller.canGoBack.value);
|
||||
});
|
||||
controller.openDashboard(widget.fullscreenDashboardId, fullscreen: true);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
113
lib/modules/dashboard/main_dashboard_page.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class MainDashboardPageController {
|
||||
|
||||
DashboardController? _dashboardController;
|
||||
_MainDashboardPageState? _mainDashboardPageState;
|
||||
|
||||
_setMainDashboardPageState(_MainDashboardPageState state) {
|
||||
_mainDashboardPageState = state;
|
||||
}
|
||||
|
||||
_setDashboardController(DashboardController controller) {
|
||||
_dashboardController = controller;
|
||||
}
|
||||
|
||||
Future<bool> dashboardGoBack() {
|
||||
if (_dashboardController != null) {
|
||||
return _dashboardController!.goBack();
|
||||
} else {
|
||||
return Future.value(true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> openDashboard(String dashboardId, {String? dashboardTitle, String? state, bool? hideToolbar}) async {
|
||||
if (dashboardTitle != null) {
|
||||
_mainDashboardPageState?._updateTitle(dashboardTitle);
|
||||
}
|
||||
await _dashboardController?.openDashboard(dashboardId, state: state, hideToolbar: hideToolbar);
|
||||
}
|
||||
}
|
||||
|
||||
class MainDashboardPage extends TbContextWidget<MainDashboardPage, _MainDashboardPageState> {
|
||||
|
||||
final String? _dashboardTitle;
|
||||
final MainDashboardPageController? _controller;
|
||||
|
||||
MainDashboardPage(TbContext tbContext,
|
||||
{MainDashboardPageController? controller,
|
||||
String? dashboardTitle}):
|
||||
_controller = controller,
|
||||
_dashboardTitle = dashboardTitle,
|
||||
super(tbContext);
|
||||
|
||||
@override
|
||||
_MainDashboardPageState createState() => _MainDashboardPageState();
|
||||
|
||||
}
|
||||
|
||||
class _MainDashboardPageState extends TbContextState<MainDashboardPage, _MainDashboardPageState> {
|
||||
|
||||
late ValueNotifier<String> dashboardTitleValue;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget._controller != null) {
|
||||
widget._controller!._setMainDashboardPageState(this);
|
||||
}
|
||||
dashboardTitleValue = ValueNotifier(widget._dashboardTitle ?? 'Dashboard');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_updateTitle(String newTitle) {
|
||||
dashboardTitleValue.value = newTitle;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
leading: BackButton(
|
||||
onPressed: () {
|
||||
maybePop();
|
||||
}
|
||||
),
|
||||
showLoadingIndicator: false,
|
||||
elevation: 0,
|
||||
title: ValueListenableBuilder<String>(
|
||||
valueListenable: dashboardTitleValue,
|
||||
builder: (context, title, widget) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(title)
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
body: Dashboard(
|
||||
tbContext,
|
||||
titleCallback: (title) {
|
||||
dashboardTitleValue.value = title;
|
||||
},
|
||||
controllerCallback: (controller) {
|
||||
if (widget._controller != null) {
|
||||
widget._controller!._setDashboardController(controller);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_app/utils/services/device_profile_cache.dart';
|
||||
import 'package:thingsboard_app/utils/services/entity_query_api.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
mixin DeviceProfilesBase on EntitiesBase<DeviceProfileInfo, PageLink> {
|
||||
@@ -50,6 +51,11 @@ mixin DeviceProfilesBase on EntitiesBase<DeviceProfileInfo, PageLink> {
|
||||
return DeviceProfileCard(tbContext, deviceProfile);
|
||||
}
|
||||
|
||||
@override
|
||||
double? gridChildAspectRatio() {
|
||||
return 156 / 200;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RefreshDeviceCounts {
|
||||
@@ -93,8 +99,10 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard, _AllDevicesCar
|
||||
Future<int> inactiveDevicesCount = EntityQueryApi.countDevices(tbClient, active: false);
|
||||
Future<List<int>> countsFuture = Future.wait([activeDevicesCount, inactiveDevicesCount]);
|
||||
countsFuture.then((counts) {
|
||||
_activeDevicesCount.add(counts[0]);
|
||||
_inactiveDevicesCount.add(counts[1]);
|
||||
if (this.mounted) {
|
||||
_activeDevicesCount.add(counts[0]);
|
||||
_inactiveDevicesCount.add(counts[1]);
|
||||
}
|
||||
});
|
||||
return countsFuture;
|
||||
}
|
||||
@@ -107,32 +115,31 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard, _AllDevicesCar
|
||||
child:
|
||||
Container(
|
||||
child: Card(
|
||||
color: Theme.of(tbContext.currentState!.context).colorScheme.primary,
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
elevation: 0,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(padding: EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||
Padding(padding: EdgeInsets.fromLTRB(16, 12, 16, 15),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('All devices',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
)
|
||||
),
|
||||
Icon(Icons.arrow_forward, color: Colors.white)
|
||||
Icon(Icons.arrow_forward, size: 18)
|
||||
],
|
||||
)
|
||||
),
|
||||
Padding(padding: EdgeInsets.all(8),
|
||||
Divider(height: 1),
|
||||
Padding(padding: EdgeInsets.all(0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
@@ -150,7 +157,7 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard, _AllDevicesCar
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, true, deviceCount, displayStatusText: true);
|
||||
return _buildDeviceCount(context, true, deviceCount);
|
||||
} else {
|
||||
return Center(child:
|
||||
Container(height: 20, width: 20,
|
||||
@@ -166,7 +173,11 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard, _AllDevicesCar
|
||||
}
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
// SizedBox(width: 4),
|
||||
Container(width: 1,
|
||||
height: 40,
|
||||
child: VerticalDivider(width: 1)
|
||||
),
|
||||
Flexible(fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
@@ -181,7 +192,7 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard, _AllDevicesCar
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, false, deviceCount, displayStatusText: true);
|
||||
return _buildDeviceCount(context, false, deviceCount);
|
||||
} else {
|
||||
return Center(child:
|
||||
Container(height: 20, width: 20,
|
||||
@@ -206,15 +217,10 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard, _AllDevicesCar
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(25),
|
||||
blurRadius: 10.0,
|
||||
color: Colors.black.withAlpha((255 * 0.05).ceil()),
|
||||
blurRadius: 6.0,
|
||||
offset: Offset(0, 4)
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(18),
|
||||
blurRadius: 30.0,
|
||||
offset: Offset(0, 10)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -275,124 +281,98 @@ class _DeviceProfileCardState extends TbContextState<DeviceProfileCard, _DeviceP
|
||||
}
|
||||
return
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: imageFit,
|
||||
child: image,
|
||||
)
|
||||
),
|
||||
hasImage ? Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x00000000),
|
||||
Color(0xb7000000)
|
||||
],
|
||||
stops: [0.4219, 1]
|
||||
)
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack (
|
||||
children: [
|
||||
SizedBox.expand(
|
||||
child: FittedBox(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
fit: imageFit,
|
||||
child: image
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
) : Container(),
|
||||
Positioned(
|
||||
bottom: 56,
|
||||
left: 16,
|
||||
right: 16,
|
||||
child: AutoSizeText(entity.name,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
minFontSize: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: hasImage ? Colors.white : Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
)
|
||||
),
|
||||
Positioned(
|
||||
bottom: 4,
|
||||
left: 4,
|
||||
right: 4,
|
||||
height: 40,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: FutureBuilder<int>(
|
||||
future: activeDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, true, deviceCount);
|
||||
} else {
|
||||
return Center(child:
|
||||
Container(height: 20, width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5)));
|
||||
}
|
||||
},
|
||||
)
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=true&deviceType=${entity.name}');
|
||||
}
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Flexible(fit: FlexFit.tight,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: FutureBuilder<int>(
|
||||
future: inactiveDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, false, deviceCount);
|
||||
} else {
|
||||
return Center(child:
|
||||
Container(height: 20, width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5)));
|
||||
}
|
||||
},
|
||||
)
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=false&deviceType=${entity.name}');
|
||||
}
|
||||
Container(
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Center(
|
||||
child: AutoSizeText(entity.name,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
minFontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
Divider(height: 1),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: FutureBuilder<int>(
|
||||
future: activeDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, true, deviceCount);
|
||||
} else {
|
||||
return Container(height: 40,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 20, width: 20,
|
||||
child:
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5))));
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=true&deviceType=${entity.name}');
|
||||
}
|
||||
),
|
||||
Divider(height: 1),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: FutureBuilder<int>(
|
||||
future: inactiveDevicesCount,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var deviceCount = snapshot.data!;
|
||||
return _buildDeviceCount(context, false, deviceCount);
|
||||
} else {
|
||||
return Container(height: 40,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 20, width: 20,
|
||||
child:
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary),
|
||||
strokeWidth: 2.5))));
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
navigateTo('/deviceList?active=false&deviceType=${entity.name}');
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildDeviceCount(BuildContext context, bool active, int count, {bool displayStatusText = false}) {
|
||||
Widget _buildDeviceCount(BuildContext context, bool active, int count) {
|
||||
Color color = active ? Color(0xFF008A00) : Color(0xFFAFAFAF);
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
@@ -412,23 +392,23 @@ Widget _buildDeviceCount(BuildContext context, bool active, int count, {bool dis
|
||||
)
|
||||
],
|
||||
),
|
||||
if (displayStatusText)
|
||||
SizedBox(width: 8.67),
|
||||
if (displayStatusText)
|
||||
Text(active ? 'Active' : 'Inactive', style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color
|
||||
)),
|
||||
SizedBox(width: 8.67),
|
||||
Text(count.toString(), style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color
|
||||
))
|
||||
],
|
||||
),
|
||||
Text(count.toString(), style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 16 / 12,
|
||||
color: color
|
||||
))
|
||||
Icon(Icons.chevron_right, size: 16, color: Color(0xFFACACAC))
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,10 +2,11 @@ import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/config/routes/router.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_page.dart';
|
||||
import 'package:thingsboard_app/modules/main/main_page.dart';
|
||||
|
||||
import 'device_details_page.dart';
|
||||
import 'devices_page.dart';
|
||||
import 'devices_list_page.dart';
|
||||
|
||||
class DeviceRoutes extends TbRoutes {
|
||||
|
||||
@@ -13,12 +14,16 @@ class DeviceRoutes extends TbRoutes {
|
||||
return MainPage(tbContext, path: '/devices');
|
||||
});
|
||||
|
||||
late var devicesPageHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return DevicesPage(tbContext);
|
||||
});
|
||||
|
||||
late var deviceListHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
var deviceType = params['deviceType']?.first;
|
||||
String? activeStr = params['active']?.first;
|
||||
bool? active = activeStr != null ? activeStr == 'true' : null;
|
||||
return DevicesPage(tbContext, searchMode: searchMode, deviceType: deviceType, active: active);
|
||||
return DevicesListPage(tbContext, searchMode: searchMode, deviceType: deviceType, active: active);
|
||||
});
|
||||
|
||||
late var deviceDetailsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
@@ -30,6 +35,7 @@ class DeviceRoutes extends TbRoutes {
|
||||
@override
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/devices", handler: devicesHandler);
|
||||
router.define("/devicesPage", handler: devicesPageHandler);
|
||||
router.define("/deviceList", handler: deviceListHandler);
|
||||
router.define("/device/:id", handler: deviceDetailsHandler);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:core';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
@@ -30,7 +31,8 @@ mixin DevicesBase on EntitiesBase<EntityData, EntityDataQuery> {
|
||||
if (profile.defaultDashboardId != null) {
|
||||
var dashboardId = profile.defaultDashboardId!.id!;
|
||||
var state = Utils.createDashboardEntityState(device.entityId, entityName: device.field('name')!, entityLabel: device.field('label')!);
|
||||
navigateTo('/dashboard/$dashboardId?title=${device.field('name')!}&state=$state');
|
||||
// navigateTo('/dashboard/$dashboardId?title=${device.field('name')!}&state=$state');
|
||||
navigateToDashboard(dashboardId, dashboardTitle: device.field('name'), state: state);
|
||||
} else {
|
||||
// navigateTo('/device/${device.entityId.id}');
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
@@ -127,49 +129,37 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
|
||||
width: widget.listWidgetCard ? 58 : 60,
|
||||
height: widget.listWidgetCard ? 58 : 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFFEEEEEE),
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(widget.listWidgetCard ? 4 : 6))
|
||||
// color: Color(0xFFEEEEEE),
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(4))
|
||||
),
|
||||
child: FutureBuilder<DeviceProfileInfo>(
|
||||
future: deviceProfileFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var profile = snapshot.data!;
|
||||
Widget image;
|
||||
BoxFit imageFit;
|
||||
if (profile.image != null) {
|
||||
var uriData = UriData.parse(profile.image!);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(widget.listWidgetCard ? 4 : 6)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: Image.memory(uriData.contentAsBytes()),
|
||||
)
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x00000000),
|
||||
Color(0xb7000000)
|
||||
],
|
||||
stops: [0.4219, 1]
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
image = Image.memory(uriData.contentAsBytes());
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
return Center(
|
||||
child: Icon(Icons.devices_other, color: Color(0xFFC2C2C2))
|
||||
);
|
||||
image = Image.asset(ThingsboardImage.deviceProfilePlaceholder);
|
||||
imageFit = BoxFit.cover;
|
||||
}
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(4)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: imageFit,
|
||||
child: image,
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return Center(child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary)
|
||||
@@ -200,12 +190,12 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
|
||||
height: 20 / 14
|
||||
))
|
||||
),
|
||||
if (!widget.listWidgetCard) Text(widget.device.attribute('active') == 'true' ? 'Active' : 'Inactive',
|
||||
if (!widget.listWidgetCard) Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.device.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: widget.device.attribute('active') == 'true' ? Color(0xFF008A00) : Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
height: 12 /12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
),
|
||||
@@ -221,12 +211,12 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
)),
|
||||
if (!widget.listWidgetCard) Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.device.createdTime!)),
|
||||
if (!widget.listWidgetCard) Text(widget.device.attribute('active') == 'true' ? 'Active' : 'Inactive',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
color: widget.device.attribute('active') == 'true' ? Color(0xFF008A00) : Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
height: 16 / 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
))
|
||||
],
|
||||
)
|
||||
|
||||
98
lib/modules/device/devices_list_page.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_base.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_list.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class DevicesListPage extends TbPageWidget<DevicesListPage, _DevicesListPageState> {
|
||||
|
||||
final String? deviceType;
|
||||
final bool? active;
|
||||
final bool searchMode;
|
||||
|
||||
DevicesListPage(TbContext tbContext, {this.deviceType, this.active, this.searchMode = false}) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DevicesListPageState createState() => _DevicesListPageState();
|
||||
|
||||
}
|
||||
|
||||
class _DevicesListPageState extends TbPageState<DevicesListPage, _DevicesListPageState> {
|
||||
|
||||
late final DeviceQueryController _deviceQueryController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_deviceQueryController = DeviceQueryController(deviceType: widget.deviceType, active: widget.active);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var devicesList = DevicesList(tbContext, _deviceQueryController, searchMode: widget.searchMode, displayDeviceImage: widget.deviceType == null);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
tbContext,
|
||||
onSearch: (searchText) => _deviceQueryController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
String titleText = widget.deviceType != null ? widget.deviceType! : 'All devices';
|
||||
String? subTitleText;
|
||||
if (widget.active != null) {
|
||||
subTitleText = widget.active == true ? 'Active' : 'Inactive';
|
||||
}
|
||||
Column title = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(titleText, style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: subTitleText != null ? 16 : 20,
|
||||
height: subTitleText != null ? 20 / 16 : 24 / 20
|
||||
)),
|
||||
if (subTitleText != null)
|
||||
Text(subTitleText, style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline6!.color!.withAlpha((0.38 * 255).ceil()),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
);
|
||||
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: title,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
List<String> params = [];
|
||||
params.add('search=true');
|
||||
if (widget.deviceType != null) {
|
||||
params.add('deviceType=${widget.deviceType}');
|
||||
}
|
||||
if (widget.active != null) {
|
||||
params.add('active=${widget.active}');
|
||||
}
|
||||
navigateTo('/deviceList?${params.join('&')}');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: devicesList
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_deviceQueryController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,12 +14,18 @@ class DevicesMainPage extends TbContextWidget<DevicesMainPage, _DevicesMainPageS
|
||||
|
||||
}
|
||||
|
||||
class _DevicesMainPageState extends TbContextState<DevicesMainPage, _DevicesMainPageState> {
|
||||
class _DevicesMainPageState extends TbContextState<DevicesMainPage, _DevicesMainPageState> with AutomaticKeepAliveClientMixin<DevicesMainPage> {
|
||||
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
var deviceProfilesList = DeviceProfilesGrid(tbContext, _pageLinkController);
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_base.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_list.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_app/modules/device/device_profiles_grid.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class DevicesPage extends TbPageWidget<DevicesPage, _DevicesPageState> {
|
||||
|
||||
final String? deviceType;
|
||||
final bool? active;
|
||||
final bool searchMode;
|
||||
|
||||
DevicesPage(TbContext tbContext, {this.deviceType, this.active, this.searchMode = false}) : super(tbContext);
|
||||
DevicesPage(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
_DevicesPageState createState() => _DevicesPageState();
|
||||
@@ -20,79 +16,23 @@ class DevicesPage extends TbPageWidget<DevicesPage, _DevicesPageState> {
|
||||
|
||||
class _DevicesPageState extends TbPageState<DevicesPage, _DevicesPageState> {
|
||||
|
||||
late final DeviceQueryController _deviceQueryController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_deviceQueryController = DeviceQueryController(deviceType: widget.deviceType, active: widget.active);
|
||||
}
|
||||
final PageLinkController _pageLinkController = PageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var devicesList = DevicesList(tbContext, _deviceQueryController, searchMode: widget.searchMode, displayDeviceImage: widget.deviceType == null);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
tbContext,
|
||||
onSearch: (searchText) => _deviceQueryController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
String titleText = widget.deviceType != null ? widget.deviceType! : 'All devices';
|
||||
String? subTitleText;
|
||||
if (widget.active != null) {
|
||||
subTitleText = widget.active == true ? 'Active' : 'Inactive';
|
||||
}
|
||||
Column title = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(titleText, style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: subTitleText != null ? 16 : 20,
|
||||
height: subTitleText != null ? 20 / 16 : 24 / 20
|
||||
)),
|
||||
if (subTitleText != null)
|
||||
Text(subTitleText, style: TextStyle(
|
||||
color: Color(0x61FFFFFF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
);
|
||||
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: title,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
List<String> params = [];
|
||||
params.add('search=true');
|
||||
if (widget.deviceType != null) {
|
||||
params.add('deviceType=${widget.deviceType}');
|
||||
}
|
||||
if (widget.active != null) {
|
||||
params.add('active=${widget.active}');
|
||||
}
|
||||
navigateTo('/deviceList?${params.join('&')}');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
var deviceProfilesList = DeviceProfilesGrid(tbContext, _pageLinkController);
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: devicesList
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Text(deviceProfilesList.title)
|
||||
),
|
||||
body: deviceProfilesList
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_deviceQueryController.dispose();
|
||||
_pageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_list_widget.dart';
|
||||
@@ -45,8 +47,15 @@ class _HomePageState extends TbContextState<HomePage, _HomePageState> with Autom
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
elevation: dashboardState ? 0 : null,
|
||||
title: const Text('Home'),
|
||||
elevation: dashboardState ? 0 : 8,
|
||||
title: Center(
|
||||
child: Container(
|
||||
height: 24,
|
||||
child: SvgPicture.asset(ThingsboardImage.thingsBoardWithTitle,
|
||||
color: Theme.of(context).primaryColor,
|
||||
semanticsLabel: 'ThingsBoard Logo')
|
||||
)
|
||||
),
|
||||
),
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
@@ -61,8 +70,7 @@ class _HomePageState extends TbContextState<HomePage, _HomePageState> with Autom
|
||||
}
|
||||
|
||||
Widget _buildDashboardHome(BuildContext context, HomeDashboardInfo dashboard) {
|
||||
return dashboardUi.Dashboard(tbContext, dashboardId: dashboard.dashboardId!.id!,
|
||||
fullscreen: false, home: true, hideToolbar: dashboard.hideDashboardToolbar);
|
||||
return HomeDashboard(tbContext, dashboard);
|
||||
}
|
||||
|
||||
Widget _buildDefaultHome(BuildContext context) {
|
||||
@@ -108,3 +116,29 @@ class _HomePageState extends TbContextState<HomePage, _HomePageState> with Autom
|
||||
];
|
||||
} */
|
||||
}
|
||||
|
||||
class HomeDashboard extends TbContextWidget<HomeDashboard, _HomeDashboardState> {
|
||||
|
||||
final HomeDashboardInfo dashboard;
|
||||
|
||||
HomeDashboard(TbContext tbContext, this.dashboard) : super(tbContext);
|
||||
|
||||
@override
|
||||
_HomeDashboardState createState() => _HomeDashboardState();
|
||||
|
||||
}
|
||||
|
||||
class _HomeDashboardState extends TbContextState<HomeDashboard, _HomeDashboardState> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return dashboardUi.Dashboard(tbContext,
|
||||
home: true,
|
||||
controllerCallback: (controller) {
|
||||
controller.openDashboard(widget.dashboard.dashboardId!.id!,
|
||||
hideToolbar: widget.dashboard.hideDashboardToolbar);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/modules/alarm/alarms_page.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_main_page.dart';
|
||||
import 'package:thingsboard_app/modules/device/devices_page.dart';
|
||||
import 'package:thingsboard_app/modules/home/home_page.dart';
|
||||
import 'package:thingsboard_app/modules/more/more_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
@@ -97,12 +95,7 @@ class MainPage extends TbPageWidget<MainPage, _MainPageState> {
|
||||
final String _path;
|
||||
|
||||
MainPage(TbContext tbContext, {required String path}):
|
||||
_path = path, super(tbContext) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Theme.of(tbContext.currentState!.context).colorScheme.primary,
|
||||
systemNavigationBarIconBrightness: Brightness.dark
|
||||
));
|
||||
}
|
||||
_path = path, super(tbContext);
|
||||
|
||||
@override
|
||||
_MainPageState createState() => _MainPageState();
|
||||
@@ -122,9 +115,24 @@ class _MainPageState extends TbPageState<MainPage, _MainPageState> with TbMainSt
|
||||
int currentIndex = _indexFromPath(widget._path);
|
||||
_tabController = TabController(initialIndex: currentIndex, length: _tabItems.length, vsync: this);
|
||||
_currentIndexNotifier = ValueNotifier(currentIndex);
|
||||
_tabController.addListener(() {
|
||||
_currentIndexNotifier.value = _tabController.index;
|
||||
});
|
||||
_tabController.animation!.addListener(_onTabAnimation);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.animation!.removeListener(_onTabAnimation);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onTabAnimation () {
|
||||
var value = _tabController.animation!.value;
|
||||
var targetIndex;
|
||||
if (value >= _tabController.previousIndex) {
|
||||
targetIndex = value.round();
|
||||
} else {
|
||||
targetIndex = value.floor();
|
||||
}
|
||||
_currentIndexNotifier.value = targetIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -142,25 +150,18 @@ class _MainPageState extends TbPageState<MainPage, _MainPageState> with TbMainSt
|
||||
controller: _tabController,
|
||||
children: _tabItems.map((item) => item.page).toList(),
|
||||
),
|
||||
bottomNavigationBar: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
canvasColor: Theme.of(context).colorScheme.primary
|
||||
bottomNavigationBar: ValueListenableBuilder<int>(
|
||||
valueListenable: _currentIndexNotifier,
|
||||
builder: (context, index, child) => BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
currentIndex: index,
|
||||
onTap: (int index) => _setIndex(index) /*_currentIndex = index*/,
|
||||
items: _tabItems.map((item) => BottomNavigationBarItem(
|
||||
icon: item.icon,
|
||||
label: item.title
|
||||
)).toList()
|
||||
),
|
||||
child: ValueListenableBuilder<int>(
|
||||
valueListenable: _currentIndexNotifier,
|
||||
builder: (context, index, child) => BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: Colors.white,
|
||||
unselectedItemColor: Colors.white.withAlpha(97),
|
||||
currentIndex: index,
|
||||
onTap: (int index) => _setIndex(index) /*_currentIndex = index*/,
|
||||
items: _tabItems.map((item) => BottomNavigationBarItem(
|
||||
icon: item.icon,
|
||||
label: item.title
|
||||
)).toList()
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -180,6 +181,11 @@ class _MainPageState extends TbPageState<MainPage, _MainPageState> with TbMainSt
|
||||
_setIndex(targetIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isHomePage() {
|
||||
return _tabController.index == 0;
|
||||
}
|
||||
|
||||
_setIndex(int index) {
|
||||
_tabController.index = index;
|
||||
}
|
||||
|
||||
@@ -4,11 +4,14 @@ import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class ProfilePage extends TbPageWidget<ProfilePage, _ProfilePageState> {
|
||||
|
||||
ProfilePage(TbContext tbContext) : super(tbContext);
|
||||
final bool _fullscreen;
|
||||
|
||||
ProfilePage(TbContext tbContext, {bool fullscreen = false}) : _fullscreen = fullscreen, super(tbContext);
|
||||
|
||||
@override
|
||||
_ProfilePageState createState() => _ProfilePageState();
|
||||
@@ -30,7 +33,17 @@ class _ProfilePageState extends TbPageState<ProfilePage, _ProfilePageState> {
|
||||
return Scaffold(
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: const Text('Profile')
|
||||
title: const Text('Profile'),
|
||||
actions: [
|
||||
if (widget._fullscreen) IconButton(
|
||||
icon: Icon(
|
||||
Icons.logout
|
||||
),
|
||||
onPressed: () {
|
||||
tbClient.logout();
|
||||
}
|
||||
)
|
||||
],
|
||||
),
|
||||
body: FutureBuilder<User>(
|
||||
future: userFuture,
|
||||
@@ -42,7 +55,9 @@ class _ProfilePageState extends TbPageState<ProfilePage, _ProfilePageState> {
|
||||
subtitle: Text('${user.firstName} ${user.lastName}'),
|
||||
);
|
||||
} else {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
return Center(child: TbProgressIndicator(
|
||||
size: 50.0,
|
||||
));
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -8,7 +8,8 @@ import 'profile_page.dart';
|
||||
class ProfileRoutes extends TbRoutes {
|
||||
|
||||
late var profileHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
return ProfilePage(tbContext);
|
||||
var fullscreen = params['fullscreen']?.first == 'true';
|
||||
return ProfilePage(tbContext, fullscreen: fullscreen);
|
||||
});
|
||||
|
||||
ProfileRoutes(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@@ -61,8 +61,11 @@ class _QrCodeScannerPageState extends TbPageState<QrCodeScannerPage, _QrCodeScan
|
||||
Positioned(
|
||||
child:
|
||||
AppBar(
|
||||
leading: Container(),
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.white
|
||||
),
|
||||
elevation: 0,
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
|
||||
@@ -3,12 +3,12 @@ import 'dart:async';
|
||||
import 'package:stream_transform/stream_transform.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/config/themes/tb_theme.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
|
||||
class TbAppBar extends TbContextWidget<TbAppBar, _TbAppBarState> implements PreferredSizeWidget {
|
||||
|
||||
final Widget? leading;
|
||||
final Widget? title;
|
||||
final List<Widget>? actions;
|
||||
final double? elevation;
|
||||
@@ -17,7 +17,7 @@ class TbAppBar extends TbContextWidget<TbAppBar, _TbAppBarState> implements Pref
|
||||
@override
|
||||
final Size preferredSize;
|
||||
|
||||
TbAppBar(TbContext tbContext, {this.title, this.actions, this.elevation,
|
||||
TbAppBar(TbContext tbContext, {this.leading, this.title, this.actions, this.elevation = 8,
|
||||
this.showLoadingIndicator = false}) :
|
||||
preferredSize = Size.fromHeight(kToolbarHeight + (showLoadingIndicator ? 4 : 0)),
|
||||
super(tbContext);
|
||||
@@ -64,9 +64,11 @@ class _TbAppBarState extends TbContextState<TbAppBar, _TbAppBarState> {
|
||||
|
||||
AppBar buildDefaultBar() {
|
||||
return AppBar(
|
||||
leading: widget.leading,
|
||||
title: widget.title,
|
||||
actions: widget.actions,
|
||||
elevation: widget.elevation,
|
||||
shadowColor: Color(0xFFFFFFFF).withAlpha(150),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -137,16 +139,17 @@ class _TbAppSearchBarState extends TbContextState<TbAppSearchBar, _TbAppSearchBa
|
||||
AppBar buildSearchBar() {
|
||||
return AppBar(
|
||||
centerTitle: true,
|
||||
title: Theme(
|
||||
data: tbDarkTheme,
|
||||
child: TextField(
|
||||
controller: _filter,
|
||||
cursorColor: Colors.white,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.only(left: 15, bottom: 11, top: 15, right: 15),
|
||||
hintText: widget.searchHint ?? 'Search',
|
||||
title: TextField(
|
||||
controller: _filter,
|
||||
autofocus: true,
|
||||
// cursorColor: Colors.white,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(
|
||||
color: Color(0xFF282828).withAlpha((255 * 0.38).ceil()),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 15, bottom: 11, top: 15, right: 15),
|
||||
hintText: widget.searchHint ?? 'Search',
|
||||
)
|
||||
),
|
||||
actions: [
|
||||
|
||||
86
lib/widgets/tb_progress_indicator.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:thingsboard_app/constants/assets_path.dart';
|
||||
|
||||
class TbProgressIndicator extends ProgressIndicator {
|
||||
|
||||
final double size;
|
||||
|
||||
const TbProgressIndicator({
|
||||
Key? key,
|
||||
this.size = 36.0,
|
||||
Animation<Color?>? valueColor,
|
||||
String? semanticsLabel,
|
||||
String? semanticsValue,
|
||||
}) : super(
|
||||
key: key,
|
||||
value: null,
|
||||
valueColor: valueColor,
|
||||
semanticsLabel: semanticsLabel,
|
||||
semanticsValue: semanticsValue,
|
||||
);
|
||||
|
||||
@override
|
||||
_TbProgressIndicatorState createState() => _TbProgressIndicatorState();
|
||||
|
||||
Color _getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).primaryColor;
|
||||
|
||||
}
|
||||
|
||||
class _TbProgressIndicatorState extends State<TbProgressIndicator> with SingleTickerProviderStateMixin {
|
||||
|
||||
late AnimationController _controller;
|
||||
late CurvedAnimation _rotation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
vsync: this, upperBound: 1, animationBehavior: AnimationBehavior.preserve);
|
||||
_rotation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
|
||||
_controller.repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(TbProgressIndicator oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (!_controller.isAnimating)
|
||||
_controller.repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
SvgPicture.asset(ThingsboardImage.thingsboardCenter,
|
||||
height: widget.size,
|
||||
width: widget.size,
|
||||
color: widget._getValueColor(context)),
|
||||
AnimatedBuilder(
|
||||
animation: _rotation,
|
||||
child: SvgPicture.asset(ThingsboardImage.thingsboardOuter,
|
||||
height: widget.size,
|
||||
width: widget.size,
|
||||
color: widget._getValueColor(context)),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Transform.rotate(
|
||||
angle: _rotation.value * pi * 2,
|
||||
child: child
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
134
lib/widgets/transition_indexed_stack.dart
Normal file
@@ -0,0 +1,134 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class TransitionIndexedStackController {
|
||||
|
||||
_TransitionIndexedStackState? _state;
|
||||
|
||||
setTransitionIndexedStackState(_TransitionIndexedStackState state) {
|
||||
_state = state;
|
||||
}
|
||||
|
||||
Future<bool> open(int index, {bool animate = true}) async {
|
||||
if (_state != null) {
|
||||
return _state!._open(index, animate: animate);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> close(int index, {bool animate = true}) async {
|
||||
if (_state != null) {
|
||||
return _state!._close(index, animate: animate);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int? get index => _state?._selectedIndex;
|
||||
|
||||
}
|
||||
|
||||
class TransitionIndexedStack extends StatefulWidget {
|
||||
final Widget first;
|
||||
final Widget second;
|
||||
final Duration duration;
|
||||
final TransitionIndexedStackController? controller;
|
||||
|
||||
const TransitionIndexedStack({
|
||||
Key? key,
|
||||
required this.first,
|
||||
required this.second,
|
||||
this.controller,
|
||||
this.duration = const Duration(milliseconds: 250)
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_TransitionIndexedStackState createState() => _TransitionIndexedStackState();
|
||||
|
||||
}
|
||||
|
||||
class _TransitionIndexedStackState extends State<TransitionIndexedStack> with TickerProviderStateMixin {
|
||||
|
||||
late List<Widget> _pages;
|
||||
List<AnimationController> _animationControllers = [];
|
||||
int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
widget.controller?.setTransitionIndexedStackState(this);
|
||||
final _duration = widget.duration;
|
||||
_animationControllers = [
|
||||
AnimationController(
|
||||
vsync: this,
|
||||
duration: _duration,
|
||||
),
|
||||
AnimationController(
|
||||
vsync: this,
|
||||
duration: _duration,
|
||||
)
|
||||
];
|
||||
_pages = [
|
||||
pageBuilder(UniqueKey(), widget.second, context, _animationControllers[1]),
|
||||
pageBuilder(UniqueKey(), widget.first, context, _animationControllers[0]),
|
||||
];
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<bool> _open(int index, {bool animate = true}) async {
|
||||
if (_selectedIndex != index) {
|
||||
_selectedIndex = index;
|
||||
setState(() {
|
||||
_pages = _pages.reversed.toList();
|
||||
});
|
||||
if (animate) {
|
||||
await _animationControllers[_selectedIndex].reverse(from: _animationControllers[_selectedIndex].upperBound);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> _close(int index, {bool animate = true}) async {
|
||||
if (_selectedIndex == index) {
|
||||
_selectedIndex = index == 1 ? 0 : 1;
|
||||
if (animate) {
|
||||
await _animationControllers[index].forward(from: _animationControllers[index].lowerBound);
|
||||
}
|
||||
setState(() {
|
||||
_pages = _pages.reversed.toList();
|
||||
});
|
||||
if (animate) {
|
||||
_animationControllers[index].value = _animationControllers[index].lowerBound;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationControllers.forEach((controller) => controller.dispose());
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
child: Stack(
|
||||
children: _pages,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget pageBuilder(Key key, Widget widget, BuildContext context, Animation<double> animation) {
|
||||
return SlideTransition(
|
||||
key: key,
|
||||
position: Tween<Offset>(
|
||||
begin: Offset.zero,
|
||||
end: const Offset(1, 0),
|
||||
).animate(animation),
|
||||
child: widget
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
10
pubspec.lock
@@ -190,7 +190,7 @@ packages:
|
||||
name: geolocator_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.0"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -356,7 +356,7 @@ packages:
|
||||
name: sliver_tools
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
version: "0.2.4"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -428,7 +428,7 @@ packages:
|
||||
name: url_launcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.4"
|
||||
version: "6.0.6"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -456,7 +456,7 @@ packages:
|
||||
name: url_launcher_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.0.1"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -487,4 +487,4 @@ packages:
|
||||
version: "3.1.0"
|
||||
sdks:
|
||||
dart: ">=2.12.0 <3.0.0"
|
||||
flutter: ">=1.26.0-17.6.pre"
|
||||
flutter: ">=2.0.0"
|
||||
|
||||