Merge remote-tracking branch 'origin/design_3.0_loan_module' into design_3.0_loan_module
# Conflicts: # lib/controllers/api_routes/urls.dartdesign_3.0_medical_gas
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="17" height="20" viewBox="0 0 17 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.82353 1.94999C3.67937 1.94999 1.94118 3.69608 1.94118 5.84998V11.2125C1.94118 14.7126 4.76573 17.55 8.25 17.55C11.7343 17.55 14.5588 14.7126 14.5588 11.2125V9.75003C14.5588 9.21155 14.9934 8.77503 15.5294 8.77503C16.0655 8.77503 16.5 9.21155 16.5 9.75003V11.2125C16.5 15.7896 12.8063 19.5 8.25 19.5C3.69365 19.5 0 15.7896 0 11.2125V5.84998C0 2.61913 2.60728 0 5.82353 0C9.03978 0 11.6471 2.61913 11.6471 5.84998V11.2125C11.6471 13.0971 10.1261 14.625 8.25 14.625C6.37386 14.625 4.85294 13.0971 4.85294 11.2125V7.31248C4.85294 6.774 5.28749 6.33748 5.82353 6.33748C6.35957 6.33748 6.79412 6.774 6.79412 7.31248V11.2125C6.79412 12.0202 7.44594 12.675 8.25 12.675C9.05406 12.675 9.70588 12.0202 9.70588 11.2125V5.84998C9.70588 3.69608 7.96769 1.94999 5.82353 1.94999Z" fill="#3DA5E5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 938 B |
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.57353 4.19999C7.42937 4.19999 5.69118 5.94608 5.69118 8.09998V13.4625C5.69118 16.9626 8.51573 19.8 12 19.8C15.4843 19.8 18.3088 16.9626 18.3088 13.4625V12C18.3088 11.4616 18.7434 11.025 19.2794 11.025C19.8155 11.025 20.25 11.4616 20.25 12V13.4625C20.25 18.0396 16.5563 21.75 12 21.75C7.44365 21.75 3.75 18.0396 3.75 13.4625V8.09998C3.75 4.86913 6.35728 2.25 9.57353 2.25C12.7898 2.25 15.3971 4.86913 15.3971 8.09998V13.4625C15.3971 15.3471 13.8761 16.875 12 16.875C10.1239 16.875 8.60294 15.3471 8.60294 13.4625V9.56248C8.60294 9.024 9.03749 8.58748 9.57353 8.58748C10.1096 8.58748 10.5441 9.024 10.5441 9.56248V13.4625C10.5441 14.2702 11.1959 14.925 12 14.925C12.8041 14.925 13.4559 14.2702 13.4559 13.4625V8.09998C13.4559 5.94608 11.7177 4.19999 9.57353 4.19999Z" fill="#7D859A"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 938 B |
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.833374 7.99992C0.833374 11.9579 4.04204 15.1666 8.00004 15.1666C11.958 15.1666 15.1667 11.9579 15.1667 7.99992C15.1667 4.04192 11.958 0.833252 8.00004 0.833252C4.04204 0.833252 0.833374 4.04192 0.833374 7.99992ZM11.1174 5.50858C11.3887 5.75724 11.4067 6.17924 11.158 6.45057L7.49138 10.4506C7.36872 10.5846 7.19604 10.6626 7.01471 10.6666C6.83271 10.6706 6.65737 10.5999 6.5287 10.4713L4.86204 8.80461C4.60137 8.54461 4.60137 8.1219 4.86204 7.8619C5.12204 7.60123 5.54471 7.60123 5.80471 7.8619L6.97937 9.03592L10.1754 5.54927C10.424 5.27793 10.846 5.25991 11.1174 5.50858Z" fill="#7D859A"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 747 B |
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.25 7C6.25 3.82436 8.82436 1.25 12 1.25C14.6852 1.25 16.9406 3.09067 17.5731 5.57911C17.6475 5.87191 17.6847 6.01832 17.5947 6.13416C17.5046 6.25 17.3425 6.25 17.0183 6.25H14C13.5858 6.25 13.25 6.58579 13.25 7C13.25 7.41421 13.5858 7.75 14 7.75H17.15C17.4328 7.75 17.5743 7.75 17.6621 7.83787C17.75 7.92574 17.75 8.06716 17.75 8.35V9.65C17.75 9.93284 17.75 10.0743 17.6621 10.1621C17.5743 10.25 17.4328 10.25 17.15 10.25H14C13.5858 10.25 13.25 10.5858 13.25 11C13.25 11.4142 13.5858 11.75 14 11.75H17.0183C17.3425 11.75 17.5046 11.75 17.5947 11.8658C17.6847 11.9817 17.6475 12.1281 17.5731 12.4209C16.9406 14.9093 14.6852 16.75 12 16.75C8.82436 16.75 6.25 14.1756 6.25 11V7Z" fill="#7D859A"/>
|
||||||
|
<path d="M5.19444 11.2115C5.19444 10.6805 4.75917 10.25 4.22222 10.25C3.68528 10.25 3.25 10.6805 3.25 11.2115C3.25 15.6659 6.65283 19.3343 11.0278 19.8126V20.8269H9.08333C8.54639 20.8269 8.11111 21.2574 8.11111 21.7885C8.11111 22.3195 8.54639 22.75 9.08333 22.75H14.9167C15.4536 22.75 15.8889 22.3195 15.8889 21.7885C15.8889 21.2574 15.4536 20.8269 14.9167 20.8269H12.9722V19.8126C17.3472 19.3343 20.75 15.6659 20.75 11.2115C20.75 10.6805 20.3147 10.25 19.7778 10.25C19.2408 10.25 18.8056 10.6805 18.8056 11.2115C18.8056 14.9288 15.7586 17.9423 12 17.9423C8.2414 17.9423 5.19444 14.9288 5.19444 11.2115Z" fill="#7D859A"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.6823 2.78682C12.7265 2.73772 11.2735 2.73773 9.3177 2.78682L9.24285 2.7887C7.73349 2.82654 6.49161 2.85768 5.49064 3.03184C4.42828 3.21668 3.53876 3.57665 2.78536 4.3322C2.0359 5.0838 1.67705 5.95955 1.49526 7.00541C1.32465 7.98693 1.29883 9.19814 1.26758 10.6641L1.26597 10.7395C1.24467 11.7377 1.24468 12.2678 1.26599 13.266L1.2676 13.3414C1.29884 14.8073 1.32466 16.0185 1.49527 17.0001C1.67707 18.0459 2.03592 18.9217 2.78537 19.6733C3.53878 20.4288 4.42829 20.7888 5.49066 20.9736C6.49161 21.1478 7.73348 21.1789 9.24282 21.2168L9.31772 21.2187C9.91661 21.2337 10.4684 21.2441 11.0018 21.2499C11.553 21.256 11.9998 20.8085 11.9998 20.2572C11.9998 19.7058 11.553 19.2594 11.0018 19.2532C10.4846 19.2474 9.94927 19.2373 9.36791 19.2227C7.76497 19.1824 6.67174 19.1525 5.83355 19.0066C5.04027 18.8686 4.5781 18.6421 4.20122 18.2641C3.82038 17.8822 3.59776 17.4261 3.46432 16.6585C3.32261 15.8432 3.29736 14.7839 3.26405 13.2234C3.24334 12.2536 3.24333 11.7519 3.26403 10.7821C3.28003 10.0328 3.29416 9.399 3.32023 8.85233C3.33957 8.44669 3.34924 8.24387 3.49608 8.1629C3.64292 8.08193 3.82343 8.18424 4.18445 8.38885L8.41985 10.7894C9.71946 11.526 10.8186 12.0027 11.9998 12.0027C13.181 12.0027 14.2801 11.526 15.5798 10.7894L19.8155 8.38864C20.1765 8.18402 20.3571 8.08172 20.5039 8.16269C20.6507 8.24366 20.6604 8.44648 20.6798 8.8521C20.7058 9.39883 20.72 10.0327 20.736 10.7821C20.7484 11.3637 20.7533 11.777 20.7509 12.2239C20.7478 12.7759 21.1833 13.2329 21.7351 13.2447C22.2868 13.2564 22.7457 12.8185 22.7492 12.2665C22.7522 11.7895 22.7471 11.3529 22.734 10.7395L22.7324 10.6641C22.7012 9.19819 22.6753 7.98692 22.5047 7.00542C22.3229 5.95955 21.9641 5.0838 21.2146 4.33221C20.7913 3.90762 20.3249 3.60795 19.809 3.39408C19.7305 3.35121 19.6464 3.3189 19.5587 3.29827C19.2289 3.18196 18.8796 3.09626 18.5093 3.03184C17.5084 2.85768 16.2665 2.82654 14.7571 2.78869L14.6823 2.78682Z" fill="#3DA5E5"/>
|
||||||
|
<path d="M12.75 17.5059C12.75 18.0581 13.1977 18.5059 13.75 18.5059L18.25 18.5059L18.25 19.0884C18.2499 19.264 18.2497 19.4797 18.2718 19.6561L18.2722 19.6595C18.288 19.7859 18.3598 20.362 18.9254 20.6363C19.4922 20.9113 19.9924 20.6094 20.1006 20.544L20.1046 20.5416C20.2577 20.4494 20.4285 20.3152 20.5691 20.2049L20.5993 20.1812C20.9752 19.8865 21.4594 19.5046 21.8504 19.1241C22.0455 18.9343 22.2467 18.717 22.4056 18.4861C22.5468 18.2809 22.75 17.9307 22.75 17.5C22.75 17.0693 22.5468 16.7191 22.4056 16.5139C22.2467 16.283 22.0455 16.0657 21.8504 15.8759C21.4594 15.4954 20.9752 15.1135 20.5993 14.8188L20.5691 14.7951C20.4285 14.6848 20.2576 14.5506 20.1046 14.4584L20.1006 14.456C19.9924 14.3907 19.4923 14.0887 18.9254 14.3637C18.3598 14.638 18.288 15.2141 18.2722 15.3405L18.2718 15.3439C18.2497 15.5203 18.2499 15.736 18.25 15.9116L18.25 16.5059L13.75 16.5059C13.1977 16.5059 12.75 16.9536 12.75 17.5059Z" fill="#3DA5E5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.99997 1.5C10.0349 1.5 11.8358 2.41647 13.0929 3.2848C13.7278 3.72335 14.2407 4.1612 14.5958 4.49009C14.769 4.65053 15.0521 4.94112 15.1554 5.04714L15.1633 5.05528C15.4089 5.32957 15.3856 5.75104 15.1114 5.99666C14.8372 6.2421 14.4162 6.21905 14.1705 5.94528C14.0888 5.86144 13.8492 5.61599 13.6897 5.46824C13.37 5.17214 12.9067 4.77665 12.3351 4.38186C11.1794 3.58353 9.6469 2.83333 7.99997 2.83333C6.35304 2.83333 4.82057 3.58353 3.66481 4.38186C3.09328 4.77665 2.6299 5.17214 2.31024 5.46824C2.15075 5.61599 1.91166 5.86086 1.82997 5.94471L1.82945 5.94528C1.58377 6.21905 1.16269 6.2421 0.888578 5.99666C0.614282 5.75104 0.59103 5.32957 0.836643 5.05528L0.844496 5.04722C0.947718 4.94126 1.23091 4.65057 1.40415 4.49009C1.7592 4.1612 2.27213 3.72335 2.90703 3.2848C4.16413 2.41647 5.965 1.5 7.99997 1.5Z" fill="#7D859A"/>
|
||||||
|
<path d="M7.99996 10.6666C7.26358 10.6666 6.66663 10.0697 6.66663 9.33329C6.66663 8.59691 7.26358 7.99996 7.99996 7.99996C8.73634 7.99996 9.33329 8.59691 9.33329 9.33329C9.33329 10.0697 8.73634 10.6666 7.99996 10.6666Z" fill="#7D859A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.68016 5.80862C4.81726 4.9209 6.28625 4.16663 7.99996 4.16663C9.71367 4.16663 11.1827 4.9209 12.3198 5.80861C13.4579 6.69713 14.3346 7.79593 14.8052 8.45578C14.9739 8.6905 15.1666 8.95849 15.1666 9.33329C15.1666 9.70809 14.9739 9.97608 14.8052 10.2108C14.3346 10.8707 13.4579 11.9695 12.3198 12.858C11.1827 13.7457 9.71367 14.5 7.99996 14.5C6.28625 14.5 4.81726 13.7457 3.68016 12.858C2.54205 11.9695 1.66532 10.8707 1.19475 10.2108C1.02598 9.97608 0.833293 9.7081 0.833293 9.33329C0.833293 8.95849 1.02598 8.6905 1.19475 8.45578C1.66532 7.79593 2.54205 6.69713 3.68016 5.80862ZM5.33329 9.33329C5.33329 10.8061 6.5272 12 7.99996 12C9.47272 12 10.6666 10.8061 10.6666 9.33329C10.6666 7.86053 9.47272 6.66663 7.99996 6.66663C6.5272 6.66663 5.33329 7.86053 5.33329 9.33329Z" fill="#7D859A"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11.0353 6.33778C11.2219 6.13422 11.2081 5.81794 11.0046 5.63134C10.801 5.44475 10.4847 5.4585 10.2981 5.66206L6.98433 9.27711L5.68693 7.9797C5.49167 7.78444 5.17508 7.78444 4.97982 7.9797C4.78456 8.17496 4.78456 8.49155 4.97982 8.68681L6.64649 10.3535C6.74292 10.4499 6.87456 10.5028 7.0109 10.4998C7.14725 10.4968 7.27647 10.4383 7.36862 10.3378L11.0353 6.33778Z" fill="#7D859A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.00004 15.1666C4.042 15.1666 0.833374 11.958 0.833374 7.99992C0.833374 4.04188 4.042 0.833252 8.00004 0.833252C11.9581 0.833252 15.1667 4.04188 15.1667 7.99992C15.1667 11.958 11.9581 15.1666 8.00004 15.1666ZM1.83337 7.99992C1.83337 11.4057 4.59428 14.1666 8.00004 14.1666C11.4058 14.1666 14.1667 11.4057 14.1667 7.99992C14.1667 4.59416 11.4058 1.83325 8.00004 1.83325C4.59428 1.83325 1.83337 4.59416 1.83337 7.99992Z" fill="#7D859A"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 981 B |
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15.9948 1.66669C17.3938 1.66669 18.4958 2.7234 19.1994 4.14924L21.5488 8.8869C21.62 9.03352 21.7889 9.24 22.0428 9.42887C22.2964 9.61755 22.5447 9.72164 22.7081 9.74909L26.961 10.4615C28.4972 10.7197 29.7847 11.4727 30.2028 12.7839C30.6204 14.094 30.0085 15.4555 28.9038 16.5622L28.9027 16.5633L25.5988 19.8945C25.4679 20.0265 25.3212 20.2753 25.2292 20.5993C25.1378 20.9211 25.1297 21.2143 25.1711 21.4037L25.1717 21.4063L26.117 25.5272C26.509 27.2424 26.3791 28.9431 25.1694 29.8323C23.9556 30.7245 22.2969 30.329 20.79 29.4315L16.8034 27.052C16.636 26.952 16.3485 26.871 16.0015 26.871C15.657 26.871 15.3634 26.9509 15.1851 27.0547L15.1826 27.0561L11.2038 29.431C9.69873 30.3316 8.04205 30.72 6.82814 29.827C5.61932 28.9376 5.48286 27.2401 5.87615 25.5263L6.8212 21.4063L6.82178 21.4037C6.86321 21.2143 6.8551 20.9211 6.76373 20.5993C6.67174 20.2753 6.52505 20.0265 6.3941 19.8945L3.0878 16.5609C1.9902 15.4542 1.38028 14.0939 1.7946 12.7857C2.21013 11.4737 3.49524 10.7198 5.03244 10.4614L9.28176 9.74961L9.28311 9.74938C9.43889 9.72235 9.68361 9.61943 9.93664 9.43027C10.1901 9.24074 10.3595 9.03383 10.4308 8.8869L10.4344 8.87958L12.7808 4.14798L12.7818 4.14612C13.4919 2.72146 14.5974 1.66669 15.9948 1.66669Z" fill="#7D859A"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15.9948 1.66669C17.3938 1.66669 18.4958 2.7234 19.1994 4.14924L21.5488 8.8869C21.62 9.03352 21.7889 9.24 22.0428 9.42887C22.2964 9.61755 22.5447 9.72164 22.7081 9.74909L26.961 10.4615C28.4972 10.7197 29.7847 11.4727 30.2028 12.7839C30.6204 14.094 30.0085 15.4555 28.9038 16.5622L28.9027 16.5633L25.5988 19.8945C25.4679 20.0265 25.3212 20.2753 25.2292 20.5993C25.1378 20.9211 25.1297 21.2143 25.1711 21.4037L25.1717 21.4063L26.117 25.5272C26.509 27.2424 26.3791 28.9431 25.1694 29.8323C23.9556 30.7245 22.2969 30.329 20.79 29.4315L16.8034 27.052C16.636 26.952 16.3485 26.871 16.0015 26.871C15.657 26.871 15.3634 26.9509 15.1851 27.0547L15.1826 27.0561L11.2038 29.431C9.69873 30.3316 8.04205 30.72 6.82814 29.827C5.61932 28.9376 5.48286 27.2401 5.87615 25.5263L6.8212 21.4063L6.82178 21.4037C6.86321 21.2143 6.8551 20.9211 6.76373 20.5993C6.67174 20.2753 6.52505 20.0265 6.3941 19.8945L3.0878 16.5609C1.9902 15.4542 1.38028 14.0939 1.7946 12.7857C2.21013 11.4737 3.49524 10.7198 5.03244 10.4614L9.28176 9.74961L9.28311 9.74938C9.43889 9.72235 9.68361 9.61943 9.93664 9.43027C10.1901 9.24074 10.3595 9.03383 10.4308 8.8869L10.4344 8.87958L12.7808 4.14798L12.7818 4.14612C13.4919 2.72146 14.5974 1.66669 15.9948 1.66669Z" fill="#FFB522"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,295 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:http/io_client.dart';
|
||||||
|
// ignore_for_file: avoid_annotating_with_dynamic
|
||||||
|
|
||||||
|
typedef FactoryConstructor<U> = U Function(dynamic);
|
||||||
|
|
||||||
|
class APIError {
|
||||||
|
dynamic errorCode;
|
||||||
|
int? errorType;
|
||||||
|
String? errorMessage;
|
||||||
|
int? errorStatusCode;
|
||||||
|
|
||||||
|
APIError(this.errorCode, this.errorMessage, this.errorType, this.errorStatusCode);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {'errorCode': errorCode, 'errorMessage': errorMessage, 'errorType': errorType, 'ErrorStatusCode': errorStatusCode};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return jsonEncode(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
APIException _throwAPIException(Response response) {
|
||||||
|
switch (response.statusCode) {
|
||||||
|
case 200:
|
||||||
|
APIError? apiError;
|
||||||
|
if (response.body != null && response.body.isNotEmpty) {
|
||||||
|
var jsonError = jsonDecode(response.body);
|
||||||
|
print(jsonError);
|
||||||
|
apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']);
|
||||||
|
}
|
||||||
|
return APIException(APIException.BAD_REQUEST, error: apiError);
|
||||||
|
case 400:
|
||||||
|
APIError? apiError;
|
||||||
|
if (response.body != null && response.body.isNotEmpty) {
|
||||||
|
var jsonError = jsonDecode(response.body);
|
||||||
|
apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']);
|
||||||
|
}
|
||||||
|
return APIException(APIException.BAD_REQUEST, error: apiError);
|
||||||
|
case 401:
|
||||||
|
return APIException(APIException.UNAUTHORIZED);
|
||||||
|
case 403:
|
||||||
|
return APIException(APIException.FORBIDDEN);
|
||||||
|
case 404:
|
||||||
|
return APIException(APIException.NOT_FOUND);
|
||||||
|
case 500:
|
||||||
|
return APIException(APIException.INTERNAL_SERVER_ERROR);
|
||||||
|
case 444:
|
||||||
|
var downloadUrl = response.headers["location"];
|
||||||
|
return APIException(APIException.UPGRADE_REQUIRED, arguments: downloadUrl);
|
||||||
|
default:
|
||||||
|
return APIException(APIException.OTHER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApiClient {
|
||||||
|
static final ApiClient _instance = ApiClient._internal();
|
||||||
|
|
||||||
|
ApiClient._internal();
|
||||||
|
|
||||||
|
factory ApiClient() => _instance;
|
||||||
|
|
||||||
|
Future<U> postJsonForObject<T, U>(FactoryConstructor<U> factoryConstructor, String url, T jsonObject,
|
||||||
|
{String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0, bool isFormData = false}) async {
|
||||||
|
var _headers = {'Accept': 'application/json'};
|
||||||
|
if (headers != null && headers.isNotEmpty) {
|
||||||
|
_headers.addAll(headers);
|
||||||
|
}
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
print("Url:$url");
|
||||||
|
var bodyJson = json.encode(jsonObject);
|
||||||
|
print("body:$bodyJson");
|
||||||
|
}
|
||||||
|
var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes, isFormData: isFormData);
|
||||||
|
try {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
// logger.i("Url: " + url);
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonData = jsonDecode(response.body);
|
||||||
|
if (jsonData["IsAuthenticated"] != null) {
|
||||||
|
// AppState().setIsAuthenticated = jsonData["IsAuthenticated"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// if(url.contains("GetOfferDiscountsConfigData")) {
|
||||||
|
// jsonData["ErrorMessage"] = "Service Not Available";
|
||||||
|
// jsonData["ErrorEndUserMessage"] = "Service Not Available";
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (jsonData["ErrorMessage"] == null) {
|
||||||
|
return factoryConstructor(jsonData);
|
||||||
|
} else if (jsonData["MessageStatus"] == 2 && jsonData["IsOTPMaxLimitExceed"] == true) {
|
||||||
|
// await Utils.performLogout(AppRoutes.navigatorKey.currentContext, null);
|
||||||
|
throw const APIException(APIException.UNAUTHORIZED, error: null);
|
||||||
|
} else {
|
||||||
|
APIError? apiError;
|
||||||
|
apiError = APIError(jsonData['ErrorCode'], jsonData['ErrorEndUserMessage'], jsonData['ErrorType'] ?? 0, jsonData['ErrorStatusCode']);
|
||||||
|
throw APIException(APIException.BAD_REQUEST, error: apiError);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex is APIException) {
|
||||||
|
rethrow;
|
||||||
|
} else {
|
||||||
|
throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> postJsonForResponse<T>(String url, T jsonObject,
|
||||||
|
{String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0, bool isFormData = false}) async {
|
||||||
|
String? requestBody;
|
||||||
|
late Map<String, String> stringObj;
|
||||||
|
if (jsonObject != null) {
|
||||||
|
requestBody = jsonEncode(jsonObject);
|
||||||
|
if (headers == null) {
|
||||||
|
headers = {'Content-Type': 'application/json'};
|
||||||
|
} else {
|
||||||
|
headers['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFormData) {
|
||||||
|
headers = {'Content-Type': 'application/x-www-form-urlencoded'};
|
||||||
|
stringObj = ((jsonObject ?? {}) as Map<String, dynamic>).map((key, value) => MapEntry(key, value?.toString() ?? ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _postForResponse(url, isFormData ? stringObj : requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> _postForResponse(String url, requestBody, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
|
||||||
|
try {
|
||||||
|
var _headers = <String, String>{};
|
||||||
|
if (token != null) {
|
||||||
|
_headers['Authorization'] = 'Bearer $token';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers != null && headers.isNotEmpty) {
|
||||||
|
_headers.addAll(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryParameters != null) {
|
||||||
|
var queryString = new Uri(queryParameters: queryParameters).query;
|
||||||
|
url = url + '?' + queryString;
|
||||||
|
}
|
||||||
|
var response = await _post(Uri.parse(url), body: requestBody, headers: _headers).timeout(Duration(seconds: 120));
|
||||||
|
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
throw _throwAPIException(response);
|
||||||
|
}
|
||||||
|
} on SocketException catch (e) {
|
||||||
|
if (retryTimes > 0) {
|
||||||
|
print('will retry after 3 seconds...');
|
||||||
|
await Future.delayed(Duration(seconds: 3));
|
||||||
|
return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
|
||||||
|
} else {
|
||||||
|
throw APIException(APIException.OTHER, arguments: e);
|
||||||
|
}
|
||||||
|
} on HttpException catch (e) {
|
||||||
|
if (retryTimes > 0) {
|
||||||
|
print('will retry after 3 seconds...');
|
||||||
|
await Future.delayed(Duration(seconds: 3));
|
||||||
|
return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
|
||||||
|
} else {
|
||||||
|
throw APIException(APIException.OTHER, arguments: e);
|
||||||
|
}
|
||||||
|
} on TimeoutException catch (e) {
|
||||||
|
throw APIException(APIException.TIMEOUT, arguments: e);
|
||||||
|
} on ClientException catch (e) {
|
||||||
|
if (retryTimes > 0) {
|
||||||
|
print('will retry after 3 seconds...');
|
||||||
|
await Future.delayed(Duration(seconds: 3));
|
||||||
|
return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
|
||||||
|
} else {
|
||||||
|
throw APIException(APIException.OTHER, arguments: e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> getJsonForResponse<T>(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
|
||||||
|
if (headers == null) {
|
||||||
|
headers = {'Content-Type': 'application/json'};
|
||||||
|
} else {
|
||||||
|
headers['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
print("Url:$url");
|
||||||
|
// var bodyJson = json.encode(jsonObject);
|
||||||
|
// print("body:$bodyJson");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> _getForResponse(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
|
||||||
|
try {
|
||||||
|
var _headers = <String, String>{};
|
||||||
|
if (token != null) {
|
||||||
|
_headers['Authorization'] = 'Bearer $token';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers != null && headers.isNotEmpty) {
|
||||||
|
_headers.addAll(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryParameters != null) {
|
||||||
|
var queryString = new Uri(queryParameters: queryParameters).query;
|
||||||
|
url = url + '?' + queryString;
|
||||||
|
}
|
||||||
|
var response = await _get(Uri.parse(url), headers: _headers).timeout(Duration(seconds: 60));
|
||||||
|
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
throw _throwAPIException(response);
|
||||||
|
}
|
||||||
|
} on SocketException catch (e) {
|
||||||
|
if (retryTimes > 0) {
|
||||||
|
print('will retry after 3 seconds...');
|
||||||
|
await Future.delayed(Duration(seconds: 3));
|
||||||
|
return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
|
||||||
|
} else {
|
||||||
|
throw APIException(APIException.OTHER, arguments: e);
|
||||||
|
}
|
||||||
|
} on HttpException catch (e) {
|
||||||
|
if (retryTimes > 0) {
|
||||||
|
print('will retry after 3 seconds...');
|
||||||
|
await Future.delayed(Duration(seconds: 3));
|
||||||
|
return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
|
||||||
|
} else {
|
||||||
|
throw APIException(APIException.OTHER, arguments: e);
|
||||||
|
}
|
||||||
|
} on TimeoutException catch (e) {
|
||||||
|
throw APIException(APIException.TIMEOUT, arguments: e);
|
||||||
|
} on ClientException catch (e) {
|
||||||
|
if (retryTimes > 0) {
|
||||||
|
print('will retry after 3 seconds...');
|
||||||
|
await Future.delayed(Duration(seconds: 3));
|
||||||
|
return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
|
||||||
|
} else {
|
||||||
|
throw APIException(APIException.OTHER, arguments: e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> _get(url, {Map<String, String>? headers}) => _withClient((client) => client.get(url, headers: headers));
|
||||||
|
|
||||||
|
bool _certificateCheck(X509Certificate cert, String host, int port) => true;
|
||||||
|
|
||||||
|
Future<T> _withClient<T>(Future<T> Function(Client) fn) async {
|
||||||
|
var httpClient = HttpClient()..badCertificateCallback = _certificateCheck;
|
||||||
|
var client = IOClient(httpClient);
|
||||||
|
try {
|
||||||
|
return await fn(client);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> _post(url, {Map<String, String>? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding));
|
||||||
|
}
|
||||||
|
|
||||||
|
class APIException implements Exception {
|
||||||
|
static const String BAD_REQUEST = 'api_common_bad_request';
|
||||||
|
static const String UNAUTHORIZED = 'api_common_unauthorized';
|
||||||
|
static const String FORBIDDEN = 'api_common_forbidden';
|
||||||
|
static const String NOT_FOUND = 'api_common_not_found';
|
||||||
|
static const String INTERNAL_SERVER_ERROR = 'api_common_internal_server_error';
|
||||||
|
static const String UPGRADE_REQUIRED = 'api_common_upgrade_required';
|
||||||
|
static const String BAD_RESPONSE_FORMAT = 'api_common_bad_response_format';
|
||||||
|
static const String OTHER = 'api_common_http_error';
|
||||||
|
static const String TIMEOUT = 'api_common_http_timeout';
|
||||||
|
static const String UNKNOWN = 'unexpected_error';
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
final APIError? error;
|
||||||
|
final arguments;
|
||||||
|
|
||||||
|
const APIException(this.message, {this.arguments, this.error});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {'message': message, 'error': error, 'arguments': '$arguments'};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return jsonEncode(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,480 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/api_manager.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||||
|
import 'package:test_sa/controllers/notification/firebase_notification_manger.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/model/chat_attachment_model.dart';
|
||||||
|
import 'api_client.dart';
|
||||||
|
import 'model/chat_login_response_model.dart';
|
||||||
|
import 'model/chat_participant_model.dart';
|
||||||
|
import 'model/get_search_user_chat_model.dart';
|
||||||
|
import 'model/get_single_user_chat_list_model.dart';
|
||||||
|
import 'model/get_user_login_token_model.dart' as userLoginTokenModel;
|
||||||
|
import 'model/user_chat_history_model.dart';
|
||||||
|
|
||||||
|
// import 'package:mohem_flutter_app/api/api_client.dart';
|
||||||
|
// import 'package:mohem_flutter_app/app_state/app_state.dart';
|
||||||
|
// import 'package:mohem_flutter_app/classes/consts.dart';
|
||||||
|
// import 'package:mohem_flutter_app/classes/utils.dart';
|
||||||
|
// import 'package:mohem_flutter_app/exceptions/api_exception.dart';
|
||||||
|
// import 'package:mohem_flutter_app/main.dart';
|
||||||
|
// import 'package:mohem_flutter_app/models/chat/chat_user_image_model.dart';
|
||||||
|
// import 'package:mohem_flutter_app/models/chat/create_group_request.dart' as createGroup;
|
||||||
|
// import 'package:mohem_flutter_app/models/chat/get_group_chat_history.dart';
|
||||||
|
// import 'package:mohem_flutter_app/models/chat/get_search_user_chat_model.dart';
|
||||||
|
// import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart' as groups;
|
||||||
|
// import 'package:mohem_flutter_app/models/chat/get_user_groups_by_id.dart';
|
||||||
|
// import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart' as user;
|
||||||
|
// import 'package:mohem_flutter_app/models/chat/make_user_favotire_unfavorite_chat_model.dart' as fav;
|
||||||
|
|
||||||
|
class ChatApiClient {
|
||||||
|
static final ChatApiClient _instance = ChatApiClient._internal();
|
||||||
|
|
||||||
|
ChatApiClient._internal();
|
||||||
|
|
||||||
|
factory ChatApiClient() => _instance;
|
||||||
|
|
||||||
|
ChatLoginResponse? chatLoginResponse;
|
||||||
|
|
||||||
|
Future<ChatLoginResponse?> getChatLoginToken(int moduleId, int requestId, String title, String employeeNumber, String assigneeEmployeeNumber) async {
|
||||||
|
String platform;
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
platform = "IOS";
|
||||||
|
} else {
|
||||||
|
if (await FirebaseNotificationManger.isGoogleServicesAvailable()) {
|
||||||
|
platform = "GOOGLE";
|
||||||
|
} else {
|
||||||
|
platform = "HUAWEI";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Response response = await ApiClient().postJsonForResponse(URLs.chatSdkToken, {
|
||||||
|
"apiKey": URLs.chatApiKey,
|
||||||
|
"employeeNumber": employeeNumber,
|
||||||
|
"assigneeEmployeeNumber": assigneeEmployeeNumber,
|
||||||
|
"userDetails": {"userName": ApiManager.instance.user?.username, "email": ApiManager.instance.user?.email},
|
||||||
|
"contextEnabled": true,
|
||||||
|
"moduleCode": moduleId.toString(),
|
||||||
|
"referenceId": requestId.toString(),
|
||||||
|
// "referenceType": "ticket",
|
||||||
|
"title": title,
|
||||||
|
"deviceToken": FirebaseNotificationManger.token,
|
||||||
|
"isHuaweiDevice": platform == "HUAWEI",
|
||||||
|
"platform": platform,
|
||||||
|
"voIPToken": null
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
// logger.i("login-res: " + response.body);
|
||||||
|
}
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
chatLoginResponse = ChatLoginResponse.fromJson(jsonDecode(response.body));
|
||||||
|
}
|
||||||
|
return chatLoginResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ChatParticipantModel?> loadParticipants(int moduleId, int referenceId, String? assigneeEmployeeNumber) async {
|
||||||
|
Response response =
|
||||||
|
await ApiClient().getJsonForResponse("${URLs.chatHubUrlApi}/chat/context/$moduleId/$referenceId?assigneeEmployeeNumber=$assigneeEmployeeNumber", token: chatLoginResponse!.token);
|
||||||
|
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
// logger.i("login-res: " + response.body);
|
||||||
|
}
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return ChatParticipantModel.fromJson(jsonDecode(response.body));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ChatAttachment>> viewAllDocuments(int moduleId, int referenceId) async {
|
||||||
|
Response response = await ApiClient().getJsonForResponse("${URLs.chatHubUrlApi}/attachments/conversation?referenceId=$referenceId&moduleCode=$moduleId", token: chatLoginResponse!.token);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
List data = jsonDecode(response.body)["response"];
|
||||||
|
return data.map((elemet) => ChatAttachment.fromJson(elemet)).toList();
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<SingleUserChatModel>> loadChatHistory(int moduleId, int referenceId, String myId, String otherId) async {
|
||||||
|
Response response = await ApiClient().postJsonForResponse(
|
||||||
|
"${URLs.chatHubUrlApi}/UserChatHistory/GetUserChatHistory/$myId/$otherId/0", {"moduleCode": moduleId.toString(), "referenceId": referenceId.toString()},
|
||||||
|
token: chatLoginResponse!.token);
|
||||||
|
try {
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
List data = jsonDecode(response.body);
|
||||||
|
return data.map((elemet) => SingleUserChatModel.fromJson(elemet)).toList();
|
||||||
|
|
||||||
|
// return UserChatHistoryModel.fromJson(jsonDecode(response.body));
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Future<ChatResponse?> sendTextMessage(String message, int conversationId) async {
|
||||||
|
try {
|
||||||
|
Response response =
|
||||||
|
await ApiClient().postJsonForResponse("${URLs.chatHubUrlApi}/chat/conversations/$conversationId/messages", {"content": message, "messageType": "Text"}, token: chatLoginResponse!.token);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return ChatResponse.fromJson(jsonDecode(response.body));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
print(ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Future<ChatUserModel> getChatMemberFromSearch(String searchParam, int cUserId, int pageNo) async {
|
||||||
|
// ChatUserModel chatUserModel;
|
||||||
|
// Response response = await ApiClient().postJsonForResponse("${ApiConsts.chatLoginTokenUrl}getUserWithStatusAndFavAsync", {"employeeNumber": cUserId, "userName": searchParam, "pageNumber": pageNo},
|
||||||
|
// token: AppState().chatDetails!.response!.token);
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// chatUserModel = chatUserModelFromJson(response.body);
|
||||||
|
// return chatUserModel;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //Get User Recent Chats
|
||||||
|
// Future<ChatUserModel> getRecentChats() async {
|
||||||
|
// try {
|
||||||
|
// Response response =
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// // await ApiManager.instance.get(URLs.getAllRequestsAndCount,h);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// await ApiClient().getJsonForResponse(
|
||||||
|
// "${ApiConsts.chatRecentUrl}getchathistorybyuserid",
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// return ChatUserModel.fromJson(
|
||||||
|
// json.decode(response.body),
|
||||||
|
// );
|
||||||
|
// } catch (e) {
|
||||||
|
// throw e;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Get Favorite Users
|
||||||
|
// Future<ChatUserModel> getFavUsers() async {
|
||||||
|
// Response favRes = await ApiClient().getJsonForResponse(
|
||||||
|
// "${ApiConsts.chatFavUser}getFavUserById/${AppState().chatDetails!.response!.id}",
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + favRes.body);
|
||||||
|
// }
|
||||||
|
// return ChatUserModel.fromJson(json.decode(favRes.body));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //Get User Chat History
|
||||||
|
// Future<Response> getSingleUserChatHistory({required int senderUID, required int receiverUID, required bool loadMore, bool isNewChat = false, required int paginationVal}) async {
|
||||||
|
// try {
|
||||||
|
// Response response = await ApiClient().getJsonForResponse(
|
||||||
|
// "${ApiConsts.chatSingleUserHistoryUrl}GetUserChatHistory/$senderUID/$receiverUID/$paginationVal",
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// return response;
|
||||||
|
// } catch (e) {
|
||||||
|
// getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal);
|
||||||
|
// throw e;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //Favorite Users
|
||||||
|
// Future<fav.FavoriteChatUser> favUser({required int userID, required int targetUserID}) async {
|
||||||
|
// Response response = await ApiClient().postJsonForResponse("${ApiConsts.chatFavUser}addFavUser", {"targetUserId": targetUserID, "userId": userID}, token: AppState().chatDetails!.response!.token);
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body);
|
||||||
|
// return favoriteChatUser;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //UnFavorite Users
|
||||||
|
// Future<fav.FavoriteChatUser> unFavUser({required int userID, required int targetUserID}) async {
|
||||||
|
// try {
|
||||||
|
// Response response = await ApiClient().postJsonForResponse(
|
||||||
|
// "${ApiConsts.chatFavUser}deleteFavUser",
|
||||||
|
// {"targetUserId": targetUserID, "userId": userID},
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body);
|
||||||
|
// return favoriteChatUser;
|
||||||
|
// } catch (e) {
|
||||||
|
// e as APIException;
|
||||||
|
// throw e;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Upload Chat Media
|
||||||
|
Future<Object?> uploadMedia(String userId, File file, String fileSource, {Map? jsonData}) async {
|
||||||
|
dynamic request = MultipartRequest('POST', Uri.parse('${URLs.chatHubUrlApi}/attachments/upload'));
|
||||||
|
request.fields.addAll({'userId': userId, 'fileSource': fileSource});
|
||||||
|
if (jsonData != null) {
|
||||||
|
request.fields.addAll(jsonData);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.files.add(await MultipartFile.fromPath('file', file.path));
|
||||||
|
request.headers.addAll({'Authorization': 'Bearer ${chatLoginResponse!.token}'});
|
||||||
|
StreamedResponse response = await request.send();
|
||||||
|
String data = await response.stream.bytesToString();
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
print("uploadMedia: $data");
|
||||||
|
}
|
||||||
|
return jsonDecode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Download File For Chat
|
||||||
|
// Future<Uint8List> downloadURL({required String fileName, required String fileTypeDescription, required int fileSource,required String url}) async {
|
||||||
|
// Response response = await ApiClient().postJsonForResponse(
|
||||||
|
// "${ApiConsts.chatMediaImageUploadUrl}download",
|
||||||
|
// {"fileType": fileTypeDescription, "fileName": fileName, "fileSource": fileSource},
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// Uint8List data = Uint8List.fromList(response.bodyBytes);
|
||||||
|
// return data;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Download File For Chat
|
||||||
|
Future<Uint8List> downloadURL(String url, {required String fileName, required String fileTypeDescription, required int fileSource}) async {
|
||||||
|
Response response = await ApiClient().postJsonForResponse(
|
||||||
|
url,
|
||||||
|
{"fileType": fileTypeDescription, "fileName": fileName, "fileSource": fileSource},
|
||||||
|
token: chatLoginResponse!.token,
|
||||||
|
);
|
||||||
|
Uint8List data = Uint8List.fromList(response.bodyBytes);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downloads a file from a secure URL that uses a 302 redirect.
|
||||||
|
///
|
||||||
|
/// [url]: The initial URL that requires the authorization token.
|
||||||
|
/// [token]: The Bearer token for authorization.
|
||||||
|
/// [saveFileName]: The name to give the downloaded file (e.g., 'my-document.pdf').
|
||||||
|
Future<File?> downloadFileWithHttp(String url, {required String fileName, required String fileTypeDescription, required int fileSource}) async {
|
||||||
|
final client = http.Client();
|
||||||
|
File? file;
|
||||||
|
try {
|
||||||
|
final request = http.Request('GET', Uri.parse(url));
|
||||||
|
request.headers['Authorization'] = 'Bearer ${chatLoginResponse!.token}';
|
||||||
|
|
||||||
|
// This is the most important part: prevent automatic redirection.
|
||||||
|
request.followRedirects = false;
|
||||||
|
|
||||||
|
print("Making initial request to: $url");
|
||||||
|
final streamedResponse = await client.send(request);
|
||||||
|
|
||||||
|
// --- Step 2: Check the response and handle the redirect ---
|
||||||
|
|
||||||
|
// Check for a 302 redirect status.
|
||||||
|
if (streamedResponse.statusCode == 302) {
|
||||||
|
// Get the new URL from the 'Location' header.
|
||||||
|
final redirectUrl = streamedResponse.headers['location'];
|
||||||
|
|
||||||
|
if (redirectUrl == null) {
|
||||||
|
throw Exception('302 Redirect did not contain a Location header.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final fileResponse = await http.get(Uri.parse(redirectUrl), headers: {'Authorization': 'Bearer ${chatLoginResponse!.token}'});
|
||||||
|
|
||||||
|
if (fileResponse.statusCode == 200) {
|
||||||
|
// Get a safe directory to save the file.
|
||||||
|
final dir = await getTemporaryDirectory();
|
||||||
|
file = File('${dir.path}/$fileName');
|
||||||
|
|
||||||
|
// Write the file to disk.
|
||||||
|
await file.writeAsBytes(fileResponse.bodyBytes);
|
||||||
|
print("File downloaded successfully and saved to: ${file.path}");
|
||||||
|
} else {
|
||||||
|
// The download from the final URL failed.
|
||||||
|
throw Exception('Failed to download from redirect URL. Status: ${fileResponse.statusCode}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle cases where the server might just send the file directly.
|
||||||
|
else if (streamedResponse.statusCode == 200) {
|
||||||
|
print("Server sent file directly without redirect.");
|
||||||
|
final dir = await getTemporaryDirectory();
|
||||||
|
file = File('${dir.path}/$fileName');
|
||||||
|
await file.writeAsBytes(await streamedResponse.stream.toBytes());
|
||||||
|
print("File downloaded successfully and saved to: ${file.path}");
|
||||||
|
}
|
||||||
|
// Handle other error statuses.
|
||||||
|
else {
|
||||||
|
throw Exception('Failed to initiate download. Status: ${streamedResponse.statusCode}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("An error occurred during download: $e");
|
||||||
|
} finally {
|
||||||
|
// Always close the client to free up resources.
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
// //Get Chat Users & Favorite Images
|
||||||
|
// Future<List<ChatUserImageModel>> getUsersImages({required List<String> encryptedEmails}) async {
|
||||||
|
// List<ChatUserImageModel> imagesData = [];
|
||||||
|
// Response response = await ApiClient().postJsonForResponse(
|
||||||
|
// "${ApiConsts.chatUserImages}images",
|
||||||
|
// {"encryptedEmails": encryptedEmails, "fromClient": false},
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// if (response.statusCode == 200) {
|
||||||
|
// imagesData = chatUserImageModelFromJson(response.body);
|
||||||
|
// } else if (response.statusCode == 500 || response.statusCode == 504) {
|
||||||
|
// getUsersImages(encryptedEmails: encryptedEmails);
|
||||||
|
// } else {
|
||||||
|
// Utils.showToast("Something went wrong while loading images");
|
||||||
|
// imagesData = [];
|
||||||
|
// }
|
||||||
|
// return imagesData;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// //group chat apis start here.
|
||||||
|
// Future<groups.GetUserGroups> getGroupsByUserId() async {
|
||||||
|
// try {
|
||||||
|
// Response response = await ApiClient().getJsonForResponse(
|
||||||
|
// "${ApiConsts.getGroupByUserId}${AppState().chatDetails!.response!.id}",
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// return groups.GetUserGroups.fromRawJson(response.body);
|
||||||
|
// } catch (e) {
|
||||||
|
// //if fail api returning 500 hence just printing here
|
||||||
|
// print(e);
|
||||||
|
// throw e;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Future<Response> deleteGroup(int? groupId) async {
|
||||||
|
// try {
|
||||||
|
// Response response = await ApiClient().postJsonForResponse(
|
||||||
|
// ApiConsts.deleteGroup,
|
||||||
|
// {"groupId": groupId},
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// return response;
|
||||||
|
// } catch (e) {
|
||||||
|
// //if fail api returning 500 hence just printing here
|
||||||
|
// print(e);
|
||||||
|
// throw e;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Future<Response> updateGroupAdmin(int? groupId, List<GroupUserList> groupList) async {
|
||||||
|
// try {
|
||||||
|
// Response response = await ApiClient().postJsonForResponse(
|
||||||
|
// ApiConsts.updateGroupAdmin,
|
||||||
|
// {"groupId": groupId, "groupUserList": groupList},
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// return response;
|
||||||
|
// } catch (e) {
|
||||||
|
// //if fail api returning 500 hence just printing here
|
||||||
|
// print(e);
|
||||||
|
// throw e;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Future<List<GetGroupChatHistoryAsync>> getGroupChatHistory(int? groupId, List<GroupUserList> groupList) async {
|
||||||
|
// try {
|
||||||
|
// Response response = await ApiClient().postJsonForResponse(
|
||||||
|
// ApiConsts.getGroupChatHistoryAsync,
|
||||||
|
// {"groupId": groupId, "targetUserList": groupList, "CurrentId": AppState().chatDetails!.response!.id},
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// List<GetGroupChatHistoryAsync> groupChat = [];
|
||||||
|
// List groupChatData = json.decode(response.body);
|
||||||
|
// for (var i in groupChatData) {
|
||||||
|
// groupChat.add(GetGroupChatHistoryAsync.fromJson(i));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// groupChat.sort((a, b) => b.createdDate!.compareTo(a.createdDate!));
|
||||||
|
// return groupChat;
|
||||||
|
// // for(GetGroupChatHistoryAsync i in groupChat) {
|
||||||
|
// // return GetGroupChatHistoryAsync.fromJson(jsonEncode(i));
|
||||||
|
// // }
|
||||||
|
// } catch (e) {
|
||||||
|
// //if fail api returning 500 hence just printing here
|
||||||
|
// print(e);
|
||||||
|
// throw e;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Future addGroupAndUsers(createGroup.CreateGroupRequest request) async {
|
||||||
|
// try {
|
||||||
|
// Response response = await ApiClient().postJsonForResponse(
|
||||||
|
// ApiConsts.addGroupsAndUsers,
|
||||||
|
// request,
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// return response.body;
|
||||||
|
// } catch (e) {
|
||||||
|
// //if fail api returning 500 hence just printing here
|
||||||
|
// print(e);
|
||||||
|
// throw e;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Future updateGroupAndUsers(createGroup.CreateGroupRequest request) async {
|
||||||
|
// try {
|
||||||
|
// Response response = await ApiClient().postJsonForResponse(
|
||||||
|
// ApiConsts.updateGroupsAndUsers,
|
||||||
|
// request,
|
||||||
|
// token: AppState().chatDetails!.response!.token,
|
||||||
|
// );
|
||||||
|
// if (!kReleaseMode) {
|
||||||
|
// logger.i("res: " + response.body);
|
||||||
|
// }
|
||||||
|
// return response.body;
|
||||||
|
// } catch (e) {
|
||||||
|
// //if fail api returning 500 hence just printing here
|
||||||
|
// print(e);
|
||||||
|
// throw e;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -0,0 +1,786 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:audio_waveforms/audio_waveforms.dart';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/helper/utils.dart';
|
||||||
|
import 'package:test_sa/models/service_request/service_request.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/service_request_detail_provider.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/chat_api_client.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/chat_provider.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/view_all_attachment_page.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||||
|
import 'package:test_sa/views/widgets/sound/sound_player.dart';
|
||||||
|
|
||||||
|
import 'helper/chat_audio_player.dart';
|
||||||
|
import 'helper/chat_file_picker.dart';
|
||||||
|
import 'helper/chat_file_viewer.dart';
|
||||||
|
import 'model/get_single_user_chat_list_model.dart';
|
||||||
|
import 'model/user_chat_history_model.dart';
|
||||||
|
|
||||||
|
enum ChatState { idle, voiceRecordingStarted, voiceRecordingCompleted }
|
||||||
|
|
||||||
|
class ChatPage extends StatefulWidget {
|
||||||
|
int moduleId;
|
||||||
|
int requestId;
|
||||||
|
String? assigneeEmployeeNumber;
|
||||||
|
String? myLoginUserID;
|
||||||
|
String? contactEmployeeINumber;
|
||||||
|
String title;
|
||||||
|
bool readOnly;
|
||||||
|
|
||||||
|
ChatPage({Key? key, required this.moduleId, required this.requestId, this.title = "Chat", this.readOnly = false, this.assigneeEmployeeNumber, this.myLoginUserID, this.contactEmployeeINumber})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChatPageState createState() {
|
||||||
|
return _ChatPageState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatPageState extends State<ChatPage> {
|
||||||
|
bool isSender = false;
|
||||||
|
bool isAudioRecording = false;
|
||||||
|
|
||||||
|
String? recordedFilePath;
|
||||||
|
final RecorderController recorderController = RecorderController();
|
||||||
|
PlayerController playerController = PlayerController();
|
||||||
|
|
||||||
|
TextEditingController textEditingController = TextEditingController();
|
||||||
|
|
||||||
|
ChatState chatState = ChatState.idle;
|
||||||
|
|
||||||
|
late String receiver;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadChatHistory();
|
||||||
|
playerController.addListener(() async {
|
||||||
|
// if (playerController.playerState == PlayerState.playing && playerController.maxDuration == await playerController.getDuration()) {
|
||||||
|
// await playerController.stopPlayer();
|
||||||
|
// setState(() {});
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadChatHistory() {
|
||||||
|
// // String assigneeEmployeeNumber = Provider.of<ServiceRequestDetailProvider>(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? "";
|
||||||
|
// // String myEmployeeId = context.userProvider.user!.username!;
|
||||||
|
// //
|
||||||
|
//
|
||||||
|
// receiver = context.userProvider.isNurse ? widget.assigneeEmployeeNumber : widget.myEmployeeID;
|
||||||
|
//
|
||||||
|
// Provider.of<ChatProvider>(context, listen: false).connectToHub(widget.moduleId, widget.requestId, widget.myEmployeeID, widget.assigneeEmployeeNumber);
|
||||||
|
//
|
||||||
|
// receiver = context.userProvider.isNurse
|
||||||
|
// ? assigneeEmployeeNumber
|
||||||
|
// :myEmployeeId;
|
||||||
|
|
||||||
|
String assigneeEmployeeNumber = widget.assigneeEmployeeNumber ?? Provider.of<ServiceRequestDetailProvider>(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? "";
|
||||||
|
String myEmployeeId = widget.myLoginUserID ?? context.userProvider.user!.username!;
|
||||||
|
|
||||||
|
receiver = context.userProvider.isNurse
|
||||||
|
? assigneeEmployeeNumber
|
||||||
|
: (context.userProvider.isEngineer
|
||||||
|
? (widget.contactEmployeeINumber ?? Provider.of<ServiceRequestDetailProvider>(context, listen: false).currentWorkOrder!.data!.workOrderContactPerson.first.employeeId!)
|
||||||
|
: "");
|
||||||
|
Provider.of<ChatProvider>(context, listen: false).connectToHub(widget.moduleId, widget.requestId, myEmployeeId, receiver, widget.readOnly, isMounted: mounted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
chatHubConnection?.stop();
|
||||||
|
playerController.dispose();
|
||||||
|
recorderController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppColor.white10,
|
||||||
|
appBar: DefaultAppBar(title: widget.title),
|
||||||
|
body: Consumer<ChatProvider>(builder: (context, chatProvider, child) {
|
||||||
|
if (chatProvider.chatLoginTokenLoading) return const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3).center;
|
||||||
|
|
||||||
|
if (chatProvider.chatLoginResponse == null) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Failed to connect chat",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: AppTextStyles.heading6.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
24.height,
|
||||||
|
AppFilledButton(
|
||||||
|
label: "Go Back",
|
||||||
|
maxWidth: true,
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
).paddingOnly(start: 48, end: 48)
|
||||||
|
],
|
||||||
|
).center;
|
||||||
|
}
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: AppColor.neutral50,
|
||||||
|
constraints: const BoxConstraints(maxHeight: 56),
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
chatProvider.recipient?.userName ?? "",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: AppTextStyles.bodyText2.copyWith(color: AppColor.white10),
|
||||||
|
),
|
||||||
|
AnimatedSize(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
child: SizedBox(
|
||||||
|
height: chatProvider.isTyping ? null : 0,
|
||||||
|
child: Text(
|
||||||
|
"Typing...",
|
||||||
|
maxLines: 1,
|
||||||
|
style: AppTextStyles.overline.copyWith(color: AppColor.white10),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
// if (chatProvider.isTyping)
|
||||||
|
// Text(
|
||||||
|
// "Typing...",
|
||||||
|
// maxLines: 1,
|
||||||
|
// style: AppTextStyles.tinyFont2.copyWith(color: AppColor.white10),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
).expanded,
|
||||||
|
4.width,
|
||||||
|
Text(
|
||||||
|
"View All Documents",
|
||||||
|
style: AppTextStyles.bodyText.copyWith(
|
||||||
|
color: AppColor.white10,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: AppColor.white10,
|
||||||
|
),
|
||||||
|
).onPress(() {
|
||||||
|
Navigator.push(context, CupertinoPageRoute(builder: (context) => ViewAllAttachmentPage(moduleId: widget.moduleId, requestId: widget.requestId)));
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: AppColor.neutral100,
|
||||||
|
child: chatProvider.userChatHistoryLoading
|
||||||
|
? ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
recipientMsgCard(true, null, msg: "Please let me know what is the issue? Please let me know what is the issue?", loading: true),
|
||||||
|
recipientMsgCard(false, null, msg: "testing", loading: true),
|
||||||
|
recipientMsgCard(false, null, msg: "testing testing testing", loading: true),
|
||||||
|
senderMsgCard(true, null, msg: "Please let me know what is the issue? Please let me know what is the issue?", loading: true),
|
||||||
|
senderMsgCard(false, null, msg: "Please let me know what is the issue?", loading: true),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: chatProvider.chatResponseList.isEmpty
|
||||||
|
? Text(
|
||||||
|
"Send a message to start conversation",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: AppTextStyles.heading6.copyWith(color: AppColor.neutral50.withOpacity(.5), fontWeight: FontWeight.w500),
|
||||||
|
).center
|
||||||
|
: ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
reverse: true,
|
||||||
|
itemBuilder: (cxt, index) {
|
||||||
|
final currentMessage = chatProvider.chatResponseList[index];
|
||||||
|
final bool showSenderName =
|
||||||
|
(index == chatProvider.chatResponseList.length - 1) || (currentMessage.currentUserId != chatProvider.chatResponseList[index + 1].currentUserId);
|
||||||
|
bool isSender = chatProvider.chatResponseList[index].currentUserId == chatProvider.sender?.userId!;
|
||||||
|
bool showDateHeader = false;
|
||||||
|
if (index == chatProvider.chatResponseList.length - 1) {
|
||||||
|
showDateHeader = true;
|
||||||
|
} else {
|
||||||
|
final nextMessage = chatProvider.chatResponseList[index + 1];
|
||||||
|
final currentDate = DateUtils.dateOnly(currentMessage.createdDate!);
|
||||||
|
final nextDate = DateUtils.dateOnly(nextMessage.createdDate!);
|
||||||
|
if (!currentDate.isAtSameMomentAs(nextDate)) {
|
||||||
|
showDateHeader = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
if (showDateHeader) dateCard(currentMessage.createdDate?.toString().chatMsgDateWithYear ?? ""),
|
||||||
|
isSender ? senderMsgCard(showSenderName, chatProvider.chatResponseList[index]) : recipientMsgCard(showSenderName, chatProvider.chatResponseList[index])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
itemCount: chatProvider.chatResponseList.length))
|
||||||
|
.expanded,
|
||||||
|
if (!widget.readOnly) ...[
|
||||||
|
Divider(height: 1, thickness: 1, color: const Color(0xff767676).withOpacity(.11)),
|
||||||
|
SafeArea(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(minHeight: 56),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (chatState == ChatState.idle) ...[
|
||||||
|
TextFormField(
|
||||||
|
controller: textEditingController,
|
||||||
|
cursorColor: AppColor.neutral50,
|
||||||
|
style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50),
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: 3,
|
||||||
|
textInputAction: TextInputAction.none,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
onTap: () {
|
||||||
|
chatHubConnection!.invoke("SendTypingAsync", args: [receiver]);
|
||||||
|
},
|
||||||
|
onTapOutside: (PointerDownEvent event) {
|
||||||
|
chatHubConnection!.invoke("SendStopTypingAsync", args: [receiver]);
|
||||||
|
},
|
||||||
|
onChanged: (text) {
|
||||||
|
chatHubConnection!.invoke("SendTypingAsync", args: [receiver]);
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
border: InputBorder.none,
|
||||||
|
errorBorder: InputBorder.none,
|
||||||
|
contentPadding: const EdgeInsets.only(left: 16, top: 8, bottom: 8),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
filled: true,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
suffixIconConstraints: const BoxConstraints(),
|
||||||
|
hintText: "Type your message here...",
|
||||||
|
hintStyle: AppTextStyles.bodyText.copyWith(color: const Color(0xffCCCCCC)),
|
||||||
|
),
|
||||||
|
).expanded,
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
File? file = (await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
|
builder: (BuildContext context) => ChatFilePicker())) as File?;
|
||||||
|
if (file != null) {
|
||||||
|
Utils.showUploadingDialog(context);
|
||||||
|
await chatProvider.uploadAttachments(context.userProvider.user!.username!, file, "1");
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: const ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
),
|
||||||
|
icon: "chat_attachment".toSvgAsset(width: 24, height: 24),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await recorderController.checkPermission();
|
||||||
|
if (recorderController.hasPermission) {
|
||||||
|
chatState = ChatState.voiceRecordingStarted;
|
||||||
|
recorderController.record();
|
||||||
|
setState(() {});
|
||||||
|
} else {
|
||||||
|
"Audio permission denied. Please enable from setting".showToast;
|
||||||
|
}
|
||||||
|
// if (!isPermissionGranted) {
|
||||||
|
// "Audio permission denied. Please enable from setting".showToast;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
style: const ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
),
|
||||||
|
icon: "chat_mic".toSvgAsset(width: 24, height: 24),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
] else if (chatState == ChatState.voiceRecordingStarted) ...[
|
||||||
|
AudioWaveforms(
|
||||||
|
size: Size(MediaQuery.of(context).size.width, 56.0),
|
||||||
|
waveStyle: const WaveStyle(waveColor: AppColor.neutral50, extendWaveform: true, showMiddleLine: false),
|
||||||
|
padding: const EdgeInsets.only(left: 16),
|
||||||
|
recorderController: recorderController, // Customize how waveforms looks.
|
||||||
|
).expanded,
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
isAudioRecording = false;
|
||||||
|
await recorderController.pause();
|
||||||
|
recordedFilePath = await recorderController.stop();
|
||||||
|
chatState = ChatState.voiceRecordingCompleted;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
style: const ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
),
|
||||||
|
icon: Icon(Icons.stop_circle_rounded),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
)
|
||||||
|
] else if (chatState == ChatState.voiceRecordingCompleted) ...[
|
||||||
|
if (playerController.playerState == PlayerState.playing)
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await playerController.pausePlayer();
|
||||||
|
await playerController.stopPlayer();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
style: const ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.stop_circle_outlined, size: 20),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await playerController.preparePlayer(path: recordedFilePath!);
|
||||||
|
await playerController.startPlayer();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
style: const ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.play_circle_fill_rounded, size: 20),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
AudioFileWaveforms(
|
||||||
|
playerController: playerController,
|
||||||
|
waveformData: recorderController.waveData,
|
||||||
|
enableSeekGesture: false,
|
||||||
|
continuousWaveform: false,
|
||||||
|
waveformType: WaveformType.long,
|
||||||
|
playerWaveStyle: const PlayerWaveStyle(
|
||||||
|
fixedWaveColor: AppColor.neutral50,
|
||||||
|
liveWaveColor: AppColor.primary10,
|
||||||
|
showSeekLine: true,
|
||||||
|
),
|
||||||
|
size: Size(MediaQuery.of(context).size.width, 56.0),
|
||||||
|
).expanded,
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await playerController.stopPlayer();
|
||||||
|
recorderController.reset();
|
||||||
|
recordedFilePath = null;
|
||||||
|
chatState = ChatState.idle;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
style: const ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
),
|
||||||
|
icon: "delete_icon".toSvgAsset(width: 24, height: 24),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
// if (recordedFilePath == null) ...[
|
||||||
|
// isAudioRecording
|
||||||
|
// ? AudioWaveforms(
|
||||||
|
// size: Size(MediaQuery.of(context).size.width, 56.0),
|
||||||
|
//
|
||||||
|
// // enableGesture: true,
|
||||||
|
//
|
||||||
|
// waveStyle: const WaveStyle(waveColor: AppColor.neutral50, extendWaveform: true, showMiddleLine: false),
|
||||||
|
// padding: const EdgeInsets.only(left: 16),
|
||||||
|
// recorderController: recorderController, // Customize how waveforms looks.
|
||||||
|
// ).expanded
|
||||||
|
// : TextFormField(
|
||||||
|
// cursorColor: AppColor.neutral50,
|
||||||
|
// style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50),
|
||||||
|
// minLines: 1,
|
||||||
|
// maxLines: 3,
|
||||||
|
// textInputAction: TextInputAction.none,
|
||||||
|
// keyboardType: TextInputType.multiline,
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// enabledBorder: InputBorder.none,
|
||||||
|
// focusedBorder: InputBorder.none,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// errorBorder: InputBorder.none,
|
||||||
|
// contentPadding: const EdgeInsets.only(left: 16, top: 8, bottom: 8),
|
||||||
|
// alignLabelWithHint: true,
|
||||||
|
// filled: true,
|
||||||
|
// constraints: const BoxConstraints(),
|
||||||
|
// suffixIconConstraints: const BoxConstraints(),
|
||||||
|
// hintText: "Type your message here...",
|
||||||
|
// hintStyle: AppTextStyles.bodyText.copyWith(color: const Color(0xffCCCCCC)),
|
||||||
|
// // suffixIcon: Row(
|
||||||
|
// // mainAxisSize: MainAxisSize.min,
|
||||||
|
// // crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
// // mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
// // children: [
|
||||||
|
// //
|
||||||
|
// // 8.width,
|
||||||
|
// // ],
|
||||||
|
// // )
|
||||||
|
// ),
|
||||||
|
// ).expanded,
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () {},
|
||||||
|
// style: const ButtonStyle(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
// ),
|
||||||
|
// icon: "chat_attachment".toSvgAsset(width: 24, height: 24),
|
||||||
|
// constraints: const BoxConstraints(),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// if (recordedFilePath == null)
|
||||||
|
// ...[]
|
||||||
|
// else ...[
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () async {
|
||||||
|
// await playerController.preparePlayer(path: recordedFilePath!);
|
||||||
|
// playerController.startPlayer();
|
||||||
|
// },
|
||||||
|
// style: const ButtonStyle(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
// ),
|
||||||
|
// icon: const Icon(Icons.play_circle_fill_rounded, size: 20),
|
||||||
|
// constraints: const BoxConstraints(),
|
||||||
|
// ),
|
||||||
|
// AudioFileWaveforms(
|
||||||
|
// playerController: playerController,
|
||||||
|
// size: Size(300, 50),
|
||||||
|
// ).expanded,
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () async {
|
||||||
|
// playerController.pausePlayer();
|
||||||
|
// },
|
||||||
|
// style: const ButtonStyle(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
// ),
|
||||||
|
// icon: const Icon(Icons.pause_circle_filled_outlined, size: 20),
|
||||||
|
// constraints: const BoxConstraints(),
|
||||||
|
// ),
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () {},
|
||||||
|
// style: const ButtonStyle(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
// ),
|
||||||
|
// icon: "delete".toSvgAsset(width: 24, height: 24),
|
||||||
|
// constraints: const BoxConstraints(),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// if (isAudioRecording && recorderController.isRecording)
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () {},
|
||||||
|
// style: const ButtonStyle(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
// ),
|
||||||
|
// icon: "chat_msg_send".toSvgAsset(width: 24, height: 24),
|
||||||
|
// constraints: const BoxConstraints(),
|
||||||
|
// ),
|
||||||
|
// if (isAudioRecording)
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () async {
|
||||||
|
// isAudioRecording = false;
|
||||||
|
// await recorderController.pause();
|
||||||
|
// recordedFilePath = await recorderController.stop();
|
||||||
|
// chatState = ChatState.voiceRecordingCompleted;
|
||||||
|
// setState(() {});
|
||||||
|
// },
|
||||||
|
// style: const ButtonStyle(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
// ),
|
||||||
|
// icon: Icon(Icons.stop_circle_rounded),
|
||||||
|
// constraints: const BoxConstraints(),
|
||||||
|
// )
|
||||||
|
// else
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () async {
|
||||||
|
// await recorderController.checkPermission();
|
||||||
|
// if (recorderController.hasPermission) {
|
||||||
|
// setState(() {
|
||||||
|
// isAudioRecording = true;
|
||||||
|
// });
|
||||||
|
// recorderController.record();
|
||||||
|
// } else {
|
||||||
|
// "Audio permission denied. Please enable from setting".showToast;
|
||||||
|
// }
|
||||||
|
// // if (!isPermissionGranted) {
|
||||||
|
// // "Audio permission denied. Please enable from setting".showToast;
|
||||||
|
// // return;
|
||||||
|
// // }
|
||||||
|
// },
|
||||||
|
// style: const ButtonStyle(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
// ),
|
||||||
|
// icon: "chat_mic".toSvgAsset(width: 24, height: 24),
|
||||||
|
// constraints: const BoxConstraints(),
|
||||||
|
// ),
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
onPressed: () async {
|
||||||
|
if (chatState == ChatState.voiceRecordingCompleted) {
|
||||||
|
Utils.showUploadingDialog(context);
|
||||||
|
try {
|
||||||
|
await chatProvider.uploadAttachments(context.userProvider.user!.username!, File(recordedFilePath!), "1");
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
|
||||||
|
await playerController.stopPlayer();
|
||||||
|
recorderController.reset();
|
||||||
|
recordedFilePath = null;
|
||||||
|
chatState = ChatState.idle;
|
||||||
|
setState(() {});
|
||||||
|
} catch (ex) {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (textEditingController.text.isEmpty) return;
|
||||||
|
chatProvider.invokeSendMessage({
|
||||||
|
"Contant": textEditingController.text,
|
||||||
|
// "ContantNo": "0cc8b126-6180-4f91-a64d-2f62443b3f3f",
|
||||||
|
// "CreatedDate": "2025-11-09T18:58:12.502Z",
|
||||||
|
"CurrentEmployeeNumber": context.userProvider.user!.username!,
|
||||||
|
"ChatEventId": 1,
|
||||||
|
"ConversationId": chatProvider.chatParticipantModel!.id!.toString(),
|
||||||
|
"ModuleCode": widget.moduleId.toString(),
|
||||||
|
"ReferenceNumber": widget.requestId.toString(),
|
||||||
|
"UserChatHistoryLineRequestList": [
|
||||||
|
{"TargetEmployeeNumber": receiver, "TargetUserStatus": 1, "IsSeen": false, "IsDelivered": true, "SeenOn": null, "DeliveredOn": null}
|
||||||
|
]
|
||||||
|
}).then((success) {
|
||||||
|
if (success) {
|
||||||
|
textEditingController.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: const ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
),
|
||||||
|
icon: chatProvider.messageIsSending
|
||||||
|
? const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: "chat_msg_send".toSvgAsset(width: 24, height: 24),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
8.width,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget dateCard(String date) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
|
margin: const EdgeInsets.only(top: 16, bottom: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.neutral50,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Text(date, style: AppTextStyles.bodyText2.copyWith(color: AppColor.white10)))
|
||||||
|
.center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget senderMsgCard(bool showHeader, SingleUserChatModel? chatResponse, {bool loading = false, String msg = ""}) {
|
||||||
|
Widget senderHeader = Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${chatResponse?.currentUserName ?? "User"}(Me)",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w600),
|
||||||
|
).toShimmer(context: context, isShow: loading),
|
||||||
|
8.width,
|
||||||
|
Container(
|
||||||
|
height: 26,
|
||||||
|
width: 26,
|
||||||
|
decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.grey),
|
||||||
|
).toShimmer(context: context, isShow: loading),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (showHeader) ...[senderHeader, 4.height] else 8.height,
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.loose,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
margin: const EdgeInsets.only(right: 8, left: 26 + 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: loading ? Colors.transparent : AppColor.white10,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (chatResponse?.downloadUrl != null) ...[
|
||||||
|
// if (chatResponse!.fileTypeResponse!.fileKind.toString().toLowerCase() == "audio")
|
||||||
|
// // ASoundPlayer(audio: chatResponse.downloadUrl!)
|
||||||
|
//
|
||||||
|
// ChatAudioPlayer(chatResponse!.fileTypeResponse!, chatResponse.downloadUrl!)
|
||||||
|
// // if (
|
||||||
|
// //
|
||||||
|
// // chatResponse!.fileTypeResponse!.fileKind.toString().toLowerCase() == "image"
|
||||||
|
// //
|
||||||
|
// // )
|
||||||
|
//
|
||||||
|
// else
|
||||||
|
ChatFileViewer(chatResponse!.downloadUrl!, chatResponse.fileTypeResponse!),
|
||||||
|
|
||||||
|
// Container(
|
||||||
|
// width: 250,
|
||||||
|
// height: 250,
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// borderRadius: BorderRadius.circular(12),
|
||||||
|
// border: Border.all(color: Colors.grey.withOpacity(.5), width: 1),
|
||||||
|
// image: DecorationImage(
|
||||||
|
// image: NetworkImage(chatResponse.downloadUrl!, headers: {'Authorization': "Bearer ${Provider.of<ChatProvider>(context, listen: false).chatLoginResponse!.token!}"}),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// // child: CachedNetworkImage(
|
||||||
|
// // imageUrl: chatResponse.downloadUrl! ?? "",
|
||||||
|
// // // fit: boxFit ?? BoxFit.cover,
|
||||||
|
// // // alignment: Alignment.center,
|
||||||
|
// // // width: width,
|
||||||
|
// // // height: height,
|
||||||
|
// // httpHeaders: {'Authorization': "Bearer ${Provider.of<ChatProvider>(context, listen: false).chatLoginResponse!.token!}"},
|
||||||
|
// // placeholder: (context, url) => const Center(child: CircularProgressIndicator()),
|
||||||
|
// // // errorWidget: (context, url, error) => Icon(showDefaultIcon ? Icons.image_outlined : Icons.broken_image_rounded),
|
||||||
|
// // ),
|
||||||
|
//
|
||||||
|
// // Image.network(
|
||||||
|
// // chatResponse.downloadUrl!,
|
||||||
|
// // headers: {'Authorization': "Bearer ${Provider.of<ChatProvider>(context, listen: false).chatLoginResponse!.token!}"},
|
||||||
|
// // ),
|
||||||
|
// )
|
||||||
|
] else
|
||||||
|
Text(
|
||||||
|
chatResponse?.contant ?? msg,
|
||||||
|
style: AppTextStyles.bodyText2.copyWith(color: AppColor.neutral120),
|
||||||
|
).toShimmer(context: context, isShow: loading),
|
||||||
|
if (loading) 4.height,
|
||||||
|
Text(
|
||||||
|
chatResponse?.createdDate?.toString().chatMsgTime ?? "2:00 PM",
|
||||||
|
style: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.neutral50.withOpacity(0.5)),
|
||||||
|
).toShimmer(context: context, isShow: loading),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
if (chatResponse != null)
|
||||||
|
if (chatResponse.isSeen!)
|
||||||
|
"chat_seen".toSvgAsset(width: 16, height: 16)
|
||||||
|
else if (chatResponse.isDelivered!)
|
||||||
|
"chat_delivered".toSvgAsset(width: 16, height: 16)
|
||||||
|
else
|
||||||
|
"chat_sent".toSvgAsset(width: 16, height: 16),
|
||||||
|
(26 + 8).width,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget recipientMsgCard(bool showHeader, SingleUserChatModel? chatResponse, {bool loading = false, String msg = ""}) {
|
||||||
|
String extraSpaces = "";
|
||||||
|
int length = 0;
|
||||||
|
if ((chatResponse?.contant ?? "").isNotEmpty) {
|
||||||
|
if (chatResponse!.contant!.length < 8) {
|
||||||
|
length = 8 - chatResponse.contant!.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String contentMsg = chatResponse?.contant == null ? msg : chatResponse!.contant! + extraSpaces;
|
||||||
|
Widget recipientHeader = Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 26,
|
||||||
|
width: 26,
|
||||||
|
decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.grey),
|
||||||
|
).toShimmer(context: context, isShow: loading),
|
||||||
|
8.width,
|
||||||
|
Text(
|
||||||
|
chatResponse?.currentUserName ?? "User",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w600),
|
||||||
|
).toShimmer(context: context, isShow: loading)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (showHeader) ...[recipientHeader, 4.height] else 8.height,
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
margin: const EdgeInsets.only(left: 26 + 8, right: 26 + 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: loading ? Colors.transparent : AppColor.primary10,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (chatResponse?.downloadUrl != null) ...[
|
||||||
|
ChatFileViewer(chatResponse!.downloadUrl!, chatResponse.fileTypeResponse!),
|
||||||
|
] else
|
||||||
|
Text(
|
||||||
|
contentMsg,
|
||||||
|
style: AppTextStyles.bodyText2.copyWith(color: AppColor.white10),
|
||||||
|
).paddingOnly(end: 6 * length).toShimmer(context: context, isShow: loading),
|
||||||
|
if (loading) 4.height,
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
widthFactor: 1,
|
||||||
|
child: Text(
|
||||||
|
chatResponse?.createdDate?.toString().chatMsgTime ?? "2:00 PM",
|
||||||
|
style: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.white10),
|
||||||
|
),
|
||||||
|
).toShimmer(context: context, isShow: loading),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/chat_page.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||||
|
|
||||||
|
class ChatRoomsPage extends StatefulWidget {
|
||||||
|
ChatRoomsPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChatPageState createState() {
|
||||||
|
return _ChatPageState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatPageState extends State<ChatRoomsPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppColor.neutral100,
|
||||||
|
appBar: const DefaultAppBar(title: "Chats"),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemBuilder: (cxt, index) => Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Request No. : 432212",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Asset No. : HMG212",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Request No. : 432212",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120),
|
||||||
|
),
|
||||||
|
6.height,
|
||||||
|
Text(
|
||||||
|
"Mon 27 Oct, 2:00 PM",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).expanded,
|
||||||
|
getStatus(),
|
||||||
|
8.width,
|
||||||
|
const Icon(
|
||||||
|
Icons.arrow_forward_ios_rounded,
|
||||||
|
size: 12,
|
||||||
|
color: AppColor.black20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddingOnly(top: 4, bottom: 4).onPress(() {
|
||||||
|
// Navigator.push(context, CupertinoPageRoute(builder: (context) => ChatPage()));
|
||||||
|
return;
|
||||||
|
}),
|
||||||
|
separatorBuilder: (cxt, index) => const Divider().defaultStyle(context),
|
||||||
|
itemCount: 10)
|
||||||
|
.toShadowContainer(context, borderRadius: 20, showShadow: false, padding: 12)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getStatus() {
|
||||||
|
Color color = const Color(0xff3B755C);
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xff62BE96).withOpacity(.6),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"In Progress",
|
||||||
|
maxLines: 1,
|
||||||
|
style: AppTextStyles.tinyFont2.copyWith(color: color),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/service_request_detail_provider.dart';
|
||||||
|
|
||||||
|
import 'chat_page.dart';
|
||||||
|
import 'chat_provider.dart';
|
||||||
|
|
||||||
|
class ChatWidget extends StatefulWidget {
|
||||||
|
int moduleId;
|
||||||
|
int requestId;
|
||||||
|
String? assigneeEmployeeNumber;
|
||||||
|
String? myLoginUserID;
|
||||||
|
String? contactEmployeeINumber;
|
||||||
|
bool isReadOnly;
|
||||||
|
bool isShow;
|
||||||
|
String title;
|
||||||
|
|
||||||
|
ChatWidget(
|
||||||
|
{Key? key,
|
||||||
|
this.isReadOnly = false,
|
||||||
|
this.title = "Chat",
|
||||||
|
required this.moduleId,
|
||||||
|
required this.requestId,
|
||||||
|
this.assigneeEmployeeNumber,
|
||||||
|
this.myLoginUserID,
|
||||||
|
this.contactEmployeeINumber,
|
||||||
|
this.isShow = false})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChatWidgetState createState() {
|
||||||
|
return _ChatWidgetState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatWidgetState extends State<ChatWidget> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
Provider.of<ChatProvider>(context, listen: false).reset();
|
||||||
|
getChatToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
void getChatToken() {
|
||||||
|
// ChatProvider cProvider = Provider.of<ChatProvider>(context, listen: false);
|
||||||
|
// if (cProvider.chatLoginResponse != null && cProvider.referenceID == widget.requestId) return;
|
||||||
|
// // String assigneeEmployeeNumber = Provider.of<ServiceRequestDetailProvider>(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? "";
|
||||||
|
// String myEmployeeId = widget.myLoginUserID;
|
||||||
|
//
|
||||||
|
// String receiver = context.userProvider.isNurse ? widget.assigneeEmployeeNumber : widget.myEmployeeID;
|
||||||
|
|
||||||
|
ChatProvider cProvider = Provider.of<ChatProvider>(context, listen: false);
|
||||||
|
if (cProvider.chatLoginResponse != null && cProvider.referenceID == widget.requestId) return;
|
||||||
|
String assigneeEmployeeNumber = widget.assigneeEmployeeNumber ?? Provider.of<ServiceRequestDetailProvider>(context, listen: false).currentWorkOrder?.data?.assignedEmployee?.employeeId ?? "";
|
||||||
|
String myEmployeeId = widget.myLoginUserID ?? context.userProvider.user!.username!;
|
||||||
|
|
||||||
|
String receiver = context.userProvider.isNurse
|
||||||
|
? assigneeEmployeeNumber
|
||||||
|
: (context.userProvider.isEngineer
|
||||||
|
? (widget.contactEmployeeINumber ?? Provider.of<ServiceRequestDetailProvider>(context, listen: false).currentWorkOrder!.data!.workOrderContactPerson.first.employeeId!)
|
||||||
|
: "");
|
||||||
|
|
||||||
|
cProvider.getUserAutoLoginTokenSilent(widget.moduleId, widget.requestId, widget.title, myEmployeeId, receiver, isMounted: mounted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<ChatProvider>(builder: (pContext, requestProvider, _) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.chat_bubble),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (context) => ChatPage(
|
||||||
|
moduleId: widget.moduleId,
|
||||||
|
requestId: widget.requestId,
|
||||||
|
title: widget.title,
|
||||||
|
readOnly: widget.isReadOnly,
|
||||||
|
assigneeEmployeeNumber: widget.assigneeEmployeeNumber,
|
||||||
|
contactEmployeeINumber: widget.contactEmployeeINumber,
|
||||||
|
myLoginUserID: widget.myLoginUserID,
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
).toShimmer(context: context, isShow: requestProvider.chatLoginTokenLoading, radius: 30, height: 30, width: 30);
|
||||||
|
});
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:audio_waveforms/audio_waveforms.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/api_client.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/chat_api_client.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/model/get_single_user_chat_list_model.dart';
|
||||||
|
|
||||||
|
import '../../../../new_views/app_style/app_color.dart';
|
||||||
|
|
||||||
|
class ChatAudioPlayer extends StatefulWidget {
|
||||||
|
String downloadURl;
|
||||||
|
FileTypeResponse file;
|
||||||
|
bool isLocalFile;
|
||||||
|
|
||||||
|
ChatAudioPlayer(this.file, this.downloadURl, {Key? key, this.isLocalFile = false}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChatAudioPlayerState createState() {
|
||||||
|
return _ChatAudioPlayerState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatAudioPlayerState extends State<ChatAudioPlayer> {
|
||||||
|
bool downloading = false;
|
||||||
|
|
||||||
|
PlayerController playerController = PlayerController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
checkFileInLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
File? audioFile;
|
||||||
|
List<double> waveformData = [];
|
||||||
|
|
||||||
|
void checkFileInLocalStorage() async {
|
||||||
|
Directory tempDir = await getTemporaryDirectory();
|
||||||
|
String tempPath = '${tempDir.path}/${widget.file.fileName}';
|
||||||
|
File tempFile = File(tempPath);
|
||||||
|
bool exists = await tempFile.exists();
|
||||||
|
if (exists) {
|
||||||
|
audioFile = tempFile;
|
||||||
|
playerController = PlayerController();
|
||||||
|
await playerController.preparePlayer(path: tempPath);
|
||||||
|
waveformData = await playerController.extractWaveformData(path: tempPath);
|
||||||
|
} else {
|
||||||
|
downloadFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void downloadFile() async {
|
||||||
|
downloading = true;
|
||||||
|
setState(() {});
|
||||||
|
try {
|
||||||
|
// Uint8List list = await ChatApiClient().downloadFileWithHttp(widget.downloadURl, fileName: widget.file.fileName, fileTypeDescription: widget.file.fileTypeDescription, fileSource: widget.file.fileTypeId!);
|
||||||
|
// audioFile = File.fromRawPath(list);
|
||||||
|
|
||||||
|
audioFile =
|
||||||
|
await ChatApiClient().downloadFileWithHttp(widget.downloadURl, fileName: widget.file.fileName, fileTypeDescription: widget.file.fileTypeDescription, fileSource: widget.file.fileTypeId!);
|
||||||
|
|
||||||
|
playerController = PlayerController();
|
||||||
|
await playerController.preparePlayer(path: audioFile!.path);
|
||||||
|
waveformData = await playerController.extractWaveformData(path: audioFile!.path);
|
||||||
|
} catch (ex) {
|
||||||
|
print(ex);
|
||||||
|
audioFile = null;
|
||||||
|
}
|
||||||
|
downloading = false;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
playerController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 56,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (downloading)
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2),
|
||||||
|
)
|
||||||
|
else if (playerController.playerState == PlayerState.playing)
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await playerController.pausePlayer();
|
||||||
|
await playerController.stopPlayer();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
style: const ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.stop_circle_outlined, size: 20),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await playerController.startPlayer();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
style: const ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.play_circle_fill_rounded, size: 20),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
AudioFileWaveforms(
|
||||||
|
playerController: playerController,
|
||||||
|
waveformData: waveformData,
|
||||||
|
enableSeekGesture: false,
|
||||||
|
continuousWaveform: false,
|
||||||
|
waveformType: WaveformType.long,
|
||||||
|
playerWaveStyle: const PlayerWaveStyle(
|
||||||
|
fixedWaveColor: AppColor.neutral50,
|
||||||
|
liveWaveColor: AppColor.primary10,
|
||||||
|
showSeekLine: true,
|
||||||
|
),
|
||||||
|
size: Size(MediaQuery.of(context).size.width, 56.0),
|
||||||
|
).expanded,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,436 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:image_cropper/image_cropper.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/models/generic_attachment_model.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
|
||||||
|
class ChatFilePicker extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
"Attach File".heading4(context),
|
||||||
|
12.height,
|
||||||
|
GridView(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12),
|
||||||
|
children: <Widget>[
|
||||||
|
gridItem(Icons.camera_enhance_rounded, context.translation.pickFromCamera).onPress(() async {
|
||||||
|
await fromMediaPicker(context, ImageSource.camera);
|
||||||
|
}),
|
||||||
|
gridItem(Icons.image_rounded, context.translation.pickFromGallery).onPress(() async {
|
||||||
|
await fromMediaPicker(context, ImageSource.gallery);
|
||||||
|
}),
|
||||||
|
gridItem(Icons.file_present_rounded, context.translation.pickFromFiles).onPress(() async {
|
||||||
|
await fromFilePicker(context);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
12.height,
|
||||||
|
],
|
||||||
|
).paddingAll(21);
|
||||||
|
}
|
||||||
|
|
||||||
|
fromMediaPicker(BuildContext context, ImageSource imageSource) async {
|
||||||
|
XFile? pickedFile = await ImagePicker().pickImage(source: imageSource, imageQuality: 70, maxWidth: 800, maxHeight: 800);
|
||||||
|
if (pickedFile != null) {
|
||||||
|
CroppedFile? croppedFile = await ImageCropper().cropImage(
|
||||||
|
sourcePath: pickedFile.path,
|
||||||
|
// aspectRatio: CropAspectRatio(ratioX: 1, ratioY: 1),
|
||||||
|
uiSettings: [
|
||||||
|
AndroidUiSettings(
|
||||||
|
toolbarTitle: 'ATOMS',
|
||||||
|
toolbarColor: Colors.white,
|
||||||
|
toolbarWidgetColor: context.settingProvider.theme == "dark" ? AppColor.neutral10 : AppColor.neutral50,
|
||||||
|
initAspectRatio: CropAspectRatioPreset.square,
|
||||||
|
lockAspectRatio: false,
|
||||||
|
),
|
||||||
|
IOSUiSettings(title: 'ATOMS'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (croppedFile != null) {
|
||||||
|
Navigator.pop(context, File(croppedFile.path));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
fromFilePicker(BuildContext context) async {
|
||||||
|
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
|
type: FileType.custom,
|
||||||
|
allowedExtensions: ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xlsx', 'pptx', 'txt'],
|
||||||
|
);
|
||||||
|
if ((result?.paths ?? []).isNotEmpty) {
|
||||||
|
Navigator.pop(context, File(result!.paths.first!));
|
||||||
|
} else {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget gridItem(IconData iconData, String title) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: const Color(0xffF1F1F1), width: 1),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Icon(iconData, color: const Color(0xff7D859A), size: 36),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static Future<File?> selectFile(BuildContext context) async {
|
||||||
|
// ImageSource source = (await showModalBottomSheet(
|
||||||
|
// context: context,
|
||||||
|
// shape: const RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.vertical(
|
||||||
|
// top: Radius.circular(20),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
|
// builder: (BuildContext context) => ChatFilePicker())) as ImageSource;
|
||||||
|
//
|
||||||
|
// final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// class AttachmentPicker extends StatefulWidget {
|
||||||
|
// final String label;
|
||||||
|
// final bool error;
|
||||||
|
// final List<GenericAttachmentModel> attachment;
|
||||||
|
//
|
||||||
|
// final bool enabled, onlyImages;
|
||||||
|
// double? buttonHeight;
|
||||||
|
// Widget? buttonIcon;
|
||||||
|
// Color? buttonColor;
|
||||||
|
// final Function(List<GenericAttachmentModel>)? onChange;
|
||||||
|
// final bool showAsGrid;
|
||||||
|
//
|
||||||
|
// AttachmentPicker(
|
||||||
|
// {Key? key,
|
||||||
|
// this.attachment = const <GenericAttachmentModel>[],
|
||||||
|
// required this.label,
|
||||||
|
// this.error = false,
|
||||||
|
// this.buttonHeight,
|
||||||
|
// this.buttonIcon,
|
||||||
|
// this.enabled = true,
|
||||||
|
// this.onlyImages = false,
|
||||||
|
// this.onChange,
|
||||||
|
// this.showAsGrid = false,
|
||||||
|
// this.buttonColor})
|
||||||
|
// : super(key: key);
|
||||||
|
//
|
||||||
|
// @override
|
||||||
|
// State<AttachmentPicker> createState() => _AttachmentPickerState();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// class _AttachmentPickerState extends State<AttachmentPicker> {
|
||||||
|
// @override
|
||||||
|
// Widget build(BuildContext context) {
|
||||||
|
// return Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// AppDashedButton(
|
||||||
|
// title: widget.label,
|
||||||
|
// height: widget.buttonHeight,
|
||||||
|
// buttonColor: widget.buttonColor,
|
||||||
|
// icon: widget.buttonIcon,
|
||||||
|
// onPressed: (widget.enabled == false)
|
||||||
|
// ? () {}
|
||||||
|
// : widget.showAsGrid
|
||||||
|
// ? showFileSourceSheet
|
||||||
|
// : onFilePicker),
|
||||||
|
// 16.height,
|
||||||
|
// if (widget.attachment.isNotEmpty)
|
||||||
|
// Wrap(
|
||||||
|
// spacing: 8.toScreenWidth,
|
||||||
|
// children: List.generate(
|
||||||
|
// widget.attachment.length,
|
||||||
|
// (index) {
|
||||||
|
// File image = File(widget.attachment[index].name!);
|
||||||
|
// return MultiFilesPickerItem(
|
||||||
|
// file: image,
|
||||||
|
// enabled: widget.enabled,
|
||||||
|
// onRemoveTap: (image) {
|
||||||
|
// if (!widget.enabled) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// widget.attachment.removeAt(index);
|
||||||
|
// if (widget.onChange != null) {
|
||||||
|
// widget.onChange!(widget.attachment);
|
||||||
|
// }
|
||||||
|
// setState(() {});
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fromFilePicker() async {
|
||||||
|
// FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
|
// type: FileType.custom,
|
||||||
|
// allowMultiple: true,
|
||||||
|
// allowedExtensions: widget.onlyImages ? ['jpg', 'jpeg', 'png'] : ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xlsx', 'pptx'],
|
||||||
|
// );
|
||||||
|
// if (result != null) {
|
||||||
|
// for (var path in result.paths) {
|
||||||
|
// widget.attachment.add(GenericAttachmentModel(id: 0, name: File(path!).path));
|
||||||
|
// }
|
||||||
|
// if (widget.onChange != null) {
|
||||||
|
// widget.onChange!(widget.attachment);
|
||||||
|
// }
|
||||||
|
// setState(() {});
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void showFileSourceSheet() async {
|
||||||
|
// // if (widget.attachment.length >= 5) {
|
||||||
|
// // Fluttertoast.showToast(msg: context.translation.maxImagesNumberIs5);
|
||||||
|
// // return;
|
||||||
|
// // }
|
||||||
|
//
|
||||||
|
// ImageSource source = (await showModalBottomSheet(
|
||||||
|
// context: context,
|
||||||
|
// shape: const RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.vertical(
|
||||||
|
// top: Radius.circular(20),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
|
// builder: (BuildContext context) => Column(
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// "Attach File".heading4(context),
|
||||||
|
// 12.height,
|
||||||
|
// GridView(
|
||||||
|
// padding: const EdgeInsets.all(0),
|
||||||
|
// shrinkWrap: true,
|
||||||
|
// physics: const NeverScrollableScrollPhysics(),
|
||||||
|
// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12),
|
||||||
|
// children: <Widget>[
|
||||||
|
// gridItem(Icons.camera_enhance_rounded, context.translation.pickFromCamera).onPress(() => Navigator.of(context).pop(ImageSource.camera)),
|
||||||
|
// gridItem(Icons.image_rounded, context.translation.pickFromGallery).onPress(() => Navigator.of(context).pop(ImageSource.gallery)),
|
||||||
|
// gridItem(Icons.file_present_rounded, context.translation.pickFromFiles).onPress(() async {
|
||||||
|
// await fromFilePicker();
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// }),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// 12.height,
|
||||||
|
// ],
|
||||||
|
// ).paddingAll(21),
|
||||||
|
// )) as ImageSource;
|
||||||
|
//
|
||||||
|
// final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800);
|
||||||
|
//
|
||||||
|
// if (pickedFile != null) {
|
||||||
|
// File fileImage = File(pickedFile.path);
|
||||||
|
// widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path));
|
||||||
|
// if (widget.onChange != null) {
|
||||||
|
// widget.onChange!(widget.attachment);
|
||||||
|
// }
|
||||||
|
// setState(() {});
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Widget gridItem(IconData iconData, String title) {
|
||||||
|
// return Container(
|
||||||
|
// padding: const EdgeInsets.all(12),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// color: Colors.white,
|
||||||
|
// borderRadius: BorderRadius.circular(12),
|
||||||
|
// border: Border.all(color: const Color(0xffF1F1F1), width: 1),
|
||||||
|
// ),
|
||||||
|
// child: Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// children: [
|
||||||
|
// Icon(iconData, color: const Color(0xff7D859A), size: 36),
|
||||||
|
// Text(
|
||||||
|
// title,
|
||||||
|
// style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// onFilePicker() async {
|
||||||
|
// //TODO removed on request by Backend as they don't have anyissue with large number of files
|
||||||
|
// // if (widget.attachment.length >= 5) {
|
||||||
|
// // Fluttertoast.showToast(msg: context.translation.maxImagesNumberIs5);
|
||||||
|
// // return;
|
||||||
|
// // }
|
||||||
|
// ImageSource? source = await showModalBottomSheet<ImageSource>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext context) {
|
||||||
|
// Widget listCard({required String icon, required String label, required VoidCallback onTap}) {
|
||||||
|
// return Container(
|
||||||
|
// padding: const EdgeInsets.all(12),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// color: AppColor.background(context),
|
||||||
|
// // color: Colors.white,
|
||||||
|
// borderRadius: BorderRadius.circular(12),
|
||||||
|
// border: Border.all(color: const Color(0xffF1F1F1), width: 1),
|
||||||
|
// ),
|
||||||
|
// child: Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// children: [
|
||||||
|
// icon.toSvgAsset(color: AppColor.iconColor(context), width: 36, height: 36),
|
||||||
|
// // Icon(iconData, color: const Color(0xff7D859A), size: 36),
|
||||||
|
// Text(
|
||||||
|
// label,
|
||||||
|
// style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ).onPress(onTap);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return SafeArea(
|
||||||
|
// top: false,
|
||||||
|
// child: Container(
|
||||||
|
// width: double.infinity,
|
||||||
|
// color: AppColor.background(context),
|
||||||
|
// child: Column(
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// "Attach File".heading4(context),
|
||||||
|
// 12.height,
|
||||||
|
// GridView(
|
||||||
|
// padding: const EdgeInsets.all(0),
|
||||||
|
// shrinkWrap: true,
|
||||||
|
// physics: const NeverScrollableScrollPhysics(),
|
||||||
|
// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12),
|
||||||
|
// children: <Widget>[
|
||||||
|
// listCard(
|
||||||
|
// icon: 'camera_icon',
|
||||||
|
// label: '${context.translation.open}\n${context.translation.camera}',
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.of(context).pop(ImageSource.camera);
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// listCard(
|
||||||
|
// icon: 'gallery_icon',
|
||||||
|
// label: '${context.translation.open}\n${context.translation.gallery}',
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.of(context).pop(ImageSource.gallery);
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// listCard(
|
||||||
|
// icon: 'file_icon',
|
||||||
|
// label: '${context.translation.open}\n${context.translation.files}',
|
||||||
|
// onTap: () async {
|
||||||
|
// await fromFilePicker();
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// // Container(
|
||||||
|
// // padding: const EdgeInsets.all(16.0),
|
||||||
|
// // child: Row(
|
||||||
|
// // mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// // children: <Widget>[
|
||||||
|
// // listCard(
|
||||||
|
// // icon: 'camera_icon',
|
||||||
|
// // label: '${context.translation.open}\n${context.translation.camera}',
|
||||||
|
// // onTap: () {
|
||||||
|
// // Navigator.of(context).pop(ImageSource.camera);
|
||||||
|
// // },
|
||||||
|
// // ),
|
||||||
|
// // listCard(
|
||||||
|
// // icon: 'gallery_icon',
|
||||||
|
// // label: '${context.translation.open}\n${context.translation.gallery}',
|
||||||
|
// // onTap: () {
|
||||||
|
// // Navigator.of(context).pop(ImageSource.gallery);
|
||||||
|
// // },
|
||||||
|
// // ),
|
||||||
|
// // listCard(
|
||||||
|
// // icon: 'file_icon',
|
||||||
|
// // label: '${context.translation.open}\n${context.translation.files}',
|
||||||
|
// // onTap: () async {
|
||||||
|
// // await fromFilePicker();
|
||||||
|
// // Navigator.pop(context);
|
||||||
|
// // },
|
||||||
|
// // ),
|
||||||
|
// // ],
|
||||||
|
// // ),
|
||||||
|
// // ),
|
||||||
|
// ],
|
||||||
|
// ).paddingAll(16),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// // ImageSource source = await showDialog(
|
||||||
|
// // context: context,
|
||||||
|
// // builder: (dialogContext) => CupertinoAlertDialog(
|
||||||
|
// // actions: <Widget>[
|
||||||
|
// // TextButton(
|
||||||
|
// // child: Text(context.translation.pickFromCamera),
|
||||||
|
// // onPressed: () {
|
||||||
|
// // Navigator.of(dialogContext).pop(ImageSource.camera);
|
||||||
|
// // },
|
||||||
|
// // ),
|
||||||
|
// // TextButton(
|
||||||
|
// // child: Text(context.translation.pickFromGallery),
|
||||||
|
// // onPressed: () {
|
||||||
|
// // Navigator.of(dialogContext).pop(ImageSource.gallery);
|
||||||
|
// // },
|
||||||
|
// // ),
|
||||||
|
// // TextButton(
|
||||||
|
// // child: Text(context.translation.pickFromFiles),
|
||||||
|
// // onPressed: () async {
|
||||||
|
// // await fromFilePicker();
|
||||||
|
// // Navigator.pop(context);
|
||||||
|
// // },
|
||||||
|
// // ),
|
||||||
|
// // ],
|
||||||
|
// // ),
|
||||||
|
// // );
|
||||||
|
// if (source == null) return;
|
||||||
|
//
|
||||||
|
// final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800);
|
||||||
|
//
|
||||||
|
// if (pickedFile != null) {
|
||||||
|
// File fileImage = File(pickedFile.path);
|
||||||
|
// widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path));
|
||||||
|
// if (widget.onChange != null) {
|
||||||
|
// widget.onChange!(widget.attachment);
|
||||||
|
// }
|
||||||
|
// setState(() {});
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// setState(() {});
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:open_file/open_file.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/chat_api_client.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/chat_provider.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/model/get_single_user_chat_list_model.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_text_style.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||||
|
import 'package:test_sa/views/widgets/images/multi_image_picker_item.dart';
|
||||||
|
import 'package:test_sa/views/widgets/sound/sound_player.dart';
|
||||||
|
|
||||||
|
class ChatFileViewer extends StatelessWidget {
|
||||||
|
String downloadUrl;
|
||||||
|
FileTypeResponse fileTypeResponse;
|
||||||
|
|
||||||
|
ChatFileViewer(this.downloadUrl, this.fileTypeResponse, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder<File?>(
|
||||||
|
future: checkFileInLocalStorage(),
|
||||||
|
builder: (BuildContext context, AsyncSnapshot<File?> snapshot) {
|
||||||
|
if (snapshot.connectionState != ConnectionState.waiting && snapshot.hasData) {}
|
||||||
|
|
||||||
|
if (fileTypeResponse!.fileKind.toString().toLowerCase() == "audio") {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return Container(
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
decoration: ShapeDecoration(
|
||||||
|
color: AppColor.background(context),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
side: BorderSide(width: 1, color: (context.isDark ? AppColor.neutral20 : AppColor.neutral30)),
|
||||||
|
borderRadius: BorderRadius.circular(32),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2).center,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return Text("Failed to load", style: AppTextStyle.tiny.copyWith(color: context.isDark ? AppColor.red50 : AppColor.red60));
|
||||||
|
}
|
||||||
|
return ASoundPlayer(audio: snapshot.data!.path);
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: Colors.grey.withOpacity(.5), width: 1),
|
||||||
|
image: snapshot.hasData ? DecorationImage(fit: BoxFit.contain, image: getImage(snapshot.data!)) : null,
|
||||||
|
),
|
||||||
|
child: snapshot.connectionState == ConnectionState.waiting
|
||||||
|
? const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2),
|
||||||
|
).center
|
||||||
|
: snapshot.hasData
|
||||||
|
? getFile(context, snapshot.data!)
|
||||||
|
: const Icon(Icons.broken_image_rounded),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getFile(BuildContext context, File file) {
|
||||||
|
bool isImage = file.path.split(".").last.toLowerCase() == "png" || file.path.split(".").last.toLowerCase() == "jpg" || file.path.split(".").last.toLowerCase() == "jpeg";
|
||||||
|
bool isPdf = file.path.split(".").last.toLowerCase() == "pdf";
|
||||||
|
bool isExcel = file.path.split(".").last.toLowerCase() == "xlsx";
|
||||||
|
|
||||||
|
return MaterialButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () async {
|
||||||
|
if (isImage) {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => Scaffold(
|
||||||
|
appBar: const DefaultAppBar(),
|
||||||
|
body: SafeArea(
|
||||||
|
child: InteractiveViewer(child: Image.file(file)).center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
OpenFile.open(file.path);
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// // if (!await launchUrl(Uri.parse(URLs.getFileUrl(file.path)!), mode: LaunchMode.externalApplication)) {
|
||||||
|
// // Fluttertoast.showToast(msg: "UnExpected Error with file.");
|
||||||
|
// // throw Exception('Could not launch');
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageProvider getImage(File file) {
|
||||||
|
bool isImage = file.path.split(".").last.toLowerCase() == "png" || file.path.split(".").last.toLowerCase() == "jpg" || file.path.split(".").last.toLowerCase() == "jpeg";
|
||||||
|
bool isPdf = file.path.split(".").last.toLowerCase() == "pdf";
|
||||||
|
bool isExcel = file.path.split(".").last.toLowerCase() == "xlsx";
|
||||||
|
|
||||||
|
if (isImage) {
|
||||||
|
return FileImage(file);
|
||||||
|
}
|
||||||
|
return AssetImage("assets/images/${isPdf ? "pdf" : isExcel ? "excel" : "doc"}.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File?> checkFileInLocalStorage() async {
|
||||||
|
Directory tempDir = await getTemporaryDirectory();
|
||||||
|
String tempPath = '${tempDir.path}/${fileTypeResponse.fileName}';
|
||||||
|
File tempFile = File(tempPath);
|
||||||
|
bool exists = await tempFile.exists();
|
||||||
|
if (exists) {
|
||||||
|
return tempFile;
|
||||||
|
} else {
|
||||||
|
return downloadFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File?> downloadFile() async {
|
||||||
|
try {
|
||||||
|
return await ChatApiClient()
|
||||||
|
.downloadFileWithHttp(downloadUrl, fileName: fileTypeResponse.fileName, fileTypeDescription: fileTypeResponse.fileTypeDescription, fileSource: fileTypeResponse.fileTypeId!);
|
||||||
|
} catch (ex) {
|
||||||
|
print("downloadFile:$ex");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
class ChatAttachment {
|
||||||
|
String? id;
|
||||||
|
String? originalFileName;
|
||||||
|
String? storedFileName;
|
||||||
|
String? contentType;
|
||||||
|
int? sizeBytes;
|
||||||
|
String? downloadUrl;
|
||||||
|
String? relativePath;
|
||||||
|
String? createdOn;
|
||||||
|
bool? isContextual;
|
||||||
|
String? moduleCode;
|
||||||
|
String? referenceId;
|
||||||
|
String? conversationId;
|
||||||
|
|
||||||
|
ChatAttachment(
|
||||||
|
{this.id,
|
||||||
|
this.originalFileName,
|
||||||
|
this.storedFileName,
|
||||||
|
this.contentType,
|
||||||
|
this.sizeBytes,
|
||||||
|
this.downloadUrl,
|
||||||
|
this.relativePath,
|
||||||
|
this.createdOn,
|
||||||
|
this.isContextual,
|
||||||
|
this.moduleCode,
|
||||||
|
this.referenceId,
|
||||||
|
this.conversationId});
|
||||||
|
|
||||||
|
ChatAttachment.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
originalFileName = json['originalFileName'];
|
||||||
|
storedFileName = json['storedFileName'];
|
||||||
|
contentType = json['contentType'];
|
||||||
|
sizeBytes = json['sizeBytes'];
|
||||||
|
downloadUrl = json['downloadUrl'];
|
||||||
|
relativePath = json['relativePath'];
|
||||||
|
createdOn = json['createdOn'];
|
||||||
|
isContextual = json['isContextual'];
|
||||||
|
moduleCode = json['moduleCode'];
|
||||||
|
referenceId = json['referenceId'];
|
||||||
|
conversationId = json['conversationId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['id'] = this.id;
|
||||||
|
data['originalFileName'] = this.originalFileName;
|
||||||
|
data['storedFileName'] = this.storedFileName;
|
||||||
|
data['contentType'] = this.contentType;
|
||||||
|
data['sizeBytes'] = this.sizeBytes;
|
||||||
|
data['downloadUrl'] = this.downloadUrl;
|
||||||
|
data['relativePath'] = this.relativePath;
|
||||||
|
data['createdOn'] = this.createdOn;
|
||||||
|
data['isContextual'] = this.isContextual;
|
||||||
|
data['moduleCode'] = this.moduleCode;
|
||||||
|
data['referenceId'] = this.referenceId;
|
||||||
|
data['conversationId'] = this.conversationId;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
class ChatLoginResponse {
|
||||||
|
String? token;
|
||||||
|
int? userId;
|
||||||
|
String? userName;
|
||||||
|
int? applicationId;
|
||||||
|
int? expiresIn;
|
||||||
|
Context? context;
|
||||||
|
|
||||||
|
ChatLoginResponse(
|
||||||
|
{this.token,
|
||||||
|
this.userId,
|
||||||
|
this.userName,
|
||||||
|
this.applicationId,
|
||||||
|
this.expiresIn,
|
||||||
|
this.context});
|
||||||
|
|
||||||
|
ChatLoginResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
token = json['token'];
|
||||||
|
userId = json['userId'];
|
||||||
|
userName = json['userName'];
|
||||||
|
applicationId = json['applicationId'];
|
||||||
|
expiresIn = json['expiresIn'];
|
||||||
|
context =
|
||||||
|
json['context'] != null ? new Context.fromJson(json['context']) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['token'] = this.token;
|
||||||
|
data['userId'] = this.userId;
|
||||||
|
data['userName'] = this.userName;
|
||||||
|
data['applicationId'] = this.applicationId;
|
||||||
|
data['expiresIn'] = this.expiresIn;
|
||||||
|
if (this.context != null) {
|
||||||
|
data['context'] = this.context!.toJson();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Context {
|
||||||
|
String? conversationId;
|
||||||
|
String? moduleCode;
|
||||||
|
String? referenceId;
|
||||||
|
String? title;
|
||||||
|
List<DeepLinks>? deepLinks;
|
||||||
|
|
||||||
|
Context(
|
||||||
|
{this.conversationId,
|
||||||
|
this.moduleCode,
|
||||||
|
this.referenceId,
|
||||||
|
this.title,
|
||||||
|
this.deepLinks});
|
||||||
|
|
||||||
|
Context.fromJson(Map<String, dynamic> json) {
|
||||||
|
conversationId = json['conversationId'];
|
||||||
|
moduleCode = json['moduleCode'];
|
||||||
|
referenceId = json['referenceId'];
|
||||||
|
title = json['title'];
|
||||||
|
if (json['deepLinks'] != null) {
|
||||||
|
deepLinks = <DeepLinks>[];
|
||||||
|
json['deepLinks'].forEach((v) {
|
||||||
|
deepLinks!.add(new DeepLinks.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['conversationId'] = this.conversationId;
|
||||||
|
data['moduleCode'] = this.moduleCode;
|
||||||
|
data['referenceId'] = this.referenceId;
|
||||||
|
data['title'] = this.title;
|
||||||
|
if (this.deepLinks != null) {
|
||||||
|
data['deepLinks'] = this.deepLinks!.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeepLinks {
|
||||||
|
String? employeeNumber;
|
||||||
|
int? userId;
|
||||||
|
String? userName;
|
||||||
|
|
||||||
|
DeepLinks({this.employeeNumber, this.userId, this.userName});
|
||||||
|
|
||||||
|
DeepLinks.fromJson(Map<String, dynamic> json) {
|
||||||
|
employeeNumber = json['employeeNumber'];
|
||||||
|
userId = json['userId'];
|
||||||
|
userName = json['userName'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['employeeNumber'] = this.employeeNumber;
|
||||||
|
data['userId'] = this.userId;
|
||||||
|
data['userName'] = this.userName;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
class ChatParticipantModel {
|
||||||
|
int? id;
|
||||||
|
String? title;
|
||||||
|
String? conversationType;
|
||||||
|
List<Participants>? participants;
|
||||||
|
String? lastMessage;
|
||||||
|
String? createdAt;
|
||||||
|
|
||||||
|
ChatParticipantModel({this.id, this.title, this.conversationType, this.participants, this.lastMessage, this.createdAt});
|
||||||
|
|
||||||
|
ChatParticipantModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
title = json['title'];
|
||||||
|
conversationType = json['conversationType'];
|
||||||
|
if (json['participants'] != null) {
|
||||||
|
participants = <Participants>[];
|
||||||
|
json['participants'].forEach((v) {
|
||||||
|
participants!.add(new Participants.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
lastMessage = json['lastMessage'];
|
||||||
|
createdAt = json['createdAt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['id'] = this.id;
|
||||||
|
data['title'] = this.title;
|
||||||
|
data['conversationType'] = this.conversationType;
|
||||||
|
if (this.participants != null) {
|
||||||
|
data['participants'] = this.participants!.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
data['lastMessage'] = this.lastMessage;
|
||||||
|
data['createdAt'] = this.createdAt;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Participants {
|
||||||
|
String? userId;
|
||||||
|
String? userName;
|
||||||
|
String? employeeNumber;
|
||||||
|
String? role;
|
||||||
|
int? userStatus;
|
||||||
|
|
||||||
|
Participants({this.userId, this.userName, this.employeeNumber, this.role, this.userStatus});
|
||||||
|
|
||||||
|
Participants.fromJson(Map<String, dynamic> json) {
|
||||||
|
userId = json['userId'];
|
||||||
|
userName = json['userName'];
|
||||||
|
employeeNumber = json['employeeNumber'];
|
||||||
|
role = json['role'];
|
||||||
|
userStatus = json['userStatus'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['userId'] = this.userId;
|
||||||
|
data['userName'] = this.userName;
|
||||||
|
data['employeeNumber'] = this.employeeNumber;
|
||||||
|
data['role'] = this.role;
|
||||||
|
data['userStatus'] = this.userStatus;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
ChatUserModel chatUserModelFromJson(String str) => ChatUserModel.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String chatUserModelToJson(ChatUserModel data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
class ChatUserModel {
|
||||||
|
ChatUserModel({
|
||||||
|
this.response,
|
||||||
|
this.errorResponses,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<ChatUser>? response;
|
||||||
|
List<ErrorResponse>? errorResponses;
|
||||||
|
|
||||||
|
factory ChatUserModel.fromJson(Map<String, dynamic> json) => ChatUserModel(
|
||||||
|
response: json["response"] == null ? null : List<ChatUser>.from(json["response"].map((x) => ChatUser.fromJson(x))),
|
||||||
|
errorResponses: json["errorResponses"] == null ? null : List<ErrorResponse>.from(json["errorResponses"].map((x) => ErrorResponse.fromJson(x))),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"response": response == null ? null : List<dynamic>.from(response!.map((x) => x.toJson())),
|
||||||
|
"errorResponses": errorResponses == null ? null : List<dynamic>.from(errorResponses!.map((x) => x.toJson())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorResponse {
|
||||||
|
ErrorResponse({
|
||||||
|
this.fieldName,
|
||||||
|
this.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
dynamic? fieldName;
|
||||||
|
String? message;
|
||||||
|
|
||||||
|
factory ErrorResponse.fromRawJson(String str) => ErrorResponse.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory ErrorResponse.fromJson(Map<String, dynamic> json) => ErrorResponse(
|
||||||
|
fieldName: json["fieldName"],
|
||||||
|
message: json["message"] == null ? null : json["message"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"fieldName": fieldName,
|
||||||
|
"message": message == null ? null : message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChatUser {
|
||||||
|
ChatUser({
|
||||||
|
this.id,
|
||||||
|
this.userName,
|
||||||
|
this.email,
|
||||||
|
this.phone,
|
||||||
|
this.title,
|
||||||
|
this.userStatus,
|
||||||
|
this.image,
|
||||||
|
this.unreadMessageCount,
|
||||||
|
this.userAction,
|
||||||
|
this.isPin,
|
||||||
|
this.isFav,
|
||||||
|
this.isAdmin,
|
||||||
|
this.rKey,
|
||||||
|
this.totalCount,
|
||||||
|
this.isTyping,
|
||||||
|
this.isImageLoaded,
|
||||||
|
this.isImageLoading,
|
||||||
|
this.userLocalDownlaodedImage,
|
||||||
|
this.isChecked
|
||||||
|
});
|
||||||
|
|
||||||
|
int? id;
|
||||||
|
String? userName;
|
||||||
|
String? email;
|
||||||
|
dynamic? phone;
|
||||||
|
String? title;
|
||||||
|
int? userStatus;
|
||||||
|
dynamic? image;
|
||||||
|
int? unreadMessageCount;
|
||||||
|
dynamic? userAction;
|
||||||
|
bool? isPin;
|
||||||
|
bool? isFav;
|
||||||
|
bool? isAdmin;
|
||||||
|
dynamic? rKey;
|
||||||
|
int? totalCount;
|
||||||
|
bool? isTyping;
|
||||||
|
bool? isImageLoaded;
|
||||||
|
bool? isImageLoading;
|
||||||
|
File? userLocalDownlaodedImage;
|
||||||
|
bool? isChecked;
|
||||||
|
factory ChatUser.fromRawJson(String str) => ChatUser.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory ChatUser.fromJson(Map<String, dynamic> json) => ChatUser(
|
||||||
|
id: json["id"] == null ? null : json["id"],
|
||||||
|
userName: json["userName"] == null ? null : json["userName"],
|
||||||
|
email: json["email"] == null ? null : json["email"],
|
||||||
|
phone: json["phone"],
|
||||||
|
title: json["title"] == null ? null : json["title"],
|
||||||
|
userStatus: json["userStatus"] == null ? null : json["userStatus"],
|
||||||
|
image: json["image"],
|
||||||
|
unreadMessageCount: json["unreadMessageCount"] == null ? null : json["unreadMessageCount"],
|
||||||
|
userAction: json["userAction"],
|
||||||
|
isPin: json["isPin"] == null ? null : json["isPin"],
|
||||||
|
isFav: json["isFav"] == null ? null : json["isFav"],
|
||||||
|
isAdmin: json["isAdmin"] == null ? null : json["isAdmin"],
|
||||||
|
rKey: json["rKey"],
|
||||||
|
totalCount: json["totalCount"] == null ? null : json["totalCount"],
|
||||||
|
isTyping: false,
|
||||||
|
isImageLoaded: false,
|
||||||
|
isImageLoading: true,
|
||||||
|
userLocalDownlaodedImage: null,
|
||||||
|
isChecked: false
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"id": id == null ? null : id,
|
||||||
|
"userName": userName == null ? null : userName,
|
||||||
|
"email": email == null ? null : email,
|
||||||
|
"phone": phone,
|
||||||
|
"title": title == null ? null : title,
|
||||||
|
"userStatus": userStatus == null ? null : userStatus,
|
||||||
|
"image": image,
|
||||||
|
"unreadMessageCount": unreadMessageCount == null ? null : unreadMessageCount,
|
||||||
|
"userAction": userAction,
|
||||||
|
"isPin": isPin == null ? null : isPin,
|
||||||
|
"isFav": isFav == null ? null : isFav,
|
||||||
|
"isAdmin": isAdmin == null ? null : isAdmin,
|
||||||
|
"rKey": rKey,
|
||||||
|
"totalCount": totalCount == null ? null : totalCount,
|
||||||
|
"isChecked":isChecked
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,210 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
|
||||||
|
List<SingleUserChatModel> singleUserChatModelFromJson(String str) => List<SingleUserChatModel>.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x)));
|
||||||
|
|
||||||
|
String singleUserChatModelToJson(List<SingleUserChatModel> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
|
||||||
|
|
||||||
|
class SingleUserChatModel {
|
||||||
|
SingleUserChatModel(
|
||||||
|
{this.userChatHistoryId,
|
||||||
|
this.userChatHistoryLineId,
|
||||||
|
this.contant,
|
||||||
|
this.contantNo,
|
||||||
|
this.currentUserId,
|
||||||
|
this.currentUserName,
|
||||||
|
this.targetUserId,
|
||||||
|
this.targetUserName,
|
||||||
|
this.encryptedTargetUserId,
|
||||||
|
this.encryptedTargetUserName,
|
||||||
|
this.currentUserEmail,
|
||||||
|
this.targetUserEmail,
|
||||||
|
this.chatEventId,
|
||||||
|
this.fileTypeId,
|
||||||
|
this.isSeen,
|
||||||
|
this.isDelivered,
|
||||||
|
this.createdDate,
|
||||||
|
this.chatSource,
|
||||||
|
this.conversationId,
|
||||||
|
this.downloadUrl,
|
||||||
|
this.fileTypeResponse,
|
||||||
|
this.userChatReplyResponse,
|
||||||
|
this.isReplied,
|
||||||
|
this.isImageLoaded,
|
||||||
|
this.image,
|
||||||
|
this.voice,
|
||||||
|
this.voiceController});
|
||||||
|
|
||||||
|
int? userChatHistoryId;
|
||||||
|
int? userChatHistoryLineId;
|
||||||
|
String? contant;
|
||||||
|
String? contantNo;
|
||||||
|
String? currentUserId;
|
||||||
|
String? currentUserName;
|
||||||
|
String? currentUserEmail;
|
||||||
|
String? targetUserId;
|
||||||
|
String? targetUserName;
|
||||||
|
String? targetUserEmail;
|
||||||
|
String? encryptedTargetUserId;
|
||||||
|
String? encryptedTargetUserName;
|
||||||
|
int? chatEventId;
|
||||||
|
dynamic? fileTypeId;
|
||||||
|
bool? isSeen;
|
||||||
|
bool? isDelivered;
|
||||||
|
DateTime? createdDate;
|
||||||
|
int? chatSource;
|
||||||
|
String? conversationId;
|
||||||
|
String? downloadUrl;
|
||||||
|
FileTypeResponse? fileTypeResponse;
|
||||||
|
UserChatReplyResponse? userChatReplyResponse;
|
||||||
|
bool? isReplied;
|
||||||
|
bool? isImageLoaded;
|
||||||
|
Uint8List? image;
|
||||||
|
File? voice;
|
||||||
|
AudioPlayer? voiceController;
|
||||||
|
|
||||||
|
factory SingleUserChatModel.fromJson(Map<String, dynamic> json) => SingleUserChatModel(
|
||||||
|
userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"],
|
||||||
|
userChatHistoryLineId: json["userChatHistoryLineId"] == null ? null : json["userChatHistoryLineId"],
|
||||||
|
contant: json["contant"] == null ? null : json["contant"],
|
||||||
|
contantNo: json["contantNo"] == null ? null : json["contantNo"],
|
||||||
|
currentUserId: json["currentUserId"] == null ? null : json["currentUserId"].toString(),
|
||||||
|
currentUserName: json["currentUserName"] == null ? null : json["currentUserName"],
|
||||||
|
targetUserId: json["targetUserId"] == null ? null : json["targetUserId"].toString(),
|
||||||
|
targetUserName: json["targetUserName"] == null ? null : json["targetUserName"],
|
||||||
|
targetUserEmail: json["targetUserEmail"] == null ? null : json["targetUserEmail"],
|
||||||
|
currentUserEmail: json["currentUserEmail"] == null ? null : json["currentUserEmail"],
|
||||||
|
encryptedTargetUserId: json["encryptedTargetUserId"] == null ? null : json["encryptedTargetUserId"],
|
||||||
|
encryptedTargetUserName: json["encryptedTargetUserName"] == null ? null : json["encryptedTargetUserName"],
|
||||||
|
chatEventId: json["chatEventId"] == null ? null : json["chatEventId"],
|
||||||
|
fileTypeId: json["fileTypeId"],
|
||||||
|
isSeen: json["isSeen"] == null ? null : json["isSeen"],
|
||||||
|
isDelivered: json["isDelivered"] == null ? null : json["isDelivered"],
|
||||||
|
createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]),
|
||||||
|
chatSource: json["chatSource"] == null ? null : json["chatSource"],
|
||||||
|
conversationId: json["conversationId"] == null ? null : json["conversationId"],
|
||||||
|
downloadUrl: json["downloadUrl"] == null ? null : json["downloadUrl"],
|
||||||
|
fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]),
|
||||||
|
userChatReplyResponse: json["userChatReplyResponse"] == null ? null : UserChatReplyResponse.fromJson(json["userChatReplyResponse"]),
|
||||||
|
isReplied: false,
|
||||||
|
isImageLoaded: false,
|
||||||
|
image: null,
|
||||||
|
voice: null,
|
||||||
|
voiceController: json["fileTypeId"] == 13 ? AudioPlayer() : null);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId,
|
||||||
|
"userChatHistoryLineId": userChatHistoryLineId == null ? null : userChatHistoryLineId,
|
||||||
|
"contant": contant == null ? null : contant,
|
||||||
|
"contantNo": contantNo == null ? null : contantNo,
|
||||||
|
"currentUserId": currentUserId == null ? null : currentUserId,
|
||||||
|
"currentUserName": currentUserName == null ? null : currentUserName,
|
||||||
|
"targetUserId": targetUserId == null ? null : targetUserId,
|
||||||
|
"targetUserName": targetUserName == null ? null : targetUserName,
|
||||||
|
"encryptedTargetUserId": encryptedTargetUserId == null ? null : encryptedTargetUserId,
|
||||||
|
"encryptedTargetUserName": encryptedTargetUserName == null ? null : encryptedTargetUserName,
|
||||||
|
"currentUserEmail": currentUserEmail == null ? null : currentUserEmail,
|
||||||
|
"targetUserEmail": targetUserEmail == null ? null : targetUserEmail,
|
||||||
|
"chatEventId": chatEventId == null ? null : chatEventId,
|
||||||
|
"fileTypeId": fileTypeId,
|
||||||
|
"isSeen": isSeen == null ? null : isSeen,
|
||||||
|
"isDelivered": isDelivered == null ? null : isDelivered,
|
||||||
|
"createdDate": createdDate == null ? null : createdDate!.toIso8601String(),
|
||||||
|
"chatSource": chatSource == null ? null : chatSource,
|
||||||
|
"conversationId": conversationId == null ? null : conversationId,
|
||||||
|
"downloadUrl": downloadUrl == null ? null : downloadUrl,
|
||||||
|
"fileTypeResponse": fileTypeResponse == null ? null : fileTypeResponse!.toJson(),
|
||||||
|
"userChatReplyResponse": userChatReplyResponse == null ? null : userChatReplyResponse!.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileTypeResponse {
|
||||||
|
FileTypeResponse({
|
||||||
|
this.fileTypeId,
|
||||||
|
this.fileTypeName,
|
||||||
|
this.fileTypeDescription,
|
||||||
|
this.fileKind,
|
||||||
|
this.fileName,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? fileTypeId;
|
||||||
|
dynamic fileTypeName;
|
||||||
|
dynamic fileTypeDescription;
|
||||||
|
dynamic fileKind;
|
||||||
|
dynamic fileName;
|
||||||
|
|
||||||
|
factory FileTypeResponse.fromJson(Map<String, dynamic> json) => FileTypeResponse(
|
||||||
|
fileTypeId: json["fileTypeId"] == null ? null : json["fileTypeId"],
|
||||||
|
fileTypeName: json["fileTypeName"],
|
||||||
|
fileTypeDescription: json["fileTypeDescription"],
|
||||||
|
fileKind: json["fileKind"],
|
||||||
|
fileName: json["fileName"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"fileTypeId": fileTypeId == null ? null : fileTypeId,
|
||||||
|
"fileTypeName": fileTypeName,
|
||||||
|
"fileTypeDescription": fileTypeDescription,
|
||||||
|
"fileKind": fileKind,
|
||||||
|
"fileName": fileName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserChatReplyResponse {
|
||||||
|
UserChatReplyResponse(
|
||||||
|
{this.userChatHistoryId,
|
||||||
|
this.chatEventId,
|
||||||
|
this.contant,
|
||||||
|
this.contantNo,
|
||||||
|
this.fileTypeId,
|
||||||
|
this.createdDate,
|
||||||
|
this.targetUserId,
|
||||||
|
this.targetUserName,
|
||||||
|
this.fileTypeResponse,
|
||||||
|
this.isImageLoaded,
|
||||||
|
this.image,
|
||||||
|
this.voice});
|
||||||
|
|
||||||
|
int? userChatHistoryId;
|
||||||
|
int? chatEventId;
|
||||||
|
String? contant;
|
||||||
|
String? contantNo;
|
||||||
|
dynamic? fileTypeId;
|
||||||
|
DateTime? createdDate;
|
||||||
|
int? targetUserId;
|
||||||
|
String? targetUserName;
|
||||||
|
FileTypeResponse? fileTypeResponse;
|
||||||
|
bool? isImageLoaded;
|
||||||
|
Uint8List? image;
|
||||||
|
Uint8List? voice;
|
||||||
|
|
||||||
|
factory UserChatReplyResponse.fromJson(Map<String, dynamic> json) => UserChatReplyResponse(
|
||||||
|
userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"],
|
||||||
|
chatEventId: json["chatEventId"] == null ? null : json["chatEventId"],
|
||||||
|
contant: json["contant"] == null ? null : json["contant"],
|
||||||
|
contantNo: json["contantNo"] == null ? null : json["contantNo"],
|
||||||
|
fileTypeId: json["fileTypeId"],
|
||||||
|
createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]),
|
||||||
|
targetUserId: json["targetUserId"] == null ? null : json["targetUserId"],
|
||||||
|
targetUserName: json["targetUserName"] == null ? null : json["targetUserName"],
|
||||||
|
fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]),
|
||||||
|
isImageLoaded: false,
|
||||||
|
image: null,
|
||||||
|
voice: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId,
|
||||||
|
"chatEventId": chatEventId == null ? null : chatEventId,
|
||||||
|
"contant": contant == null ? null : contant,
|
||||||
|
"contantNo": contantNo == null ? null : contantNo,
|
||||||
|
"fileTypeId": fileTypeId,
|
||||||
|
"createdDate": createdDate == null ? null : createdDate!.toIso8601String(),
|
||||||
|
"targetUserId": targetUserId == null ? null : targetUserId,
|
||||||
|
"targetUserName": targetUserName == null ? null : targetUserName,
|
||||||
|
"fileTypeResponse": fileTypeResponse == null ? null : fileTypeResponse!.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
UserAutoLoginModel userAutoLoginModelFromJson(String str) => UserAutoLoginModel.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String userAutoLoginModelToJson(UserAutoLoginModel data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
class UserAutoLoginModel {
|
||||||
|
UserAutoLoginModel({this.response, this.errorResponses, this.StatusCode});
|
||||||
|
|
||||||
|
Response? response;
|
||||||
|
List<ErrorResponse>? errorResponses;
|
||||||
|
int? StatusCode;
|
||||||
|
|
||||||
|
factory UserAutoLoginModel.fromJson(Map<String, dynamic> json) => UserAutoLoginModel(
|
||||||
|
response: json["response"] == null ? null : Response.fromJson(json["response"]),
|
||||||
|
StatusCode: json["StatusCode"],
|
||||||
|
errorResponses: json["errorResponses"] == null ? null : List<ErrorResponse>.from(json["errorResponses"].map((x) => ErrorResponse.fromJson(x))),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"response": response == null ? null : response!.toJson(),
|
||||||
|
"StatusCode": StatusCode,
|
||||||
|
"errorResponses": errorResponses == null ? null : List<dynamic>.from(errorResponses!.map((x) => x.toJson())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Response {
|
||||||
|
Response({
|
||||||
|
this.id,
|
||||||
|
this.userName,
|
||||||
|
this.email,
|
||||||
|
this.phone,
|
||||||
|
this.title,
|
||||||
|
this.token,
|
||||||
|
this.isDomainUser,
|
||||||
|
this.isActiveCode,
|
||||||
|
this.encryptedUserId,
|
||||||
|
this.encryptedUserName,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? id;
|
||||||
|
String? userName;
|
||||||
|
String? email;
|
||||||
|
String? phone;
|
||||||
|
String? title;
|
||||||
|
String? token;
|
||||||
|
bool? isDomainUser;
|
||||||
|
bool? isActiveCode;
|
||||||
|
String? encryptedUserId;
|
||||||
|
String? encryptedUserName;
|
||||||
|
|
||||||
|
factory Response.fromJson(Map<String, dynamic> json) => Response(
|
||||||
|
id: json["id"] == null ? null : json["id"],
|
||||||
|
userName: json["userName"] == null ? null : json["userName"],
|
||||||
|
email: json["email"] == null ? null : json["email"],
|
||||||
|
phone: json["phone"] == null ? null : json["phone"],
|
||||||
|
title: json["title"] == null ? null : json["title"],
|
||||||
|
token: json["token"] == null ? null : json["token"],
|
||||||
|
isDomainUser: json["isDomainUser"] == null ? null : json["isDomainUser"],
|
||||||
|
isActiveCode: json["isActiveCode"] == null ? null : json["isActiveCode"],
|
||||||
|
encryptedUserId: json["encryptedUserId"] == null ? null : json["encryptedUserId"],
|
||||||
|
encryptedUserName: json["encryptedUserName"] == null ? null : json["encryptedUserName"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"id": id == null ? null : id,
|
||||||
|
"userName": userName == null ? null : userName,
|
||||||
|
"email": email == null ? null : email,
|
||||||
|
"phone": phone == null ? null : phone,
|
||||||
|
"title": title == null ? null : title,
|
||||||
|
"token": token == null ? null : token,
|
||||||
|
"isDomainUser": isDomainUser == null ? null : isDomainUser,
|
||||||
|
"isActiveCode": isActiveCode == null ? null : isActiveCode,
|
||||||
|
"encryptedUserId": encryptedUserId == null ? null : encryptedUserId,
|
||||||
|
"encryptedUserName": encryptedUserName == null ? null : encryptedUserName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorResponse {
|
||||||
|
ErrorResponse({
|
||||||
|
this.fieldName,
|
||||||
|
this.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? fieldName;
|
||||||
|
String? message;
|
||||||
|
|
||||||
|
factory ErrorResponse.fromJson(Map<String, dynamic> json) => ErrorResponse(
|
||||||
|
fieldName: json["fieldName"] == null ? null : json["fieldName"],
|
||||||
|
message: json["message"] == null ? null : json["message"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"fieldName": fieldName == null ? null : fieldName,
|
||||||
|
"message": message == null ? null : message,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,224 @@
|
|||||||
|
// class UserChatHistoryModel {
|
||||||
|
// List<ChatResponse>? response;
|
||||||
|
// bool? isSuccess;
|
||||||
|
// List<String>? onlineUserConnId;
|
||||||
|
//
|
||||||
|
// UserChatHistoryModel({this.response, this.isSuccess, this.onlineUserConnId});
|
||||||
|
//
|
||||||
|
// UserChatHistoryModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
// if (json['response'] != null) {
|
||||||
|
// response = <ChatResponse>[];
|
||||||
|
// json['response'].forEach((v) {
|
||||||
|
// response!.add(new ChatResponse.fromJson(v));
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// isSuccess = json['isSuccess'];
|
||||||
|
// onlineUserConnId = json['onlineUserConnId'].cast<String>();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Map<String, dynamic> toJson() {
|
||||||
|
// final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
// if (this.response != null) {
|
||||||
|
// data['response'] = this.response!.map((v) => v.toJson()).toList();
|
||||||
|
// }
|
||||||
|
// data['isSuccess'] = this.isSuccess;
|
||||||
|
// data['onlineUserConnId'] = this.onlineUserConnId;
|
||||||
|
// return data;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// class ChatResponse {
|
||||||
|
// int? id;
|
||||||
|
// int? conversationId;
|
||||||
|
// String? userId;
|
||||||
|
// int? userIdInt;
|
||||||
|
// String? userName;
|
||||||
|
// String? content;
|
||||||
|
// String? messageType;
|
||||||
|
// String? createdAt;
|
||||||
|
//
|
||||||
|
// ChatResponse({this.id, this.conversationId, this.userId, this.userIdInt, this.userName, this.content, this.messageType, this.createdAt});
|
||||||
|
//
|
||||||
|
// ChatResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
// id = json['id'];
|
||||||
|
// conversationId = json['conversationId'];
|
||||||
|
// userId = json['userId'];
|
||||||
|
// userIdInt = json['userIdInt'];
|
||||||
|
// userName = json['userName'];
|
||||||
|
// content = json['content'];
|
||||||
|
// messageType = json['messageType'];
|
||||||
|
// createdAt = json['createdAt'];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Map<String, dynamic> toJson() {
|
||||||
|
// final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
// data['id'] = this.id;
|
||||||
|
// data['conversationId'] = this.conversationId;
|
||||||
|
// data['userId'] = this.userId;
|
||||||
|
// data['userIdInt'] = this.userIdInt;
|
||||||
|
// data['userName'] = this.userName;
|
||||||
|
// data['content'] = this.content;
|
||||||
|
// data['messageType'] = this.messageType;
|
||||||
|
// data['createdAt'] = this.createdAt;
|
||||||
|
// return data;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// class ChatHistoryResponse {
|
||||||
|
// int? userChatHistoryId;
|
||||||
|
// int? userChatHistoryLineId;
|
||||||
|
// String? contant;
|
||||||
|
// String? contantNo;
|
||||||
|
// String? currentUserId;
|
||||||
|
// String? currentEmployeeNumber;
|
||||||
|
// String? currentUserName;
|
||||||
|
// String? currentUserEmail;
|
||||||
|
// String? currentFullName;
|
||||||
|
// String? targetUserId;
|
||||||
|
// String? targetEmployeeNumber;
|
||||||
|
// String? targetUserName;
|
||||||
|
// String? targetUserEmail;
|
||||||
|
// String? targetFullName;
|
||||||
|
// String? encryptedTargetUserId;
|
||||||
|
// String? encryptedTargetUserName;
|
||||||
|
// int? chatEventId;
|
||||||
|
// String? fileTypeId;
|
||||||
|
// bool? isSeen;
|
||||||
|
// bool? isDelivered;
|
||||||
|
// String? createdDate;
|
||||||
|
// int? chatSource;
|
||||||
|
// String? conversationId;
|
||||||
|
// FileTypeResponse? fileTypeResponse;
|
||||||
|
// String? userChatReplyResponse;
|
||||||
|
// String? deviceToken;
|
||||||
|
// bool? isHuaweiDevice;
|
||||||
|
// String? platform;
|
||||||
|
// String? voipToken;
|
||||||
|
//
|
||||||
|
// ChatHistoryResponse(
|
||||||
|
// {this.userChatHistoryId,
|
||||||
|
// this.userChatHistoryLineId,
|
||||||
|
// this.contant,
|
||||||
|
// this.contantNo,
|
||||||
|
// this.currentUserId,
|
||||||
|
// this.currentEmployeeNumber,
|
||||||
|
// this.currentUserName,
|
||||||
|
// this.currentUserEmail,
|
||||||
|
// this.currentFullName,
|
||||||
|
// this.targetUserId,
|
||||||
|
// this.targetEmployeeNumber,
|
||||||
|
// this.targetUserName,
|
||||||
|
// this.targetUserEmail,
|
||||||
|
// this.targetFullName,
|
||||||
|
// this.encryptedTargetUserId,
|
||||||
|
// this.encryptedTargetUserName,
|
||||||
|
// this.chatEventId,
|
||||||
|
// this.fileTypeId,
|
||||||
|
// this.isSeen,
|
||||||
|
// this.isDelivered,
|
||||||
|
// this.createdDate,
|
||||||
|
// this.chatSource,
|
||||||
|
// this.conversationId,
|
||||||
|
// this.fileTypeResponse,
|
||||||
|
// this.userChatReplyResponse,
|
||||||
|
// this.deviceToken,
|
||||||
|
// this.isHuaweiDevice,
|
||||||
|
// this.platform,
|
||||||
|
// this.voipToken});
|
||||||
|
//
|
||||||
|
// ChatHistoryResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
// userChatHistoryId = json['userChatHistoryId'];
|
||||||
|
// userChatHistoryLineId = json['userChatHistoryLineId'];
|
||||||
|
// contant = json['contant'];
|
||||||
|
// contantNo = json['contantNo'];
|
||||||
|
// currentUserId = json['currentUserId'];
|
||||||
|
// currentEmployeeNumber = json['currentEmployeeNumber'];
|
||||||
|
// currentUserName = json['currentUserName'];
|
||||||
|
// currentUserEmail = json['currentUserEmail'];
|
||||||
|
// currentFullName = json['currentFullName'];
|
||||||
|
// targetUserId = json['targetUserId'];
|
||||||
|
// targetEmployeeNumber = json['targetEmployeeNumber'];
|
||||||
|
// targetUserName = json['targetUserName'];
|
||||||
|
// targetUserEmail = json['targetUserEmail'];
|
||||||
|
// targetFullName = json['targetFullName'];
|
||||||
|
// encryptedTargetUserId = json['encryptedTargetUserId'];
|
||||||
|
// encryptedTargetUserName = json['encryptedTargetUserName'];
|
||||||
|
// chatEventId = json['chatEventId'];
|
||||||
|
// fileTypeId = json['fileTypeId'];
|
||||||
|
// isSeen = json['isSeen'];
|
||||||
|
// isDelivered = json['isDelivered'];
|
||||||
|
// createdDate = json['createdDate'];
|
||||||
|
// chatSource = json['chatSource'];
|
||||||
|
// conversationId = json['conversationId'];
|
||||||
|
// fileTypeResponse = json['fileTypeResponse'] != null ? new FileTypeResponse.fromJson(json['fileTypeResponse']) : null;
|
||||||
|
// userChatReplyResponse = json['userChatReplyResponse'];
|
||||||
|
// deviceToken = json['deviceToken'];
|
||||||
|
// isHuaweiDevice = json['isHuaweiDevice'];
|
||||||
|
// platform = json['platform'];
|
||||||
|
// voipToken = json['voipToken'];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Map<String, dynamic> toJson() {
|
||||||
|
// final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
// data['userChatHistoryId'] = this.userChatHistoryId;
|
||||||
|
// data['userChatHistoryLineId'] = this.userChatHistoryLineId;
|
||||||
|
// data['contant'] = this.contant;
|
||||||
|
// data['contantNo'] = this.contantNo;
|
||||||
|
// data['currentUserId'] = this.currentUserId;
|
||||||
|
// data['currentEmployeeNumber'] = this.currentEmployeeNumber;
|
||||||
|
// data['currentUserName'] = this.currentUserName;
|
||||||
|
// data['currentUserEmail'] = this.currentUserEmail;
|
||||||
|
// data['currentFullName'] = this.currentFullName;
|
||||||
|
// data['targetUserId'] = this.targetUserId;
|
||||||
|
// data['targetEmployeeNumber'] = this.targetEmployeeNumber;
|
||||||
|
// data['targetUserName'] = this.targetUserName;
|
||||||
|
// data['targetUserEmail'] = this.targetUserEmail;
|
||||||
|
// data['targetFullName'] = this.targetFullName;
|
||||||
|
// data['encryptedTargetUserId'] = this.encryptedTargetUserId;
|
||||||
|
// data['encryptedTargetUserName'] = this.encryptedTargetUserName;
|
||||||
|
// data['chatEventId'] = this.chatEventId;
|
||||||
|
// data['fileTypeId'] = this.fileTypeId;
|
||||||
|
// data['isSeen'] = this.isSeen;
|
||||||
|
// data['isDelivered'] = this.isDelivered;
|
||||||
|
// data['createdDate'] = this.createdDate;
|
||||||
|
// data['chatSource'] = this.chatSource;
|
||||||
|
// data['conversationId'] = this.conversationId;
|
||||||
|
// if (this.fileTypeResponse != null) {
|
||||||
|
// data['fileTypeResponse'] = this.fileTypeResponse!.toJson();
|
||||||
|
// }
|
||||||
|
// data['userChatReplyResponse'] = this.userChatReplyResponse;
|
||||||
|
// data['deviceToken'] = this.deviceToken;
|
||||||
|
// data['isHuaweiDevice'] = this.isHuaweiDevice;
|
||||||
|
// data['platform'] = this.platform;
|
||||||
|
// data['voipToken'] = this.voipToken;
|
||||||
|
// return data;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// class FileTypeResponse {
|
||||||
|
// int? fileTypeId;
|
||||||
|
// String? fileTypeName;
|
||||||
|
// String? fileTypeDescription;
|
||||||
|
// String? fileKind;
|
||||||
|
// String? fileName;
|
||||||
|
//
|
||||||
|
// FileTypeResponse({this.fileTypeId, this.fileTypeName, this.fileTypeDescription, this.fileKind, this.fileName});
|
||||||
|
//
|
||||||
|
// FileTypeResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
// fileTypeId = json['fileTypeId'];
|
||||||
|
// fileTypeName = json['fileTypeName'];
|
||||||
|
// fileTypeDescription = json['fileTypeDescription'];
|
||||||
|
// fileKind = json['fileKind'];
|
||||||
|
// fileName = json['fileName'];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Map<String, dynamic> toJson() {
|
||||||
|
// final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
// data['fileTypeId'] = this.fileTypeId;
|
||||||
|
// data['fileTypeName'] = this.fileTypeName;
|
||||||
|
// data['fileTypeDescription'] = this.fileTypeDescription;
|
||||||
|
// data['fileKind'] = this.fileKind;
|
||||||
|
// data['fileName'] = this.fileName;
|
||||||
|
// return data;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@ -0,0 +1,309 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:open_file/open_file.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/chat_provider.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/model/chat_attachment_model.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_text_style.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||||
|
import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
|
||||||
|
import 'package:test_sa/views/widgets/sound/sound_player.dart';
|
||||||
|
|
||||||
|
import 'chat_api_client.dart';
|
||||||
|
|
||||||
|
class ViewAllAttachmentPage extends StatefulWidget {
|
||||||
|
int moduleId;
|
||||||
|
int requestId;
|
||||||
|
|
||||||
|
ViewAllAttachmentPage({required this.moduleId, required this.requestId, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ViewAllAttachmentPageState createState() {
|
||||||
|
return _ViewAllAttachmentPageState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ViewAllAttachmentPageState extends State<ViewAllAttachmentPage> {
|
||||||
|
List<ChatAttachment>? attachments;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ChatAttachment>> viewAllDocuments(int moduleId, int requestId) async {
|
||||||
|
try {
|
||||||
|
attachments ??= await ChatApiClient().viewAllDocuments(moduleId, requestId);
|
||||||
|
return attachments!;
|
||||||
|
} catch (ex) {}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppColor.neutral100,
|
||||||
|
appBar: const DefaultAppBar(title: "Documents"),
|
||||||
|
body: FutureBuilder<List<ChatAttachment>>(
|
||||||
|
future: viewAllDocuments(widget.moduleId, widget.requestId),
|
||||||
|
builder: (BuildContext context, AsyncSnapshot<List<ChatAttachment>> snapshot) {
|
||||||
|
bool isLoading = false;
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
isLoading = true;
|
||||||
|
}
|
||||||
|
if (snapshot.hasData && snapshot.data!.isEmpty) {
|
||||||
|
return NoDataFound(message: context.translation.noDataFound).center;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
itemCount: isLoading ? 9 : snapshot.data!.length,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
separatorBuilder: (cxt, index) => 12.height,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (isLoading) {
|
||||||
|
return Container(height: 48).toShimmer(context: context, isShow: isLoading);
|
||||||
|
}
|
||||||
|
return AttachmentFileViewer(snapshot.data![index]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AttachmentFileViewer extends StatelessWidget {
|
||||||
|
ChatAttachment chatAttachment;
|
||||||
|
|
||||||
|
AttachmentFileViewer(this.chatAttachment, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder<File?>(
|
||||||
|
future: checkFileInLocalStorage(),
|
||||||
|
builder: (BuildContext context, AsyncSnapshot<File?> snapshot) {
|
||||||
|
bool isImage = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "png" ||
|
||||||
|
chatAttachment.storedFileName!.split(".").last.toLowerCase() == "jpg" ||
|
||||||
|
chatAttachment.storedFileName!.split(".").last.toLowerCase() == "jpeg";
|
||||||
|
bool isPdf = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "pdf";
|
||||||
|
bool isExcel = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "xlsx";
|
||||||
|
bool isAudio = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "m4a" || chatAttachment.storedFileName!.split(".").last.toLowerCase() == "mp3";
|
||||||
|
|
||||||
|
bool isLoading = snapshot.connectionState == ConnectionState.waiting;
|
||||||
|
|
||||||
|
Widget widget;
|
||||||
|
if (isLoading) {
|
||||||
|
if (isAudio) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 48,
|
||||||
|
decoration: ShapeDecoration(
|
||||||
|
color: AppColor.background(context),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
side: BorderSide(width: 1, color: (context.isDark ? AppColor.neutral20 : AppColor.neutral30)),
|
||||||
|
borderRadius: BorderRadius.circular(32),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SizedBox(width: 24, height: 24, child: const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2).center),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
widget = const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2));
|
||||||
|
} else {
|
||||||
|
if (isImage) {
|
||||||
|
widget = Image.file(snapshot.data!, width: 48, height: 48, fit: BoxFit.contain);
|
||||||
|
} else {
|
||||||
|
widget = Image.asset(
|
||||||
|
"assets/images/${isPdf ? "pdf" : isExcel ? "excel" : "doc"}.png",
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
width: 48,
|
||||||
|
height: 48);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isAudio) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return Text("Failed to load", style: AppTextStyle.tiny.copyWith(color: context.isDark ? AppColor.red50 : AppColor.red60));
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ASoundPlayer(audio: snapshot.data!.path),
|
||||||
|
4.height,
|
||||||
|
Text(
|
||||||
|
chatAttachment.createdOn!.toServiceRequestDetailsFormat,
|
||||||
|
style: AppTextStyles.textFieldLabelStyle.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
).paddingOnly(start: 60),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ClipRRect(borderRadius: BorderRadius.circular(8.0), child: widget),
|
||||||
|
12.width,
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
chatAttachment.storedFileName!,
|
||||||
|
style: AppTextStyles.textFieldLabelStyle.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
4.height,
|
||||||
|
Text(
|
||||||
|
chatAttachment.createdOn!.toServiceRequestDetailsFormat,
|
||||||
|
style: AppTextStyles.textFieldLabelStyle.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).expanded
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).onPress(() {
|
||||||
|
if (isImage) {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => Scaffold(
|
||||||
|
appBar: const DefaultAppBar(),
|
||||||
|
body: SafeArea(
|
||||||
|
child: InteractiveViewer(child: Image.file(snapshot.data!)).center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
OpenFile.open(snapshot.data!.path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
child: snapshot.connectionState == ConnectionState.waiting
|
||||||
|
? const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2),
|
||||||
|
).center
|
||||||
|
: snapshot.hasData
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
getImage(snapshot.data!),
|
||||||
|
getFile(context, snapshot.data!),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const Icon(Icons.broken_image_rounded),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
child: snapshot.connectionState == ConnectionState.waiting
|
||||||
|
? const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2),
|
||||||
|
).center
|
||||||
|
: snapshot.hasData
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
getImage(snapshot.data!),
|
||||||
|
getFile(context, snapshot.data!),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const Icon(Icons.broken_image_rounded),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getFile(BuildContext context, File file) {
|
||||||
|
bool isImage = file.path.split(".").last.toLowerCase() == "png" || file.path.split(".").last.toLowerCase() == "jpg" || file.path.split(".").last.toLowerCase() == "jpeg";
|
||||||
|
bool isPdf = file.path.split(".").last.toLowerCase() == "pdf";
|
||||||
|
bool isExcel = file.path.split(".").last.toLowerCase() == "xlsx";
|
||||||
|
|
||||||
|
return MaterialButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () async {
|
||||||
|
if (isImage) {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => Scaffold(
|
||||||
|
appBar: const DefaultAppBar(),
|
||||||
|
body: SafeArea(
|
||||||
|
child: InteractiveViewer(child: Image.file(file)).center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
OpenFile.open(file.path);
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// // if (!await launchUrl(Uri.parse(URLs.getFileUrl(file.path)!), mode: LaunchMode.externalApplication)) {
|
||||||
|
// // Fluttertoast.showToast(msg: "UnExpected Error with file.");
|
||||||
|
// // throw Exception('Could not launch');
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getImage(File file) {
|
||||||
|
bool isImage = file.path.split(".").last.toLowerCase() == "png" || file.path.split(".").last.toLowerCase() == "jpg" || file.path.split(".").last.toLowerCase() == "jpeg";
|
||||||
|
bool isPdf = file.path.split(".").last.toLowerCase() == "pdf";
|
||||||
|
bool isExcel = file.path.split(".").last.toLowerCase() == "xlsx";
|
||||||
|
|
||||||
|
if (isImage) {
|
||||||
|
return Image.file(file);
|
||||||
|
}
|
||||||
|
return Image.asset("assets/images/${isPdf ? "pdf" : isExcel ? "excel" : "doc"}.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File?> checkFileInLocalStorage() async {
|
||||||
|
Directory tempDir = await getTemporaryDirectory();
|
||||||
|
String tempPath = '${tempDir.path}/${chatAttachment.storedFileName}';
|
||||||
|
File tempFile = File(tempPath);
|
||||||
|
bool exists = await tempFile.exists();
|
||||||
|
if (exists) {
|
||||||
|
return tempFile;
|
||||||
|
} else {
|
||||||
|
return downloadFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File?> downloadFile() async {
|
||||||
|
try {
|
||||||
|
return await ChatApiClient().downloadFileWithHttp(chatAttachment.downloadUrl!, fileName: chatAttachment.storedFileName!, fileTypeDescription: "", fileSource: 0);
|
||||||
|
} catch (ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,172 @@
|
|||||||
|
class Questionnaire {
|
||||||
|
int? questionnaireId;
|
||||||
|
int? surveySubmissionId;
|
||||||
|
SurveyType? surveyType;
|
||||||
|
String? surveyName;
|
||||||
|
String? surveyDescription;
|
||||||
|
ServiceRequestDetails? serviceRequestDetails;
|
||||||
|
List<SurveyQuestions>? surveyQuestions;
|
||||||
|
|
||||||
|
Questionnaire({this.questionnaireId, this.surveySubmissionId, this.surveyType, this.surveyName, this.surveyDescription, this.serviceRequestDetails, this.surveyQuestions});
|
||||||
|
|
||||||
|
Questionnaire.fromJson(Map<String, dynamic> json) {
|
||||||
|
questionnaireId = json['questionnaireId'];
|
||||||
|
surveySubmissionId = json['surveySubmissionId'];
|
||||||
|
surveyType = json['surveyType'] != null ? new SurveyType.fromJson(json['surveyType']) : null;
|
||||||
|
surveyName = json['surveyName'];
|
||||||
|
surveyDescription = json['surveyDescription'];
|
||||||
|
serviceRequestDetails = json['serviceRequestDetails'] != null ? new ServiceRequestDetails.fromJson(json['serviceRequestDetails']) : null;
|
||||||
|
if (json['surveyQuestions'] != null) {
|
||||||
|
surveyQuestions = <SurveyQuestions>[];
|
||||||
|
json['surveyQuestions'].forEach((v) {
|
||||||
|
surveyQuestions!.add(new SurveyQuestions.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['questionnaireId'] = this.questionnaireId;
|
||||||
|
data['surveySubmissionId'] = this.surveySubmissionId;
|
||||||
|
if (this.surveyType != null) {
|
||||||
|
data['surveyType'] = this.surveyType!.toJson();
|
||||||
|
}
|
||||||
|
data['surveyName'] = this.surveyName;
|
||||||
|
data['surveyDescription'] = this.surveyDescription;
|
||||||
|
if (this.serviceRequestDetails != null) {
|
||||||
|
data['serviceRequestDetails'] = this.serviceRequestDetails!.toJson();
|
||||||
|
}
|
||||||
|
if (this.surveyQuestions != null) {
|
||||||
|
data['surveyQuestions'] = this.surveyQuestions!.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SurveyType {
|
||||||
|
int? id;
|
||||||
|
String? name;
|
||||||
|
int? value;
|
||||||
|
|
||||||
|
SurveyType({this.id, this.name, this.value});
|
||||||
|
|
||||||
|
SurveyType.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
name = json['name'];
|
||||||
|
value = json['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['id'] = this.id;
|
||||||
|
data['name'] = this.name;
|
||||||
|
data['value'] = this.value;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceRequestDetails {
|
||||||
|
int? serviceRequestTypeId;
|
||||||
|
String? serviceRequestType;
|
||||||
|
String? serviceRequestNo;
|
||||||
|
|
||||||
|
ServiceRequestDetails({this.serviceRequestTypeId, this.serviceRequestType, this.serviceRequestNo});
|
||||||
|
|
||||||
|
ServiceRequestDetails.fromJson(Map<String, dynamic> json) {
|
||||||
|
serviceRequestTypeId = json['serviceRequestTypeId'];
|
||||||
|
serviceRequestType = json['serviceRequestType'];
|
||||||
|
serviceRequestNo = json['serviceRequestNo'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['serviceRequestTypeId'] = this.serviceRequestTypeId;
|
||||||
|
data['serviceRequestType'] = this.serviceRequestType;
|
||||||
|
data['serviceRequestNo'] = this.serviceRequestNo;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SurveyQuestions {
|
||||||
|
int? questionId;
|
||||||
|
String? questionText;
|
||||||
|
SurveyType? questionType;
|
||||||
|
bool? isMandatory;
|
||||||
|
List<SurveyAnswerOptions>? surveyAnswerOptions;
|
||||||
|
|
||||||
|
SurveyQuestions({this.questionId, this.questionText, this.questionType, this.surveyAnswerOptions, this.isMandatory});
|
||||||
|
|
||||||
|
SurveyQuestions.fromJson(Map<String, dynamic> json) {
|
||||||
|
questionId = json['questionId'];
|
||||||
|
questionText = json['questionText'];
|
||||||
|
isMandatory = json['isMandatory'];
|
||||||
|
questionType = json['questionType'] != null ? new SurveyType.fromJson(json['questionType']) : null;
|
||||||
|
if (json['surveyAnswerOptions'] != null) {
|
||||||
|
surveyAnswerOptions = <SurveyAnswerOptions>[];
|
||||||
|
json['surveyAnswerOptions'].forEach((v) {
|
||||||
|
surveyAnswerOptions!.add(new SurveyAnswerOptions.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['questionId'] = this.questionId;
|
||||||
|
data['questionText'] = this.questionText;
|
||||||
|
data['isMandatory'] = this.isMandatory;
|
||||||
|
if (this.questionType != null) {
|
||||||
|
data['questionType'] = this.questionType!.toJson();
|
||||||
|
}
|
||||||
|
if (this.surveyAnswerOptions != null) {
|
||||||
|
data['surveyAnswerOptions'] = this.surveyAnswerOptions!.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SurveyAnswerOptions {
|
||||||
|
int? optionId;
|
||||||
|
String? optionText;
|
||||||
|
int? displayOrder;
|
||||||
|
|
||||||
|
SurveyAnswerOptions({this.optionId, this.optionText, this.displayOrder});
|
||||||
|
|
||||||
|
SurveyAnswerOptions.fromJson(Map<String, dynamic> json) {
|
||||||
|
optionId = json['optionId'];
|
||||||
|
optionText = json['optionText'];
|
||||||
|
displayOrder = json['displayOrder'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['optionId'] = this.optionId;
|
||||||
|
data['optionText'] = this.optionText;
|
||||||
|
data['displayOrder'] = this.displayOrder;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SurveyAnswers {
|
||||||
|
int? questionId;
|
||||||
|
int? surveyAnswerOptionId;
|
||||||
|
String? surveyAnswerText;
|
||||||
|
int? surveyAnswerRating;
|
||||||
|
|
||||||
|
SurveyAnswers({this.questionId, this.surveyAnswerOptionId, this.surveyAnswerText, this.surveyAnswerRating});
|
||||||
|
|
||||||
|
SurveyAnswers.fromJson(Map<String, dynamic> json) {
|
||||||
|
questionId = json['questionId'];
|
||||||
|
surveyAnswerOptionId = json['surveyAnswerOptionId'];
|
||||||
|
surveyAnswerText = json['surveyAnswerText'];
|
||||||
|
surveyAnswerRating = json['surveyAnswerRating'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['questionId'] = this.questionId;
|
||||||
|
data['surveyAnswerOptionId'] = this.surveyAnswerOptionId;
|
||||||
|
data['surveyAnswerText'] = this.surveyAnswerText;
|
||||||
|
data['surveyAnswerRating'] = this.surveyAnswerRating;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,216 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/helper/utils.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/chat/chat_rooms_page.dart';
|
||||||
|
import 'package:test_sa/modules/cx_module/survey/questionnaire_model.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||||
|
|
||||||
|
import 'survey_provider.dart';
|
||||||
|
|
||||||
|
class SurveyPage extends StatefulWidget {
|
||||||
|
// int moduleId;
|
||||||
|
// int requestId;
|
||||||
|
int surveyId;
|
||||||
|
|
||||||
|
SurveyPage({Key? key,
|
||||||
|
// required this.moduleId, required this.requestId,
|
||||||
|
required this.surveyId}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SurveyPageState createState() {
|
||||||
|
return _SurveyPageState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SurveyPageState extends State<SurveyPage> {
|
||||||
|
// int serviceSatisfiedRating = -1;
|
||||||
|
int serviceProvidedRating = -1;
|
||||||
|
String comments = "";
|
||||||
|
String message = "";
|
||||||
|
|
||||||
|
bool loading = false;
|
||||||
|
|
||||||
|
Questionnaire? questionnaire;
|
||||||
|
|
||||||
|
List<SurveyAnswers> answers = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
getSurveyQuestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
void getSurveyQuestion() async {
|
||||||
|
loading = true;
|
||||||
|
setState(() {});
|
||||||
|
await Provider.of<SurveyProvider>(context, listen: false).getQuestionnaire(widget.surveyId, (msg, questions) {
|
||||||
|
message = msg;
|
||||||
|
questionnaire = questions;
|
||||||
|
});
|
||||||
|
for (int i = 0; i < (questionnaire?.surveyQuestions?.length ?? 0); i++) {
|
||||||
|
answers.add(SurveyAnswers(
|
||||||
|
questionId: questionnaire!.surveyQuestions![i].questionId!,
|
||||||
|
surveyAnswerRating: -1,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
loading = false;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppColor.neutral100,
|
||||||
|
appBar: const DefaultAppBar(title: "Survey"),
|
||||||
|
body: loading
|
||||||
|
? const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3).center
|
||||||
|
: (questionnaire == null)
|
||||||
|
? Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
message.isNotEmpty ? message : "Failed to get questionnaire",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: AppTextStyles.heading6.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
24.height,
|
||||||
|
AppFilledButton(
|
||||||
|
label: message.isNotEmpty ? "Go Back " : "Retry",
|
||||||
|
maxWidth: true,
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
onPressed: () {
|
||||||
|
message.isNotEmpty ? Navigator.pop(context) : getSurveyQuestion();
|
||||||
|
},
|
||||||
|
).paddingOnly(start: 48, end: 48)
|
||||||
|
],
|
||||||
|
).center
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (cxt, index) {
|
||||||
|
SurveyQuestions question = questionnaire!.surveyQuestions![index];
|
||||||
|
return question.questionType?.value == 4
|
||||||
|
? Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
question.questionText ?? "",
|
||||||
|
style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
12.height,
|
||||||
|
SizedBox(
|
||||||
|
height: 32,
|
||||||
|
child: ListView.separated(
|
||||||
|
itemBuilder: (cxt, _index) => (answers[index].surveyAnswerRating! >= _index ? 'star_filled'.toSvgAsset() : 'star_empty'.toSvgAsset()).onPress(() {
|
||||||
|
setState(() {
|
||||||
|
answers[index].surveyAnswerRating = _index;
|
||||||
|
// serviceSatisfiedRating = _index;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
separatorBuilder: (cxt, index) => 8.width,
|
||||||
|
itemCount: 5,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: AppTextFormField(
|
||||||
|
initialValue: answers[index].surveyAnswerText,
|
||||||
|
textInputType: TextInputType.multiline,
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
labelText: questionnaire!.surveyQuestions![index].questionText,
|
||||||
|
backgroundColor: AppColor.fieldBgColor(context),
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: context.isDark ? Colors.white : const Color(0xff3B3D4A)),
|
||||||
|
labelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: context.isDark ? Colors.white : const Color(0xff767676)),
|
||||||
|
floatingLabelStyle:
|
||||||
|
TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: context.isDark ? Colors.white : const Color(0xff3B3D4A)),
|
||||||
|
showShadow: false,
|
||||||
|
onChange: (value) {
|
||||||
|
answers[index].surveyAnswerText = value;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (cxt, index) => 16.height,
|
||||||
|
itemCount: questionnaire?.surveyQuestions?.length ?? 0)
|
||||||
|
.toShadowContainer(context, borderRadius: 20, showShadow: false),
|
||||||
|
).expanded,
|
||||||
|
FooterActionButton.footerContainer(
|
||||||
|
context: context,
|
||||||
|
child: AppFilledButton(
|
||||||
|
label: context.translation.submit,
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
onPressed: () async {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
if (validateAnswers()) {
|
||||||
|
Map<String, dynamic> payload = {
|
||||||
|
"surveySubmissionId": questionnaire!.surveySubmissionId,
|
||||||
|
"questionnaireId": questionnaire!.questionnaireId,
|
||||||
|
"submittedUserId": context.userProvider.user!.userID,
|
||||||
|
"surveySubmissionStatusId": 0,
|
||||||
|
"surveyAnswers": answers.map((element) => element.toJson()).toList(),
|
||||||
|
};
|
||||||
|
Utils.showLoading(context);
|
||||||
|
bool isSuccess = await Provider.of<SurveyProvider>(context, listen: false).submitQuestionare(payload);
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
if (isSuccess) {
|
||||||
|
context.showConfirmDialog("Thanks for submitting the Feedback. Its value for us to improve our system and overall experience.", title: "Thank you!", onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.pop(context);
|
||||||
|
});
|
||||||
|
} //reload Data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateAnswers() {
|
||||||
|
bool status = true;
|
||||||
|
for (int i = 0; i < answers.length; i++) {
|
||||||
|
if (questionnaire!.surveyQuestions![i].isMandatory ?? false) {
|
||||||
|
if (questionnaire!.surveyQuestions![i].questionType!.value == 4) {
|
||||||
|
if (answers[i].surveyAnswerRating! < 0) {
|
||||||
|
"Please rate (${questionnaire!.surveyQuestions![i].questionText})".showToast;
|
||||||
|
status = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (questionnaire!.surveyQuestions![i].questionType!.value == 3) {
|
||||||
|
answers[i].surveyAnswerRating = null;
|
||||||
|
if ((answers[i].surveyAnswerText ?? "").isEmpty) {
|
||||||
|
"Please answer (${questionnaire!.surveyQuestions![i].questionText})".showToast;
|
||||||
|
status = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/api_manager.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
|
||||||
|
import 'questionnaire_model.dart';
|
||||||
|
|
||||||
|
class SurveyProvider with ChangeNotifier {
|
||||||
|
bool loading = false;
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
loading = false;
|
||||||
|
// questionnaire = null;
|
||||||
|
// ChatApiClient().chatLoginResponse = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getQuestionnaire(int surveySubmissionId, Function(String message, Questionnaire? questionnaire) onResponse) async {
|
||||||
|
String responseMsg = "";
|
||||||
|
Questionnaire? q;
|
||||||
|
try {
|
||||||
|
final response = await ApiManager.instance.get("${URLs.getQuestionnaire}?surveySubmissionId=$surveySubmissionId", enableToastMessage: false);
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
q = Questionnaire.fromJson(jsonDecode(response.body)["data"]);
|
||||||
|
} else {
|
||||||
|
if (jsonDecode(response.body)["message"] != null && jsonDecode(response.body)["message"].toString().isNotEmpty) {
|
||||||
|
responseMsg = jsonDecode(response.body)["message"];
|
||||||
|
} else {}
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
"Failed, Retry.".showToast;
|
||||||
|
}
|
||||||
|
onResponse(responseMsg, q);
|
||||||
|
|
||||||
|
// return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> submitQuestionare(Map<String, dynamic> payload) async {
|
||||||
|
try {
|
||||||
|
final response = await ApiManager.instance.post(URLs.submitSurvey, body: payload);
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
"Failed, Retry.".showToast;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
|
||||||
|
class EngineerData {
|
||||||
|
int? id;
|
||||||
|
String? debrief;
|
||||||
|
String? startTime;
|
||||||
|
String? endTime;
|
||||||
|
double? totalHours;
|
||||||
|
bool? isComplete;
|
||||||
|
int? statusId;
|
||||||
|
int? requestId;
|
||||||
|
List<InternalAuditAttachments>? attachments;
|
||||||
|
|
||||||
|
EngineerData({
|
||||||
|
this.id,
|
||||||
|
this.debrief,
|
||||||
|
this.startTime,
|
||||||
|
this.endTime,
|
||||||
|
this.totalHours,
|
||||||
|
this.isComplete,
|
||||||
|
this.statusId,
|
||||||
|
this.requestId,
|
||||||
|
this.attachments,
|
||||||
|
});
|
||||||
|
|
||||||
|
EngineerData.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
debrief = json['debrief'];
|
||||||
|
startTime = json['startTime'];
|
||||||
|
endTime = json['endTime'];
|
||||||
|
totalHours = (json['totalHours'] as num?)?.toDouble();
|
||||||
|
isComplete = json['isComplete'];
|
||||||
|
statusId = json['statusId'];
|
||||||
|
requestId = json['requestId'];
|
||||||
|
attachments = json['attachments'] != null ? (json['attachments'] as List).map((e) => InternalAuditAttachments.fromJson(e)).toList() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = {};
|
||||||
|
data['id'] = id;
|
||||||
|
data['debrief'] = debrief;
|
||||||
|
data['startTime'] = startTime;
|
||||||
|
data['endTime'] = endTime;
|
||||||
|
data['totalHours'] = totalHours;
|
||||||
|
data['isComplete'] = isComplete;
|
||||||
|
data['statusId'] = statusId;
|
||||||
|
data['requestId'] = requestId;
|
||||||
|
if (attachments != null) {
|
||||||
|
data['attachments'] = attachments!.map((e) => e.toJson()).toList();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,300 @@
|
|||||||
|
import 'package:test_sa/models/timer_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/engineer_data_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart';
|
||||||
|
|
||||||
|
class EquipmentInternalAuditDataModel {
|
||||||
|
int? id;
|
||||||
|
String? requestNo;
|
||||||
|
String? createdDate;
|
||||||
|
int? requestNoSequence;
|
||||||
|
Auditor? auditor;
|
||||||
|
Status? status;
|
||||||
|
Asset? asset;
|
||||||
|
AssignEmployee? assignEmployee;
|
||||||
|
dynamic manufacture;
|
||||||
|
String? remarks;
|
||||||
|
List<EquipmentsFinding>? equipmentsFindings;
|
||||||
|
List<InternalAuditAttachments>? attachments;
|
||||||
|
EngineerData? engineerData;
|
||||||
|
|
||||||
|
|
||||||
|
EquipmentInternalAuditDataModel({
|
||||||
|
this.id,
|
||||||
|
this.requestNo,
|
||||||
|
this.createdDate,
|
||||||
|
this.requestNoSequence,
|
||||||
|
this.auditor,
|
||||||
|
this.status,
|
||||||
|
this.asset,
|
||||||
|
this.assignEmployee,
|
||||||
|
this.manufacture,
|
||||||
|
this.remarks,
|
||||||
|
this.equipmentsFindings,
|
||||||
|
this.attachments,
|
||||||
|
this.engineerData,
|
||||||
|
});
|
||||||
|
|
||||||
|
EquipmentInternalAuditDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
requestNo = json['requestNo'];
|
||||||
|
createdDate = json['createdDate'];
|
||||||
|
requestNoSequence = json['requestNoSequence'];
|
||||||
|
auditor = json['auditor'] != null ? Auditor.fromJson(json['auditor']) : null;
|
||||||
|
status = json['status'] != null ? Status.fromJson(json['status']) : null;
|
||||||
|
asset = json['asset'] != null ? Asset.fromJson(json['asset']) : null;
|
||||||
|
assignEmployee = json['assignEmployee'] != null ? AssignEmployee.fromJson(json['assignEmployee']) : null;
|
||||||
|
manufacture = json['manufacture'];
|
||||||
|
remarks = json['remarks'];
|
||||||
|
if (json['equipmentsFindings'] != null) {
|
||||||
|
equipmentsFindings = <EquipmentsFinding>[];
|
||||||
|
json['equipmentsFindings'].forEach((v) {
|
||||||
|
equipmentsFindings!.add(EquipmentsFinding.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
attachments = json['attachments'] != null
|
||||||
|
? (json['attachments'] as List)
|
||||||
|
.map((e) => InternalAuditAttachments.fromJson(e))
|
||||||
|
.where((e) => e.name != null) // optional filter if you want to skip null names
|
||||||
|
.toList()
|
||||||
|
: [];
|
||||||
|
engineerData = json['engineerData'] != null ? EngineerData.fromJson(json['engineerData']) : null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = {};
|
||||||
|
data['id'] = id;
|
||||||
|
data['requestNo'] = requestNo;
|
||||||
|
data['createdDate'] = createdDate;
|
||||||
|
data['requestNoSequence'] = requestNoSequence;
|
||||||
|
if (auditor != null) {
|
||||||
|
data['auditor'] = auditor!.toJson();
|
||||||
|
}
|
||||||
|
if (status != null) {
|
||||||
|
data['status'] = status!.toJson();
|
||||||
|
}
|
||||||
|
if (asset != null) {
|
||||||
|
data['asset'] = asset!.toJson();
|
||||||
|
}
|
||||||
|
if (assignEmployee != null) {
|
||||||
|
data['assignEmployee'] = assignEmployee!.toJson();
|
||||||
|
}
|
||||||
|
data['manufacture'] = manufacture;
|
||||||
|
data['remarks'] = remarks;
|
||||||
|
if (equipmentsFindings != null) {
|
||||||
|
data['equipmentsFindings'] =
|
||||||
|
equipmentsFindings!.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
data['attachments'] = attachments;
|
||||||
|
if (engineerData != null) data['engineerData'] = engineerData!.toJson();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Auditor {
|
||||||
|
String? userId;
|
||||||
|
String? userName;
|
||||||
|
String? email;
|
||||||
|
String? employeeId;
|
||||||
|
int? languageId;
|
||||||
|
String? extensionNo;
|
||||||
|
String? phoneNumber;
|
||||||
|
bool? isActive;
|
||||||
|
|
||||||
|
Auditor({
|
||||||
|
this.userId,
|
||||||
|
this.userName,
|
||||||
|
this.email,
|
||||||
|
this.employeeId,
|
||||||
|
this.languageId,
|
||||||
|
this.extensionNo,
|
||||||
|
this.phoneNumber,
|
||||||
|
this.isActive,
|
||||||
|
});
|
||||||
|
|
||||||
|
Auditor.fromJson(Map<String, dynamic> json) {
|
||||||
|
userId = json['userId'];
|
||||||
|
userName = json['userName'];
|
||||||
|
email = json['email'];
|
||||||
|
employeeId = json['employeeId'];
|
||||||
|
languageId = json['languageId'];
|
||||||
|
extensionNo = json['extensionNo'];
|
||||||
|
phoneNumber = json['phoneNumber'];
|
||||||
|
isActive = json['isActive'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = {};
|
||||||
|
data['userId'] = userId;
|
||||||
|
data['userName'] = userName;
|
||||||
|
data['email'] = email;
|
||||||
|
data['employeeId'] = employeeId;
|
||||||
|
data['languageId'] = languageId;
|
||||||
|
data['extensionNo'] = extensionNo;
|
||||||
|
data['phoneNumber'] = phoneNumber;
|
||||||
|
data['isActive'] = isActive;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Status {
|
||||||
|
int? id;
|
||||||
|
String? name;
|
||||||
|
int? value;
|
||||||
|
|
||||||
|
Status({this.id, this.name, this.value});
|
||||||
|
|
||||||
|
Status.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
name = json['name'];
|
||||||
|
value = json['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = {};
|
||||||
|
data['id'] = id;
|
||||||
|
data['name'] = name;
|
||||||
|
data['value'] = value;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Asset {
|
||||||
|
int? id;
|
||||||
|
String? assetNumber;
|
||||||
|
String? assetSerialNo;
|
||||||
|
String? assetName;
|
||||||
|
String? modelName;
|
||||||
|
String? manufacturerName;
|
||||||
|
String? siteName;
|
||||||
|
int? siteId;
|
||||||
|
String? modelDefinition;
|
||||||
|
|
||||||
|
Asset({
|
||||||
|
this.id,
|
||||||
|
this.assetNumber,
|
||||||
|
this.assetSerialNo,
|
||||||
|
this.assetName,
|
||||||
|
this.modelName,
|
||||||
|
this.manufacturerName,
|
||||||
|
this.siteName,
|
||||||
|
this.siteId,
|
||||||
|
this.modelDefinition,
|
||||||
|
});
|
||||||
|
|
||||||
|
Asset.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
assetNumber = json['assetNumber'];
|
||||||
|
assetSerialNo = json['assetSerialNo'];
|
||||||
|
assetName = json['assetName'];
|
||||||
|
modelName = json['modelName'];
|
||||||
|
manufacturerName = json['manufacturerName'];
|
||||||
|
siteName = json['siteName'];
|
||||||
|
siteId = json['siteId'];
|
||||||
|
modelDefinition = json['modelDefinition'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = {};
|
||||||
|
data['id'] = id;
|
||||||
|
data['assetNumber'] = assetNumber;
|
||||||
|
data['assetSerialNo'] = assetSerialNo;
|
||||||
|
data['assetName'] = assetName;
|
||||||
|
data['modelName'] = modelName;
|
||||||
|
data['manufacturerName'] = manufacturerName;
|
||||||
|
data['siteName'] = siteName;
|
||||||
|
data['siteId'] = siteId;
|
||||||
|
data['modelDefinition'] = modelDefinition;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssignEmployee {
|
||||||
|
String? userId;
|
||||||
|
String? userName;
|
||||||
|
String? email;
|
||||||
|
String? employeeId;
|
||||||
|
int? languageId;
|
||||||
|
String? extensionNo;
|
||||||
|
String? phoneNumber;
|
||||||
|
bool? isActive;
|
||||||
|
|
||||||
|
AssignEmployee({
|
||||||
|
this.userId,
|
||||||
|
this.userName,
|
||||||
|
this.email,
|
||||||
|
this.employeeId,
|
||||||
|
this.languageId,
|
||||||
|
this.extensionNo,
|
||||||
|
this.phoneNumber,
|
||||||
|
this.isActive,
|
||||||
|
});
|
||||||
|
|
||||||
|
AssignEmployee.fromJson(Map<String, dynamic> json) {
|
||||||
|
userId = json['userId'];
|
||||||
|
userName = json['userName'];
|
||||||
|
email = json['email'];
|
||||||
|
employeeId = json['employeeId'];
|
||||||
|
languageId = json['languageId'];
|
||||||
|
extensionNo = json['extensionNo'];
|
||||||
|
phoneNumber = json['phoneNumber'];
|
||||||
|
isActive = json['isActive'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = {};
|
||||||
|
data['userId'] = userId;
|
||||||
|
data['userName'] = userName;
|
||||||
|
data['email'] = email;
|
||||||
|
data['employeeId'] = employeeId;
|
||||||
|
data['languageId'] = languageId;
|
||||||
|
data['extensionNo'] = extensionNo;
|
||||||
|
data['phoneNumber'] = phoneNumber;
|
||||||
|
data['isActive'] = isActive;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EquipmentsFinding {
|
||||||
|
int? id;
|
||||||
|
Finding? finding;
|
||||||
|
|
||||||
|
EquipmentsFinding({this.id, this.finding});
|
||||||
|
|
||||||
|
EquipmentsFinding.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
finding = json['finding'] != null ? Finding.fromJson(json['finding']) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = {};
|
||||||
|
data['id'] = id;
|
||||||
|
if (finding != null) {
|
||||||
|
data['finding'] = finding!.toJson();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Finding {
|
||||||
|
int? id;
|
||||||
|
String? name;
|
||||||
|
int? value;
|
||||||
|
|
||||||
|
Finding({this.id, this.name, this.value});
|
||||||
|
|
||||||
|
Finding.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
name = json['name'];
|
||||||
|
value = json['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = {};
|
||||||
|
data['id'] = id;
|
||||||
|
data['name'] = name;
|
||||||
|
data['value'] = value;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import 'package:flutter/src/widgets/framework.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/models/device/asset.dart';
|
||||||
|
import 'package:test_sa/models/fault_description.dart';
|
||||||
|
import 'package:test_sa/models/lookup.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
|
||||||
|
class EquipmentInternalAuditFormModel {
|
||||||
|
int? id=0;
|
||||||
|
String? deviceArName;
|
||||||
|
String? auditorId;
|
||||||
|
String? woOrderNo;
|
||||||
|
List<String>? devicePhotos;
|
||||||
|
List<Lookup> findings =[];
|
||||||
|
List<InternalAuditAttachments> attachments = [];
|
||||||
|
String? remarks;
|
||||||
|
Asset? device;
|
||||||
|
EquipmentInternalAuditFormModel({
|
||||||
|
this.id,
|
||||||
|
this.deviceArName,
|
||||||
|
this.auditorId,
|
||||||
|
this.devicePhotos,
|
||||||
|
this.woOrderNo,
|
||||||
|
this.remarks,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id??0,
|
||||||
|
'assetId': device?.id,
|
||||||
|
'auditorId': auditorId,
|
||||||
|
'findings': findings.map((e) => {'findingId': e.value}).toList(),
|
||||||
|
'attachments': attachments.map((e) => e.toJson()).toList(),
|
||||||
|
'remarks': remarks,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
class InternalAuditAttachments {
|
||||||
|
InternalAuditAttachments({this.id, this.originalName, this.name, this.createdBy});
|
||||||
|
|
||||||
|
int? id;
|
||||||
|
String? name;
|
||||||
|
String? originalName;
|
||||||
|
String? createdBy;
|
||||||
|
|
||||||
|
InternalAuditAttachments.fromJson(Map<String, dynamic> json) {
|
||||||
|
log('name is ${json['name']}');
|
||||||
|
id = json['id'];
|
||||||
|
name = (json['name'] != null && !json['name'].toString().startsWith('data:image/jpeg')) ? json['name'] : null;
|
||||||
|
originalName = json['originalName'];
|
||||||
|
createdBy = json['createdBy'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
|
data['id'] = id;
|
||||||
|
data['name'] = name;
|
||||||
|
data['originalName'] = originalName;
|
||||||
|
// data['createdBy'] = createdBy;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
class InternalAuditTimerModel {
|
||||||
|
int? id;
|
||||||
|
String? startDate;
|
||||||
|
String? endDate;
|
||||||
|
double? totalWorkingHour;
|
||||||
|
String? comment;
|
||||||
|
|
||||||
|
InternalAuditTimerModel({
|
||||||
|
this.id,
|
||||||
|
this.startDate,
|
||||||
|
this.endDate,
|
||||||
|
this.totalWorkingHour,
|
||||||
|
this.comment,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory InternalAuditTimerModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return InternalAuditTimerModel(
|
||||||
|
id: json['id'] as int?,
|
||||||
|
startDate: json['startDate'],
|
||||||
|
endDate: json['endDate'],
|
||||||
|
totalWorkingHour: (json['totalWorkingHours'] as num?)?.toDouble(),
|
||||||
|
comment: json['comment'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'startDate': startDate,
|
||||||
|
'endDate': endDate,
|
||||||
|
'totalWorkingHours': totalWorkingHour,
|
||||||
|
'comment': comment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
import 'package:test_sa/models/lookup.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/engineer_data_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart';
|
||||||
|
|
||||||
|
class SystemInternalAuditDataModel {
|
||||||
|
int? id;
|
||||||
|
int? assetGroupId;
|
||||||
|
String? requestNo;
|
||||||
|
int? requestNoSequence;
|
||||||
|
Auditor? auditor;
|
||||||
|
Lookup? status;
|
||||||
|
Lookup? findingType;
|
||||||
|
String? findingDescription;
|
||||||
|
Lookup? workOrderType;
|
||||||
|
Auditor? assignEmployee;
|
||||||
|
SystemAuditWorkOrderDetailModel? workOrderDetails; // Already exists as a separate model
|
||||||
|
String? createdBy;
|
||||||
|
String? createdDate;
|
||||||
|
String? modifiedBy;
|
||||||
|
String? modifiedDate;
|
||||||
|
List<InternalAuditAttachments>? attachments;
|
||||||
|
EngineerData? engineerData;
|
||||||
|
|
||||||
|
SystemInternalAuditDataModel({
|
||||||
|
this.id,
|
||||||
|
this.assetGroupId,
|
||||||
|
this.requestNo,
|
||||||
|
this.requestNoSequence,
|
||||||
|
this.auditor,
|
||||||
|
this.status,
|
||||||
|
this.findingType,
|
||||||
|
this.findingDescription,
|
||||||
|
this.workOrderType,
|
||||||
|
this.assignEmployee,
|
||||||
|
this.workOrderDetails,
|
||||||
|
this.createdBy,
|
||||||
|
this.createdDate,
|
||||||
|
this.modifiedBy,
|
||||||
|
this.modifiedDate,
|
||||||
|
this.attachments,
|
||||||
|
this.engineerData,
|
||||||
|
});
|
||||||
|
|
||||||
|
SystemInternalAuditDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
assetGroupId = json['assetGroupId'];
|
||||||
|
requestNo = json['requestNo'];
|
||||||
|
requestNoSequence = json['requestNoSequence'];
|
||||||
|
auditor = json['auditor'] != null ? Auditor.fromJson(json['auditor']) : null;
|
||||||
|
status = json['status'] != null ? Lookup.fromJson(json['status']) : null;
|
||||||
|
findingType = json['findingType'] != null ? Lookup.fromJson(json['findingType']) : null;
|
||||||
|
findingDescription = json['findingDescription'];
|
||||||
|
workOrderType = json['workOrderType'] != null ? Lookup.fromJson(json['workOrderType']) : null;
|
||||||
|
assignEmployee = json['assignEmployee'] != null ? Auditor.fromJson(json['assignEmployee']) : null;
|
||||||
|
workOrderDetails = json['workOrderDetails'] != null ? SystemAuditWorkOrderDetailModel.fromJson(json['workOrderDetails']) : null; // Keep as-is
|
||||||
|
createdBy = json['createdBy'];
|
||||||
|
createdDate = json['createdDate'];
|
||||||
|
modifiedBy = json['modifiedBy'];
|
||||||
|
modifiedDate = json['modifiedDate'];
|
||||||
|
attachments = json['attachments'] != null
|
||||||
|
? (json['attachments'] as List)
|
||||||
|
.map((e) => InternalAuditAttachments.fromJson(e))
|
||||||
|
.where((e) => e.name != null) // optional filter if you want to skip null names
|
||||||
|
.toList()
|
||||||
|
: [];
|
||||||
|
engineerData = json['engineerData'] != null ? EngineerData.fromJson(json['engineerData']) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = {};
|
||||||
|
data['id'] = id;
|
||||||
|
data['assetGroupId'] = assetGroupId;
|
||||||
|
data['requestNo'] = requestNo;
|
||||||
|
data['requestNoSequence'] = requestNoSequence;
|
||||||
|
if (auditor != null) data['auditor'] = auditor!.toJson();
|
||||||
|
if (status != null) data['status'] = status!.toJson();
|
||||||
|
if (findingType != null) data['findingType'] = findingType!.toJson();
|
||||||
|
data['findingDescription'] = findingDescription;
|
||||||
|
if (workOrderType != null) data['workOrderType'] = workOrderType!.toJson();
|
||||||
|
if (assignEmployee != null) data['assignEmployee'] = assignEmployee!.toJson();
|
||||||
|
data['workOrderDetails'] = workOrderDetails;
|
||||||
|
data['createdBy'] = createdBy;
|
||||||
|
data['createdDate'] = createdDate;
|
||||||
|
data['modifiedBy'] = modifiedBy;
|
||||||
|
data['modifiedDate'] = modifiedDate;
|
||||||
|
if (engineerData != null) data['engineerData'] = engineerData!.toJson();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Auditor {
|
||||||
|
String? userId;
|
||||||
|
String? userName;
|
||||||
|
String? email;
|
||||||
|
String? employeeId;
|
||||||
|
int? languageId;
|
||||||
|
String? extensionNo;
|
||||||
|
String? phoneNumber;
|
||||||
|
bool? isActive;
|
||||||
|
|
||||||
|
Auditor({
|
||||||
|
this.userId,
|
||||||
|
this.userName,
|
||||||
|
this.email,
|
||||||
|
this.employeeId,
|
||||||
|
this.languageId,
|
||||||
|
this.extensionNo,
|
||||||
|
this.phoneNumber,
|
||||||
|
this.isActive,
|
||||||
|
});
|
||||||
|
|
||||||
|
Auditor.fromJson(Map<String, dynamic> json) {
|
||||||
|
userId = json['userId'];
|
||||||
|
userName = json['userName'];
|
||||||
|
email = json['email'];
|
||||||
|
employeeId = json['employeeId'];
|
||||||
|
languageId = json['languageId'];
|
||||||
|
extensionNo = json['extensionNo'];
|
||||||
|
phoneNumber = json['phoneNumber'];
|
||||||
|
isActive = json['isActive'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = {};
|
||||||
|
data['userId'] = userId;
|
||||||
|
data['userName'] = userName;
|
||||||
|
data['email'] = email;
|
||||||
|
data['employeeId'] = employeeId;
|
||||||
|
data['languageId'] = languageId;
|
||||||
|
data['extensionNo'] = extensionNo;
|
||||||
|
data['phoneNumber'] = phoneNumber;
|
||||||
|
data['isActive'] = isActive;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,202 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:test_sa/models/lookup.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
|
||||||
|
class SystemInternalAuditFormModel {
|
||||||
|
int? id;
|
||||||
|
String? auditorId;
|
||||||
|
String? assignEmployeeId;
|
||||||
|
Lookup? findingType;
|
||||||
|
String? findingDescription;
|
||||||
|
Lookup? workOrderType;
|
||||||
|
int? correctiveMaintenanceId;
|
||||||
|
int? planPreventiveVisitId;
|
||||||
|
int? assetTransferId;
|
||||||
|
List<InternalAuditAttachments>? attachments = [];
|
||||||
|
int? taskJobId;
|
||||||
|
int? taskAlertJobId;
|
||||||
|
int? gasRefillId;
|
||||||
|
int? planRecurrentTaskId;
|
||||||
|
int? statusId;
|
||||||
|
SystemAuditWorkOrderDetailModel? workOrderDetailModel;
|
||||||
|
|
||||||
|
SystemInternalAuditFormModel({
|
||||||
|
this.id,
|
||||||
|
this.auditorId,
|
||||||
|
this.assignEmployeeId,
|
||||||
|
this.findingType,
|
||||||
|
this.findingDescription,
|
||||||
|
this.workOrderType,
|
||||||
|
this.correctiveMaintenanceId,
|
||||||
|
this.planPreventiveVisitId,
|
||||||
|
this.assetTransferId,
|
||||||
|
this.taskJobId,
|
||||||
|
this.taskAlertJobId,
|
||||||
|
this.gasRefillId,
|
||||||
|
this.planRecurrentTaskId,
|
||||||
|
this.workOrderDetailModel,
|
||||||
|
this.statusId,
|
||||||
|
this.attachments,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
// 'id': id,
|
||||||
|
'auditorId': auditorId,
|
||||||
|
'assignEmployeeId': assignEmployeeId,
|
||||||
|
'findingTypeId': findingType?.id,
|
||||||
|
'findingDescription': findingDescription,
|
||||||
|
'workOrderTypeId': workOrderType?.id,
|
||||||
|
'correctiveMaintenanceId': correctiveMaintenanceId,
|
||||||
|
'planPreventiveVisitId': planPreventiveVisitId,
|
||||||
|
'assetTransferId': assetTransferId,
|
||||||
|
'taskJobId': taskJobId,
|
||||||
|
'taskAlertJobId': taskAlertJobId,
|
||||||
|
'gasRefillId': gasRefillId,
|
||||||
|
'planRecurrentTaskId': planRecurrentTaskId,
|
||||||
|
'statusId': statusId,
|
||||||
|
'attachments': attachments?.map((e) => e.toJson()).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WoAutoCompleteModel {
|
||||||
|
int? id;
|
||||||
|
String? workOrderNo;
|
||||||
|
String? displayName;
|
||||||
|
|
||||||
|
WoAutoCompleteModel({this.id, this.workOrderNo, this.displayName});
|
||||||
|
|
||||||
|
WoAutoCompleteModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
workOrderNo = json['workOrderNo'];
|
||||||
|
displayName = json['workOrderNo'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
|
data['id'] = id;
|
||||||
|
data['woOrderNo'] = workOrderNo;
|
||||||
|
data['displayName'] = workOrderNo;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SystemAuditWorkOrderDetailModel {
|
||||||
|
String? createdBy;
|
||||||
|
DateTime? createdDate;
|
||||||
|
String? modifiedBy;
|
||||||
|
DateTime? modifiedDate;
|
||||||
|
int? id;
|
||||||
|
String? workOrderNo;
|
||||||
|
String? woAssignedEngineer;
|
||||||
|
String? woAssetNo;
|
||||||
|
String? wosn;
|
||||||
|
String? woAssetName;
|
||||||
|
String? woModel;
|
||||||
|
String? woManufacturer;
|
||||||
|
String? woSite;
|
||||||
|
String? woDepartment;
|
||||||
|
String? wOPMScheduleDate;
|
||||||
|
String? wOPMActualVisitDate;
|
||||||
|
String? wOAssetTransferDestinationSite;
|
||||||
|
String? wOAssetTransferSenderEngineer;
|
||||||
|
String? wOAssetTransferReceiverEngineer;
|
||||||
|
String? wOTaskAlertJobImpactedStatus;
|
||||||
|
String? wOTaskAlertJobAcknowledgment;
|
||||||
|
String? wOGasRefillType;
|
||||||
|
String? wORecurrentTaskTaskType;
|
||||||
|
String? wORecurrentTaskPlanNo;
|
||||||
|
String? wORecurrentTaskPlanName;
|
||||||
|
String? wORecurrentTaskStatus;
|
||||||
|
|
||||||
|
SystemAuditWorkOrderDetailModel({
|
||||||
|
this.createdBy,
|
||||||
|
this.createdDate,
|
||||||
|
this.modifiedBy,
|
||||||
|
this.modifiedDate,
|
||||||
|
this.id,
|
||||||
|
this.workOrderNo,
|
||||||
|
this.woAssignedEngineer,
|
||||||
|
this.woAssetNo,
|
||||||
|
this.wosn,
|
||||||
|
this.woAssetName,
|
||||||
|
this.woModel,
|
||||||
|
this.woManufacturer,
|
||||||
|
this.woSite,
|
||||||
|
this.woDepartment,
|
||||||
|
this.wOPMScheduleDate,
|
||||||
|
this.wOPMActualVisitDate,
|
||||||
|
this.wOAssetTransferDestinationSite,
|
||||||
|
this.wOAssetTransferSenderEngineer,
|
||||||
|
this.wOAssetTransferReceiverEngineer,
|
||||||
|
this.wOTaskAlertJobImpactedStatus,
|
||||||
|
this.wOTaskAlertJobAcknowledgment,
|
||||||
|
this.wOGasRefillType,
|
||||||
|
this.wORecurrentTaskTaskType,
|
||||||
|
this.wORecurrentTaskPlanNo,
|
||||||
|
this.wORecurrentTaskPlanName,
|
||||||
|
this.wORecurrentTaskStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
SystemAuditWorkOrderDetailModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
createdBy = json['createdBy'];
|
||||||
|
createdDate = json['createdDate'] != null ? DateTime.tryParse(json['createdDate']) : null;
|
||||||
|
modifiedBy = json['modifiedBy'];
|
||||||
|
modifiedDate = json['modifiedDate'] != null ? DateTime.tryParse(json['modifiedDate']) : null;
|
||||||
|
id = json['id'];
|
||||||
|
workOrderNo = json['workOrderNo'];
|
||||||
|
woAssignedEngineer = json['woAssignedEngineer'];
|
||||||
|
woAssetNo = json['woAssetNo'];
|
||||||
|
wosn = json['wosn'];
|
||||||
|
woAssetName = json['woAssetName'];
|
||||||
|
woModel = json['woModel'];
|
||||||
|
woManufacturer = json['woManufacturer'];
|
||||||
|
woSite = json['woSite'];
|
||||||
|
woDepartment = json['woDepartment'];
|
||||||
|
wOPMScheduleDate = json['wO_PM_ScheduleDate'];
|
||||||
|
wOPMActualVisitDate = json['wO_PM_ActualVisitDate'];
|
||||||
|
wOAssetTransferDestinationSite = json['wO_AssetTransfer_DestinationSite'];
|
||||||
|
wOAssetTransferSenderEngineer = json['wO_AssetTransfer_SenderEngineer'];
|
||||||
|
wOAssetTransferReceiverEngineer = json['wO_AssetTransfer_ReceiverEngineer'];
|
||||||
|
wOTaskAlertJobImpactedStatus = json['wO_TaskAlertJob_ImpactedStatus'];
|
||||||
|
wOTaskAlertJobAcknowledgment = json['wO_TaskAlertJob_Acknowledgment'];
|
||||||
|
wOGasRefillType = json['wO_GasRefill_Type'];
|
||||||
|
wORecurrentTaskTaskType = json['wO_RecurrentTask_TaskType'];
|
||||||
|
wORecurrentTaskPlanNo = json['wO_RecurrentTask_PlanNo'];
|
||||||
|
wORecurrentTaskPlanName = json['wO_RecurrentTask_PlanName'];
|
||||||
|
wORecurrentTaskStatus = json['wO_RecurrentTask_Status'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'createdBy': createdBy,
|
||||||
|
'createdDate': createdDate?.toIso8601String(),
|
||||||
|
'modifiedBy': modifiedBy,
|
||||||
|
'modifiedDate': modifiedDate?.toIso8601String(),
|
||||||
|
'id': id,
|
||||||
|
'workOrderNo': workOrderNo,
|
||||||
|
'woAssignedEngineer': woAssignedEngineer,
|
||||||
|
'woAssetNo': woAssetNo,
|
||||||
|
'wosn': wosn,
|
||||||
|
'woAssetName': woAssetName,
|
||||||
|
'woModel': woModel,
|
||||||
|
'woManufacturer': woManufacturer,
|
||||||
|
'woSite': woSite,
|
||||||
|
'woDepartment': woDepartment,
|
||||||
|
'wO_PM_ScheduleDate': wOPMScheduleDate,
|
||||||
|
'wO_PM_ActualVisitDate': wOPMActualVisitDate,
|
||||||
|
'wO_AssetTransfer_DestinationSite': wOAssetTransferDestinationSite,
|
||||||
|
'wO_AssetTransfer_SenderEngineer': wOAssetTransferSenderEngineer,
|
||||||
|
'wO_AssetTransfer_ReceiverEngineer': wOAssetTransferReceiverEngineer,
|
||||||
|
'wO_TaskAlertJob_ImpactedStatus': wOTaskAlertJobImpactedStatus,
|
||||||
|
'wO_TaskAlertJob_Acknowledgment': wOTaskAlertJobAcknowledgment,
|
||||||
|
'wO_GasRefill_Type': wOGasRefillType,
|
||||||
|
'wO_RecurrentTask_TaskType': wORecurrentTaskTaskType,
|
||||||
|
'wO_RecurrentTask_PlanNo': wORecurrentTaskPlanNo,
|
||||||
|
'wO_RecurrentTask_PlanName': wORecurrentTaskPlanName,
|
||||||
|
'wO_RecurrentTask_Status': wORecurrentTaskStatus,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
import 'package:test_sa/models/timer_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart';
|
||||||
|
|
||||||
|
class AuditFormModel {
|
||||||
|
int? id;
|
||||||
|
String? createdDate;
|
||||||
|
int? requestId;
|
||||||
|
String? debrief;
|
||||||
|
DateTime? startTime;
|
||||||
|
DateTime? endTime;
|
||||||
|
double? totalHours;
|
||||||
|
List<InternalAuditAttachments>? attachments;
|
||||||
|
bool? isComplete;
|
||||||
|
TimerModel? auditTimerModel = TimerModel();
|
||||||
|
List<InternalAuditTimerModel>? auditTimers = [];
|
||||||
|
TimerModel? auditTimePicker;
|
||||||
|
List<TimerModel>? timerModelList = [];
|
||||||
|
|
||||||
|
AuditFormModel(
|
||||||
|
{this.id,
|
||||||
|
this.requestId,
|
||||||
|
this.debrief,
|
||||||
|
this.startTime,
|
||||||
|
this.endTime,
|
||||||
|
this.totalHours,
|
||||||
|
this.createdDate,
|
||||||
|
this.attachments,
|
||||||
|
this.isComplete,
|
||||||
|
this.auditTimerModel,
|
||||||
|
this.timerModelList,
|
||||||
|
this.auditTimePicker,
|
||||||
|
this.auditTimers});
|
||||||
|
|
||||||
|
AuditFormModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
requestId = json['requestId'];
|
||||||
|
debrief = json['debrief'];
|
||||||
|
startTime = json['startTime'] != null ? DateTime.tryParse(json['startTime']) : null;
|
||||||
|
endTime = json['endTime'] != null ? DateTime.tryParse(json['endTime']) : null;
|
||||||
|
totalHours = json['totalHours']?.toDouble();
|
||||||
|
if (json['attachments'] != null) {
|
||||||
|
attachments = (json['attachments'] as List).map((e) => InternalAuditAttachments.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
isComplete = json['isComplete'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'requestId': requestId,
|
||||||
|
'debrief': debrief,
|
||||||
|
'startTime': startTime?.toIso8601String(),
|
||||||
|
'endTime': endTime?.toIso8601String(),
|
||||||
|
// 'auditTimer': auditTimers,
|
||||||
|
'totalHours': totalHours,
|
||||||
|
'attachments': attachments?.map((e) => e.toJson()).toList(),
|
||||||
|
'isComplete': isComplete,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,147 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/models/generic_attachment_model.dart';
|
||||||
|
import 'package:test_sa/models/lookup.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/multiple_item_drop_down_menu.dart';
|
||||||
|
import 'package:test_sa/views/widgets/equipment/asset_picker.dart';
|
||||||
|
import 'package:test_sa/views/widgets/images/multi_image_picker.dart';
|
||||||
|
|
||||||
|
class CreateEquipmentInternalAuditForm extends StatefulWidget {
|
||||||
|
static const String id = "/create-equipment-internal-audit-form";
|
||||||
|
|
||||||
|
const CreateEquipmentInternalAuditForm({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CreateEquipmentInternalAuditFormState createState() => _CreateEquipmentInternalAuditFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateEquipmentInternalAuditFormState extends State<CreateEquipmentInternalAuditForm> with TickerProviderStateMixin {
|
||||||
|
late TextEditingController _commentController;
|
||||||
|
final EquipmentInternalAuditFormModel _equipmentinternalAuditModel = EquipmentInternalAuditFormModel();
|
||||||
|
final List<GenericAttachmentModel> _attachments = [];
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
FocusNode auditCheckListNode = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_commentController = TextEditingController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_commentController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
appBar: DefaultAppBar(title: 'Equipment Internal Audit Checklist'.addTranslation),
|
||||||
|
body: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AssetPicker(
|
||||||
|
device: _equipmentinternalAuditModel.device,
|
||||||
|
borderColor: AppColor.black20,
|
||||||
|
buttonColor: AppColor.white936,
|
||||||
|
onPick: (asset) async {
|
||||||
|
_equipmentinternalAuditModel.device = asset;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
16.height,
|
||||||
|
//TODO need to check layout issue...
|
||||||
|
MultipleItemDropDownMenu<Lookup, InternalAuditCheckListProvider>(
|
||||||
|
context: context,
|
||||||
|
showAsBottomSheet: true,
|
||||||
|
backgroundColor: AppColor.neutral100,
|
||||||
|
showShadow: false,
|
||||||
|
showCancel: true,
|
||||||
|
requestById: context.userProvider.user?.clientId,
|
||||||
|
title: 'Internal Audit Checklist'.addTranslation,
|
||||||
|
initialValue: _equipmentinternalAuditModel.findings,
|
||||||
|
onSelect: (value) {
|
||||||
|
_equipmentinternalAuditModel.findings = value ?? [];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
16.height,
|
||||||
|
AppTextFormField(
|
||||||
|
backgroundColor: AppColor.fieldBgColor(context),
|
||||||
|
labelText: 'Remarks'.addTranslation,
|
||||||
|
labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
textInputType: TextInputType.multiline,
|
||||||
|
showShadow: false,
|
||||||
|
onSaved: (value) {
|
||||||
|
_equipmentinternalAuditModel.remarks = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
16.height,
|
||||||
|
AttachmentPicker(
|
||||||
|
label: context.translation.attachments,
|
||||||
|
attachment: _attachments,
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
onlyImages: false,
|
||||||
|
onChange: (value) {},
|
||||||
|
buttonIcon: 'attachment_icon'.toSvgAsset(color: AppColor.primary10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).toShadowContainer(context),
|
||||||
|
).expanded,
|
||||||
|
FooterActionButton.footerContainer(
|
||||||
|
context: context,
|
||||||
|
child: AppFilledButton(label: context.translation.submitRequest, buttonColor: AppColor.primary10, onPressed: _submit),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
InternalAuditProvider internalAuditProvider = Provider.of<InternalAuditProvider>(context, listen: false);
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
_equipmentinternalAuditModel.attachments = [];
|
||||||
|
for (var item in _attachments) {
|
||||||
|
String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? '';
|
||||||
|
_equipmentinternalAuditModel.attachments.add(InternalAuditAttachments(id: item.id, name: fileName));
|
||||||
|
}
|
||||||
|
showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading());
|
||||||
|
_equipmentinternalAuditModel.auditorId = context.userProvider.user?.userID;
|
||||||
|
bool status = await internalAuditProvider.addEquipmentInternalAudit(context: context, request: _equipmentinternalAuditModel);
|
||||||
|
Navigator.pop(context);
|
||||||
|
if(status){
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,207 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||||
|
import 'package:test_sa/views/widgets/images/files_list.dart';
|
||||||
|
import 'package:test_sa/views/widgets/loaders/app_loading.dart';
|
||||||
|
import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
|
||||||
|
|
||||||
|
class EquipmentInternalAuditDetailPage extends StatefulWidget {
|
||||||
|
static const String id = "/details-internal-audit";
|
||||||
|
|
||||||
|
final int auditId;
|
||||||
|
|
||||||
|
const EquipmentInternalAuditDetailPage({Key? key, required this.auditId}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_EquipmentInternalAuditDetailPageState createState() {
|
||||||
|
return _EquipmentInternalAuditDetailPageState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EquipmentInternalAuditDetailPageState extends State<EquipmentInternalAuditDetailPage> {
|
||||||
|
bool isWoType = true;
|
||||||
|
EquipmentInternalAuditDataModel? model;
|
||||||
|
late InternalAuditProvider _internalAuditProvider;
|
||||||
|
List<InternalAuditAttachments> allAttachments = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_internalAuditProvider = Provider.of<InternalAuditProvider>(context, listen: false);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
getAuditData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getAuditData() async {
|
||||||
|
model = await _internalAuditProvider.getEquipmentInternalAuditById(widget.auditId);
|
||||||
|
allAttachments.clear();
|
||||||
|
allAttachments = [
|
||||||
|
...(model?.attachments ?? []),
|
||||||
|
...(model?.engineerData?.attachments ?? []),
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
//TODO need to check when implementing provider needed or not .
|
||||||
|
return Scaffold(
|
||||||
|
appBar: const DefaultAppBar(title: "Request Details"),
|
||||||
|
body: Selector<InternalAuditProvider, bool>(
|
||||||
|
selector: (_, provider) => provider.isLoading,
|
||||||
|
builder: (_, isLoading, __) {
|
||||||
|
if (isLoading) return const ALoading();
|
||||||
|
if (model == null) return NoDataFound(message: context.translation.noDataFound).center;
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
assetDetails(),
|
||||||
|
8.height,
|
||||||
|
requestDetails(),
|
||||||
|
8.height,
|
||||||
|
if (model?.remarks?.isNotEmpty ?? false) ...[
|
||||||
|
const Divider().defaultStyle(context),
|
||||||
|
model!.remarks!.bodyText(context),
|
||||||
|
8.height,
|
||||||
|
],
|
||||||
|
if (allAttachments.isNotEmpty) ...[
|
||||||
|
const Divider().defaultStyle(context),
|
||||||
|
Text(
|
||||||
|
"Attachments".addTranslation,
|
||||||
|
style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
8.height,
|
||||||
|
FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.name ?? '') ?? '').toList() ?? []),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
).paddingAll(0).toShadowContainer(context),
|
||||||
|
).expanded,
|
||||||
|
if (context.userProvider.isEngineer)
|
||||||
|
FooterActionButton.footerContainer(
|
||||||
|
context: context,
|
||||||
|
child: AppFilledButton(
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
label: "Update",
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => UpdateEquipmentInternalAuditPage(model: model),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (result == true) {
|
||||||
|
await getAuditData();
|
||||||
|
setState(() {}); // refresh UI with new model
|
||||||
|
}
|
||||||
|
// Navigator.of(context).push(MaterialPageRoute(builder: (_) => UpdateEquipmentInternalAuditPage(model: model)));
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget workOrderInformation() {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider().defaultStyle(context),
|
||||||
|
Text(
|
||||||
|
"WO Info",
|
||||||
|
style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
6.height,
|
||||||
|
'${context.translation.woNumber}: ${'-'}'.bodyText(context),
|
||||||
|
'${'WO Type'.addTranslation}: ${'-'}'.bodyText(context),
|
||||||
|
'${context.translation.site}: ${'-'}'.bodyText(context),
|
||||||
|
'${context.translation.assetName}: ${'-'}'.bodyText(context),
|
||||||
|
'${context.translation.manufacture}: ${'-'}'.bodyText(context),
|
||||||
|
'${context.translation.model}: ${'-'}'.bodyText(context),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget requestDetails() {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider().defaultStyle(context),
|
||||||
|
Text(
|
||||||
|
"Request Details",
|
||||||
|
style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
6.height,
|
||||||
|
if (model != null && model!.equipmentsFindings != null && model!.equipmentsFindings!.isNotEmpty)
|
||||||
|
...model!.equipmentsFindings!.map((item) {
|
||||||
|
final findingName = item.finding?.name ?? 'Unknown';
|
||||||
|
return checklistWidget(value: findingName.addTranslation);
|
||||||
|
}).toList()
|
||||||
|
else
|
||||||
|
Text('No findings available'.addTranslation),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget assetDetails() {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Asset Details",
|
||||||
|
style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
6.height,
|
||||||
|
'${context.translation.assetName}: ${model?.asset?.assetName ?? '-'}'.bodyText(context),
|
||||||
|
'${context.translation.assetNo}: ${model?.asset?.assetNumber ?? '-'}'.bodyText(context),
|
||||||
|
'${context.translation.manufacture}: ${model?.asset?.manufacturerName ?? '-'}'.bodyText(context),
|
||||||
|
'${context.translation.model}: ${model?.asset?.modelName ?? '-'}'.bodyText(context),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget checklistWidget({required String value}) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Checkbox(
|
||||||
|
value: true,
|
||||||
|
activeColor: AppColor.neutral120,
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -3),
|
||||||
|
onChanged: (value) {},
|
||||||
|
),
|
||||||
|
value.bodyText(context),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/models/all_requests_and_count_model.dart';
|
||||||
|
import 'package:test_sa/models/new_models/dashboard_detail.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/views/widgets/requests/request_status.dart';
|
||||||
|
|
||||||
|
class EquipmentInternalAuditItemView extends StatelessWidget {
|
||||||
|
final Data? requestData;
|
||||||
|
final RequestsDetails? requestDetails;
|
||||||
|
final bool showShadow;
|
||||||
|
|
||||||
|
const EquipmentInternalAuditItemView({Key? key, this.requestData, this.requestDetails, this.showShadow = true}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
//TODO need to refactor this code repetation @waseem
|
||||||
|
if (requestData != null) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
StatusLabel(
|
||||||
|
label: requestData!.statusName!,
|
||||||
|
textColor: AppColor.getRequestStatusTextColorByName(context, requestData!.statusName!),
|
||||||
|
backgroundColor: AppColor.getRequestStatusColorByName(context, requestData!.statusName!),
|
||||||
|
),
|
||||||
|
1.width.expanded,
|
||||||
|
Text(
|
||||||
|
requestData!.transactionDate?.toServiceRequestCardFormat ?? "",
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
8.height,
|
||||||
|
(requestData?.typeTransaction ?? "Internal Audit Request").heading5(context),
|
||||||
|
infoWidget(label: context.translation.assetNumber, value: requestData?.assetNumber, context: context),
|
||||||
|
|
||||||
|
8.height,
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.translation.viewDetails,
|
||||||
|
style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)),
|
||||||
|
),
|
||||||
|
4.width,
|
||||||
|
Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).toShadowContainer(context, withShadow: showShadow).onPress(() async {
|
||||||
|
Navigator.push(context, MaterialPageRoute(builder: (context) => EquipmentInternalAuditDetailPage(auditId: requestDetails!.id!)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
StatusLabel(
|
||||||
|
label: requestDetails!.status!,
|
||||||
|
textColor: AppColor.getRequestStatusTextColorByName(context, requestDetails?.status!),
|
||||||
|
backgroundColor: AppColor.getRequestStatusColorByName(context, requestDetails?.status!),
|
||||||
|
),
|
||||||
|
1.width.expanded,
|
||||||
|
Text(
|
||||||
|
requestDetails!.date?.toServiceRequestCardFormat ?? "",
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
8.height,
|
||||||
|
(requestDetails?.nameOfType ?? "Internal Audit Request").heading5(context),
|
||||||
|
8.height,
|
||||||
|
infoWidget(label: context.translation.assetNumber, value: requestDetails!.assetNo, context: context),
|
||||||
|
infoWidget(label: context.translation.assetSN, value: requestDetails!.assetSN, context: context),
|
||||||
|
infoWidget(label: context.translation.model, value: requestDetails!.model, context: context),
|
||||||
|
8.height,
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.translation.viewDetails,
|
||||||
|
style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)),
|
||||||
|
),
|
||||||
|
4.width,
|
||||||
|
Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).toShadowContainer(context, withShadow: showShadow).onPress(() async {
|
||||||
|
Navigator.push(context, MaterialPageRoute(builder: (context) => EquipmentInternalAuditDetailPage(auditId: requestDetails!.id!)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget infoWidget({required String label, String? value, required BuildContext context}) {
|
||||||
|
if (value != null && value.isNotEmpty) {
|
||||||
|
return '$label: $value'.bodyText(context);
|
||||||
|
}
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,322 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/api_manager.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/models/generic_attachment_model.dart';
|
||||||
|
import 'package:test_sa/models/timer_model.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/update_audit_form_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/working_time_tile.dart';
|
||||||
|
import 'package:test_sa/views/widgets/images/multi_image_picker.dart';
|
||||||
|
import 'package:test_sa/views/widgets/loaders/loading_manager.dart';
|
||||||
|
import 'package:test_sa/views/widgets/timer/app_timer.dart';
|
||||||
|
import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart';
|
||||||
|
|
||||||
|
class UpdateEquipmentInternalAuditPage extends StatefulWidget {
|
||||||
|
static const String id = "update-equipment-internal-audit";
|
||||||
|
EquipmentInternalAuditDataModel? model;
|
||||||
|
|
||||||
|
UpdateEquipmentInternalAuditPage({Key? key, this.model}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<UpdateEquipmentInternalAuditPage> createState() => _UpdateEquipmentInternalAuditPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UpdateEquipmentInternalAuditPageState extends State<UpdateEquipmentInternalAuditPage> {
|
||||||
|
final bool _isLoading = false;
|
||||||
|
double totalWorkingHours = 0.0;
|
||||||
|
AuditFormModel formModel = AuditFormModel();
|
||||||
|
final TextEditingController _workingHoursController = TextEditingController();
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
List<GenericAttachmentModel> _attachments = [];
|
||||||
|
|
||||||
|
//TODO need to check if it's needed or not..
|
||||||
|
List<TimerHistoryModel> timerList = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
populateForm();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void populateForm() {
|
||||||
|
formModel.requestId = widget.model?.id;
|
||||||
|
formModel.id = widget.model?.id;
|
||||||
|
_attachments = widget.model?.engineerData?.attachments?.map((e) => GenericAttachmentModel(id: e.id?.toInt() ?? 0, name: e.name!)).toList() ?? [];
|
||||||
|
|
||||||
|
calculateWorkingTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
void calculateWorkingTime() {
|
||||||
|
final helperModel = formModel;
|
||||||
|
final timers = helperModel.auditTimers ?? [];
|
||||||
|
totalWorkingHours = timers.fold<double>(0.0, (sum, item) {
|
||||||
|
if (item.startDate == null || item.endDate == null) return sum;
|
||||||
|
try {
|
||||||
|
final start = DateTime.parse(item.startDate!);
|
||||||
|
final end = DateTime.parse(item.endDate!);
|
||||||
|
final diffInHours = end.difference(start).inSeconds / 3600.0; // convert to hours
|
||||||
|
return sum + diffInHours;
|
||||||
|
} catch (_) {
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
timerList = timers.map((e) {
|
||||||
|
return TimerHistoryModel(
|
||||||
|
id: e.id,
|
||||||
|
startTime: e.startDate,
|
||||||
|
endTime: e.endDate,
|
||||||
|
workingHours: e.startDate,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onSubmit(BuildContext context) async {
|
||||||
|
bool isTimerPickerEnable = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false;
|
||||||
|
InternalAuditProvider provider = Provider.of<InternalAuditProvider>(context, listen: false);
|
||||||
|
|
||||||
|
showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading());
|
||||||
|
formModel.auditTimers ??= [];
|
||||||
|
if (formModel.auditTimePicker != null) {
|
||||||
|
int durationInSecond = formModel.auditTimePicker!.endAt!.difference(formModel.auditTimePicker!.startAt!).inSeconds;
|
||||||
|
formModel.auditTimers?.add(
|
||||||
|
InternalAuditTimerModel(
|
||||||
|
id: 0,
|
||||||
|
startDate: formModel.auditTimePicker!.startAt!.toIso8601String(), // Handle potential null
|
||||||
|
endDate: formModel.auditTimePicker!.endAt?.toIso8601String(), // Handle potential null
|
||||||
|
totalWorkingHour: ((durationInSecond) / 60 / 60),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
formModel.timerModelList?.forEach((timer) {
|
||||||
|
int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds;
|
||||||
|
formModel.auditTimers?.add(
|
||||||
|
InternalAuditTimerModel(
|
||||||
|
id: 0,
|
||||||
|
startDate: timer.startAt!.toIso8601String(), // Handle potential null
|
||||||
|
endDate: timer.endAt?.toIso8601String(), // Handle potential null
|
||||||
|
totalWorkingHour: ((durationInSecond) / 60 / 60),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
formModel.attachments = [];
|
||||||
|
for (var item in _attachments) {
|
||||||
|
String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name?.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? '';
|
||||||
|
formModel.attachments!.add(
|
||||||
|
InternalAuditAttachments(
|
||||||
|
id: item.id,
|
||||||
|
originalName: fileName,
|
||||||
|
name: fileName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final success = await provider.updateEquipmentInternalAudit(model: formModel);
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (formModel.isComplete == true) {
|
||||||
|
//Navigate to List screen.
|
||||||
|
} else if (formModel.isComplete == false) {
|
||||||
|
//Navigate to Detail screen.
|
||||||
|
if (success) {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
} else {
|
||||||
|
Fluttertoast.showToast(msg: 'Failed to update');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_workingHoursController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateTimer({TimerModel? timer}) {
|
||||||
|
if (timer?.startAt != null && timer?.endAt != null) {
|
||||||
|
final start = timer!.startAt!;
|
||||||
|
final end = timer.endAt!;
|
||||||
|
|
||||||
|
final difference = end.difference(start);
|
||||||
|
final totalHours = difference.inSeconds / 3600.0;
|
||||||
|
formModel.startTime = start;
|
||||||
|
formModel.endTime = end;
|
||||||
|
formModel.totalHours = totalHours;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: DefaultAppBar(
|
||||||
|
title: 'Update Information'.addTranslation,
|
||||||
|
onWillPopScope: () {
|
||||||
|
formModel.isComplete = false;
|
||||||
|
_onSubmit(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
key: _scaffoldKey,
|
||||||
|
body: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: LoadingManager(
|
||||||
|
isLoading: _isLoading,
|
||||||
|
isFailedLoading: false,
|
||||||
|
stateCode: 200,
|
||||||
|
onRefresh: () async {},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
8.height,
|
||||||
|
AppTextFormField(
|
||||||
|
labelText: 'Debrief'.addTranslation,
|
||||||
|
textInputType: TextInputType.multiline,
|
||||||
|
hintStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10),
|
||||||
|
labelStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
initialValue: formModel.debrief,
|
||||||
|
backgroundColor: AppColor.fieldBgColor(context),
|
||||||
|
showShadow: false,
|
||||||
|
onSaved: (value) {
|
||||||
|
formModel.debrief = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
8.height,
|
||||||
|
_timerWidget(context, totalWorkingHours),
|
||||||
|
16.height,
|
||||||
|
AttachmentPicker(
|
||||||
|
label: 'Upload Attachment',
|
||||||
|
attachment: _attachments,
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
onlyImages: false,
|
||||||
|
buttonIcon: 'attachment_icon'.toSvgAsset(
|
||||||
|
color: AppColor.primary10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
8.height,
|
||||||
|
],
|
||||||
|
).toShadowContainer(context),
|
||||||
|
).expanded,
|
||||||
|
FooterActionButton.footerContainer(
|
||||||
|
context: context,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
AppFilledButton(
|
||||||
|
label: context.translation.save,
|
||||||
|
buttonColor: context.isDark ? AppColor.neutral70 : AppColor.white60,
|
||||||
|
textColor: context.isDark ? AppColor.white10 : AppColor.black10,
|
||||||
|
onPressed: () {
|
||||||
|
formModel.isComplete = false;
|
||||||
|
_onSubmit(context);
|
||||||
|
}).expanded,
|
||||||
|
12.width,
|
||||||
|
AppFilledButton(
|
||||||
|
label: context.translation.complete,
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
onPressed: () {
|
||||||
|
formModel.isComplete = true;
|
||||||
|
_onSubmit(context);
|
||||||
|
}).expanded,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
).handlePopScope(
|
||||||
|
cxt: context,
|
||||||
|
onSave: () {
|
||||||
|
formModel.isComplete = false;
|
||||||
|
_onSubmit(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _timerWidget(BuildContext context, double totalWorkingHours) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
//TODO multiple timer ..
|
||||||
|
// AppTimer(
|
||||||
|
// label: context.translation.timer,
|
||||||
|
// timer: formModel.auditTimerModel,
|
||||||
|
// // enabled: enableTimer,
|
||||||
|
// pickerTimer: formModel.auditTimePicker,
|
||||||
|
// pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''),
|
||||||
|
// onPick: (time) {
|
||||||
|
// formModel.auditTimePicker = time;
|
||||||
|
// setState(() {});
|
||||||
|
// log('Time picker start ${formModel.auditTimePicker?.startAt}');
|
||||||
|
// log('Time picker end ${formModel.auditTimePicker?.endAt}');
|
||||||
|
// },
|
||||||
|
// timerProgress: (isRunning) {},
|
||||||
|
// onChange: (timer) async {
|
||||||
|
// formModel.auditTimerModel = timer;
|
||||||
|
// log('start ${formModel.auditTimerModel?.startAt}');
|
||||||
|
// log('end ${formModel.auditTimerModel?.endAt}');
|
||||||
|
// if (timer.startAt != null && timer.endAt != null) {
|
||||||
|
// formModel.timerModelList = formModel.timerModelList ?? [];
|
||||||
|
// formModel.timerModelList!.add(timer);
|
||||||
|
// }
|
||||||
|
// setState(() {});
|
||||||
|
// log('list length ${formModel.timerModelList?.length}');
|
||||||
|
// return true;
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
AppTimer(
|
||||||
|
label: context.translation.workingHours,
|
||||||
|
timer: formModel.auditTimerModel,
|
||||||
|
pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''),
|
||||||
|
pickerTimer: formModel.auditTimePicker,
|
||||||
|
showPicker: false,
|
||||||
|
onPick: (timer) {
|
||||||
|
updateTimer(timer: timer);
|
||||||
|
},
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.fieldBgColor(context),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
timerProgress: (isRunning) {},
|
||||||
|
onChange: (timer) async {
|
||||||
|
updateTimer(timer: timer);
|
||||||
|
log('here onChange ${timer.startAt}');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (totalWorkingHours > 0.0) ...[
|
||||||
|
12.height,
|
||||||
|
WorkingTimeTile(
|
||||||
|
timerList: timerList,
|
||||||
|
totalWorkingTime: totalWorkingHours,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,278 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/models/device/asset.dart';
|
||||||
|
import 'package:test_sa/models/generic_attachment_model.dart';
|
||||||
|
import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart';
|
||||||
|
import 'package:test_sa/models/lookup.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_audit_work_order_auto_complete_field.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart';
|
||||||
|
import 'package:test_sa/views/widgets/equipment/asset_picker.dart';
|
||||||
|
import 'package:test_sa/views/widgets/images/multi_image_picker.dart';
|
||||||
|
import 'package:test_sa/views/widgets/parts/auto_complete_parts_field.dart';
|
||||||
|
import '../../../../../../../new_views/common_widgets/default_app_bar.dart';
|
||||||
|
|
||||||
|
class CreateSystemInternalAuditForm extends StatefulWidget {
|
||||||
|
static const String id = "/create-system-audit-view";
|
||||||
|
|
||||||
|
const CreateSystemInternalAuditForm({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CreateSystemInternalAuditFormState createState() => _CreateSystemInternalAuditFormState();
|
||||||
|
}
|
||||||
|
//TODO remove unnecessary code
|
||||||
|
|
||||||
|
class _CreateSystemInternalAuditFormState extends State<CreateSystemInternalAuditForm> with TickerProviderStateMixin {
|
||||||
|
late TextEditingController _commentController;
|
||||||
|
late InternalAuditProvider _internalAuditProvider;
|
||||||
|
final SystemInternalAuditFormModel _model = SystemInternalAuditFormModel();
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
final List<GenericAttachmentModel> _attachments = [];
|
||||||
|
late TextEditingController _woAutoCompleteController;
|
||||||
|
bool showLoading = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_commentController = TextEditingController();
|
||||||
|
_woAutoCompleteController = TextEditingController();
|
||||||
|
_internalAuditProvider = Provider.of<InternalAuditProvider>(context, listen: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_commentController.dispose();
|
||||||
|
_woAutoCompleteController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
appBar: DefaultAppBar(title: 'System Internal Audit Checklist'.addTranslation),
|
||||||
|
body: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SingleItemDropDownMenu<Lookup, InternalAuditWoTypeProvider>(
|
||||||
|
context: context,
|
||||||
|
height: 56.toScreenHeight,
|
||||||
|
title: 'Wo Type'.addTranslation,
|
||||||
|
showShadow: false,
|
||||||
|
backgroundColor: AppColor.fieldBgColor(context),
|
||||||
|
showAsBottomSheet: true,
|
||||||
|
initialValue: _model.workOrderType,
|
||||||
|
onSelect: (status) {
|
||||||
|
if (status != null) {
|
||||||
|
_model.workOrderType = status;
|
||||||
|
_model.workOrderDetailModel = SystemAuditWorkOrderDetailModel();
|
||||||
|
_clearWorkOrderSelectionIds();
|
||||||
|
_woAutoCompleteController.clear();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
12.height,
|
||||||
|
SystemAuditWoAutoCompleteField(
|
||||||
|
woTypeId: _model.workOrderType?.value,
|
||||||
|
controller: _woAutoCompleteController,
|
||||||
|
clearAfterPick: false,
|
||||||
|
initialValue: '',
|
||||||
|
onPick: (wo) async {
|
||||||
|
InternalAuditProvider provider = Provider.of<InternalAuditProvider>(context, listen: false);
|
||||||
|
setState(() {
|
||||||
|
showLoading = true;
|
||||||
|
});
|
||||||
|
_model.workOrderDetailModel = await provider.loadAllWorkOrderDetailsByID(workOrderId: wo.id!, workOrderTypeId: _model.workOrderType!.value!);
|
||||||
|
updateWorkOrderReference(woTypeId: _model.workOrderType?.value, selectedId: wo.id);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
showLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
12.height,
|
||||||
|
// if(_internalAuditModel.woOrderNo!=null)
|
||||||
|
_workOrderInfoWidget(context),
|
||||||
|
12.height,
|
||||||
|
SingleItemDropDownMenu<Lookup, InternalAuditFindingTypeProvider>(
|
||||||
|
context: context,
|
||||||
|
height: 56.toScreenHeight,
|
||||||
|
title: 'Finding Type'.addTranslation,
|
||||||
|
showShadow: false,
|
||||||
|
backgroundColor: AppColor.fieldBgColor(context),
|
||||||
|
showAsBottomSheet: true,
|
||||||
|
initialValue: _model.findingType,
|
||||||
|
onSelect: (status) {
|
||||||
|
if (status != null) {
|
||||||
|
_model.findingType = status;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
12.height,
|
||||||
|
AppTextFormField(
|
||||||
|
backgroundColor: AppColor.fieldBgColor(context),
|
||||||
|
labelText: 'Finding Description'.addTranslation,
|
||||||
|
labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
textInputType: TextInputType.multiline,
|
||||||
|
showShadow: false,
|
||||||
|
onSaved: (value) {
|
||||||
|
_model.findingDescription = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
16.height,
|
||||||
|
AttachmentPicker(
|
||||||
|
label: context.translation.attachments,
|
||||||
|
attachment: _attachments,
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
onlyImages: false,
|
||||||
|
buttonIcon: 'attachment_icon'.toSvgAsset(color: AppColor.primary10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).toShadowContainer(context),
|
||||||
|
).expanded,
|
||||||
|
FooterActionButton.footerContainer(
|
||||||
|
context: context,
|
||||||
|
child: AppFilledButton(
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
label: context.translation.submitRequest,
|
||||||
|
onPressed: _submit,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
InternalAuditProvider internalAuditProvider = Provider.of<InternalAuditProvider>(context, listen: false);
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
_model.attachments=[];
|
||||||
|
for (var item in _attachments) {
|
||||||
|
String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? '';
|
||||||
|
_model.attachments?.add(InternalAuditAttachments(id: item.id, name: fileName));
|
||||||
|
}
|
||||||
|
showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading());
|
||||||
|
_model.auditorId = context.userProvider.user?.userID;
|
||||||
|
bool status = await internalAuditProvider.addSystemInternalAudit(context: context, request: _model);
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (status) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _workOrderInfoWidget(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: showLoading ? Colors.white : const Color(0xffF4F6FC),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xff212936).withOpacity(.03),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_model.workOrderType?.name ?? 'WO Type',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.toScreenWidth,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontStyle: FontStyle.normal,
|
||||||
|
color: Colors.black87,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
).expanded,
|
||||||
|
const Icon(
|
||||||
|
Icons.info,
|
||||||
|
color: Color(0xff7D859A),
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
6.height,
|
||||||
|
"${context.translation.site}: ${_model.workOrderDetailModel?.woSite ?? '-'}".bodyText(context),
|
||||||
|
"${'Eng. Name'.addTranslation}: ${_model.workOrderDetailModel?.woAssignedEngineer ?? '-'}".bodyText(context),
|
||||||
|
"${'Asset Name'.addTranslation}: ${_model.workOrderDetailModel?.woAssetName ?? '-'}".bodyText(context),
|
||||||
|
"${context.translation.model}: ${_model.workOrderDetailModel?.woModel ?? '-'}".bodyText(context),
|
||||||
|
"${context.translation.manufacture}: ${_model.workOrderDetailModel?.woManufacturer ?? '-'}".bodyText(context),
|
||||||
|
"${'SN'.addTranslation}: ${_model.workOrderDetailModel?.wosn ?? '-'}".bodyText(context),
|
||||||
|
"${context.translation.assetNo}: ${_model.workOrderDetailModel?.woAssetNo ?? '-'}".bodyText(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).toShimmer(isShow: showLoading, context: context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateWorkOrderReference({int? woTypeId, int? selectedId}) {
|
||||||
|
_clearWorkOrderSelectionIds();
|
||||||
|
if (woTypeId == null || selectedId == null) return;
|
||||||
|
switch (woTypeId) {
|
||||||
|
case 1: // CM
|
||||||
|
_model.correctiveMaintenanceId = selectedId;
|
||||||
|
break;
|
||||||
|
case 2: // Gas Refill
|
||||||
|
_model.gasRefillId = selectedId;
|
||||||
|
break;
|
||||||
|
case 3: // Asset Transfer
|
||||||
|
_model.assetTransferId = selectedId;
|
||||||
|
break;
|
||||||
|
case 4: // PPM Request
|
||||||
|
_model.planPreventiveVisitId = selectedId;
|
||||||
|
break;
|
||||||
|
case 5: // Recurrent WOx
|
||||||
|
_model.planRecurrentTaskId = selectedId;
|
||||||
|
break;
|
||||||
|
case 6: // Task
|
||||||
|
_model.taskJobId = selectedId;
|
||||||
|
break;
|
||||||
|
case 7: // Recall & Alert
|
||||||
|
_model.taskAlertJobId = selectedId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearWorkOrderSelectionIds() {
|
||||||
|
_model.correctiveMaintenanceId = null;
|
||||||
|
_model.planPreventiveVisitId = null;
|
||||||
|
_model.assetTransferId = null;
|
||||||
|
_model.taskJobId = null;
|
||||||
|
_model.taskAlertJobId = null;
|
||||||
|
_model.gasRefillId = null;
|
||||||
|
_model.planRecurrentTaskId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,149 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_text_style.dart';
|
||||||
|
import 'package:test_sa/views/app_style/sizing.dart';
|
||||||
|
|
||||||
|
class SystemAuditWoAutoCompleteField extends StatefulWidget {
|
||||||
|
final String initialValue;
|
||||||
|
final int? woTypeId;
|
||||||
|
final TextEditingController controller;
|
||||||
|
final bool clearAfterPick;
|
||||||
|
final Function(WoAutoCompleteModel) onPick;
|
||||||
|
|
||||||
|
const SystemAuditWoAutoCompleteField({Key? key, required this.initialValue, this.woTypeId, required this.onPick, this.clearAfterPick = true, required this.controller}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SystemAuditWoAutoCompleteFieldState createState() => _SystemAuditWoAutoCompleteFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SystemAuditWoAutoCompleteFieldState extends State<SystemAuditWoAutoCompleteField> {
|
||||||
|
late InternalAuditProvider _provider;
|
||||||
|
|
||||||
|
//
|
||||||
|
// late TextEditingController _controller;
|
||||||
|
|
||||||
|
bool loading = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
// w controller = widget.controller ?? TextEditingController(text: widget.initialValue);
|
||||||
|
super.initState();
|
||||||
|
_provider = Provider.of<InternalAuditProvider>(context, listen: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant SystemAuditWoAutoCompleteField oldWidget) {
|
||||||
|
// if (widget.initialValue != oldWidget.initialValue) {
|
||||||
|
// // _controller = widget.controller ?? TextEditingController(text: widget.initialValue);
|
||||||
|
// }
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (widget.controller == null) {
|
||||||
|
// _controller.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10));
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.background(context),
|
||||||
|
borderRadius: BorderRadius.circular(AppStyle.borderRadius * AppStyle.getScaleFactor(context)),
|
||||||
|
// boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)],
|
||||||
|
),
|
||||||
|
child: Autocomplete<WoAutoCompleteModel>(
|
||||||
|
optionsBuilder: (TextEditingValue textEditingValue) async {
|
||||||
|
if (textEditingValue.text.isEmpty) {
|
||||||
|
if (loading) {
|
||||||
|
setState(() {
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return const Iterable<WoAutoCompleteModel>.empty();
|
||||||
|
}
|
||||||
|
if (!loading) {
|
||||||
|
setState(() {
|
||||||
|
loading = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
List<WoAutoCompleteModel> workOrders = (await _provider.getWorkOrderByWoType(text: textEditingValue.text, woId:widget.woTypeId));
|
||||||
|
setState(() {
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
return workOrders;
|
||||||
|
},
|
||||||
|
displayStringForOption: (WoAutoCompleteModel option) => option.displayName ?? '',
|
||||||
|
fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) {
|
||||||
|
return TextField(
|
||||||
|
controller: widget.controller,
|
||||||
|
focusNode: fieldFocusNode,
|
||||||
|
style: AppTextStyles.bodyText.copyWith(color: AppColor.black10),
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: border,
|
||||||
|
disabledBorder: border,
|
||||||
|
focusedBorder: border,
|
||||||
|
enabledBorder: border,
|
||||||
|
errorBorder: border,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 8.toScreenHeight, horizontal: 16.toScreenWidth),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
suffixIconConstraints: const BoxConstraints(maxHeight: 24, maxWidth: 24 + 8),
|
||||||
|
filled: true,
|
||||||
|
fillColor: AppColor.fieldBgColor(context),
|
||||||
|
errorStyle: AppTextStyle.tiny.copyWith(color: context.isDark ? AppColor.red50 : AppColor.red60),
|
||||||
|
floatingLabelStyle: AppTextStyle.body1.copyWith(fontWeight: FontWeight.w500, color: context.isDark ? null : AppColor.neutral20),
|
||||||
|
labelText: context.translation.woNumber,
|
||||||
|
labelStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.textColor(context)),
|
||||||
|
suffixIcon: loading ? const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3.0).paddingOnly(end: 8) : null,
|
||||||
|
),
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: (text) {
|
||||||
|
fieldTextEditingController.text = text;
|
||||||
|
},
|
||||||
|
onSubmitted: (String value) {
|
||||||
|
onFieldSubmitted();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (WoAutoCompleteModel selection) {
|
||||||
|
if (widget.clearAfterPick) {
|
||||||
|
widget.controller.clear();
|
||||||
|
} else {
|
||||||
|
widget.controller.text = (selection.displayName ?? "");
|
||||||
|
}
|
||||||
|
widget.onPick(selection);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// String getUrl(int? woTypeId) {
|
||||||
|
// if (woTypeId == null) return '';
|
||||||
|
// String base = URLs.woAutoCompleteBase;
|
||||||
|
// var urlMap = {
|
||||||
|
// 1: '$base/ServiceRequest/WorkOrderAutoComplete', // CM
|
||||||
|
// 2: '$base/GasRefill/GetGasRefillAutoComplete', // Gas Refill
|
||||||
|
// 3: '$base/AssetTransfer/GetAssetTransferAutoComplete', // Asset Transfer
|
||||||
|
// 4: '$base/PlanPreventiveVisit/GetAutoCompletePlanPreventiveVisit', // PPM Request
|
||||||
|
// 5: '$base/PlanRecurrentTasks/GetPlanRecurrentTask', // Recurrent WO
|
||||||
|
// 6: '$base/TaskJobs/AutocompleteTaskJob', // Task
|
||||||
|
// 7: '$base/TaskJobs/AutocompleteTaskJob', // Recall & Alert
|
||||||
|
// };
|
||||||
|
// return urlMap[woTypeId] ?? '';
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -0,0 +1,219 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_data_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||||
|
import 'package:test_sa/views/widgets/images/files_list.dart';
|
||||||
|
|
||||||
|
import 'package:test_sa/views/widgets/loaders/app_loading.dart';
|
||||||
|
|
||||||
|
class SystemInternalAuditDetailPage extends StatefulWidget {
|
||||||
|
static const String id = "/details-system-internal-audit";
|
||||||
|
final int auditId;
|
||||||
|
|
||||||
|
SystemInternalAuditDetailPage({Key? key, required this.auditId}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SystemInternalAuditDetailPageState createState() {
|
||||||
|
return _SystemInternalAuditDetailPageState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SystemInternalAuditDetailPageState extends State<SystemInternalAuditDetailPage> {
|
||||||
|
bool isWoType = true;
|
||||||
|
SystemInternalAuditDataModel? model;
|
||||||
|
late InternalAuditProvider _internalAuditProvider;
|
||||||
|
List<InternalAuditAttachments> allAttachments = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_internalAuditProvider = Provider.of<InternalAuditProvider>(context, listen: false);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
getAuditData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getAuditData() async {
|
||||||
|
model = await _internalAuditProvider.getInternalSystemAuditById(widget.auditId);
|
||||||
|
allAttachments.clear();
|
||||||
|
allAttachments = [
|
||||||
|
...(model?.attachments ?? []),
|
||||||
|
...(model?.engineerData?.attachments ?? []),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
//TODO need to check when implementing provider needed or not .
|
||||||
|
return Scaffold(
|
||||||
|
appBar: const DefaultAppBar(title: "Request Details"),
|
||||||
|
body: Selector<InternalAuditProvider, bool>(
|
||||||
|
selector: (_, provider) => provider.isLoading,
|
||||||
|
builder: (_, isLoading, __) {
|
||||||
|
if (isLoading) return const ALoading();
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
assetInformation(),
|
||||||
|
8.height,
|
||||||
|
workOrderInformation(),
|
||||||
|
8.height,
|
||||||
|
//TODO need to check for comments
|
||||||
|
if (model?.findingDescription?.isNotEmpty ?? false) ...[
|
||||||
|
const Divider().defaultStyle(context),
|
||||||
|
// Text(
|
||||||
|
// "Comments".addTranslation,
|
||||||
|
// style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
// ),
|
||||||
|
model!.findingDescription!.bodyText(context),
|
||||||
|
],
|
||||||
|
//TODO need to check for attachments
|
||||||
|
if (allAttachments.isNotEmpty) ...[
|
||||||
|
const Divider().defaultStyle(context),
|
||||||
|
Text(
|
||||||
|
"Attachments".addTranslation,
|
||||||
|
style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
8.height,
|
||||||
|
FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.name ?? '') ?? '').toList() ?? []),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
).paddingAll(0).toShadowContainer(context),
|
||||||
|
).expanded,
|
||||||
|
if (context.userProvider.isEngineer)
|
||||||
|
FooterActionButton.footerContainer(
|
||||||
|
context: context,
|
||||||
|
child: AppFilledButton(
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
label: "Update",
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => UpdateSystemInternalAuditPage(model: model),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (result == true) {
|
||||||
|
await getAuditData();
|
||||||
|
setState(() {}); // refresh UI with new model
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget workOrderInformation() {
|
||||||
|
final details = model?.workOrderDetails;
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider().defaultStyle(context),
|
||||||
|
Text(
|
||||||
|
"WO Info",
|
||||||
|
style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
6.height,
|
||||||
|
labelValueText(context, context.translation.woNumber, details?.workOrderNo),
|
||||||
|
labelValueText(context, 'WO Type'.addTranslation, model?.workOrderType?.name),
|
||||||
|
labelValueText(context, context.translation.site, details?.woSite),
|
||||||
|
labelValueText(context, context.translation.assetName, details?.woAssetName),
|
||||||
|
labelValueText(context, context.translation.manufacture, details?.woManufacturer),
|
||||||
|
labelValueText(context, context.translation.model, details?.woModel),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget requestDetails() {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider().defaultStyle(context),
|
||||||
|
Text(
|
||||||
|
"Request Details",
|
||||||
|
style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
6.height,
|
||||||
|
checklistWidget(value: 'Asset Tag'.addTranslation),
|
||||||
|
checklistWidget(value: 'Expired PM Tag'.addTranslation),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget assetInformation() {
|
||||||
|
final details = model?.workOrderDetails;
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Asset Details",
|
||||||
|
style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
6.height,
|
||||||
|
labelValueText(context, context.translation.assetName, details?.woAssetName),
|
||||||
|
labelValueText(context, context.translation.assetNo, details?.woAssetNo),
|
||||||
|
labelValueText(context, context.translation.manufacture, details?.woManufacturer),
|
||||||
|
labelValueText(context, context.translation.model, details?.woModel),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget labelValueText(BuildContext context, String label, String? value) {
|
||||||
|
if (value == null || value.isEmpty) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 4),
|
||||||
|
child: Text(
|
||||||
|
'$label: $value',
|
||||||
|
style: AppTextStyles.bodyText.copyWith(
|
||||||
|
color: context.isDark ? AppColor.neutral30 : AppColor.neutral120,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget checklistWidget({required String value}) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Checkbox(
|
||||||
|
value: true,
|
||||||
|
activeColor: AppColor.neutral120,
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -3),
|
||||||
|
onChanged: (value) {},
|
||||||
|
),
|
||||||
|
value.bodyText(context),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/models/all_requests_and_count_model.dart';
|
||||||
|
import 'package:test_sa/models/new_models/dashboard_detail.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_detail_page.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/views/widgets/requests/request_status.dart';
|
||||||
|
|
||||||
|
class SystemInternalAuditItemView extends StatelessWidget {
|
||||||
|
final Data? requestData;
|
||||||
|
final RequestsDetails? requestDetails;
|
||||||
|
final bool showShadow;
|
||||||
|
|
||||||
|
const SystemInternalAuditItemView({Key? key, this.requestData, this.requestDetails, this.showShadow = true}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
//TODO need to refactor this code repetation @waseem
|
||||||
|
if (requestData != null) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
StatusLabel(
|
||||||
|
label: requestData!.statusName!,
|
||||||
|
textColor: AppColor.getRequestStatusTextColorByName(context, requestData!.statusName!),
|
||||||
|
backgroundColor: AppColor.getRequestStatusColorByName(context, requestData!.statusName!),
|
||||||
|
),
|
||||||
|
1.width.expanded,
|
||||||
|
Text(
|
||||||
|
requestData!.transactionDate?.toServiceRequestCardFormat ?? "",
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
8.height,
|
||||||
|
(requestData?.typeTransaction ?? "Internal Audit Request").heading5(context),
|
||||||
|
infoWidget(label: context.translation.assetNo, value: requestData?.assetNumber, context: context),
|
||||||
|
|
||||||
|
8.height,
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.translation.viewDetails,
|
||||||
|
style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)),
|
||||||
|
),
|
||||||
|
4.width,
|
||||||
|
Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).toShadowContainer(context, withShadow: showShadow).onPress(() async {
|
||||||
|
Navigator.push(context, MaterialPageRoute(builder: (context) => SystemInternalAuditDetailPage(auditId: requestDetails!.id!)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
StatusLabel(
|
||||||
|
label: requestDetails!.status!,
|
||||||
|
textColor: AppColor.getRequestStatusTextColorByName(context, requestDetails?.status!),
|
||||||
|
backgroundColor: AppColor.getRequestStatusColorByName(context, requestDetails?.status!),
|
||||||
|
),
|
||||||
|
1.width.expanded,
|
||||||
|
Text(
|
||||||
|
requestDetails!.date?.toServiceRequestCardFormat ?? "",
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
8.height,
|
||||||
|
(requestDetails?.nameOfType ?? "Internal Audit Request").heading5(context),
|
||||||
|
infoWidget(label: context.translation.assetNumber, value: requestDetails!.assetNo, context: context),
|
||||||
|
infoWidget(label: context.translation.assetSN, value: requestDetails!.assetSN, context: context),
|
||||||
|
infoWidget(label: context.translation.model, value: requestDetails!.model, context: context),
|
||||||
|
8.height,
|
||||||
|
// infoWidget(label: context.translation.site, value: requestDetails!.site, context: context),
|
||||||
|
8.height,
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.translation.viewDetails,
|
||||||
|
style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)),
|
||||||
|
),
|
||||||
|
4.width,
|
||||||
|
Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).toShadowContainer(context, withShadow: showShadow).onPress(() async {
|
||||||
|
Navigator.push(context, MaterialPageRoute(builder: (context) => SystemInternalAuditDetailPage(auditId: requestDetails!.id!)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget infoWidget({required String label, String? value, required BuildContext context}) {
|
||||||
|
if (value != null && value.isNotEmpty) {
|
||||||
|
return '$label: $value'.bodyText(context);
|
||||||
|
}
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,316 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/api_manager.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/string_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/models/generic_attachment_model.dart';
|
||||||
|
import 'package:test_sa/models/timer_model.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart';
|
||||||
|
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_data_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/update_audit_form_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/working_time_tile.dart';
|
||||||
|
import 'package:test_sa/views/widgets/images/multi_image_picker.dart';
|
||||||
|
import 'package:test_sa/views/widgets/loaders/loading_manager.dart';
|
||||||
|
import 'package:test_sa/views/widgets/timer/app_timer.dart';
|
||||||
|
import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart';
|
||||||
|
|
||||||
|
class UpdateSystemInternalAuditPage extends StatefulWidget {
|
||||||
|
static const String id = "update-system-internal-audit";
|
||||||
|
SystemInternalAuditDataModel? model;
|
||||||
|
|
||||||
|
UpdateSystemInternalAuditPage({Key? key, this.model}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<UpdateSystemInternalAuditPage> createState() => _UpdateSystemInternalAuditPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UpdateSystemInternalAuditPageState extends State<UpdateSystemInternalAuditPage> {
|
||||||
|
final bool _isLoading = false;
|
||||||
|
double totalWorkingHours = 0.0;
|
||||||
|
AuditFormModel formModel = AuditFormModel();
|
||||||
|
final TextEditingController _workingHoursController = TextEditingController();
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
List<GenericAttachmentModel> _attachments = [];
|
||||||
|
|
||||||
|
//TODO need to check if it's needed or not..
|
||||||
|
List<TimerHistoryModel> timerList = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
populateForm();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void populateForm() {
|
||||||
|
formModel.requestId = widget.model?.id;
|
||||||
|
formModel.id = widget.model?.id;
|
||||||
|
_attachments = widget.model?.engineerData?.attachments?.map((e) => GenericAttachmentModel(id: e.id?.toInt() ?? 0, name: e.name!)).toList() ?? [];
|
||||||
|
|
||||||
|
calculateWorkingTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
void calculateWorkingTime() {
|
||||||
|
final helperModel = formModel;
|
||||||
|
final timers = helperModel.auditTimers ?? [];
|
||||||
|
totalWorkingHours = timers.fold<double>(0.0, (sum, item) {
|
||||||
|
if (item.startDate == null || item.endDate == null) return sum;
|
||||||
|
try {
|
||||||
|
final start = DateTime.parse(item.startDate!);
|
||||||
|
final end = DateTime.parse(item.endDate!);
|
||||||
|
final diffInHours = end.difference(start).inSeconds / 3600.0; // convert to hours
|
||||||
|
return sum + diffInHours;
|
||||||
|
} catch (_) {
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
timerList = timers.map((e) {
|
||||||
|
return TimerHistoryModel(
|
||||||
|
id: e.id,
|
||||||
|
startTime: e.startDate,
|
||||||
|
endTime: e.endDate,
|
||||||
|
workingHours: e.startDate,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onSubmit(BuildContext context) async {
|
||||||
|
bool isTimerPickerEnable = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false;
|
||||||
|
InternalAuditProvider provider = Provider.of<InternalAuditProvider>(context, listen: false);
|
||||||
|
showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading());
|
||||||
|
|
||||||
|
formModel.auditTimers ??= [];
|
||||||
|
if (formModel.auditTimePicker != null) {
|
||||||
|
int durationInSecond = formModel.auditTimePicker!.endAt!.difference(formModel.auditTimePicker!.startAt!).inSeconds;
|
||||||
|
formModel.auditTimers?.add(
|
||||||
|
InternalAuditTimerModel(
|
||||||
|
id: 0,
|
||||||
|
startDate: formModel.auditTimePicker!.startAt!.toIso8601String(), // Handle potential null
|
||||||
|
endDate: formModel.auditTimePicker!.endAt?.toIso8601String(), // Handle potential null
|
||||||
|
totalWorkingHour: ((durationInSecond) / 60 / 60),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
formModel.timerModelList?.forEach((timer) {
|
||||||
|
int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds;
|
||||||
|
formModel.auditTimers?.add(
|
||||||
|
InternalAuditTimerModel(
|
||||||
|
id: 0,
|
||||||
|
startDate: timer.startAt!.toIso8601String(), // Handle potential null
|
||||||
|
endDate: timer.endAt?.toIso8601String(), // Handle potential null
|
||||||
|
totalWorkingHour: ((durationInSecond) / 60 / 60),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
formModel.attachments = [];
|
||||||
|
for (var item in _attachments) {
|
||||||
|
String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name?.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? '';
|
||||||
|
formModel.attachments!.add(
|
||||||
|
InternalAuditAttachments(
|
||||||
|
id: item.id,
|
||||||
|
// originalName: fileName,
|
||||||
|
name: fileName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
log('submit press');
|
||||||
|
log('data ${formModel.toJson()}');
|
||||||
|
final success = await provider.updateSystemInternalAudit(model: formModel);
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (formModel.isComplete == true) {
|
||||||
|
//Navigate to List screen.
|
||||||
|
} else if (formModel.isComplete == false) {
|
||||||
|
if (success) {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
} else {
|
||||||
|
Fluttertoast.showToast(msg: 'Failed to update');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_workingHoursController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateTimer({TimerModel? timer}) {
|
||||||
|
if (timer?.startAt != null && timer?.endAt != null) {
|
||||||
|
final start = timer!.startAt!;
|
||||||
|
final end = timer.endAt!;
|
||||||
|
|
||||||
|
final difference = end.difference(start);
|
||||||
|
final totalHours = difference.inSeconds / 3600.0;
|
||||||
|
formModel.startTime = start;
|
||||||
|
formModel.endTime = end;
|
||||||
|
formModel.totalHours = totalHours;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: DefaultAppBar(
|
||||||
|
title: 'Update Information'.addTranslation,
|
||||||
|
onWillPopScope: () {
|
||||||
|
formModel.isComplete = false;
|
||||||
|
_onSubmit(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
key: _scaffoldKey,
|
||||||
|
body: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: LoadingManager(
|
||||||
|
isLoading: _isLoading,
|
||||||
|
isFailedLoading: false,
|
||||||
|
stateCode: 200,
|
||||||
|
onRefresh: () async {},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
8.height,
|
||||||
|
AppTextFormField(
|
||||||
|
labelText: 'Debrief'.addTranslation,
|
||||||
|
textInputType: TextInputType.multiline,
|
||||||
|
hintStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10),
|
||||||
|
labelStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
initialValue: formModel.debrief,
|
||||||
|
backgroundColor: AppColor.fieldBgColor(context),
|
||||||
|
showShadow: false,
|
||||||
|
onSaved: (value) {
|
||||||
|
formModel.debrief = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
8.height,
|
||||||
|
_timerWidget(context, totalWorkingHours),
|
||||||
|
16.height,
|
||||||
|
AttachmentPicker(
|
||||||
|
label: 'Upload Attachment',
|
||||||
|
attachment: _attachments,
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
onlyImages: false,
|
||||||
|
buttonIcon: 'attachment_icon'.toSvgAsset(
|
||||||
|
color: AppColor.primary10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
8.height,
|
||||||
|
],
|
||||||
|
).toShadowContainer(context),
|
||||||
|
).expanded,
|
||||||
|
FooterActionButton.footerContainer(
|
||||||
|
context: context,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
AppFilledButton(
|
||||||
|
label: context.translation.save,
|
||||||
|
buttonColor: context.isDark ? AppColor.neutral70 : AppColor.white60,
|
||||||
|
textColor: context.isDark ? AppColor.white10 : AppColor.black10,
|
||||||
|
onPressed: () {
|
||||||
|
log('button press ');
|
||||||
|
formModel.isComplete = false;
|
||||||
|
_onSubmit(context);
|
||||||
|
}).expanded,
|
||||||
|
12.width,
|
||||||
|
AppFilledButton(
|
||||||
|
label: context.translation.complete,
|
||||||
|
buttonColor: AppColor.primary10,
|
||||||
|
onPressed: () {
|
||||||
|
formModel.isComplete = true;
|
||||||
|
_onSubmit(context);
|
||||||
|
}).expanded,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
).handlePopScope(
|
||||||
|
cxt: context,
|
||||||
|
onSave: () {
|
||||||
|
formModel.isComplete = false;
|
||||||
|
_onSubmit(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _timerWidget(BuildContext context, double totalWorkingHours) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
///this is for multiple timers...
|
||||||
|
// AppTimer(
|
||||||
|
// label: context.translation.timer,
|
||||||
|
// timer: formModel.auditTimerModel,
|
||||||
|
// // enabled: enableTimer,
|
||||||
|
// pickerTimer: formModel.auditTimePicker,
|
||||||
|
// pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''),
|
||||||
|
// onPick: (time) {
|
||||||
|
// formModel.auditTimePicker = time;
|
||||||
|
// },
|
||||||
|
// timerProgress: (isRunning) {},
|
||||||
|
// onChange: (timer) async {
|
||||||
|
// formModel.auditTimerModel = timer;
|
||||||
|
// if (timer.startAt != null && timer.endAt != null) {
|
||||||
|
// formModel.timerModelList = formModel.timerModelList ?? [];
|
||||||
|
// formModel.timerModelList!.add(timer);
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
AppTimer(
|
||||||
|
label: context.translation.workingHours,
|
||||||
|
showPicker: false,
|
||||||
|
timer: formModel.auditTimerModel,
|
||||||
|
pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''),
|
||||||
|
pickerTimer: formModel.auditTimePicker,
|
||||||
|
onPick: (time) {
|
||||||
|
updateTimer(timer: time);
|
||||||
|
},
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.fieldBgColor(context),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
timerProgress: (isRunning) {},
|
||||||
|
onChange: (timer) async {
|
||||||
|
updateTimer(timer: timer);
|
||||||
|
log('here onChange ${timer.startAt}');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (totalWorkingHours > 0.0) ...[
|
||||||
|
12.height,
|
||||||
|
WorkingTimeTile(
|
||||||
|
timerList: timerList,
|
||||||
|
totalWorkingTime: totalWorkingHours,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/api_manager.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||||
|
import 'package:test_sa/models/lookup.dart';
|
||||||
|
import 'package:test_sa/providers/loading_list_notifier.dart';
|
||||||
|
|
||||||
|
class InternalAuditCheckListProvider extends LoadingListNotifier<Lookup> {
|
||||||
|
@override
|
||||||
|
Future getData({int? id}) async {
|
||||||
|
if (loading ?? false) return -2;
|
||||||
|
loading = true;
|
||||||
|
notifyListeners();
|
||||||
|
Response response;
|
||||||
|
try {
|
||||||
|
response = await ApiManager.instance.get(URLs.getInternalAuditChecklist);
|
||||||
|
} catch (error) {
|
||||||
|
loading = false;
|
||||||
|
stateCode = -1;
|
||||||
|
notifyListeners();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
stateCode = response.statusCode;
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
// client's request was successfully received
|
||||||
|
List listJson = json.decode(response.body)["data"];
|
||||||
|
items = listJson.map((department) => Lookup.fromJson(department)).toList();
|
||||||
|
}
|
||||||
|
loading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return response.statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/api_manager.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||||
|
import 'package:test_sa/models/lookup.dart';
|
||||||
|
import 'package:test_sa/providers/loading_list_notifier.dart';
|
||||||
|
|
||||||
|
class InternalAuditFindingTypeProvider extends LoadingListNotifier<Lookup> {
|
||||||
|
@override
|
||||||
|
Future getData({int? id}) async {
|
||||||
|
if (loading ?? false) return -2;
|
||||||
|
loading = true;
|
||||||
|
notifyListeners();
|
||||||
|
Response response;
|
||||||
|
try {
|
||||||
|
response = await ApiManager.instance.get(URLs.getInternalAuditFindingType);
|
||||||
|
} catch (error) {
|
||||||
|
loading = false;
|
||||||
|
stateCode = -1;
|
||||||
|
notifyListeners();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
stateCode = response.statusCode;
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
// client's request was successfully received
|
||||||
|
List listJson = json.decode(response.body)["data"];
|
||||||
|
items = listJson.map((department) => Lookup.fromJson(department)).toList();
|
||||||
|
}
|
||||||
|
loading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return response.statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,211 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/api_manager.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||||
|
import 'package:test_sa/extensions/context_extension.dart';
|
||||||
|
import 'package:test_sa/models/device/asset_search.dart';
|
||||||
|
import 'package:test_sa/models/lookup.dart';
|
||||||
|
import 'package:test_sa/models/new_models/asset_nd_auto_complete_by_dynamic_codes_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/update_audit_form_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_data_model.dart';
|
||||||
|
import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart';
|
||||||
|
|
||||||
|
class InternalAuditProvider extends ChangeNotifier {
|
||||||
|
final pageItemNumber = 10;
|
||||||
|
final searchPageItemNumber = 10;
|
||||||
|
int pageNo = 1;
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
pageNo = 1;
|
||||||
|
stateCode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int? stateCode;
|
||||||
|
bool isDetailLoading = false;
|
||||||
|
bool nextPage = false;
|
||||||
|
bool isNextPageLoading = false;
|
||||||
|
bool isLoading = false;
|
||||||
|
|
||||||
|
Future<EquipmentInternalAuditDataModel?> getEquipmentInternalAuditById(int id) async {
|
||||||
|
try {
|
||||||
|
isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
Response response = await ApiManager.instance.get(
|
||||||
|
"${URLs.getInternalAuditEquipmentById}?internalAuditEquipmentId=$id",
|
||||||
|
);
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
final decodedBody = jsonDecode(response.body);
|
||||||
|
EquipmentInternalAuditDataModel model = EquipmentInternalAuditDataModel.fromJson(decodedBody["data"]);
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return model;
|
||||||
|
} else {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SystemInternalAuditDataModel?> getInternalSystemAuditById(int id) async {
|
||||||
|
try {
|
||||||
|
isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
Response response = await ApiManager.instance.get("${URLs.getInternalAuditSystemById}?AuditSystemId=$id");
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
final decodedBody = jsonDecode(response.body);
|
||||||
|
SystemInternalAuditDataModel model = SystemInternalAuditDataModel.fromJson(decodedBody["data"]);
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return model;
|
||||||
|
} else {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SystemAuditWorkOrderDetailModel?> loadAllWorkOrderDetailsByID({required int workOrderTypeId, required int workOrderId}) async {
|
||||||
|
try {
|
||||||
|
isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
Response response = await ApiManager.instance.get("${URLs.loadAllWorkOrderDetailsByID}?workOrderTypeId=$workOrderTypeId&workOrderId=$workOrderId");
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
final decodedBody = jsonDecode(response.body);
|
||||||
|
SystemAuditWorkOrderDetailModel model = SystemAuditWorkOrderDetailModel.fromJson(decodedBody["data"]);
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return model;
|
||||||
|
} else {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> updateEquipmentInternalAudit({required AuditFormModel model}) async {
|
||||||
|
isLoading = true;
|
||||||
|
Response response;
|
||||||
|
try {
|
||||||
|
response = await ApiManager.instance.put(URLs.updateAuditEquipmentsEngineer, body: model.toJson());
|
||||||
|
stateCode = response.statusCode;
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
if (stateCode == 200) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
isLoading = false;
|
||||||
|
stateCode = -1;
|
||||||
|
notifyListeners();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> updateSystemInternalAudit({required AuditFormModel model}) async {
|
||||||
|
isLoading = true;
|
||||||
|
Response response;
|
||||||
|
try {
|
||||||
|
response = await ApiManager.instance.put(URLs.updateAuditSystemEngineer, body: model.toJson());
|
||||||
|
stateCode = response.statusCode;
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
log('status code ${stateCode}');
|
||||||
|
if (stateCode == 200) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
isLoading = false;
|
||||||
|
stateCode = -1;
|
||||||
|
notifyListeners();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> addEquipmentInternalAudit({
|
||||||
|
required BuildContext context,
|
||||||
|
required EquipmentInternalAuditFormModel request,
|
||||||
|
}) async {
|
||||||
|
bool status = false;
|
||||||
|
Response response;
|
||||||
|
try {
|
||||||
|
response = await ApiManager.instance.post(URLs.addOrUpdateEquipmentInternalAudit, body: request.toJson());
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
status = true;
|
||||||
|
notifyListeners();
|
||||||
|
} else {
|
||||||
|
Fluttertoast.showToast(msg: "${context.translation.failedRequestMessage} :${json.decode(response.body)['message']}");
|
||||||
|
status = false;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
} catch (error) {
|
||||||
|
print(error);
|
||||||
|
status = false;
|
||||||
|
notifyListeners();
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> addSystemInternalAudit({
|
||||||
|
required BuildContext context,
|
||||||
|
required SystemInternalAuditFormModel request,
|
||||||
|
}) async {
|
||||||
|
bool status = false;
|
||||||
|
Response response;
|
||||||
|
try {
|
||||||
|
response = await ApiManager.instance.post(URLs.addOrUpdateInternalAuditSystem, body: request.toJson());
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
status = true;
|
||||||
|
notifyListeners();
|
||||||
|
} else {
|
||||||
|
Fluttertoast.showToast(msg: "${context.translation.failedRequestMessage} :${json.decode(response.body)['message']}");
|
||||||
|
status = false;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
} catch (error) {
|
||||||
|
print(error);
|
||||||
|
status = false;
|
||||||
|
notifyListeners();
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<WoAutoCompleteModel>> getWorkOrderByWoType({String? text, required int? woId}) async {
|
||||||
|
late Response response;
|
||||||
|
try {
|
||||||
|
Response response = await ApiManager.instance.get("${URLs.getWoAutoComplete}?workOrderTypeId=$woId&search=$text");
|
||||||
|
// response = await ApiManager.instance.post(url, body: {"search": text});
|
||||||
|
List<WoAutoCompleteModel> woOrderList = [];
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
List categoriesListJson = json.decode(response.body)["data"];
|
||||||
|
woOrderList = categoriesListJson.map((wo) => WoAutoCompleteModel.fromJson(wo)).toList();
|
||||||
|
}
|
||||||
|
return woOrderList;
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/api_manager.dart';
|
||||||
|
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||||
|
import 'package:test_sa/models/lookup.dart';
|
||||||
|
import 'package:test_sa/providers/loading_list_notifier.dart';
|
||||||
|
|
||||||
|
class InternalAuditWoTypeProvider extends LoadingListNotifier<Lookup> {
|
||||||
|
@override
|
||||||
|
Future getData({int? id}) async {
|
||||||
|
if (loading ?? false) return -2;
|
||||||
|
loading = true;
|
||||||
|
notifyListeners();
|
||||||
|
Response response;
|
||||||
|
try {
|
||||||
|
response = await ApiManager.instance.get(URLs.getInternalAuditWoType);
|
||||||
|
} catch (error) {
|
||||||
|
loading = false;
|
||||||
|
stateCode = -1;
|
||||||
|
notifyListeners();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
stateCode = response.statusCode;
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
List listJson = json.decode(response.body)["data"];
|
||||||
|
items = listJson.map((department) => Lookup.fromJson(department)).toList();
|
||||||
|
}
|
||||||
|
loading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return response.statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||||
|
import 'package:test_sa/views/app_style/sizing.dart';
|
||||||
|
|
||||||
|
class UploadingDialog extends StatelessWidget {
|
||||||
|
const UploadingDialog({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Container(
|
||||||
|
height: 150.toScreenWidth,
|
||||||
|
width: 200.toScreenWidth,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.neutral30,
|
||||||
|
borderRadius: BorderRadius.circular(20.0),
|
||||||
|
boxShadow: [AppStyle.boxShadow],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const CircularProgressIndicator(strokeWidth: 3),
|
||||||
|
16.height,
|
||||||
|
Text(
|
||||||
|
"Uploading...",
|
||||||
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColor.textColor(context), height: 35 / 24, letterSpacing: -0.96),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||