Compare commits
47 Commits
tree_struc
...
access_man
Author | SHA1 | Date | |
---|---|---|---|
4e1c2ab5ce | |||
6d64408360 | |||
9d21b32607 | |||
13ae978908 | |||
820da1ecfb | |||
cf1a21e121 | |||
f5a7441b3c | |||
e4f8924e93 | |||
4b7567a6fe | |||
50ef283b52 | |||
bb959bcc61 | |||
1204563c55 | |||
0cf5053f8b | |||
5b9a6197e6 | |||
21f036388a | |||
753aa29a8a | |||
869a10f92c | |||
e610f7335d | |||
213fa48741 | |||
20ed718c64 | |||
5cce050013 | |||
75d5c7a4ac | |||
cd372402f7 | |||
562bce8c4f | |||
f4cb117464 | |||
de46bc9872 | |||
6efcc6081d | |||
2d0f85bded | |||
350888c9da | |||
3f1fdf4f93 | |||
cb0ebcca37 | |||
1d226742e6 | |||
ab0551b2ce | |||
b177010c73 | |||
8e62177a54 | |||
8e8f810d1e | |||
73d49d5518 | |||
8983d2ce53 | |||
e88be3cf17 | |||
1f5a119c60 | |||
eb686b20cf | |||
f6517575d2 | |||
a3805d62ec | |||
09dc49b630 | |||
1d65617d18 | |||
69abad24b7 | |||
7e9b24a95b |
9
assets/images/Integrations_icon.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg width="102" height="79" viewBox="0 0 102 79" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M30.0189 25.2035C31.8575 24.454 33.8241 24.0683 35.8102 24.0683C36.4974 24.0683 37.0549 23.5116 37.0549 22.8236C37.0549 22.1365 36.4974 21.579 35.8102 21.579C25.9989 21.6851 18.1008 29.6683 18.1008 39.4804C18.1008 49.2925 25.9989 57.2757 35.8102 57.3818C36.4974 57.3818 37.0549 56.8243 37.0549 56.1372C37.0549 55.4492 36.4974 54.8926 35.8102 54.8926C28.4372 54.8917 22.0981 49.6693 20.6865 42.4332C19.275 35.1971 23.1872 27.9748 30.0189 25.2035Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M2.01226 46.0851L6.49248 47.602C7.25822 50.2882 8.3797 52.8602 9.82772 55.2481L7.87568 59.6044C7.66176 60.0808 7.76872 60.64 8.14227 61.0046L15.1499 67.8412C15.5275 68.2099 16.0963 68.2998 16.5703 68.0665L20.8601 65.9475C23.2813 67.3493 25.8978 68.3817 28.6237 69.0105L30.2865 73.4794C30.468 73.9672 30.9331 74.2905 31.4534 74.2905H31.4736L35.8298 74.2151C36.5178 74.2038 37.0656 73.6366 37.0534 72.9494C37.0413 72.2615 36.4749 71.7145 35.7869 71.7258L32.3107 71.7858L30.72 67.5057C30.5701 67.103 30.2241 66.8064 29.8044 66.7205C26.8897 66.1225 24.1014 65.0229 21.5643 63.4696C21.1997 63.2467 20.7467 63.2265 20.3634 63.4153L16.2576 65.4435L10.5027 59.8305L12.3697 55.663C12.5407 55.2814 12.5082 54.8397 12.2838 54.4865C10.6802 51.9656 9.47848 49.2113 8.72246 46.3209C8.61631 45.9158 8.31406 45.5916 7.91701 45.4571L3.64018 44.0099L3.50404 35.9838L7.78331 34.3923C8.18603 34.2432 8.48261 33.8972 8.56931 33.4767C9.16732 30.5612 10.2669 27.7721 11.8203 25.2342C12.0431 24.8695 12.0634 24.4166 11.8738 24.0333L9.84555 19.9266L15.461 14.1726L19.6276 16.0395C20.0093 16.2105 20.4517 16.1789 20.8042 15.9545C23.3251 14.3509 26.0802 13.1492 28.9697 12.3923C29.3749 12.287 29.6998 11.9847 29.8343 11.5877L31.2824 7.31086L35.8315 7.23469C36.5194 7.22335 37.0672 6.65694 37.0559 5.96898C37.0445 5.28184 36.4781 4.73407 35.7901 4.74541L30.3635 4.83779C29.8376 4.8467 29.3741 5.18541 29.2055 5.68294L27.6878 10.164C25.0016 10.9289 22.4305 12.0512 20.0417 13.4992L15.6855 11.5464C15.2098 11.3333 14.6507 11.4394 14.2861 11.813L7.44946 18.8189C7.07995 19.1973 6.99001 19.7662 7.22338 20.2402L9.34316 24.53C7.94132 26.952 6.90898 29.5693 6.27937 32.296L1.8113 33.9572C1.3162 34.1403 0.991265 34.6168 1.00018 35.1451L1.16629 44.928C1.17521 45.4539 1.51392 45.9166 2.01226 46.0851Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M38.3125 38.2357V40.725H87.9741C88.6159 44.0513 91.6805 46.3453 95.0522 46.0228C98.4247 45.7003 101 42.8683 101 39.4804C101 36.0925 98.4247 33.2604 95.0522 32.9379C91.6805 32.6154 88.6159 34.9094 87.9741 38.2357H38.3125ZM91.5371 36.5973C93.1293 35.005 95.711 35.005 97.3033 36.5965C98.8947 38.1887 98.8947 40.7704 97.3033 42.3627C95.711 43.9549 93.1293 43.9549 91.5371 42.3627C89.9472 40.7696 89.9472 38.1904 91.5371 36.5973Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M77.402 15.239C74.2555 15.239 71.5515 17.4714 70.9535 20.5603H60.4664C56.7876 20.5644 53.8057 23.5463 53.8016 27.2252C53.7992 29.5305 51.9306 31.3983 49.6261 31.4015H38.3125V33.8908H49.6261C53.3049 33.8867 56.2869 30.9048 56.2909 27.2252C56.2933 24.9206 58.1611 23.052 60.4664 23.0496H70.9535C71.6099 26.4513 74.7968 28.7583 78.2334 28.3191C81.6707 27.8799 84.1746 24.8469 83.955 21.3885C83.7354 17.9309 80.8669 15.239 77.402 15.239ZM80.2851 24.6881C78.6936 26.2779 76.1136 26.2771 74.5229 24.6856C72.9323 23.0942 72.9323 20.515 74.5229 18.9235C76.1136 17.3321 78.6936 17.3312 80.2851 18.9211C81.8757 20.515 81.8757 23.095 80.2851 24.6881Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M77.402 63.7217C80.8661 63.7209 83.733 61.0291 83.9525 57.5723C84.1721 54.1163 81.6683 51.0833 78.2326 50.6441C74.7968 50.2049 71.6107 52.5102 70.9535 55.9111H60.4664C58.1611 55.9087 56.2933 54.0401 56.2909 51.7356C56.2869 48.0559 53.3049 45.0748 49.6261 45.0699H38.3125V47.5592H49.6261C51.9306 47.5625 53.7992 49.4302 53.8016 51.7356C53.8057 55.4144 56.7876 58.3963 60.4664 58.4004H70.9535C71.5515 61.4901 74.2555 63.7217 77.402 63.7217ZM74.5189 54.2727C76.1112 52.6804 78.692 52.6804 80.2843 54.2727C81.8765 55.8649 81.8765 58.4466 80.2843 60.0389C78.692 61.6311 76.1112 61.6311 74.5189 60.0389C73.7515 59.2755 73.3196 58.2375 73.3196 57.1558C73.3204 56.0732 73.7515 55.036 74.5189 54.2727Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M61.3829 66.7604C60.3028 67.8349 59.6334 69.2537 59.49 70.7706H58.0793C55.7472 70.7641 53.6623 69.3129 52.8463 67.1274L49.3206 57.6087C48.14 54.4493 45.1256 52.3514 41.7523 52.3417H38.3125V54.831H41.7523C44.0852 54.8374 46.1701 56.2887 46.9861 58.4733L50.5126 67.9921C51.6932 71.1523 54.7076 73.2502 58.0801 73.2599H59.7266C60.5613 76.0976 63.1899 78.027 66.1475 77.9727C69.1052 77.9192 71.6617 75.895 72.3926 73.0281C73.1235 70.1621 71.8489 67.1607 69.2778 65.6972C66.7075 64.2338 63.4759 64.669 61.3837 66.7604H61.3829ZM70.1035 71.4035C70.1027 73.3466 68.7292 75.0191 66.8233 75.3975C64.9175 75.7759 63.0092 74.7557 62.2653 72.9609C61.5223 71.1652 62.1503 69.0949 63.766 68.0147C65.381 66.9346 67.5348 67.1461 68.9091 68.5196C69.6765 69.2829 70.1067 70.3209 70.1035 71.4035Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M49.3206 21.352L52.8463 11.8333C53.6623 9.64788 55.7464 8.19661 58.0793 8.19012H59.49C59.819 11.5489 62.6454 14.1078 66.0203 14.1022C69.3945 14.0973 72.2127 11.5286 72.5304 8.16906C72.8488 4.8095 70.5629 1.75867 67.2488 1.11934C63.9354 0.480809 60.6787 2.46445 59.7258 5.70084H58.0793C54.7067 5.71057 51.6916 7.80847 50.5118 10.9687L46.9853 20.4874C46.1693 22.672 44.0852 24.1225 41.7523 24.1298H38.3125V26.6191H41.7523C45.1256 26.6093 48.14 24.5114 49.3206 21.352ZM63.1437 4.67418C64.5188 3.29827 66.6743 3.08677 68.2908 4.16692C69.9082 5.24788 70.537 7.31985 69.7923 9.11631C69.0485 10.9136 67.1386 11.9346 65.2311 11.5554C63.3236 11.1761 61.9493 9.50202 61.9493 7.55727C61.9477 6.4755 62.378 5.4383 63.1437 4.67499V4.67418Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 5.8 KiB |
3
assets/images/Password_invisible.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="17" height="13" viewBox="0 0 17 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.1551 5.18632C15.4564 4.85579 15.6902 4.49821 15.8445 4.11887C15.9791 3.78757 15.7945 3.41924 15.4321 3.29613C15.0698 3.17301 14.6666 3.34169 14.5319 3.67299C14.2555 4.35274 13.533 4.98462 12.4974 5.45235C11.3781 5.95792 9.9584 6.23632 8.50002 6.23632C7.04165 6.23632 5.62204 5.95792 4.50267 5.45235C3.4671 4.98466 2.74455 4.35278 2.46811 3.67307C2.33341 3.34182 1.93051 3.17309 1.56797 3.29625C1.20557 3.41937 1.02099 3.78774 1.15569 4.11904C1.30993 4.49834 1.54371 4.85587 1.84496 5.18636L0.205067 6.68523C-0.0683557 6.93514 -0.0683557 7.34028 0.205067 7.59019C0.341779 7.71514 0.520918 7.77759 0.700103 7.77759C0.879288 7.77759 1.05847 7.71514 1.19514 7.59019L2.87865 6.05143C3.33414 6.34903 3.85618 6.60866 4.43008 6.82324L3.77375 8.88066C3.66551 9.21994 3.87875 9.57517 4.24993 9.6741C4.31534 9.69155 4.38129 9.69982 4.44619 9.69982C4.74941 9.69982 5.02883 9.51835 5.11798 9.23888L5.76259 7.21815C6.40253 7.36311 7.08297 7.45824 7.78919 7.49663V9.61595C7.78919 9.96935 8.10266 10.2559 8.48931 10.2559C8.87597 10.2559 9.18944 9.96935 9.18944 9.61595V7.49787C9.90329 7.46024 10.5911 7.3646 11.2375 7.21819L11.8821 9.23892C11.9713 9.51839 12.2507 9.69987 12.5539 9.69987C12.6188 9.69987 12.6848 9.69155 12.7502 9.67414C13.1214 9.57521 13.3346 9.21998 13.2263 8.8807L12.57 6.82328C13.1438 6.6087 13.6659 6.34907 14.1214 6.05147L15.8049 7.59023C15.9416 7.71518 16.1207 7.77764 16.2999 7.77764C16.479 7.77764 16.6582 7.71514 16.7949 7.59023C17.0684 7.34033 17.0684 6.93518 16.7949 6.68528L15.1551 5.18632Z" fill="#D5D5D5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
3
assets/images/access_icon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="104" height="124" viewBox="0 0 104 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M51.995 36.999C45.1 36.999 39.495 42.604 39.495 49.499C39.495 55.539 43.795 60.589 49.495 61.749V79.499C49.495 80.879 50.615 81.999 51.995 81.999C53.375 81.999 54.495 80.879 54.495 79.499V61.749C60.195 60.589 64.495 55.539 64.495 49.499C64.495 42.604 58.89 36.999 51.995 36.999ZM51.995 56.999C47.86 56.999 44.495 53.634 44.495 49.499C44.495 45.364 47.86 41.999 51.995 41.999C56.13 41.999 59.495 45.364 59.495 49.499C59.495 53.634 56.13 56.999 51.995 56.999ZM85.815 13.079L52.78 2.12399C52.27 1.95899 51.715 1.95899 51.21 2.12399L18.18 13.074C8.5 16.289 2 25.289 2 35.484V61.664C2 94.709 37.795 115.174 48.77 120.664C48.77 120.664 50.365 121.994 52.015 121.994C53.665 121.994 55.15 120.924 55.15 120.924C66.14 116.504 102 99.169 102 61.664V35.484C102 25.284 95.495 16.279 85.82 13.074L85.815 13.079ZM96.995 61.674C96.995 96.044 63.535 112.169 53.275 116.294L52.12 116.764L51 116.204C40.68 111.044 6.995 91.859 6.995 61.679V35.499C6.995 27.464 12.12 20.364 19.75 17.834L51.995 7.13899L84.24 17.834C91.865 20.364 96.995 27.464 96.995 35.499V61.674Z" fill="white" stroke="white" stroke-width="3"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
32
assets/images/asset_icon.svg
Normal file
@ -0,0 +1,32 @@
|
||||
<svg width="102" height="112" viewBox="0 0 102 112" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M99.1482 107.173H96.0618V10.2594C96.0618 9.23635 95.2326 8.40755 94.21 8.40755H59.642C58.6192 8.40755 57.7902 9.23635 57.7902 10.2594V18.284H54.0866V10.2594C54.0866 9.23635 53.2574 8.40755 52.2348 8.40755H44.21V2.85193C44.21 1.82892 43.3808 1.00012 42.3582 1.00012C41.3356 1.00012 40.5064 1.82892 40.5064 2.85193V8.40755H32.4817C31.4589 8.40755 30.6299 9.23635 30.6299 10.2594V18.284H25.0743C24.0515 18.284 23.2225 19.1128 23.2225 20.1358V35.5678H7.79045C6.76765 35.5678 5.93864 36.3966 5.93864 37.4197V107.173H2.8518C1.82901 107.173 1 108.002 1 109.025C1 110.048 1.82922 110.877 2.8518 110.877H99.1482C100.171 110.877 101 110.048 101 109.025C101 108.002 100.171 107.173 99.1482 107.173ZM34.3333 12.1112H50.3826V18.284H34.3333V12.1112ZM28.1605 107.173H26.9259V104.087C26.9259 103.064 26.0967 102.235 25.0741 102.235C24.0515 102.235 23.2223 103.064 23.2223 104.087V107.173H21.9877V101H28.1605V107.173ZM40.5062 59.6419V62.8277V107.173H31.8641V99.1483C31.8641 98.1253 31.0349 97.2965 30.0123 97.2965H20.1359C19.1131 97.2965 18.2841 98.1253 18.2841 99.1483V107.173H9.64204V39.2717H40.5062V59.6419ZM42.358 35.5681H26.9259V21.9878H57.79V57.7901H44.21V51.6173H49.7656C50.7884 51.6173 51.6174 50.7885 51.6174 49.7655C51.6174 48.7425 50.7882 47.9137 49.7656 47.9137H44.21V41.7409H49.7656C50.7884 41.7409 51.6174 40.9121 51.6174 39.8891C51.6174 38.8661 50.7882 38.0373 49.7656 38.0373H44.21V37.4201C44.21 36.3969 43.3808 35.5681 42.358 35.5681ZM75.0741 107.173H44.21V62.8277V61.494H75.0741V107.173ZM92.358 107.173H78.7777V59.6419V57.1727C78.7777 56.1497 77.9485 55.3209 76.9259 55.3209C75.9033 55.3209 75.0741 56.1497 75.0741 57.1727V57.7899H71.3705V57.1727C71.3705 56.1497 70.5413 55.3209 69.5187 55.3209C68.4961 55.3209 67.6669 56.1497 67.6669 57.1727V57.7899H61.4941V20.136V12.1112H92.3582V107.173H92.358Z" fill="white" stroke="white"/>
|
||||
<path d="M52.2346 103.47C53.2574 103.47 54.0864 102.64 54.0864 101.618V67.0501C54.0864 66.027 53.2572 65.1982 52.2346 65.1982C51.212 65.1982 50.3828 66.027 50.3828 67.0501V101.618C50.3828 102.641 51.2118 103.47 52.2346 103.47Z" fill="white" stroke="white"/>
|
||||
<path d="M59.6421 103.47C60.6649 103.47 61.4939 102.64 61.4939 101.618V67.0501C61.4939 66.027 60.6647 65.1982 59.6421 65.1982C58.6195 65.1982 57.7903 66.027 57.7903 67.0501V101.618C57.7901 102.641 58.6193 103.47 59.6421 103.47Z" fill="white" stroke="white"/>
|
||||
<path d="M67.0493 103.47C68.0721 103.47 68.9011 102.64 68.9011 101.618V67.0501C68.9011 66.027 68.0719 65.1982 67.0493 65.1982C66.0267 65.1982 65.1975 66.027 65.1975 67.0501V101.618C65.1975 102.641 66.0267 103.47 67.0493 103.47Z" fill="white" stroke="white"/>
|
||||
<path d="M69.5186 24.4568C70.5413 24.4568 71.3704 23.628 71.3704 22.605V20.136C71.3704 19.113 70.5411 18.2842 69.5186 18.2842C68.496 18.2842 67.6667 19.113 67.6667 20.136V22.6052C67.6667 23.628 68.4958 24.4568 69.5186 24.4568Z" fill="white" stroke="white"/>
|
||||
<path d="M76.926 24.4568C77.9488 24.4568 78.7778 23.628 78.7778 22.605V20.136C78.7778 19.113 77.9486 18.2842 76.926 18.2842C75.9034 18.2842 75.0742 19.113 75.0742 20.136V22.6052C75.0742 23.628 75.9032 24.4568 76.926 24.4568Z" fill="white" stroke="white"/>
|
||||
<path d="M84.3335 24.4568C85.3563 24.4568 86.1853 23.628 86.1853 22.605V20.136C86.1853 19.113 85.3561 18.2842 84.3335 18.2842C83.3109 18.2842 82.4817 19.113 82.4817 20.136V22.6052C82.4817 23.628 83.3107 24.4568 84.3335 24.4568Z" fill="white" stroke="white"/>
|
||||
<path d="M69.5186 36.8025C70.5413 36.8025 71.3704 35.9737 71.3704 34.9507V32.4814C71.3704 31.4584 70.5411 30.6296 69.5186 30.6296C68.496 30.6296 67.6667 31.4584 67.6667 32.4814V34.9507C67.6667 35.9737 68.4958 36.8025 69.5186 36.8025Z" fill="white" stroke="white"/>
|
||||
<path d="M76.926 36.8025C77.9488 36.8025 78.7778 35.9737 78.7778 34.9507V32.4814C78.7778 31.4584 77.9486 30.6296 76.926 30.6296C75.9034 30.6296 75.0742 31.4584 75.0742 32.4814V34.9507C75.0742 35.9737 75.9032 36.8025 76.926 36.8025Z" fill="white" stroke="white"/>
|
||||
<path d="M84.3335 36.8025C85.3563 36.8025 86.1853 35.9737 86.1853 34.9507V32.4814C86.1853 31.4584 85.3561 30.6296 84.3335 30.6296C83.3109 30.6296 82.4817 31.4584 82.4817 32.4814V34.9507C82.4817 35.9737 83.3107 36.8025 84.3335 36.8025Z" fill="white" stroke="white"/>
|
||||
<path d="M69.5186 49.1483C70.5413 49.1483 71.3704 48.3195 71.3704 47.2965V44.8273C71.3704 43.8043 70.5411 42.9755 69.5186 42.9755C68.496 42.9755 67.6667 43.8043 67.6667 44.8273V47.2965C67.6667 48.3193 68.4958 49.1483 69.5186 49.1483Z" fill="white" stroke="white"/>
|
||||
<path d="M76.926 49.1483C77.9488 49.1483 78.7778 48.3195 78.7778 47.2965V44.8273C78.7778 43.8043 77.9486 42.9755 76.926 42.9755C75.9034 42.9755 75.0742 43.8043 75.0742 44.8273V47.2965C75.0742 48.3193 75.9032 49.1483 76.926 49.1483Z" fill="white" stroke="white"/>
|
||||
<path d="M84.3335 49.1483C85.3563 49.1483 86.1853 48.3195 86.1853 47.2965V44.8273C86.1853 43.8043 85.3561 42.9755 84.3335 42.9755C83.3109 42.9755 82.4817 43.8043 82.4817 44.8273V47.2965C82.4817 48.3193 83.3107 49.1483 84.3335 49.1483Z" fill="white" stroke="white"/>
|
||||
<path d="M17.6667 54.0865C18.6895 54.0865 19.5186 53.2577 19.5186 52.2347V49.7655C19.5186 48.7425 18.6893 47.9137 17.6667 47.9137C16.6442 47.9137 15.8149 48.7425 15.8149 49.7655V52.2347C15.8149 53.2575 16.6442 54.0865 17.6667 54.0865Z" fill="white" stroke="white"/>
|
||||
<path d="M25.0737 54.0865C26.0965 54.0865 26.9255 53.2577 26.9255 52.2347V49.7655C26.9255 48.7425 26.0963 47.9137 25.0737 47.9137C24.0511 47.9137 23.2219 48.7425 23.2219 49.7655V52.2347C23.2219 53.2575 24.0509 54.0865 25.0737 54.0865Z" fill="white" stroke="white"/>
|
||||
<path d="M32.4812 54.0865C33.504 54.0865 34.333 53.2577 34.333 52.2347V49.7655C34.333 48.7425 33.5038 47.9137 32.4812 47.9137C31.4586 47.9137 30.6294 48.7425 30.6294 49.7655V52.2347C30.6294 53.2575 31.4584 54.0865 32.4812 54.0865Z" fill="white" stroke="white"/>
|
||||
<path d="M17.6667 66.4322C18.6895 66.4322 19.5186 65.6034 19.5186 64.5804V62.1112C19.5186 61.0882 18.6893 60.2594 17.6667 60.2594C16.6442 60.2594 15.8149 61.0882 15.8149 62.1112V64.5804C15.8149 65.6032 16.6442 66.4322 17.6667 66.4322Z" fill="white" stroke="white"/>
|
||||
<path d="M25.0737 66.4322C26.0965 66.4322 26.9255 65.6034 26.9255 64.5804V62.1112C26.9255 61.0882 26.0963 60.2594 25.0737 60.2594C24.0511 60.2594 23.2219 61.0882 23.2219 62.1112V64.5804C23.2219 65.6032 24.0509 66.4322 25.0737 66.4322Z" fill="white" stroke="white"/>
|
||||
<path d="M32.4812 66.4322C33.504 66.4322 34.333 65.6034 34.333 64.5804V62.1112C34.333 61.0882 33.5038 60.2594 32.4812 60.2594C31.4586 60.2594 30.6294 61.0882 30.6294 62.1112V64.5804C30.6294 65.6032 31.4584 66.4322 32.4812 66.4322Z" fill="white" stroke="white"/>
|
||||
<path d="M17.6667 78.7778C18.6895 78.7778 19.5186 77.949 19.5186 76.926V74.4568C19.5186 73.4338 18.6893 72.605 17.6667 72.605C16.6442 72.605 15.8149 73.4338 15.8149 74.4568V76.926C15.8149 77.949 16.6442 78.7778 17.6667 78.7778Z" fill="white" stroke="white"/>
|
||||
<path d="M25.0737 78.7778C26.0965 78.7778 26.9255 77.949 26.9255 76.926V74.4568C26.9255 73.4338 26.0963 72.605 25.0737 72.605C24.0511 72.605 23.2219 73.4338 23.2219 74.4568V76.926C23.2219 77.949 24.0509 78.7778 25.0737 78.7778Z" fill="white" stroke="white"/>
|
||||
<path d="M32.4812 78.7778C33.504 78.7778 34.333 77.949 34.333 76.926V74.4568C34.333 73.4338 33.5038 72.605 32.4812 72.605C31.4586 72.605 30.6294 73.4338 30.6294 74.4568V76.926C30.6294 77.949 31.4584 78.7778 32.4812 78.7778Z" fill="white" stroke="white"/>
|
||||
<path d="M17.6667 91.1235C18.6895 91.1235 19.5186 90.2947 19.5186 89.2717V86.8025C19.5186 85.7795 18.6893 84.9507 17.6667 84.9507C16.6442 84.9507 15.8149 85.7795 15.8149 86.8025V89.2717C15.8149 90.2947 16.6442 91.1235 17.6667 91.1235Z" fill="white" stroke="white"/>
|
||||
<path d="M25.0737 91.1235C26.0965 91.1235 26.9255 90.2947 26.9255 89.2717V86.8025C26.9255 85.7795 26.0963 84.9507 25.0737 84.9507C24.0511 84.9507 23.2219 85.7795 23.2219 86.8025V89.2717C23.2219 90.2947 24.0509 91.1235 25.0737 91.1235Z" fill="white" stroke="white"/>
|
||||
<path d="M32.4812 91.1235C33.504 91.1235 34.333 90.2947 34.333 89.2717V86.8025C34.333 85.7795 33.5038 84.9507 32.4812 84.9507C31.4586 84.9507 30.6294 85.7795 30.6294 86.8025V89.2717C30.6294 90.2947 31.4584 91.1235 32.4812 91.1235Z" fill="white" stroke="white"/>
|
||||
<path d="M84.3335 61.494C85.3563 61.494 86.1853 60.6652 86.1853 59.6422V57.173C86.1853 56.15 85.3561 55.3212 84.3335 55.3212C83.3109 55.3212 82.4817 56.15 82.4817 57.173V59.6422C82.4817 60.665 83.3107 61.494 84.3335 61.494Z" fill="white" stroke="white"/>
|
||||
<path d="M84.3335 73.8396C85.3563 73.8396 86.1853 73.0108 86.1853 71.9878V69.5186C86.1853 68.4955 85.3561 67.6667 84.3335 67.6667C83.3109 67.6667 82.4817 68.4955 82.4817 69.5186V71.9878C82.4817 73.0106 83.3107 73.8396 84.3335 73.8396Z" fill="white" stroke="white"/>
|
||||
<path d="M84.3335 86.1853C85.3563 86.1853 86.1853 85.3565 86.1853 84.3335V81.8643C86.1853 80.8412 85.3561 80.0125 84.3335 80.0125C83.3109 80.0125 82.4817 80.8412 82.4817 81.8643V84.3335C82.4817 85.3563 83.3107 86.1853 84.3335 86.1853Z" fill="white" stroke="white"/>
|
||||
<path d="M84.3335 98.531C85.3563 98.531 86.1853 97.7022 86.1853 96.6792V94.21C86.1853 93.1869 85.3561 92.3582 84.3335 92.3582C83.3109 92.3582 82.4817 93.1869 82.4817 94.21V96.6792C82.4817 97.702 83.3107 98.531 84.3335 98.531Z" fill="white" stroke="white"/>
|
||||
<path d="M51.6169 30.0124C51.6169 28.9894 50.7876 28.1606 49.7651 28.1606H34.9502C33.9274 28.1606 33.0984 28.9894 33.0984 30.0124C33.0984 31.0355 33.9276 31.8643 34.9502 31.8643H49.7651C50.7878 31.8643 51.6169 31.0355 51.6169 30.0124Z" fill="white" stroke="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 9.3 KiB |
13
assets/images/calendar_icon.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1338_8425)">
|
||||
<path d="M8.70833 0.916667H8.25V0.458333C8.25 0.336776 8.20171 0.220197 8.11576 0.134243C8.0298 0.0482886 7.91322 0 7.79167 0C7.67011 0 7.55353 0.0482886 7.46758 0.134243C7.38162 0.220197 7.33333 0.336776 7.33333 0.458333V0.916667H3.66667V0.458333C3.66667 0.336776 3.61838 0.220197 3.53242 0.134243C3.44647 0.0482886 3.32989 0 3.20833 0C3.08678 0 2.9702 0.0482886 2.88424 0.134243C2.79829 0.220197 2.75 0.336776 2.75 0.458333V0.916667H2.29167C1.6841 0.917394 1.10163 1.15907 0.672017 1.58868C0.242404 2.0183 0.000727768 2.60077 0 3.20833L0 8.70833C0.000727768 9.3159 0.242404 9.89837 0.672017 10.328C1.10163 10.7576 1.6841 10.9993 2.29167 11H8.70833C9.3159 10.9993 9.89837 10.7576 10.328 10.328C10.7576 9.89837 10.9993 9.3159 11 8.70833V3.20833C10.9993 2.60077 10.7576 2.0183 10.328 1.58868C9.89837 1.15907 9.3159 0.917394 8.70833 0.916667ZM0.916667 3.20833C0.916667 2.84366 1.06153 2.49392 1.31939 2.23606C1.57726 1.9782 1.92699 1.83333 2.29167 1.83333H8.70833C9.07301 1.83333 9.42274 1.9782 9.68061 2.23606C9.93847 2.49392 10.0833 2.84366 10.0833 3.20833V3.66667H0.916667V3.20833ZM8.70833 10.0833H2.29167C1.92699 10.0833 1.57726 9.93847 1.31939 9.68061C1.06153 9.42274 0.916667 9.07301 0.916667 8.70833V4.58333H10.0833V8.70833C10.0833 9.07301 9.93847 9.42274 9.68061 9.68061C9.42274 9.93847 9.07301 10.0833 8.70833 10.0833Z" fill="#999999"/>
|
||||
<path d="M5.5 7.5625C5.8797 7.5625 6.1875 7.2547 6.1875 6.875C6.1875 6.4953 5.8797 6.1875 5.5 6.1875C5.1203 6.1875 4.8125 6.4953 4.8125 6.875C4.8125 7.2547 5.1203 7.5625 5.5 7.5625Z" fill="#999999"/>
|
||||
<path d="M3.20825 7.5625C3.58795 7.5625 3.89575 7.2547 3.89575 6.875C3.89575 6.4953 3.58795 6.1875 3.20825 6.1875C2.82856 6.1875 2.52075 6.4953 2.52075 6.875C2.52075 7.2547 2.82856 7.5625 3.20825 7.5625Z" fill="#999999"/>
|
||||
<path d="M7.79175 7.5625C8.17144 7.5625 8.47925 7.2547 8.47925 6.875C8.47925 6.4953 8.17144 6.1875 7.79175 6.1875C7.41205 6.1875 7.10425 6.4953 7.10425 6.875C7.10425 7.2547 7.41205 7.5625 7.79175 7.5625Z" fill="#999999"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1338_8425">
|
||||
<rect width="11" height="11" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
31
assets/images/construction_Icon.svg
Normal file
@ -0,0 +1,31 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28.333 56.6664H31.6663V59.9997H28.333V56.6664Z" fill="white"/>
|
||||
<path d="M34.9998 56.6664H38.3331V59.9997H34.9998V56.6664Z" fill="white"/>
|
||||
<path d="M41.6663 56.6664H44.9996V59.9997H41.6663V56.6664Z" fill="white"/>
|
||||
<path d="M28.333 63.3329H31.6663V66.6662H28.333V63.3329Z" fill="white"/>
|
||||
<path d="M34.9998 63.3329H38.3331V66.6662H34.9998V63.3329Z" fill="white"/>
|
||||
<path d="M41.6663 63.3329H44.9996V66.6662H41.6663V63.3329Z" fill="white"/>
|
||||
<path d="M48.333 63.3329H51.6663V66.6662H48.333V63.3329Z" fill="white"/>
|
||||
<path d="M54.9995 63.3329H58.3328V66.6662H54.9995V63.3329Z" fill="white"/>
|
||||
<path d="M28.333 69.9995H31.6663V73.3328H28.333V69.9995Z" fill="white"/>
|
||||
<path d="M34.9998 69.9995H38.3331V73.3328H34.9998V69.9995Z" fill="white"/>
|
||||
<path d="M41.6663 69.9995H44.9996V73.3328H41.6663V69.9995Z" fill="white"/>
|
||||
<path d="M48.333 69.9995H51.6663V73.3328H48.333V69.9995Z" fill="white"/>
|
||||
<path d="M54.9995 69.9995H58.3328V73.3328H54.9995V69.9995Z" fill="white"/>
|
||||
<path d="M28.333 76.6661H31.6663V79.9994H28.333V76.6661Z" fill="white"/>
|
||||
<path d="M34.9998 76.6661H38.3331V79.9994H34.9998V76.6661Z" fill="white"/>
|
||||
<path d="M41.6663 76.6661H44.9996V79.9994H41.6663V76.6661Z" fill="white"/>
|
||||
<path d="M48.333 76.6661H51.6663V79.9994H48.333V76.6661Z" fill="white"/>
|
||||
<path d="M54.9995 76.6661H58.3328V79.9994H54.9995V76.6661Z" fill="white"/>
|
||||
<path d="M28.333 83.3329H31.6663V86.6662H28.333V83.3329Z" fill="white"/>
|
||||
<path d="M34.9998 83.3329H38.3331V86.6662H34.9998V83.3329Z" fill="white"/>
|
||||
<path d="M41.6663 83.3329H44.9996V86.6662H41.6663V83.3329Z" fill="white"/>
|
||||
<path d="M48.333 83.3329H51.6663V86.6662H48.333V83.3329Z" fill="white"/>
|
||||
<path d="M54.9995 83.3329H58.3328V86.6662H54.9995V83.3329Z" fill="white"/>
|
||||
<path d="M28.333 89.9994H31.6663V93.3327H28.333V89.9994Z" fill="white"/>
|
||||
<path d="M34.9998 89.9994H38.3331V93.3327H34.9998V89.9994Z" fill="white"/>
|
||||
<path d="M41.6663 89.9994H44.9996V93.3327H41.6663V89.9994Z" fill="white"/>
|
||||
<path d="M48.333 89.9994H51.6663V93.3327H48.333V89.9994Z" fill="white"/>
|
||||
<path d="M54.9995 89.9994H58.3328V93.3327H54.9995V89.9994Z" fill="white"/>
|
||||
<path d="M98.3325 5.00008H86.666V1.66677C86.666 0.746372 85.9197 0.00012207 84.9993 0.00012207H68.3328C67.4124 0.00012207 66.6661 0.746372 66.6661 1.66677V5.00008H1.66665C0.74625 5.00008 0 5.74633 0 6.66673V13.3333C0 14.2546 0.74625 15 1.66665 15H6.66661V46.6664C6.66661 47.5876 7.41286 48.3331 8.33327 48.3331C9.25367 48.3331 9.99992 49.0793 9.99992 49.9997C9.99992 50.9209 9.25367 51.6664 8.33327 51.6664C7.41286 51.6664 6.66661 50.9209 6.66661 49.9997H3.33331C3.33819 51.2196 3.79147 52.3955 4.60852 53.3021L0.333656 59.0003L0.354815 59.0166C0.131835 59.2965 0.00732415 59.6424 0 59.9996V66.6662C0 67.1089 0.17578 67.5321 0.488277 67.8446L3.82158 71.1779C4.13408 71.4904 4.55807 71.6662 4.99996 71.6662H11.6666C12.1085 71.6662 12.5324 71.4904 12.8449 71.1779L16.1783 67.8446C16.4908 67.5321 16.6665 67.1089 16.6665 66.6662V59.9996C16.6592 59.6424 16.5347 59.2965 16.3117 59.0166L16.3329 59.0003L12.058 53.3021C13.1713 52.0651 13.5912 50.3529 13.177 48.7416C12.7636 47.1303 11.5705 45.8331 9.99992 45.2854V15H66.6661V16.6667C66.6661 17.5879 67.4124 18.3333 68.3328 18.3333H71.6661V96.666H64.9995V29.9999H61.6662V36.6665H51.6662V33.3332H48.3329V36.6665H38.333V34.9998H34.9997V36.6665H24.9998V31.6665H46.6663V28.3332H24.9998V21.6666H21.6665V96.666H19.0233L11.1783 88.8218C10.5273 88.1708 9.47258 88.1708 8.82154 88.8218L0.976555 96.666H0V99.9993H99.9992V96.666H98.6996C99.8722 95.3859 100.289 93.5841 99.7974 91.9183C99.3066 90.2533 97.9793 88.9659 96.2989 88.5264C97.146 86.4374 96.4917 84.0408 94.6997 82.6728C92.9086 81.3048 90.4232 81.3048 88.6321 82.6728C86.8401 84.0408 86.1858 86.4374 87.033 88.5264C85.3525 88.9659 84.0252 90.2533 83.5345 91.9183C83.0429 93.5841 83.4596 95.3859 84.6323 96.666H81.666V18.3333H84.9993C85.9197 18.3333 86.666 17.5879 86.666 16.6667V15H98.3325C99.2529 15 99.9992 14.2546 99.9992 13.3333V6.66673C99.9992 5.74633 99.2529 5.00008 98.3325 5.00008ZM9.16659 54.9997L11.6666 58.333H4.99996L7.49994 54.9997H9.16659ZM13.3332 65.977L10.9765 68.3329H5.69006L3.33331 65.977V61.6663H13.3332V65.977ZM91.1662 8.33339L86.666 11.3338V8.33339H91.1662ZM69.9994 3.33343H76.666V10H69.9994V3.33343ZM24.9998 8.66704L29.5001 11.6667H20.4995L24.9998 8.66704ZM14.9999 11.3338L10.4996 8.33339H19.5002L14.9999 11.3338ZM30.4994 8.33339H39.5L34.9997 11.3338L30.4994 8.33339ZM44.9996 8.66704L49.4999 11.6667H40.4993L44.9996 8.66704ZM50.4993 8.33339H59.4998L54.9995 11.3338L50.4993 8.33339ZM3.33331 8.33339H4.50029L9.50025 11.6667H3.33331V8.33339ZM60.4992 11.6667L65.4991 8.33339H66.6661V11.6667H60.4992ZM78.3327 91.1664L75.333 86.6661L78.3327 82.1666V91.1664ZM78.3327 51.1667L75.333 46.6664L78.3327 42.1669V51.1667ZM74.9994 52.1668L77.999 56.6663L74.9994 61.1666V52.1668ZM74.9994 41.1668V32.167L77.999 36.6665L74.9994 41.1668ZM74.9994 72.1667L77.999 76.6662L74.9994 81.1664V72.1667ZM78.3327 71.1568L75.333 66.6662L78.3327 62.1668V71.1568ZM75.333 26.6666L78.3327 22.1671V31.1669L75.333 26.6666ZM74.9994 21.1669V18.3333H76.8858L74.9994 21.1669ZM61.6662 39.9998V56.6663H51.6662V39.9998H61.6662ZM48.3329 39.9998V54.3332L44.5 50.545C44.1891 50.2382 43.77 50.0648 43.333 50.0632H38.333V39.9998H48.3329ZM34.9997 39.9998V42.7781H24.9998V39.9998H34.9997ZM9.99992 92.3561L14.3098 96.666H5.69006L9.99992 92.3561ZM24.9998 96.666V46.1114H34.9997V51.7363C34.9997 52.6576 35.746 53.403 36.6664 53.403H42.6478L48.8326 59.5187C49.1435 59.8255 49.5626 59.9988 49.9996 59.9996H61.6662V96.666H24.9998ZM74.9994 92.1665L77.999 96.666H74.9994V92.1665ZM94.9992 94.9993C94.0788 94.9993 93.3326 94.2539 93.3326 93.3327C93.3326 92.4123 94.0788 91.666 94.9992 91.666C95.9196 91.666 96.6659 92.4123 96.6659 93.3327C96.6659 94.2539 95.9196 94.9993 94.9992 94.9993ZM91.6659 84.9994C92.5863 84.9994 93.3326 85.7457 93.3326 86.6661C93.3326 87.5873 92.5863 88.3327 91.6659 88.3327C90.7455 88.3327 89.9993 87.5873 89.9993 86.6661C89.9993 85.7457 90.7455 84.9994 91.6659 84.9994ZM88.3326 91.666C89.253 91.666 89.9993 92.4123 89.9993 93.3327C89.9993 94.2539 89.253 94.9993 88.3326 94.9993C87.4122 94.9993 86.666 94.2539 86.666 93.3327C86.666 92.4123 87.4122 91.666 88.3326 91.666ZM83.3326 15H69.9994V13.3333H78.3327C79.2531 13.3333 79.9993 12.5879 79.9993 11.6667V3.33343H83.3326V15ZM92.1656 11.6667L96.6659 8.66704V11.6667H92.1656Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 6.3 KiB |
5
assets/images/device_note.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.64844 2.51953H6.35149V6.7031H5.64844V2.51953Z" fill="#FFC800"/>
|
||||
<path d="M5.64844 8.82422H6.35149V9.52734H5.64844V8.82422Z" fill="#FFC800"/>
|
||||
<path d="M10.2427 1.75734C9.10945 0.624141 7.60266 0 6 0C4.39734 0 2.89055 0.624141 1.75734 1.75734C0.624141 2.89055 0 4.39734 0 6C0 7.60266 0.624141 9.10945 1.75734 10.2427C2.89055 11.3759 4.39734 12 6 12C7.60266 12 9.10945 11.3759 10.2427 10.2427C11.3759 9.10945 12 7.60266 12 6C12 4.39734 11.3759 2.89055 10.2427 1.75734ZM7.05469 10.2305H4.94531V8.12109H7.05469V10.2305ZM7.05469 7.40625H4.94531V1.81641H7.05469V7.40625Z" fill="#FFC800"/>
|
||||
</svg>
|
After Width: | Height: | Size: 697 B |
3
assets/images/devices_icon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="104" height="93" viewBox="0 0 104 93" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M102 59.5005V74.5005C102 79.0955 98.2617 82.8338 93.6667 82.8338H10.3333C5.73833 82.8338 2 79.0955 2 74.5005V59.5005C2 54.9055 5.73833 51.1672 10.3333 51.1672H13.6667V8.14632C13.6667 4.75757 16.4237 2.00049 19.8125 2.00049C23.2013 2.00049 25.9583 4.75757 25.9583 8.14632V46.5838C25.9583 47.5042 25.2121 48.2505 24.2917 48.2505C23.3713 48.2505 22.625 47.5042 22.625 46.5838V8.14632C22.625 6.59549 21.3633 5.33382 19.8125 5.33382C18.2617 5.33382 17 6.59549 17 8.14632V51.1672H67.764C68.6844 51.1672 69.4306 51.9134 69.4306 52.8338C69.4306 53.7542 68.6844 54.5005 67.764 54.5005H10.3333C7.57646 54.5005 5.33333 56.7434 5.33333 59.5005V74.5005C5.33333 77.2576 7.57646 79.5005 10.3333 79.5005H93.6667C96.4235 79.5005 98.6667 77.2576 98.6667 74.5005V59.5005C98.6667 56.7434 96.4235 54.5005 93.6667 54.5005H79.7083C78.7879 54.5005 78.0417 53.7542 78.0417 52.8338V8.14632C78.0417 4.75757 80.7987 2.00049 84.1875 2.00049C87.5762 2.00049 90.3333 4.75757 90.3333 8.14632V46.5838C90.3333 47.5042 89.5871 48.2505 88.6667 48.2505C87.7462 48.2505 87 47.5042 87 46.5838V8.14632C87 6.59549 85.7383 5.33382 84.1875 5.33382C82.6367 5.33382 81.375 6.59549 81.375 8.14632V51.1672H93.6667C98.2617 51.1672 102 54.9055 102 59.5005ZM17.4167 65.3338C16.4962 65.3338 15.75 66.0801 15.75 67.0005C15.75 67.9209 16.4962 68.6672 17.4167 68.6672H21.861C22.7815 68.6672 23.5277 67.9209 23.5277 67.0005C23.5277 66.0801 22.7815 65.3338 21.861 65.3338H17.4167ZM29.1527 65.3338C28.2323 65.3338 27.486 66.0801 27.486 67.0005C27.486 67.9209 28.2323 68.6672 29.1527 68.6672H40.9583C41.8787 68.6672 42.625 67.9209 42.625 67.0005C42.625 66.0801 41.8787 65.3338 40.9583 65.3338H29.1527ZM26.5833 88.0422H14.9167C13.9962 88.0422 13.25 88.7884 13.25 89.7088C13.25 90.6292 13.9962 91.3755 14.9167 91.3755H26.5833C27.5037 91.3755 28.25 90.6292 28.25 89.7088C28.25 88.7884 27.5037 88.0422 26.5833 88.0422ZM89.0833 88.0422H77.4167C76.4962 88.0422 75.75 88.7884 75.75 89.7088C75.75 90.6292 76.4962 91.3755 77.4167 91.3755H89.0833C90.0037 91.3755 90.75 90.6292 90.75 89.7088C90.75 88.7884 90.0037 88.0422 89.0833 88.0422ZM54.2815 33.2659C56.2308 35.2153 54.8671 38.6005 52.0717 38.6005C49.3087 38.6005 47.8921 35.2357 49.8619 33.2659C50.4523 32.6757 51.2371 32.3505 52.0717 32.3505C53.4754 32.3507 54.2815 33.2659 54.2815 33.2659ZM59.7321 30.1724C60.3831 29.5215 60.3831 28.4661 59.7321 27.8153C55.4981 23.5813 48.6456 23.5811 44.4115 27.8153C43.7604 28.4661 43.7604 29.5213 44.4115 30.1724C45.0623 30.8232 46.1175 30.8232 46.7683 30.1724C49.6996 27.2411 54.4433 27.2409 57.375 30.1724C58.0258 30.8232 59.0813 30.8232 59.7321 30.1724ZM40.5812 23.9851C46.9321 17.6342 57.2108 17.6338 63.5623 23.9851C63.8877 24.3105 64.3142 24.4732 64.7408 24.4732C66.2123 24.4732 66.9723 22.6809 65.9194 21.628C58.2658 13.9747 45.8783 13.9738 38.2244 21.628C37.5733 22.2788 37.5733 23.334 38.2244 23.9851C38.875 24.6361 39.9304 24.6361 40.5812 23.9851Z" fill="white" stroke="white" stroke-width="3"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
23
assets/images/empty_table.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<svg width="100" height="88" viewBox="0 0 100 88" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.7">
|
||||
<path d="M99.9982 31V87.5508H23.9567C22.6831 87.5508 21.6528 86.5186 21.6528 85.2469V23.2094C21.6528 21.9358 22.6831 20.9055 23.9567 20.9055H51.8961C52.5057 20.9055 53.0912 21.1469 53.5238 21.5795L59.9664 28.0221C60.399 28.4547 60.9845 28.6961 61.596 28.6961H97.6943C98.966 28.6963 99.9982 29.7285 99.9982 31Z" fill="#756F6F"/>
|
||||
<path d="M63.2924 39.9286C66.3941 39.9286 68.9086 37.4142 68.9086 34.3124C68.9086 31.2107 66.3941 28.6963 63.2924 28.6963C60.1907 28.6963 57.6763 31.2107 57.6763 34.3124C57.6763 37.4142 60.1907 39.9286 63.2924 39.9286Z" fill="#5B5555"/>
|
||||
<path d="M42.6767 47.5693C43.6907 48.5814 42.9745 50.3158 41.54 50.3158H23.8057C23.0794 50.3158 22.361 50.2795 21.6528 50.2111V23.2094C21.6528 21.9358 22.6831 20.9055 23.9567 20.9055H43.9786C44.9122 23.3219 45.4253 25.9498 45.4253 28.6963C45.4253 34.6661 43.0048 40.0704 39.0933 43.9838L42.6767 47.5693Z" fill="#5B5555"/>
|
||||
<path d="M99.9997 31.0001V87.551H92.3498V31.0001C92.3498 29.7284 91.3195 28.6962 90.0459 28.6962H97.6956C98.9675 28.6964 99.9997 29.7286 99.9997 31.0001Z" fill="#5B5555"/>
|
||||
<path d="M76.5031 58.662L99.9992 87.5516H22.7485C22.0552 87.5516 21.3989 87.2393 20.9614 86.7014L0.520807 61.5688C-0.7034 60.0636 0.367684 57.8118 2.3079 57.8118H74.7158C75.4094 57.8118 76.0658 58.1241 76.5031 58.662Z" fill="#64B9FC"/>
|
||||
<path d="M99.9988 87.5508H90.0915L66.5968 58.6618C66.1581 58.1245 65.5022 57.8107 64.8101 57.8107H74.7154C75.4096 57.8107 76.0654 58.1245 76.5021 58.6618L99.9988 87.5508Z" fill="#31A7FB"/>
|
||||
<path d="M82.504 80.7751H62.1786C61.5502 80.7751 60.9581 80.4815 60.5774 79.9815L55.3685 73.1373C54.8644 72.475 55.3368 71.522 56.169 71.522H76.4944C77.1228 71.522 77.7149 71.8156 78.0956 72.3156L83.3045 79.1599C83.8086 79.822 83.3364 80.7751 82.504 80.7751Z" fill="#F8F3F1"/>
|
||||
<path d="M82.5047 80.7746H75.9655C76.7985 80.7746 77.2713 79.8229 76.7663 79.159L71.5571 72.3161C71.1768 71.8151 70.5852 71.5214 69.9575 71.5214H76.4948C77.1244 71.5214 77.716 71.8151 78.0963 72.3161L83.3054 79.159C83.8084 79.823 83.3377 80.7746 82.5047 80.7746Z" fill="#EFE2DD"/>
|
||||
<path d="M49.999 82.2837H40.7663C39.9329 82.2837 39.2573 81.6081 39.2573 80.7748C39.2573 79.9414 39.9329 79.2658 40.7663 79.2658H49.999C50.8324 79.2658 51.508 79.9414 51.508 80.7748C51.508 81.6081 50.8324 82.2837 49.999 82.2837Z" fill="#F8F3F1"/>
|
||||
<path d="M46.6315 77.2784H42.7126C41.8792 77.2784 41.2036 76.6029 41.2036 75.7695C41.2036 74.9361 41.8792 74.2605 42.7126 74.2605H46.6315C47.4649 74.2605 48.1405 74.9361 48.1405 75.7695C48.1407 76.6029 47.4651 77.2784 46.6315 77.2784Z" fill="#F8F3F1"/>
|
||||
<path d="M45.4271 21.6202C45.4271 9.68008 35.7477 0.000671387 23.8077 0.000671387C11.8787 0.000671387 2.18799 9.69141 2.18799 21.6204C2.18818 33.5605 11.8674 43.2397 23.8075 43.2397H41.5417C42.9755 43.2397 43.6937 41.5062 42.6798 40.4923L39.0949 36.9074C43.0074 32.9953 45.4271 27.5903 45.4271 21.6202Z" fill="#31A7FB"/>
|
||||
<path d="M42.6806 40.4929C43.6946 41.507 42.9784 43.2394 41.5438 43.2394H34.2521C35.6847 43.2394 36.4031 41.507 35.3888 40.4929L33.1658 38.2687C32.4264 37.5289 32.3795 36.348 33.0537 35.5482C36.2251 31.7865 38.1372 26.9256 38.1372 21.6198C38.1372 10.9238 30.3688 2.04051 20.1655 0.308107C21.1528 0.138772 22.1626 0.0379915 23.1901 0.00889019C35.2808 -0.331732 45.2247 9.15764 45.4258 21.2515C45.5276 27.3669 43.0886 32.9154 39.097 36.9072L42.6806 40.4929Z" fill="#1C96F9"/>
|
||||
<path d="M28.074 21.6202L32.0957 17.5986C33.2744 16.4199 33.2744 14.509 32.0957 13.3304C30.9172 12.1517 29.0061 12.1517 27.8276 13.3304L23.8059 17.3521L19.7845 13.3303C18.606 12.1516 16.6949 12.1516 15.5163 13.3303C14.3376 14.5088 14.3376 16.4197 15.5163 17.5984L19.538 21.62L15.5163 25.6417C14.3376 26.8204 14.3376 28.7313 15.5163 29.9098C16.1056 30.4992 16.8781 30.7938 17.6505 30.7938C18.423 30.7938 19.1952 30.4991 19.7847 29.9098L23.8063 25.8882L27.828 29.9098C28.4174 30.4992 29.1895 30.7938 29.9621 30.7938C30.7344 30.7938 31.5068 30.4991 32.0963 29.9098C33.275 28.7313 33.275 26.8204 32.0963 25.6417L28.074 21.6202Z" fill="#F8F3F1"/>
|
||||
<path d="M63.2939 35.1746C66.3956 35.1746 68.9101 32.6601 68.9101 29.5584C68.9101 26.4567 66.3956 23.9423 63.2939 23.9423C60.1922 23.9423 57.6777 26.4567 57.6777 29.5584C57.6777 32.6601 60.1922 35.1746 63.2939 35.1746Z" fill="#BCEA73"/>
|
||||
<path d="M68.9087 29.5576C68.9087 32.6605 66.3935 35.1758 63.2931 35.1758C62.2537 35.1758 61.2785 34.8933 60.4443 34.398C62.649 33.8898 64.2916 31.9162 64.2916 29.5576C64.2916 27.2016 62.6515 25.2255 60.4469 24.7173C61.281 24.2245 62.2537 23.9423 63.2931 23.9423C66.3937 23.9421 68.9087 26.4573 68.9087 29.5576Z" fill="#99D53B"/>
|
||||
<path d="M82.9628 22.8247C85.4105 22.8247 87.3947 20.8404 87.3947 18.3927C87.3947 15.945 85.4105 13.9607 82.9628 13.9607C80.515 13.9607 78.5308 15.945 78.5308 18.3927C78.5308 20.8404 80.515 22.8247 82.9628 22.8247Z" fill="#BCEA73"/>
|
||||
<path d="M87.3925 18.3923C87.3925 20.8409 85.4078 22.8257 82.9611 22.8257C82.1408 22.8257 81.3713 22.6028 80.7129 22.212C82.4527 21.811 83.7488 20.2534 83.7488 18.3923C83.7488 16.5329 82.4547 14.9736 80.7148 14.5724C81.373 14.1835 82.1406 13.9607 82.9609 13.9607C85.4078 13.9607 87.3925 15.9456 87.3925 18.3923Z" fill="#99D53B"/>
|
||||
<path d="M72.8344 10.292L73.6508 9.4756C74.2403 8.88615 74.2403 7.93089 73.6508 7.34144C73.0616 6.75219 72.1061 6.75219 71.5167 7.34144L70.7003 8.15784L69.8839 7.34144C69.2946 6.75199 68.3392 6.75199 67.7497 7.34144C67.1603 7.93069 67.1603 8.88615 67.7497 9.4756L68.5661 10.292L67.7497 11.1084C67.1603 11.6978 67.1603 12.6531 67.7497 13.2426C68.0444 13.5373 68.4306 13.6845 68.8167 13.6845C69.2028 13.6845 69.5891 13.5371 69.8837 13.2426L70.7001 12.4262L71.5165 13.2426C71.8112 13.5373 72.1973 13.6845 72.5835 13.6845C72.9696 13.6845 73.3559 13.5371 73.6504 13.2426C74.2399 12.6533 74.2399 11.6978 73.6504 11.1084L72.8344 10.292Z" fill="#64B9FC"/>
|
||||
<path d="M11.2994 44.5503C10.7102 43.961 9.75473 43.961 9.16528 44.5503L7.74381 45.9718L6.32234 44.5503C5.73309 43.961 4.77763 43.961 4.18818 44.5503C3.59873 45.1395 3.59873 46.095 4.18818 46.6844L5.60965 48.1059L4.18818 49.5274C3.59873 50.1166 3.59873 51.0721 4.18818 51.6615C4.4829 51.9563 4.86903 52.1035 5.25516 52.1035C5.64129 52.1035 6.02762 51.9561 6.32214 51.6615L7.74362 50.2401L9.16509 51.6615C9.45981 51.9563 9.84594 52.1035 10.2321 52.1035C10.6182 52.1035 11.0045 51.9561 11.2991 51.6615C11.8885 51.0723 11.8885 50.1168 11.2991 49.5274L9.87758 48.1059L11.2991 46.6844C11.8887 46.095 11.8887 45.1395 11.2994 44.5503Z" fill="#A8E7EF"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.4 KiB |
11
assets/images/energy_icon.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg width="102" height="102" viewBox="0 0 102 102" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M51.0001 13.5001C49.8501 13.5001 48.9167 12.5668 48.9167 11.4168V3.08346C48.9167 1.93346 49.8501 1.00012 51.0001 1.00012C52.1501 1.00012 53.0834 1.93346 53.0834 3.08346V11.4168C53.0834 12.5668 52.1501 13.5001 51.0001 13.5001Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M78.9916 25.0917C78.4583 25.0917 77.9249 24.8875 77.5166 24.4834C76.7041 23.6709 76.7041 22.35 77.5166 21.5375L83.4083 15.6459C84.2208 14.8334 85.5416 14.8334 86.3541 15.6459C87.1666 16.4584 87.1666 17.7792 86.3541 18.5917L80.4624 24.4834C80.0583 24.8875 79.5249 25.0917 78.9916 25.0917Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M98.9167 53.0835H90.5833C89.4333 53.0835 88.5 52.1502 88.5 51.0002C88.5 49.8502 89.4333 48.9169 90.5833 48.9169H98.9167C100.067 48.9169 101 49.8502 101 51.0002C101 52.1502 100.067 53.0835 98.9167 53.0835Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M84.8833 86.9667C84.3499 86.9667 83.8166 86.7625 83.4083 86.3584L77.5166 80.4667C76.7041 79.6542 76.7041 78.3334 77.5166 77.5209C78.3291 76.7084 79.6499 76.7084 80.4624 77.5209L86.3541 83.4125C87.1666 84.225 87.1666 85.5459 86.3541 86.3584C85.9499 86.7625 85.4166 86.9667 84.8833 86.9667Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M51.0001 101C49.8501 101 48.9167 100.067 48.9167 98.9168V90.5835C48.9167 89.4335 49.8501 88.5001 51.0001 88.5001C52.1501 88.5001 53.0834 89.4335 53.0834 90.5835V98.9168C53.0834 100.067 52.1501 101 51.0001 101Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M17.1166 86.9667C16.5833 86.9667 16.0499 86.7625 15.6416 86.3584C14.8291 85.5459 14.8291 84.225 15.6416 83.4125L21.5333 77.5209C22.3458 76.7084 23.6666 76.7084 24.4791 77.5209C25.2916 78.3334 25.2916 79.6542 24.4791 80.4667L18.5874 86.3584C18.1833 86.7625 17.6499 86.9667 17.1166 86.9667Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M11.4167 53.0835H3.08333C1.93333 53.0835 1 52.1502 1 51.0002C1 49.8502 1.93333 48.9169 3.08333 48.9169H11.4167C12.5667 48.9169 13.5 49.8502 13.5 51.0002C13.5 52.1502 12.5667 53.0835 11.4167 53.0835Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M23.0083 25.0917C22.4749 25.0917 21.9416 24.8875 21.5333 24.4834L15.6458 18.5917C14.8333 17.7792 14.8333 16.4584 15.6458 15.6459C16.4583 14.8334 17.7791 14.8334 18.5916 15.6459L24.4833 21.5375C25.2958 22.35 25.2958 23.6709 24.4833 24.4834C24.0749 24.8875 23.5416 25.0917 23.0083 25.0917Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M45.0542 84.3335C44.8084 84.3335 44.5667 84.2919 44.3251 84.2044C43.4126 83.8627 42.8584 82.9377 42.9876 81.9794L46.5417 55.1669H32.2501C31.4626 55.1669 30.7417 54.721 30.3876 54.0169C30.0334 53.3127 30.1084 52.4669 30.5834 51.8335L55.5834 18.5002C56.1626 17.7252 57.1917 17.4502 58.0709 17.8335C58.9542 18.2127 59.4667 19.1419 59.3084 20.0919L55.5417 42.6669H69.7501C70.5167 42.6669 71.2209 43.0877 71.5834 43.7627C71.9459 44.4377 71.9084 45.2544 71.4917 45.9002L46.7959 83.4002C46.4001 83.996 45.7376 84.3335 45.0542 84.3335ZM36.4167 51.0002H48.9167C49.5209 51.0002 50.0876 51.2627 50.4834 51.7127C50.8792 52.1585 51.0626 52.7627 50.9834 53.3585L48.3126 73.5169L65.8834 46.8335H53.0834C52.4709 46.8335 51.8917 46.5669 51.4917 46.096C51.0959 45.6294 50.9251 45.0085 51.0292 44.4085L53.7917 27.8335L36.4167 51.0002Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
4
assets/images/facebook.svg
Normal file
@ -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="M24 12C24 17.9897 19.6116 22.9542 13.875 23.8542V15.4688H16.6711L17.2031 12H13.875V9.74906C13.875 8.79984 14.34 7.875 15.8306 7.875H17.3438V4.92188C17.3438 4.92188 15.9703 4.6875 14.6573 4.6875C11.9166 4.6875 10.125 6.34875 10.125 9.35625V12H7.07812V15.4688H10.125V23.8542C4.38844 22.9542 0 17.9897 0 12C0 5.37281 5.37281 0 12 0C18.6272 0 24 5.37281 24 12Z" fill="#1877F2"/>
|
||||
<path d="M16.6711 15.4688L17.2031 12H13.875V9.74902C13.875 8.80003 14.3399 7.875 15.8306 7.875H17.3438V4.92188C17.3438 4.92188 15.9705 4.6875 14.6576 4.6875C11.9165 4.6875 10.125 6.34875 10.125 9.35625V12H7.07812V15.4688H10.125V23.8542C10.736 23.95 11.3621 24 12 24C12.6379 24 13.264 23.95 13.875 23.8542V15.4688H16.6711Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 825 B |
8
assets/images/google.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23.0938 9.91351L13.3045 9.91304C12.8722 9.91304 12.5218 10.2634 12.5218 10.6957V13.8229C12.5218 14.2551 12.8722 14.6055 13.3044 14.6055H18.8172C18.2135 16.1721 17.0869 17.4841 15.6494 18.3177L18 22.3869C21.7707 20.2061 24 16.3798 24 12.0965C24 11.4866 23.955 11.0506 23.8651 10.5597C23.7968 10.1867 23.473 9.91351 23.0938 9.91351Z" fill="#167EE6"/>
|
||||
<path d="M11.9999 19.3043C9.30209 19.3043 6.94691 17.8303 5.68199 15.649L1.61298 17.9944C3.68367 21.5832 7.56275 23.9999 11.9999 23.9999C14.1767 23.9999 16.2306 23.4138 17.9999 22.3925V22.3869L15.6493 18.3177C14.5741 18.9413 13.3298 19.3043 11.9999 19.3043Z" fill="#12B347"/>
|
||||
<path d="M17.9999 22.3925V22.3869L15.6493 18.3177C14.5741 18.9413 13.3299 19.3043 12 19.3043V23.9999C14.1767 23.9999 16.2307 23.4138 17.9999 22.3925Z" fill="#0F993E"/>
|
||||
<path d="M4.69564 12C4.69564 10.6702 5.05854 9.42607 5.68203 8.3509L1.61301 6.00557C0.586029 7.76933 0 9.81767 0 12C0 14.1823 0.586029 16.2306 1.61301 17.9944L5.68203 15.649C5.05854 14.5739 4.69564 13.3298 4.69564 12Z" fill="#FFD500"/>
|
||||
<path d="M11.9999 4.69564C13.7592 4.69564 15.3751 5.32076 16.6373 6.36059C16.9487 6.61709 17.4013 6.59857 17.6865 6.31334L19.9023 4.09756C20.2259 3.77394 20.2029 3.24421 19.8572 2.9443C17.7424 1.10967 14.9909 0 11.9999 0C7.56275 0 3.68367 2.41673 1.61298 6.00556L5.68199 8.35089C6.94691 6.16967 9.30209 4.69564 11.9999 4.69564Z" fill="#FF4B26"/>
|
||||
<path d="M16.6374 6.36059C16.9488 6.61709 17.4014 6.59857 17.6866 6.31334L19.9023 4.09756C20.2259 3.77394 20.2029 3.24421 19.8572 2.9443C17.7424 1.10962 14.9909 0 12 0V4.69564C13.7592 4.69564 15.3752 5.32076 16.6374 6.36059Z" fill="#D93F21"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/lift_line.png
Normal file
After Width: | Height: | Size: 179 B |
5
assets/images/movein_icon.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="104" height="113" viewBox="0 0 104 113" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M34.4818 61.7591C33.5955 62.6454 33.5955 64.0864 34.4818 64.9728C34.9274 65.4136 35.509 65.6365 36.0909 65.6365C36.6728 65.6365 37.2545 65.4138 37.6956 64.9728L51.3319 51.3364C51.5409 51.1274 51.7093 50.8728 51.8228 50.5956C51.8865 50.4364 51.8955 50.2728 51.9228 50.1046C51.9456 49.9774 52.0001 49.8592 52.0001 49.7273C52.0001 49.5545 51.9364 49.4 51.9002 49.2409C51.8729 49.1181 51.8729 48.9862 51.8228 48.8682C51.7046 48.5818 51.532 48.3272 51.3183 48.109L37.6956 34.4863C36.8092 33.5999 35.3682 33.5999 34.4818 34.4863C33.5955 35.3727 33.5955 36.8137 34.4818 37.7L44.2318 47.4545H4.2728C3.01825 47.4545 2 48.4728 2 49.7273C2 50.9818 3.01825 52.0001 4.2728 52.0001H44.241L34.4818 61.7591Z" fill="white" stroke="white" stroke-width="3"/>
|
||||
<path d="M92.1865 2C91.8364 2 91.4955 2.06818 91.1546 2.10462C90.982 2.06371 90.8183 2 90.6364 2H36.0909C29.8273 2 24.7273 7.1 24.7273 13.3636V36.0909C24.7273 37.3455 25.7456 38.3637 27.0001 38.3637C28.2547 38.3637 29.2729 37.3455 29.2729 36.0909V13.3636C29.2729 9.60441 32.3319 6.54539 36.0911 6.54539H79.5639L62.4956 13.859C58.882 15.409 56.5457 18.9498 56.5457 22.8818V92.909H36.0911C32.8228 92.909 30.0457 90.6362 29.3684 89.6953L29.2729 63.3543C29.2684 62.1042 28.2502 61.0907 27.0001 61.0907C27.0001 61.0907 26.9956 61.0907 26.9909 61.0907C25.7364 61.0951 24.7228 62.1179 24.7273 63.3724L24.8319 89.8587C24.7409 90.8542 25.2093 91.895 26.2546 93.0405C28.1136 95.0768 31.8046 97.4542 36.0909 97.4542H56.5455V101.277C56.5455 106.686 60.95 111.091 66.3592 111.091C67.7 111.091 69.0046 110.822 70.2227 110.3L96.05 99.2314C99.6637 97.6813 102 94.1405 102 90.2086V11.8137C102 6.40455 97.5956 2 92.1865 2ZM97.4546 90.2092C97.4546 92.3184 96.2 94.2228 94.2592 95.0501L68.4319 106.118C67.7773 106.405 67.0774 106.545 66.3592 106.545C63.4546 106.545 61.091 104.182 61.091 101.277V22.8818C61.091 20.7726 62.3456 18.8682 64.2864 18.0408L90.1137 6.9728C90.7683 6.68644 91.4682 6.5456 92.1865 6.5456C95.091 6.5456 97.4546 8.90917 97.4546 11.8137V90.2092Z" fill="white" stroke="white" stroke-width="3"/>
|
||||
<path d="M67.9093 52.0001C66.6547 52.0001 65.6365 53.0184 65.6365 54.2729V63.3637C65.6365 64.6182 66.6547 65.6365 67.9093 65.6365C69.1638 65.6365 70.1821 64.6182 70.1821 63.3637V54.2727C70.1819 53.0182 69.1638 52.0001 67.9093 52.0001Z" fill="white" stroke="white" stroke-width="3"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
3
assets/images/password_visible.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="15" height="13" viewBox="0 0 15 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.6809 10.3829C14.394 10.5298 14.0409 10.4175 13.8928 10.1291H13.8928C12.9943 8.37829 10.609 5.86828 7.49997 5.86828C4.71173 5.86828 2.23389 7.93348 1.10719 10.1291C0.958976 10.4164 0.606008 10.5299 0.318459 10.3821C0.0309393 10.2344 -0.0828497 9.88216 0.0644257 9.5944C1.35894 7.06664 4.20568 4.69641 7.49997 4.69641C11.257 4.69641 13.9478 7.6657 14.9355 9.5944C15.083 9.88245 14.969 10.2355 14.6809 10.3829ZM0.227668 4.08996L1.53267 5.26723C1.77437 5.48528 2.14474 5.46346 2.36022 5.22463C2.57695 4.98434 2.55791 4.61385 2.31762 4.39708L1.01262 3.21981C0.772414 3.00311 0.401926 3.02209 0.18507 3.26241C-0.0316681 3.5027 -0.0126251 3.87319 0.227668 4.08996ZM13.4672 5.26723L14.7722 4.08996C15.0125 3.87319 15.0316 3.5027 14.8148 3.26241C14.598 3.02215 14.2276 3.00305 13.9873 3.21981L12.6823 4.39708C12.442 4.61382 12.423 4.98434 12.6397 5.22463C12.8554 5.46372 13.2258 5.48502 13.4672 5.26723ZM7.49994 2.93127C7.82356 2.93127 8.08588 2.66892 8.08588 2.34533V0.585937C8.08588 0.262354 7.82356 0 7.49994 0C7.17633 0 6.914 0.262354 6.914 0.585937V2.34533C6.91403 2.66895 7.17636 2.93127 7.49994 2.93127ZM10.3784 9.86136C10.3784 11.4509 9.0871 12.7441 7.49997 12.7441C5.91284 12.7441 4.62158 11.4509 4.62158 9.86136C4.62158 8.2718 5.91284 6.97857 7.49997 6.97857C9.0871 6.97857 10.3784 8.2718 10.3784 9.86136ZM9.20651 9.86136C9.20651 8.91797 8.44099 8.15045 7.5 8.15045C6.55901 8.15045 5.79346 8.91797 5.79346 9.86136C5.79346 10.8047 6.55899 11.5723 7.49997 11.5723C8.44096 11.5723 9.20651 10.8047 9.20651 9.86136Z" fill="#D5D5D5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/images/right_line.png
Normal file
After Width: | Height: | Size: 188 B |
6
assets/images/spase_management_icon.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="102" height="102" viewBox="0 0 102 102" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M89.8807 70.7131C89.5176 70.3497 89.0137 70.1409 88.5 70.1409C87.9863 70.1409 87.4824 70.3497 87.1191 70.7131C86.7559 71.0766 86.5469 71.5782 86.5469 72.094C86.5469 72.6077 86.7557 73.1116 87.1191 73.4747C87.4824 73.8379 87.9863 74.0471 88.5 74.0471C89.0137 74.0471 89.5176 73.8381 89.8807 73.4747C90.2441 73.1116 90.4531 72.6077 90.4531 72.094C90.4531 71.5803 90.2441 71.0764 89.8807 70.7131Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M99.0469 97.0938H90.4531V78.9298C90.4531 77.8513 89.5785 76.9767 88.5 76.9767C87.4215 76.9767 86.5469 77.8513 86.5469 78.9298V97.0938H24.8281V78.7345C24.8281 77.656 23.9535 76.7813 22.875 76.7813C21.7965 76.7813 20.9219 77.656 20.9219 78.7345V97.0938H15.4531V45.7271L51 17.9757L86.5469 45.7271V64.2813C86.5469 65.3599 87.4215 66.2345 88.5 66.2345C89.5785 66.2345 90.4531 65.3599 90.4531 64.2813V48.7767L90.7945 49.0433C91.1494 49.3204 91.5734 49.4569 91.9957 49.4569C92.498 49.4569 92.9984 49.2638 93.3773 48.8849L100.422 41.8399C100.818 41.4442 101.025 40.8978 100.99 40.3392C100.956 39.7806 100.684 39.2636 100.243 38.9192L88.5 29.7517V10.1798C88.5 9.10107 87.6254 8.22666 86.5469 8.22666H75.227C74.1484 8.22666 73.2738 9.10107 73.2738 10.1798V17.8646L52.2018 1.41377C51.4957 0.862402 50.5043 0.862402 49.798 1.41377L1.75703 38.9192C1.31582 39.2636 1.04375 39.7806 1.00938 40.3392C0.975195 40.8978 1.18223 41.444 1.57773 41.8399L8.62246 48.8849C9.31973 49.5819 10.4279 49.6503 11.2055 49.0433L11.5469 48.7767V97.0938H2.95312C1.87461 97.0938 1 97.9685 1 99.047C1 100.125 1.87461 101 2.95312 101H99.0469C100.125 101 101 100.125 101 99.047C101 97.9685 100.125 97.0938 99.0469 97.0938ZM77.1801 12.1329H84.5938V26.702L77.1801 20.9142V12.1329ZM10.1633 44.9013L5.90156 40.6394L51 5.43096L96.0984 40.6394L91.8367 44.9013L52.2018 13.9583C51.8486 13.6825 51.4244 13.5446 51 13.5446C50.5756 13.5446 50.1512 13.6825 49.7982 13.9583L10.1633 44.9013Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M69.75 43.5784H32.25C31.1715 43.5784 30.2969 44.4528 30.2969 45.5315V83.0315C30.2969 84.11 31.1715 84.9846 32.25 84.9846H69.75C70.8285 84.9846 71.7031 84.11 71.7031 83.0315V45.5315C71.7031 44.4528 70.8285 43.5784 69.75 43.5784ZM49.0469 81.0784H34.2031V66.2346H49.0469V81.0784ZM49.0469 62.3284H34.2031V47.4846H49.0469V62.3284ZM67.7969 81.0784H52.9531V66.2346H67.7969V81.0784ZM67.7969 62.3284H52.9531V47.4846H67.7969V62.3284Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
<path d="M22.875 69.0679C21.7965 69.0679 20.9219 69.9423 20.9219 71.021V71.0524C20.9219 72.131 21.7965 73.0056 22.875 73.0056C23.9535 73.0056 24.8281 72.131 24.8281 71.0524V71.021C24.8281 69.9425 23.9535 69.0679 22.875 69.0679Z" fill="white" stroke="white" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
4
assets/images/time_icon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.00005 0C2.24302 0 0 2.24297 0 4.99995C0 7.75712 2.24302 10.0002 5.00005 10.0002C7.75703 10.0002 10 7.75708 10 4.99995C10 2.24297 7.75703 0 5.00005 0ZM5.00005 9.28343C2.63824 9.28343 0.716757 7.36186 0.716757 4.99995C0.716757 2.63819 2.63824 0.716757 5.00005 0.716757C7.36181 0.716757 9.28324 2.63819 9.28324 4.99995C9.28324 7.36186 7.36181 9.28343 5.00005 9.28343Z" fill="#999999"/>
|
||||
<path d="M7.57087 4.91629H5.22115V2.34782C5.22115 2.1499 5.06074 1.98944 4.86277 1.98944C4.66485 1.98944 4.50439 2.1499 4.50439 2.34782V5.27467C4.50439 5.47259 4.66485 5.63305 4.86277 5.63305H7.57087C7.76884 5.63305 7.92925 5.47259 7.92925 5.27467C7.92925 5.07675 7.76879 4.91629 7.57087 4.91629Z" fill="#999999"/>
|
||||
</svg>
|
After Width: | Height: | Size: 813 B |
@ -14,6 +14,8 @@
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
E44A9405B1EB1B638DD05A58 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ABF0EC746A2D686A0ED574F /* Pods_RunnerTests.framework */; };
|
||||
FF49F60EC38658783D8D66DA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AFAE479A87ECDEBD5D6EB30 /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -42,12 +44,19 @@
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
22428D486F110EE0B969469D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
253C5EA6840355311DB030EA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
2AFAE479A87ECDEBD5D6EB30 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2C0D722D2ED971BF672D18D5 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
544621C7727C798253BAB2C8 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7ABF0EC746A2D686A0ED574F /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
877FDC97D8B87080E35B3EB7 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@ -55,19 +64,52 @@
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D3AD250AADBF93406007C9EB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
759A57780A409ED209817654 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E44A9405B1EB1B638DD05A58 /* Pods_RunnerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FF49F60EC38658783D8D66DA /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
1454C118FFCECEEDF59152D2 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
253C5EA6840355311DB030EA /* Pods-Runner.debug.xcconfig */,
|
||||
22428D486F110EE0B969469D /* Pods-Runner.release.xcconfig */,
|
||||
D3AD250AADBF93406007C9EB /* Pods-Runner.profile.xcconfig */,
|
||||
2C0D722D2ED971BF672D18D5 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
877FDC97D8B87080E35B3EB7 /* Pods-RunnerTests.release.xcconfig */,
|
||||
544621C7727C798253BAB2C8 /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
20A3C64D2B1CFED5A81C3251 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2AFAE479A87ECDEBD5D6EB30 /* Pods_Runner.framework */,
|
||||
7ABF0EC746A2D686A0ED574F /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -94,6 +136,8 @@
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
1454C118FFCECEEDF59152D2 /* Pods */,
|
||||
20A3C64D2B1CFED5A81C3251 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -128,8 +172,10 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
B9A66CAAF434B6A1BD8C4E09 /* [CP] Check Pods Manifest.lock */,
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
759A57780A409ED209817654 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -145,12 +191,14 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
C1C48B0232C0B26BFF405512 /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
33590C9CD073D3D5EBA02CDE /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -222,6 +270,23 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
33590C9CD073D3D5EBA02CDE /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
@ -253,6 +318,50 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
B9A66CAAF434B6A1BD8C4E09 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
C1C48B0232C0B26BFF405512 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@ -378,6 +487,7 @@
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 2C0D722D2ED971BF672D18D5 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@ -395,6 +505,7 @@
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 877FDC97D8B87080E35B3EB7 /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@ -410,6 +521,7 @@
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 544621C7727C798253BAB2C8 /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
|
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
@ -1,33 +1,66 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/login_page.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/view/home_page.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||
import 'package:syncrow_web/services/locator.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
void main() {
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
initialSetup();
|
||||
runApp(const MyApp());
|
||||
initialSetup(); // Perform initial setup, e.g., dependency injection
|
||||
String checkToken = await AuthBloc.getTokenAndValidate();
|
||||
runApp(MyApp(
|
||||
isLoggedIn: checkToken,
|
||||
));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
final dynamic isLoggedIn;
|
||||
const MyApp({
|
||||
super.key,
|
||||
required this.isLoggedIn,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (context) => HomeBloc()),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),)
|
||||
],
|
||||
child: MaterialApp(
|
||||
debugShowCheckedModeBanner: false, // Hide debug banner
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.stylus,
|
||||
PointerDeviceKind.unknown
|
||||
PointerDeviceKind.unknown,
|
||||
},
|
||||
),
|
||||
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
useMaterial3: true,
|
||||
textTheme: const TextTheme(
|
||||
bodySmall: TextStyle(fontSize: 13, color: ColorsManager.whiteColors, fontWeight: FontWeight.bold),
|
||||
bodyMedium: TextStyle(color: Colors.black87, fontSize: 14),
|
||||
bodyLarge: TextStyle(fontSize: 16, color: Colors.white),
|
||||
headlineSmall: TextStyle(color: Colors.black87, fontSize: 18),
|
||||
headlineMedium: TextStyle(color: Colors.black87, fontSize: 20),
|
||||
headlineLarge: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme
|
||||
useMaterial3: true, // Enable Material 3
|
||||
),
|
||||
home: const LoginPage(),
|
||||
);
|
||||
home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
229
lib/pages/access_management/bloc/access_bloc.dart
Normal file
@ -0,0 +1,229 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
|
||||
import 'package:syncrow_web/pages/access_management/model/password_model.dart';
|
||||
import 'package:syncrow_web/services/access_mang_api.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/const.dart';
|
||||
import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
|
||||
class AccessBloc extends Bloc<AccessEvent, AccessState> {
|
||||
AccessBloc() : super((AccessInitial())) {
|
||||
on<FetchTableData>(_onFetchTableData);
|
||||
// on<TabChangedEvent>(selectFilterTap);
|
||||
on<SelectTime>(selectTime);
|
||||
on<FilterDataEvent>(_filterData);
|
||||
on<ResetSearch>(resetSearch);
|
||||
on<TabChangedEvent>(onTabChanged);
|
||||
}
|
||||
String startTime = 'Start Date';
|
||||
String endTime = 'End Date';
|
||||
|
||||
int? effectiveTimeTimeStamp;
|
||||
int? expirationTimeTimeStamp;
|
||||
TextEditingController passwordName= TextEditingController();
|
||||
List<PasswordModel> filteredData = [];
|
||||
List<PasswordModel> data=[];
|
||||
|
||||
Future<void> _onFetchTableData(
|
||||
FetchTableData event, Emitter<AccessState> emit) async {
|
||||
try {
|
||||
emit(AccessLoaded());
|
||||
data = await AccessMangApi().fetchVisitorPassword();
|
||||
filteredData= data;
|
||||
updateTabsCount();
|
||||
emit(TableLoaded(data));
|
||||
} catch (e) {
|
||||
emit(FailedState(e.toString()));
|
||||
}
|
||||
}
|
||||
void updateTabsCount() {
|
||||
int toBeEffectiveCount = data.where((item) => item.passwordStatus.value== 'To Be Effective').length;
|
||||
int effectiveCount = data.where((item) => item.passwordStatus.value == 'Effective').length;
|
||||
int expiredCount = data.where((item) => item.passwordStatus.value == 'Expired').length;
|
||||
tabs[1] = 'To Be Effective ($toBeEffectiveCount)';
|
||||
tabs[2] = 'Effective ($effectiveCount)';
|
||||
tabs[3] = 'Expired ($expiredCount)';
|
||||
}
|
||||
|
||||
|
||||
|
||||
int selectedIndex = 0;
|
||||
final List<String> tabs = [
|
||||
'All',
|
||||
'To Be Effective (0)',
|
||||
'Effective (0)',
|
||||
'Expired'
|
||||
];
|
||||
|
||||
|
||||
Future selectFilterTap(TabChangedEvent event, Emitter<AccessState> emit) async {
|
||||
try {
|
||||
emit(AccessLoaded());
|
||||
selectedIndex= event.selectedIndex;
|
||||
emit(AccessInitial());
|
||||
emit(TableLoaded(data));
|
||||
} catch (e) {
|
||||
emit(FailedState( e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> selectTime(SelectTime event, Emitter<AccessState> emit) async {
|
||||
emit(AccessLoaded());
|
||||
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: event.context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(2015, 8),
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
if (picked != null) {
|
||||
final TimeOfDay? timePicked = await showTimePicker(
|
||||
context: event.context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: ColorsManager.primaryColor,
|
||||
onSurface: Colors.black,
|
||||
),
|
||||
buttonTheme: const ButtonThemeData(
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Colors.green,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (timePicked != null) {
|
||||
final selectedDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
picked.day,
|
||||
timePicked.hour,
|
||||
timePicked.minute,
|
||||
);
|
||||
final selectedTimestamp = DateTime(
|
||||
selectedDateTime.year,
|
||||
selectedDateTime.month,
|
||||
selectedDateTime.day,
|
||||
selectedDateTime.hour,
|
||||
selectedDateTime.minute,
|
||||
).millisecondsSinceEpoch ~/ 1000; // Divide by 1000 to remove milliseconds
|
||||
if (event.isStart) {
|
||||
if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.');
|
||||
} else {
|
||||
startTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds
|
||||
effectiveTimeTimeStamp = selectedTimestamp;
|
||||
}
|
||||
} else {
|
||||
if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.');
|
||||
} else {
|
||||
endTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds
|
||||
expirationTimeTimeStamp = selectedTimestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emit(ChangeTimeState());
|
||||
}
|
||||
|
||||
|
||||
Future<void> _filterData(FilterDataEvent event, Emitter<AccessState> emit) async {
|
||||
emit(AccessLoaded());
|
||||
try {
|
||||
filteredData = data.where((item) {
|
||||
bool matchesCriteria = true;
|
||||
if (event.passwordName != null && event.passwordName!.isNotEmpty) {
|
||||
final bool matchesName = item.passwordName != null &&
|
||||
item.passwordName.contains(event.passwordName);
|
||||
if (!matchesName) {
|
||||
matchesCriteria = false;
|
||||
}
|
||||
}
|
||||
if (event.startTime != null && event.endTime != null) {
|
||||
final int? effectiveTime = int.tryParse(item.effectiveTime.toString());
|
||||
final int? invalidTime = int.tryParse(item.invalidTime.toString());
|
||||
if (effectiveTime == null || invalidTime == null) {
|
||||
matchesCriteria = false;
|
||||
} else {
|
||||
final bool matchesStartTime = effectiveTime >= event.startTime!;
|
||||
final bool matchesEndTime = invalidTime <= event.endTime!;
|
||||
if (!matchesStartTime || !matchesEndTime) {
|
||||
matchesCriteria = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event.selectedTabIndex == 1 && item.passwordStatus.value != 'To Be Effective') {
|
||||
matchesCriteria = false;
|
||||
} else if (event.selectedTabIndex == 2 && item.passwordStatus.value != 'Effective') {
|
||||
matchesCriteria = false;
|
||||
} else if (event.selectedTabIndex == 3 && item.passwordStatus.value != 'Expired') {
|
||||
matchesCriteria = false;
|
||||
}
|
||||
return matchesCriteria;
|
||||
}).toList();
|
||||
emit(TableLoaded(filteredData));
|
||||
} catch (e) {
|
||||
emit(FailedState(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
resetSearch(ResetSearch event, Emitter<AccessState> emit) async{
|
||||
emit(AccessLoaded());
|
||||
startTime = 'Start Time';
|
||||
endTime = 'End Time';
|
||||
passwordName.clear();
|
||||
selectedIndex=0;
|
||||
effectiveTimeTimeStamp=null;
|
||||
expirationTimeTimeStamp=null;
|
||||
add(FetchTableData());
|
||||
}
|
||||
|
||||
String timestampToDate(dynamic timestamp) {
|
||||
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000);
|
||||
return "${dateTime.year}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')}";
|
||||
}
|
||||
|
||||
Future<void> onTabChanged(TabChangedEvent event, Emitter<AccessState> emit) async {
|
||||
try {
|
||||
emit(AccessLoaded());
|
||||
selectedIndex = event.selectedIndex;
|
||||
switch (selectedIndex) {
|
||||
case 0: // All
|
||||
filteredData = data;
|
||||
break;
|
||||
case 1: // To Be Effective
|
||||
filteredData = data.where((item) => item.passwordStatus.value == "To Be Effective").toList();
|
||||
break;
|
||||
case 2: // Effective
|
||||
filteredData = data.where((item) => item.passwordStatus.value == "Effective").toList();
|
||||
break;
|
||||
case 3: // Expired
|
||||
filteredData = data.where((item) => item.passwordStatus.value == "Expired").toList();
|
||||
break;
|
||||
default:
|
||||
filteredData = data;
|
||||
}
|
||||
add(FilterDataEvent(
|
||||
selectedTabIndex: selectedIndex,
|
||||
passwordName: passwordName.text.toLowerCase(),
|
||||
startTime: effectiveTimeTimeStamp,
|
||||
endTime: expirationTimeTimeStamp
|
||||
));
|
||||
emit(TableLoaded(filteredData));
|
||||
} catch (e) {
|
||||
emit(FailedState(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
46
lib/pages/access_management/bloc/access_event.dart
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class AccessEvent extends Equatable {
|
||||
const AccessEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
class FetchTableData extends AccessEvent {}
|
||||
class ResetSearch extends AccessEvent {}
|
||||
|
||||
class TabChangedEvent extends AccessEvent {
|
||||
final int selectedIndex;
|
||||
|
||||
const TabChangedEvent(this.selectedIndex);
|
||||
}
|
||||
|
||||
|
||||
class SelectTime extends AccessEvent {
|
||||
final BuildContext context;
|
||||
final bool isStart;
|
||||
const SelectTime({required this.context,required this.isStart});
|
||||
@override
|
||||
List<Object> get props => [context,isStart];
|
||||
}
|
||||
|
||||
|
||||
class FilterDataEvent extends AccessEvent {
|
||||
final String? passwordName;
|
||||
final int? startTime;
|
||||
final int? endTime;
|
||||
final int selectedTabIndex; // Add this field
|
||||
|
||||
const FilterDataEvent({
|
||||
this.passwordName,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
required this.selectedTabIndex, // Initialize this field
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
40
lib/pages/access_management/bloc/access_state.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/access_management/model/password_model.dart';
|
||||
|
||||
abstract class AccessState extends Equatable {
|
||||
const AccessState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class AccessInitial extends AccessState {}
|
||||
|
||||
class AccessLoaded extends AccessState {}
|
||||
class FailedState extends AccessState {
|
||||
final String message;
|
||||
|
||||
FailedState(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
||||
class TableLoaded extends AccessState {
|
||||
final List<PasswordModel> data;
|
||||
|
||||
const TableLoaded(this.data);
|
||||
|
||||
@override
|
||||
List<Object> get props => [data];
|
||||
}
|
||||
|
||||
class TabState extends AccessState {
|
||||
final int selectedIndex;
|
||||
|
||||
const TabState({required this.selectedIndex});
|
||||
}
|
||||
|
||||
class ChangeTimeState extends AccessState {}
|
||||
|
||||
class TimeSelectedState extends AccessState {}
|
54
lib/pages/access_management/model/password_model.dart
Normal file
@ -0,0 +1,54 @@
|
||||
import 'package:syncrow_web/utils/constants/const.dart';
|
||||
|
||||
class PasswordModel {
|
||||
final dynamic passwordId;
|
||||
final dynamic invalidTime;
|
||||
final dynamic effectiveTime;
|
||||
final dynamic passwordCreated;
|
||||
final dynamic createdTime;
|
||||
final dynamic passwordName; // New field
|
||||
final AccessStatus passwordStatus;
|
||||
final AccessType passwordType;
|
||||
final dynamic deviceUuid;
|
||||
|
||||
PasswordModel({
|
||||
this.passwordId,
|
||||
this.invalidTime,
|
||||
this.effectiveTime,
|
||||
this.passwordCreated,
|
||||
this.createdTime,
|
||||
this.passwordName, // New field
|
||||
required this.passwordStatus,
|
||||
required this.passwordType,
|
||||
this.deviceUuid,
|
||||
});
|
||||
|
||||
factory PasswordModel.fromJson(Map<String, dynamic> json) {
|
||||
return PasswordModel(
|
||||
passwordId: json['passwordId'],
|
||||
invalidTime: json['invalidTime'],
|
||||
effectiveTime: json['effectiveTime'],
|
||||
passwordCreated: json['passwordCreated'],
|
||||
createdTime: json['createdTime'],
|
||||
passwordName: json['passwordName']??'No name', // New field
|
||||
passwordStatus:AccessStatusExtension.fromString(json['passwordStatus']),
|
||||
passwordType:AccessTypeExtension.fromString(json['passwordType']),
|
||||
deviceUuid: json['deviceUuid'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'passwordId': passwordId,
|
||||
'invalidTime': invalidTime,
|
||||
'effectiveTime': effectiveTime,
|
||||
'passwordCreated': passwordCreated,
|
||||
'createdTime': createdTime,
|
||||
'passwodName': passwordName, // New field
|
||||
'passwordStatus': passwordStatus,
|
||||
'passwordType': passwordType,
|
||||
'deviceUuid': deviceUuid,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
271
lib/pages/access_management/view/access_management.dart
Normal file
@ -0,0 +1,271 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
|
||||
import 'package:syncrow_web/pages/common/custom_table.dart';
|
||||
import 'package:syncrow_web/pages/common/date_time_widget.dart';
|
||||
import 'package:syncrow_web/pages/common/default_button.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/constants/const.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
|
||||
class AccessManagementPage extends StatelessWidget {
|
||||
const AccessManagementPage({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Size size = MediaQuery.of(context).size;
|
||||
return WebScaffold(
|
||||
enableMenuSideba: false,
|
||||
appBarTitle: Row(
|
||||
children: [
|
||||
Text('Access Management',
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
)
|
||||
],
|
||||
),
|
||||
appBarBody: [
|
||||
Text('Physical Access',
|
||||
style: Theme.of(context).textTheme
|
||||
.headlineMedium!
|
||||
.copyWith(color: Colors.white),
|
||||
),
|
||||
],
|
||||
scaffoldBody: BlocProvider(create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
|
||||
child: BlocConsumer<AccessBloc, AccessState>(listener: (context, state) {
|
||||
}, builder: (context, state) {
|
||||
final accessBloc = BlocProvider.of<AccessBloc>(context);
|
||||
final filteredData = accessBloc.filteredData;
|
||||
return state is AccessLoaded?
|
||||
const Center(child: CircularProgressIndicator()):
|
||||
Container(
|
||||
padding: EdgeInsets.all(30),
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
decoration: containerDecoration,
|
||||
height: size.height * 0.05,
|
||||
child: Flexible(
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: BlocProvider.of<AccessBloc>(context).tabs.length,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final isSelected = index == BlocProvider.of<AccessBloc>(context).selectedIndex;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
BlocProvider.of<AccessBloc>(context).add(TabChangedEvent(index));
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue : Colors.transparent,
|
||||
width: 2.0,
|
||||
),
|
||||
borderRadius: index == 0
|
||||
? const BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10))
|
||||
: index == 3
|
||||
? const BorderRadius.only(
|
||||
topRight: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10))
|
||||
: null,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
child: Center(
|
||||
child: Text(
|
||||
BlocProvider.of<AccessBloc>(context).tabs[index],
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.blue : Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
textBaseline: TextBaseline.ideographic, children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Name',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: Colors.black,fontSize: 13),),
|
||||
const SizedBox(height: 5,),
|
||||
Container(
|
||||
height:43,
|
||||
width: size.width * 0.15,
|
||||
decoration: containerDecoration,
|
||||
child: TextFormField(
|
||||
controller: accessBloc.passwordName,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
decoration: textBoxDecoration()!
|
||||
.copyWith(hintText: 'Please enter'),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
DateTimeWebWidget(
|
||||
icon: Assets.calendarIcon,
|
||||
isRequired: false,
|
||||
title: 'Access Time',
|
||||
size: size,
|
||||
endTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: false));
|
||||
},
|
||||
startTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: true));
|
||||
},
|
||||
firstString:BlocProvider.of<AccessBloc>(context).startTime ,
|
||||
secondString:BlocProvider.of<AccessBloc>(context).endTime ,
|
||||
) ,
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height:45,
|
||||
|
||||
width: size.width * 0.06,
|
||||
child:Container(
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex, // Pass the selected tab index
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp
|
||||
));
|
||||
}, borderRadius: 9,
|
||||
child: const Text('Search'))),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
SizedBox(
|
||||
height:45,
|
||||
width: size.width * 0.06,
|
||||
child: Container(
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
accessBloc.add(ResetSearch());
|
||||
},
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
borderRadius: 9,
|
||||
child: Text(
|
||||
'Reset',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: size.width * 0.15,
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return const VisitorPasswordDialog();
|
||||
},
|
||||
).then((v){
|
||||
if(v!=null){
|
||||
accessBloc.add(FetchTableData());
|
||||
}
|
||||
});
|
||||
},
|
||||
borderRadius: 8,
|
||||
child: const Text('+ Create Visitor Password ')),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Container(
|
||||
width: size.width * 0.12,
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
borderRadius: 8,
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
child: Text(
|
||||
'Admin Password',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.black),
|
||||
)))
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Expanded(
|
||||
child: DynamicTable(
|
||||
isEmpty: filteredData.isEmpty ,
|
||||
withCheckBox: false,
|
||||
size: size,
|
||||
cellDecoration: containerDecoration,
|
||||
headers: const [
|
||||
'Name',
|
||||
'Access Type',
|
||||
'Access Period',
|
||||
'Accessible Device',
|
||||
'Authorizer',
|
||||
'Authorization Date & Time',
|
||||
'Access Status'
|
||||
],
|
||||
data: filteredData.map((item) {
|
||||
return [
|
||||
item.passwordName.toString(),
|
||||
item.passwordType.value,
|
||||
('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'),
|
||||
item.deviceUuid.toString(),
|
||||
'',
|
||||
'',
|
||||
item.passwordStatus.value
|
||||
];
|
||||
}).toList(),
|
||||
)
|
||||
// : const Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
|
390
lib/pages/auth/bloc/auth_bloc.dart
Normal file
@ -0,0 +1,390 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_state.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/login_with_email_model.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/region_model.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/token.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/user_model.dart';
|
||||
import 'package:syncrow_web/services/auth_api.dart';
|
||||
import 'package:syncrow_web/utils/constants/strings_manager.dart';
|
||||
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
|
||||
import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
|
||||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
AuthBloc() : super(LoginInitial()) {
|
||||
on<LoginButtonPressed>(_login);
|
||||
on<CheckBoxEvent>(checkBoxToggle);
|
||||
on<ChangePasswordEvent>(changePassword);
|
||||
on<StartTimerEvent>(_onStartTimer);
|
||||
on<StopTimerEvent>(_onStopTimer);
|
||||
on<UpdateTimerEvent>(_onUpdateTimer);
|
||||
on<PasswordVisibleEvent>(_passwordVisible);
|
||||
on<RegionInitialEvent>(_fetchRegion);
|
||||
on<SelectRegionEvent>(selectRegion);
|
||||
on<CheckEnableEvent>(checkEnable);
|
||||
on<ChangeValidateEvent>(changeValidate);
|
||||
}
|
||||
|
||||
////////////////////////////// forget password //////////////////////////////////
|
||||
final TextEditingController forgetEmailController = TextEditingController();
|
||||
final TextEditingController forgetPasswordController =
|
||||
TextEditingController();
|
||||
final TextEditingController forgetOtp = TextEditingController();
|
||||
final forgetFormKey = GlobalKey<FormState>();
|
||||
late bool checkValidate = false;
|
||||
|
||||
Timer? _timer;
|
||||
int _remainingTime = 0;
|
||||
List<RegionModel>? regionList = [RegionModel(name: 'name', id: 'id')];
|
||||
|
||||
Future<void> _onStartTimer(
|
||||
StartTimerEvent event, Emitter<AuthState> emit) async {
|
||||
if (_validateInputs(emit)) return;
|
||||
if (_timer != null && _timer!.isActive) {
|
||||
return;
|
||||
}
|
||||
_remainingTime = 1;
|
||||
add(UpdateTimerEvent(
|
||||
remainingTime: _remainingTime, isButtonEnabled: false));
|
||||
_remainingTime = (await AuthenticationAPI.sendOtp(
|
||||
email: forgetEmailController.text, regionUuid: regionUuid))!;
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_remainingTime--;
|
||||
if (_remainingTime <= 0) {
|
||||
_timer?.cancel();
|
||||
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
|
||||
} else {
|
||||
add(UpdateTimerEvent(
|
||||
remainingTime: _remainingTime,
|
||||
isButtonEnabled: false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onStopTimer(StopTimerEvent event, Emitter<AuthState> emit) {
|
||||
_timer?.cancel();
|
||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||
}
|
||||
|
||||
Future<void> changePassword(
|
||||
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||
try {
|
||||
emit(LoadingForgetState());
|
||||
var response = await AuthenticationAPI.verifyOtp(
|
||||
email: forgetEmailController.text, otpCode: forgetOtp.text);
|
||||
if (response == true) {
|
||||
await AuthenticationAPI.forgetPassword(
|
||||
password: forgetPasswordController.text,
|
||||
email: forgetEmailController.text);
|
||||
_timer?.cancel();
|
||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||
emit(SuccessForgetState());
|
||||
} else if (response == "You entered wrong otp") {
|
||||
forgetValidate = 'Wrong one time password.';
|
||||
emit(AuthInitialState());
|
||||
}else if (response == "OTP expired") {
|
||||
forgetValidate = 'One time password has been expired.';
|
||||
emit(AuthInitialState());
|
||||
}
|
||||
} catch (failure) {
|
||||
// forgetValidate='Invalid Credentials!';
|
||||
emit(AuthInitialState());
|
||||
// emit(FailureForgetState(error: failure.toString()));
|
||||
}
|
||||
}
|
||||
//925207
|
||||
String? validateCode(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Code is required';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
|
||||
emit(TimerState(
|
||||
isButtonEnabled: event.isButtonEnabled,
|
||||
remainingTime: event.remainingTime));
|
||||
}
|
||||
|
||||
///////////////////////////////////// login /////////////////////////////////////
|
||||
final TextEditingController loginEmailController = TextEditingController();
|
||||
final TextEditingController loginPasswordController = TextEditingController();
|
||||
final loginFormKey = GlobalKey<FormState>();
|
||||
bool isChecked = false;
|
||||
bool obscureText = true;
|
||||
String newPassword = '';
|
||||
String maskedEmail = '';
|
||||
String otpCode = '';
|
||||
String validate = '';
|
||||
String forgetValidate = '';
|
||||
String regionUuid = '';
|
||||
static Token token = Token.emptyConstructor();
|
||||
static UserModel? user;
|
||||
bool showValidationMessage = false;
|
||||
|
||||
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
||||
emit(AuthLoading());
|
||||
|
||||
if (isChecked) {
|
||||
try {
|
||||
if (event.username.isEmpty || event.password.isEmpty) {
|
||||
CustomSnackBar.displaySnackBar('Please enter your credentials');
|
||||
emit(const LoginFailure(error: 'Something went wrong'));
|
||||
return;
|
||||
}
|
||||
token = await AuthenticationAPI.loginWithEmail(
|
||||
model: LoginWithEmailModel(
|
||||
email: event.username,
|
||||
password: event.password,
|
||||
regionUuid: event.regionUuid),
|
||||
);
|
||||
} catch (failure) {
|
||||
validate = 'Invalid Credentials!';
|
||||
emit(AuthInitialState());
|
||||
// emit(const LoginFailure(error: 'Something went wrong'));
|
||||
return;
|
||||
}
|
||||
if (token.accessTokenIsNotEmpty) {
|
||||
FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||
await storage.write(
|
||||
key: Token.loginAccessTokenKey, value: token.accessToken);
|
||||
const FlutterSecureStorage().write(
|
||||
key: UserModel.userUuidKey,
|
||||
value: Token.decodeToken(token.accessToken)['uuid'].toString());
|
||||
user = UserModel.fromToken(token);
|
||||
loginEmailController.clear();
|
||||
loginPasswordController.clear();
|
||||
emit(LoginSuccess());
|
||||
} else {
|
||||
emit(const LoginFailure(error: 'Something went wrong'));
|
||||
}
|
||||
} else {
|
||||
|
||||
emit(const LoginFailure(error: 'Accept terms and condition'));
|
||||
}
|
||||
}
|
||||
|
||||
checkBoxToggle(CheckBoxEvent event, Emitter<AuthState> emit,) {
|
||||
emit(AuthLoading());
|
||||
isChecked = event.newValue!;
|
||||
add(CheckEnableEvent());
|
||||
emit(LoginInitial());
|
||||
}
|
||||
|
||||
checkOtpCode(ChangePasswordEvent event, Emitter<AuthState> emit,) async {
|
||||
emit(LoadingForgetState());
|
||||
await AuthenticationAPI.verifyOtp(email: forgetEmailController.text, otpCode: forgetOtp.text);
|
||||
emit(SuccessForgetState());
|
||||
}
|
||||
|
||||
void _passwordVisible(PasswordVisibleEvent event, Emitter<AuthState> emit) {
|
||||
emit(AuthLoading());
|
||||
obscureText = !event.newValue!;
|
||||
emit(PasswordVisibleState());
|
||||
}
|
||||
|
||||
void launchURL(String url) {}
|
||||
|
||||
/////////////////////////////////////VALIDATORS/////////////////////////////////////
|
||||
String? validatePassword(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateEmail(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Email is required';
|
||||
} else if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
|
||||
return 'Enter a valid email address';
|
||||
} else if (regionUuid == '') {
|
||||
return 'Please select your region';
|
||||
}
|
||||
validate='';
|
||||
return null;
|
||||
}
|
||||
|
||||
String? loginValidateEmail(String? value) {
|
||||
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value!)) {
|
||||
return '';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool _validateInputs(Emitter<AuthState> emit) {
|
||||
emit(LoadingForgetState());
|
||||
final nameError = validateEmail(forgetEmailController.text);
|
||||
if (nameError != null) {
|
||||
emit(FailureForgetState(error: nameError));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String? validateRegion(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please select a region';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? passwordValidator(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter your password';
|
||||
}
|
||||
List<String> validationErrors = [];
|
||||
|
||||
if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) {
|
||||
validationErrors.add(' - one lowercase letter');
|
||||
}
|
||||
if (!RegExp(r'^(?=.*[A-Z])').hasMatch(value)) {
|
||||
validationErrors.add(' - one uppercase letter');
|
||||
}
|
||||
if (!RegExp(r'^(?=.*\d)').hasMatch(value)) {
|
||||
validationErrors.add(' - one number');
|
||||
}
|
||||
if (!RegExp(r'^(?=.*[@$!%*?&])').hasMatch(value)) {
|
||||
validationErrors.add(' - one special character');
|
||||
}
|
||||
if (value.length < 8) {
|
||||
validationErrors.add(' - minimum 8 characters');
|
||||
}
|
||||
|
||||
if (validationErrors.isNotEmpty) {
|
||||
return 'Password must contain at least:\n${validationErrors.join('\n')}';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? fullNameValidator(String? value) {
|
||||
if (value == null) return 'Full name is required';
|
||||
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
|
||||
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
|
||||
return 'Full name must be between 2 and 30 characters long';
|
||||
}
|
||||
if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
|
||||
return 'Only alphanumeric characters, space, dash and single quote are allowed';
|
||||
}
|
||||
final parts = withoutExtraSpaces.split(' ');
|
||||
if (parts.length < 2) return 'Full name must contain first and last names';
|
||||
if (parts.length > 3) return 'Full name can at most contain 3 parts';
|
||||
if (parts.any((part) => part.length < 2 || part.length > 30)) {
|
||||
return 'Full name parts must be between 2 and 30 characters long';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String maskEmail(String email) {
|
||||
final emailParts = email.split('@');
|
||||
if (emailParts.length != 2) return email;
|
||||
|
||||
final localPart = emailParts[0];
|
||||
final domainPart = emailParts[1];
|
||||
|
||||
if (localPart.length < 3) return email;
|
||||
|
||||
final start = localPart.substring(0, 2);
|
||||
final end = localPart.substring(localPart.length - 1);
|
||||
|
||||
final maskedLocalPart = '$start******$end';
|
||||
return '$maskedLocalPart@$domainPart';
|
||||
}
|
||||
|
||||
static Future<String> getTokenAndValidate() async {
|
||||
try {
|
||||
const storage = FlutterSecureStorage();
|
||||
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
|
||||
StringsManager.firstLaunch) ??
|
||||
true;
|
||||
if (firstLaunch) {
|
||||
storage.deleteAll();
|
||||
}
|
||||
await SharedPreferencesHelper.saveBoolToSP(
|
||||
StringsManager.firstLaunch, false);
|
||||
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
|
||||
if (value.isEmpty) {
|
||||
return 'Token not found';
|
||||
}
|
||||
final tokenData = Token.decodeToken(value);
|
||||
if (tokenData.containsKey('exp')) {
|
||||
final exp = tokenData['exp'] ?? 0;
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
if (currentTime < exp) {
|
||||
return 'Success';
|
||||
} else {
|
||||
return 'expired';
|
||||
}
|
||||
} else {
|
||||
return 'Something went wrong';
|
||||
}
|
||||
} catch (_) {
|
||||
return 'Something went wrong';
|
||||
}
|
||||
}
|
||||
|
||||
void _fetchRegion(RegionInitialEvent event, Emitter<AuthState> emit) async {
|
||||
try {
|
||||
emit(AuthLoading());
|
||||
regionList = await AuthenticationAPI.fetchRegion();
|
||||
emit(AuthInitialState());
|
||||
} catch (e) {
|
||||
emit(LoginFailure(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future selectRegion(SelectRegionEvent event, Emitter<AuthState> emit) async {
|
||||
try {
|
||||
emit(AuthLoading());
|
||||
regionUuid = event.val;
|
||||
emit(AuthInitialState());
|
||||
} catch (e) {
|
||||
emit(FailureForgetState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String formattedTime(int time) {
|
||||
final int days = (time / 86400).floor(); // 86400 seconds in a day
|
||||
final int hours = ((time % 86400) / 3600).floor();
|
||||
final int minutes = (((time % 86400) % 3600) / 60).floor();
|
||||
final int seconds = (((time % 86400) % 3600) % 60).floor();
|
||||
|
||||
final String formattedTime = [
|
||||
if (days > 0) '${days}d', // Append 'd' for days
|
||||
if (days > 0 || hours > 0)
|
||||
hours.toString().padLeft(2, '0'), // Show hours if there are days or hours
|
||||
minutes.toString().padLeft(2, '0'),
|
||||
seconds.toString().padLeft(2, '0'),
|
||||
].join(':');
|
||||
|
||||
return formattedTime;
|
||||
}
|
||||
|
||||
bool checkEnable( CheckEnableEvent event, Emitter<AuthState> emit,) {
|
||||
emit(AuthLoading());
|
||||
checkValidate = isChecked==true &&
|
||||
loginPasswordController.text.isNotEmpty &&
|
||||
loginEmailController.text.isNotEmpty &&
|
||||
regionUuid != '';
|
||||
emit(LoginInitial());
|
||||
return checkValidate;
|
||||
}
|
||||
|
||||
changeValidate(ChangeValidateEvent event, Emitter<AuthState> emit,){
|
||||
emit(AuthLoading());
|
||||
validate='';
|
||||
emit(LoginInitial());
|
||||
}
|
||||
changeForgetValidate(ChangeValidateEvent event, Emitter<AuthState> emit,){
|
||||
emit(AuthLoading());
|
||||
forgetValidate='';
|
||||
emit(LoginInitial());
|
||||
}
|
||||
}
|
65
lib/pages/auth/bloc/auth_event.dart
Normal file
@ -0,0 +1,65 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class AuthEvent extends Equatable {
|
||||
const AuthEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginButtonPressed extends AuthEvent {
|
||||
final String username;
|
||||
final String password;
|
||||
final String regionUuid;
|
||||
|
||||
const LoginButtonPressed({required this.username, required this.password, required this.regionUuid, });
|
||||
|
||||
@override
|
||||
List<Object> get props => [username, password,regionUuid];
|
||||
}
|
||||
|
||||
class CheckBoxEvent extends AuthEvent {
|
||||
final bool? newValue;
|
||||
|
||||
const CheckBoxEvent({required this.newValue,});
|
||||
|
||||
@override
|
||||
List<Object> get props => [newValue!,];
|
||||
}
|
||||
|
||||
class GetCodeEvent extends AuthEvent{}
|
||||
|
||||
class SubmitEvent extends AuthEvent{}
|
||||
|
||||
class StartTimerEvent extends AuthEvent{}
|
||||
|
||||
class StopTimerEvent extends AuthEvent{}
|
||||
|
||||
class UpdateTimerEvent extends AuthEvent {
|
||||
final int remainingTime;
|
||||
final bool isButtonEnabled;
|
||||
const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled});
|
||||
}
|
||||
|
||||
class ChangePasswordEvent extends AuthEvent{}
|
||||
|
||||
class SendOtpEvent extends AuthEvent{}
|
||||
|
||||
class PasswordVisibleEvent extends AuthEvent{
|
||||
final bool? newValue;
|
||||
|
||||
const PasswordVisibleEvent({required this.newValue,});
|
||||
}
|
||||
|
||||
class RegionInitialEvent extends AuthEvent {}
|
||||
class CheckEnableEvent extends AuthEvent {}
|
||||
class ChangeValidateEvent extends AuthEvent {}
|
||||
|
||||
class SelectRegionEvent extends AuthEvent {
|
||||
final String val;
|
||||
const SelectRegionEvent({required this.val});
|
||||
@override
|
||||
List<Object> get props => [val];
|
||||
}
|
||||
|
85
lib/pages/auth/bloc/auth_state.dart
Normal file
@ -0,0 +1,85 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class AuthState extends Equatable {
|
||||
const AuthState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginInitial extends AuthState {}
|
||||
|
||||
class AuthTokenLoading extends AuthState {}
|
||||
|
||||
class AuthLoading extends AuthState {}
|
||||
class AuthInitialState extends AuthState {}
|
||||
|
||||
class LoginSuccess extends AuthState {}
|
||||
|
||||
class LoginFailure extends AuthState {
|
||||
final String error;
|
||||
|
||||
const LoginFailure({required this.error});
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class LoginValid extends AuthState {}
|
||||
|
||||
class LoginInvalid extends AuthState {
|
||||
final String error;
|
||||
|
||||
const LoginInvalid({required this.error});
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class InitialForgetState extends AuthState {}
|
||||
|
||||
class LoadingForgetState extends AuthState {}
|
||||
|
||||
class SuccessForgetState extends AuthState {}
|
||||
|
||||
class PasswordVisibleState extends AuthState {}
|
||||
|
||||
class FailureForgetState extends AuthState {
|
||||
final String error;
|
||||
const FailureForgetState({required this.error});
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class TimerState extends AuthState {
|
||||
final bool isButtonEnabled;
|
||||
final int remainingTime;
|
||||
|
||||
const TimerState({required this.isButtonEnabled, required this.remainingTime});
|
||||
|
||||
@override
|
||||
List<Object> get props => [isButtonEnabled, remainingTime];
|
||||
}
|
||||
|
||||
class AuthError extends AuthState {
|
||||
final String message;
|
||||
String? code;
|
||||
AuthError({required this.message, this.code});
|
||||
}
|
||||
|
||||
class AuthTokenError extends AuthError {
|
||||
AuthTokenError({required super.message, super.code});
|
||||
}
|
||||
|
||||
class AuthSuccess extends AuthState {}
|
||||
|
||||
class AuthTokenSuccess extends AuthSuccess {}
|
||||
class TimerUpdated extends AuthState {
|
||||
final String formattedTime;
|
||||
final bool isButtonEnabled;
|
||||
|
||||
TimerUpdated({
|
||||
required this.formattedTime,
|
||||
required this.isButtonEnabled,
|
||||
});
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/login_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/login_state.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/login_with_email_model.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/token.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/user_model.dart';
|
||||
import 'package:syncrow_web/services/auth_api.dart';
|
||||
import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
|
||||
class LoginBloc extends Bloc<LoginEvent, LoginState> {
|
||||
LoginBloc() : super(LoginInitial()) {
|
||||
on<LoginButtonPressed>(_login);
|
||||
}
|
||||
|
||||
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
|
||||
String fullName = '';
|
||||
String email = '';
|
||||
String forgetPasswordEmail = '';
|
||||
String signUpPassword = '';
|
||||
String newPassword = '';
|
||||
String maskedEmail = '';
|
||||
String otpCode = '';
|
||||
|
||||
final loginFormKey = GlobalKey<FormState>();
|
||||
final signUpFormKey = GlobalKey<FormState>();
|
||||
final checkEmailFormKey = GlobalKey<FormState>();
|
||||
final createNewPasswordKey = GlobalKey<FormState>();
|
||||
static Token token = Token.emptyConstructor();
|
||||
static UserModel? user;
|
||||
|
||||
bool isPasswordVisible = false;
|
||||
bool showValidationMessage = false;
|
||||
|
||||
|
||||
/////////////////////////////////////VALIDATORS/////////////////////////////////////
|
||||
String? passwordValidator(String? value) {
|
||||
if (value != null) {
|
||||
if (value.isEmpty) {
|
||||
return 'Please enter your password';
|
||||
}
|
||||
if (value.isNotEmpty) {
|
||||
if (!RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$')
|
||||
.hasMatch(value)) {
|
||||
return 'Password must contain at least:\n - one uppercase letter.\n - one lowercase letter.\n - one number. \n - special character';
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? fullNameValidator(String? value) {
|
||||
if (value == null) return 'Full name is required';
|
||||
|
||||
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
|
||||
|
||||
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
|
||||
return 'Full name must be between 2 and 30 characters long';
|
||||
}
|
||||
|
||||
// Test if it contains anything but alphanumeric spaces and single quote
|
||||
|
||||
if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
|
||||
return 'Only alphanumeric characters, space, dash and single quote are allowed';
|
||||
}
|
||||
|
||||
final parts = withoutExtraSpaces.split(' ');
|
||||
|
||||
if (parts.length < 2) return 'Full name must contain first and last names';
|
||||
|
||||
if (parts.length > 3) return 'Full name can at most contain 3 parts';
|
||||
|
||||
if (parts.any((part) => part.length < 2 || part.length > 30)) {
|
||||
return 'Full name parts must be between 2 and 30 characters long';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? reEnterPasswordCheck(String? value) {
|
||||
passwordValidator(value);
|
||||
if (signUpPassword == value) {
|
||||
return null;
|
||||
} else {
|
||||
return 'Passwords do not match';
|
||||
}
|
||||
}
|
||||
|
||||
String? reEnterPasswordCheckForgetPass(String? value) {
|
||||
passwordValidator(value);
|
||||
if (newPassword == value) {
|
||||
return null;
|
||||
} else {
|
||||
return 'Passwords do not match';
|
||||
}
|
||||
}
|
||||
|
||||
String? emailAddressValidator(String? value) {
|
||||
if (value != null && value.isNotEmpty && value != "") {
|
||||
if (checkValidityOfEmail(value)) {
|
||||
return null;
|
||||
} else {
|
||||
return 'Please enter a valid email';
|
||||
}
|
||||
} else {
|
||||
return 'Email address is required';
|
||||
}
|
||||
}
|
||||
|
||||
bool checkValidityOfEmail(String? email) {
|
||||
if (email != null) {
|
||||
return RegExp(
|
||||
r"^[a-zA-Z0-9]+([.!#$%&'*+/=?^_`{|}~-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9]+([.-]?[a-zA-Z0-9]+)*\.[a-zA-Z0-9]{2,}$")
|
||||
.hasMatch(email);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String maskEmail(String email) {
|
||||
final emailParts = email.split('@');
|
||||
if (emailParts.length != 2) return email;
|
||||
|
||||
final localPart = emailParts[0];
|
||||
final domainPart = emailParts[1];
|
||||
|
||||
if (localPart.length < 3) return email;
|
||||
|
||||
final start = localPart.substring(0, 2);
|
||||
final end = localPart.substring(localPart.length - 1);
|
||||
|
||||
final maskedLocalPart = '$start******$end';
|
||||
return '$maskedLocalPart@$domainPart';
|
||||
}
|
||||
|
||||
_login(LoginButtonPressed event, Emitter<LoginState> emit) async {
|
||||
emit(LoginLoading());
|
||||
try {
|
||||
if (event.username.isEmpty ||event.password.isEmpty) {
|
||||
CustomSnackBar.displaySnackBar('Please enter your credentials');
|
||||
emit(const LoginFailure(error: 'Something went wrong'));
|
||||
return;
|
||||
}
|
||||
|
||||
token = await AuthenticationAPI.loginWithEmail(
|
||||
model: LoginWithEmailModel(
|
||||
email: event.username,
|
||||
password: event.password,
|
||||
),
|
||||
);
|
||||
} catch (failure) {
|
||||
emit( LoginFailure(error: failure.toString()));
|
||||
return;
|
||||
}
|
||||
if (token.accessTokenIsNotEmpty) {
|
||||
debugPrint('token: ${token.accessToken}');
|
||||
FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
|
||||
|
||||
const FlutterSecureStorage().write(
|
||||
key: UserModel.userUuidKey,
|
||||
value: Token.decodeToken(token.accessToken)['uuid'].toString()
|
||||
);
|
||||
|
||||
user = UserModel.fromToken(token);
|
||||
emailController.clear();
|
||||
passwordController.clear();
|
||||
emit(LoginSuccess());
|
||||
} else {
|
||||
emit(const LoginFailure(error: 'Something went wrong'));
|
||||
}
|
||||
}
|
||||
|
||||
// signUp() async {
|
||||
// emit(LoginLoading());
|
||||
// final response;
|
||||
// try {
|
||||
// List<String> userFullName = fullName.split(' ');
|
||||
// response = await AuthenticationAPI.signUp(
|
||||
// model: SignUpModel(
|
||||
// email: email.toLowerCase(),
|
||||
// password: signUpPassword,
|
||||
// firstName: userFullName[0],
|
||||
// lastName: userFullName[1]),
|
||||
// );
|
||||
// } catch (failure) {
|
||||
// emit(AuthLoginError(message: failure.toString()));
|
||||
// return;
|
||||
// }
|
||||
// if (response) {
|
||||
// maskedEmail = maskEmail(email);
|
||||
// await sendOtp();
|
||||
// } else {
|
||||
// emit(AuthLoginError(message: 'Something went wrong'));
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class LoginEvent extends Equatable {
|
||||
const LoginEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginButtonPressed extends LoginEvent {
|
||||
final String username;
|
||||
final String password;
|
||||
|
||||
const LoginButtonPressed({required this.username, required this.password});
|
||||
|
||||
@override
|
||||
List<Object> get props => [username, password];
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class LoginState extends Equatable {
|
||||
const LoginState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginInitial extends LoginState {}
|
||||
|
||||
class LoginLoading extends LoginState {}
|
||||
|
||||
class LoginSuccess extends LoginState {}
|
||||
|
||||
class LoginFailure extends LoginState {
|
||||
final String error;
|
||||
|
||||
const LoginFailure({required this.error});
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
@ -1,16 +1,19 @@
|
||||
class LoginWithEmailModel {
|
||||
final String email;
|
||||
final String password;
|
||||
final String regionUuid;
|
||||
|
||||
LoginWithEmailModel({
|
||||
required this.email,
|
||||
required this.password,
|
||||
required this.regionUuid,
|
||||
});
|
||||
|
||||
factory LoginWithEmailModel.fromJson(Map<String, dynamic> json) {
|
||||
return LoginWithEmailModel(
|
||||
email: json['email'],
|
||||
password: json['password'],
|
||||
regionUuid: json['regionUuid'],
|
||||
);
|
||||
}
|
||||
|
||||
@ -18,6 +21,7 @@ class LoginWithEmailModel {
|
||||
return {
|
||||
'email': email,
|
||||
'password': password,
|
||||
'regionUuid': regionUuid,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
25
lib/pages/auth/model/region_model.dart
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
|
||||
class RegionModel {
|
||||
final String name;
|
||||
final String id;
|
||||
|
||||
RegionModel({
|
||||
required this.name,
|
||||
required this.id,
|
||||
});
|
||||
|
||||
factory RegionModel.fromJson(Map<String, dynamic> json) {
|
||||
return RegionModel(
|
||||
name: json['regionName'],
|
||||
id: json['uuid'].toString(), // Ensure id is a String
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'regionName': name,
|
||||
'uuid': id,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,22 +1,22 @@
|
||||
|
||||
|
||||
import 'package:syncrow_web/pages/auth/model/token.dart';
|
||||
|
||||
class UserModel {
|
||||
static String userUuidKey = 'userUuid';
|
||||
final String? uuid;
|
||||
final String? email;
|
||||
final String? name;
|
||||
final String? firstName;
|
||||
final String? lastName;
|
||||
final String? photoUrl;
|
||||
|
||||
final String? phoneNumber;
|
||||
|
||||
final bool? isEmailVerified;
|
||||
|
||||
final bool? isAgreementAccepted;
|
||||
|
||||
UserModel({
|
||||
required this.uuid,
|
||||
required this.email,
|
||||
required this.name,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.photoUrl,
|
||||
required this.phoneNumber,
|
||||
required this.isEmailVerified,
|
||||
@ -27,7 +27,8 @@ class UserModel {
|
||||
return UserModel(
|
||||
uuid: json['id'],
|
||||
email: json['email'],
|
||||
name: json['name'],
|
||||
firstName: json['firstName'],
|
||||
lastName: json['lastName'],
|
||||
photoUrl: json['photoUrl'],
|
||||
phoneNumber: json['phoneNumber'],
|
||||
isEmailVerified: json['isEmailVerified'],
|
||||
@ -44,7 +45,8 @@ class UserModel {
|
||||
return UserModel(
|
||||
uuid: tempJson['uuid'].toString(),
|
||||
email: tempJson['email'],
|
||||
name: null,
|
||||
firstName: null,
|
||||
lastName: null,
|
||||
photoUrl: null,
|
||||
phoneNumber: null,
|
||||
isEmailVerified: null,
|
||||
@ -56,7 +58,8 @@ class UserModel {
|
||||
return {
|
||||
'id': uuid,
|
||||
'email': email,
|
||||
'name': name,
|
||||
'firstName': firstName,
|
||||
'lastName': lastName,
|
||||
'photoUrl': photoUrl,
|
||||
'phoneNumber': phoneNumber,
|
||||
'isEmailVerified': isEmailVerified,
|
||||
|
11
lib/pages/auth/view/forget_password_mobile_page.dart
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ForgetPasswordMobilePage extends StatelessWidget {
|
||||
const ForgetPasswordMobilePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
19
lib/pages/auth/view/forget_password_page.dart
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/forget_password_mobile_page.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/forget_password_web_page.dart';
|
||||
import 'package:syncrow_web/utils/responsive_layout.dart';
|
||||
|
||||
|
||||
class ForgetPasswordPage extends StatelessWidget {
|
||||
const ForgetPasswordPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const ResponsiveLayout(
|
||||
desktopBody: ForgetPasswordWebPage(),
|
||||
mobileBody:ForgetPasswordWebPage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
363
lib/pages/auth/view/forget_password_web_page.dart
Normal file
@ -0,0 +1,363 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_state.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/region_model.dart';
|
||||
import 'package:syncrow_web/pages/common/default_button.dart';
|
||||
import 'package:syncrow_web/pages/common/first_layer.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class ForgetPasswordWebPage extends StatelessWidget {
|
||||
const ForgetPasswordWebPage({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: BlocProvider(
|
||||
create: (context) => AuthBloc()..add(RegionInitialEvent()),
|
||||
child: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is SuccessForgetState) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Change Password Successfully '),
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
} else if (state is FailureForgetState) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.error),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return _buildForm(context, state);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildForm(BuildContext context, AuthState state) {
|
||||
late ScrollController _scrollController;
|
||||
_scrollController = ScrollController();
|
||||
void _scrollToCenter() {
|
||||
final double middlePosition =
|
||||
_scrollController.position.maxScrollExtent / 2;
|
||||
_scrollController.animateTo(
|
||||
middlePosition,
|
||||
duration: const Duration(seconds: 1),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToCenter();
|
||||
});
|
||||
final forgetBloc = BlocProvider.of<AuthBloc>(context);
|
||||
Size size = MediaQuery.of(context).size;
|
||||
return FirstLayer(
|
||||
second: Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
if (state is AuthLoading)
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
ListView(
|
||||
shrinkWrap: true,
|
||||
controller: _scrollController,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(size.width * 0.02),
|
||||
margin: EdgeInsets.all(size.width * 0.09),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Center(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: SvgPicture.asset(
|
||||
Assets.loginLogo,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(30)),
|
||||
border: Border.all(
|
||||
color: ColorsManager.graysColor.withOpacity(0.2)),
|
||||
),
|
||||
child: Form(
|
||||
key: forgetBloc.forgetFormKey,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: size.width * 0.02,
|
||||
vertical: size.width * 0.003),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'Forget Password',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Please fill in your account information to\nretrieve your password',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400),
|
||||
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: DropdownButtonFormField<String>(
|
||||
validator: forgetBloc.validateRegion,
|
||||
icon: const Icon(
|
||||
Icons.keyboard_arrow_down_outlined,
|
||||
),
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: null,
|
||||
),
|
||||
hint: SizedBox(
|
||||
width: size.width * 0.12,
|
||||
child: const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Select your region/country',
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
isDense: true,
|
||||
style:
|
||||
const TextStyle(color: Colors.black),
|
||||
items: forgetBloc.regionList!
|
||||
.map((RegionModel region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region.id,
|
||||
child: SizedBox(
|
||||
width: size.width*0.06,
|
||||
|
||||
child: Text(region.name)),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? value) {
|
||||
forgetBloc.add(SelectRegionEvent(
|
||||
val: value!,
|
||||
));
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Account",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400),
|
||||
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: forgetBloc.validateEmail,
|
||||
controller:
|
||||
forgetBloc.forgetEmailController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'Enter your email'),
|
||||
style:
|
||||
const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"One Time Password",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: forgetBloc.validateCode,
|
||||
keyboardType:
|
||||
TextInputType.visiblePassword,
|
||||
controller: forgetBloc.forgetOtp,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'Enter Code',
|
||||
suffixIcon: SizedBox(
|
||||
width: 100,
|
||||
child: Center(
|
||||
child: InkWell(
|
||||
onTap:state is TimerState && !state.isButtonEnabled && state.remainingTime!=1?null: () {
|
||||
forgetBloc.add(StartTimerEvent());
|
||||
},
|
||||
child: Text(
|
||||
'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime!=1? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}',
|
||||
style: TextStyle(
|
||||
color: state is TimerState &&
|
||||
!state.isButtonEnabled
|
||||
? Colors.grey
|
||||
: ColorsManager.btnColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
style:
|
||||
const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
if (forgetBloc.forgetValidate != '') // Check if there is a validation message
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
forgetBloc.forgetValidate,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.red,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Password",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: forgetBloc.passwordValidator,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: forgetBloc.forgetPasswordController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
backgroundColor: ColorsManager.btnColor,
|
||||
child: const Text('Submit'),
|
||||
onPressed: () {
|
||||
if (forgetBloc.forgetFormKey.currentState!.validate()) {
|
||||
forgetBloc.add(ChangePasswordEvent());
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
SizedBox(
|
||||
child: Text(
|
||||
forgetBloc.validate,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.red),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
width: size.width * 0.2,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Flexible(
|
||||
child: Text(
|
||||
"Do you have an account? ",
|
||||
style: TextStyle(color: Colors.white),
|
||||
)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
forgetBloc.add(StopTimerEvent());
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Flexible(
|
||||
child: Text(
|
||||
"Sign in",
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
@ -1,28 +1,33 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_state.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/region_model.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/forget_password_page.dart';
|
||||
import 'package:syncrow_web/pages/common/default_button.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/login_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/login_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/login_state.dart';
|
||||
import 'package:syncrow_web/pages/home/view/home_page.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class LoginMobilePage extends StatelessWidget {
|
||||
const LoginMobilePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: BlocProvider(
|
||||
create: (context) => LoginBloc(),
|
||||
child: BlocConsumer<LoginBloc, LoginState>(
|
||||
create: (context) => AuthBloc(),
|
||||
child: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is LoginSuccess) {
|
||||
// Navigate to home screen after successful login
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const HomePage()),
|
||||
MaterialPageRoute(builder: (context) => HomePage()),
|
||||
);
|
||||
} else if (state is LoginFailure) {
|
||||
// Show error message
|
||||
@ -34,7 +39,7 @@ class LoginMobilePage extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is LoginLoading) {
|
||||
if (state is AuthLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return _buildLoginForm(context);
|
||||
@ -42,12 +47,11 @@ class LoginMobilePage extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginForm(BuildContext context) {
|
||||
final loginBloc = BlocProvider.of<LoginBloc>(context);
|
||||
final loginBloc = BlocProvider.of<AuthBloc>(context);
|
||||
final TextEditingController _usernameController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
return Center(
|
||||
@ -94,121 +98,235 @@ class LoginMobilePage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.all(15),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.3),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(30))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 15),
|
||||
const Text(
|
||||
'Login',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Email",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Email',
|
||||
labelStyle: TextStyle(color: Colors.white),
|
||||
hintText: 'username@gmail.com',
|
||||
hintStyle: TextStyle(color: Colors.grey),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(color: Colors.white),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(color: Colors.white),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: 16.0, horizontal: 12.0),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 15.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Password",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
labelStyle: TextStyle(color: Colors.white),
|
||||
hintText: 'Password',
|
||||
hintStyle: TextStyle(color: Colors.grey),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(color: Colors.white),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(color: Colors.white),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: 16.0, horizontal: 12.0),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
)),
|
||||
const SizedBox(height: 20.0),
|
||||
const Text(
|
||||
"Forgot Password?",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
// Trigger login event
|
||||
loginBloc.add(
|
||||
LoginButtonPressed(
|
||||
username: _usernameController.text,
|
||||
password: _passwordController.text,
|
||||
margin: EdgeInsets.all(30),
|
||||
padding: EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(30)),
|
||||
border: Border.all(
|
||||
color:
|
||||
ColorsManager.graysColor.withOpacity(0.2))),
|
||||
child: Form(
|
||||
key: loginBloc.loginFormKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 15),
|
||||
const Text(
|
||||
'Login',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Login'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
child: DropdownButtonFormField<String>(
|
||||
validator: loginBloc.validateRegion,
|
||||
icon: const Icon(
|
||||
Icons.keyboard_arrow_down_outlined,
|
||||
),
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: null,
|
||||
),
|
||||
hint: const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Select your region/country',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
isDense: true,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
items:loginBloc.regionList!.map((RegionModel region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region.name,
|
||||
child: Text(region.name),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? value) {
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Email",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: loginBloc.validateEmail,
|
||||
controller: loginBloc.loginEmailController,
|
||||
decoration: textBoxDecoration()!
|
||||
.copyWith(hintText: 'Enter your email'),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Password",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: loginBloc.validatePassword,
|
||||
obscureText: loginBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: loginBloc.loginPasswordController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
const ForgetPasswordPage(),
|
||||
));
|
||||
},
|
||||
child: Text(
|
||||
"Forgot Password?",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Transform.scale(
|
||||
scale: 1.2, // Adjust the scale as needed
|
||||
child: Checkbox(
|
||||
fillColor: MaterialStateProperty.all<Color>(
|
||||
Colors.white),
|
||||
activeColor: Colors.white,
|
||||
value: loginBloc.isChecked,
|
||||
checkColor: Colors.black,
|
||||
shape: const CircleBorder(),
|
||||
onChanged: (bool? newValue) {
|
||||
loginBloc.add(
|
||||
CheckBoxEvent(newValue: newValue));
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.5,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: 'Agree to ',
|
||||
style:
|
||||
const TextStyle(color: Colors.white),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '(Terms of Service)',
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/terms');
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: ' (Legal Statement)',
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/legal');
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: ' (Privacy Statement)',
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/privacy');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
SizedBox(
|
||||
child: DefaultButton(
|
||||
backgroundColor: loginBloc.isChecked
|
||||
? ColorsManager.btnColor
|
||||
: ColorsManager.grayColor,
|
||||
child: const Text('Sign in'),
|
||||
onPressed: () {
|
||||
if (loginBloc.loginFormKey.currentState!.validate()) {
|
||||
loginBloc.add(
|
||||
LoginButtonPressed(
|
||||
regionUuid:'' ,
|
||||
username: loginBloc.loginEmailController.text,
|
||||
password: loginBloc.loginPasswordController.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
"Don't you have an account? ",
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontSize: 13),
|
||||
)),
|
||||
Text(
|
||||
"Sign up",
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,18 +1,16 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/login_mobile_page.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/login_web_page.dart';
|
||||
import 'package:syncrow_web/utils/responsive_layout.dart';
|
||||
|
||||
class LoginPage extends StatelessWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const ResponsiveLayout(
|
||||
desktopBody: LoginWebPage(),
|
||||
mobileBody:LoginMobilePage()
|
||||
mobileBody:LoginWebPage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,42 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_state.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/region_model.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/forget_password_page.dart';
|
||||
import 'package:syncrow_web/pages/common/default_button.dart';
|
||||
import 'package:syncrow_web/pages/common/first_layer.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/login_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/login_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/login_state.dart';
|
||||
import 'package:syncrow_web/pages/home/view/home_page.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class LoginWebPage extends StatelessWidget {
|
||||
class LoginWebPage extends StatefulWidget {
|
||||
const LoginWebPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginWebPage> createState() => _LoginWebPageState();
|
||||
}
|
||||
|
||||
class _LoginWebPageState extends State<LoginWebPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: BlocProvider(
|
||||
create: (context) => LoginBloc(),
|
||||
child: BlocConsumer<LoginBloc, LoginState>(
|
||||
create: (BuildContext context) => AuthBloc()..add(RegionInitialEvent()),
|
||||
child: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is LoginSuccess) {
|
||||
// Navigate to home screen after successful login
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const HomePage()),
|
||||
MaterialPageRoute(builder: (context) => HomePage()),
|
||||
);
|
||||
} else if (state is LoginFailure) {
|
||||
// Show error message
|
||||
@ -34,161 +48,347 @@ class LoginWebPage extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is LoginLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return _buildLoginForm(context);
|
||||
}
|
||||
return _buildLoginForm(context,state);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginForm(BuildContext context) {
|
||||
final loginBloc = BlocProvider.of<LoginBloc>(context);
|
||||
final TextEditingController _usernameController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
return Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
height: MediaQuery.sizeOf(context).height,
|
||||
child: SvgPicture.asset(
|
||||
Assets.webBackground,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
height: MediaQuery.sizeOf(context).height,
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(Assets.vector),
|
||||
fit: BoxFit.cover,
|
||||
opacity: 0.9,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.all(50),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child:Center(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SvgPicture.asset(
|
||||
Assets.loginLogo,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.3),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 15),
|
||||
const Text(
|
||||
'Login',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Email",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.2,
|
||||
child: TextFormField(
|
||||
controller: _usernameController,
|
||||
decoration: textBoxDecoration()!.copyWith(hintText: 'Email'),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Password",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.2,
|
||||
child: TextFormField(
|
||||
controller: _passwordController,
|
||||
decoration: textBoxDecoration()!.copyWith(hintText: 'Password' ,),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
const Text(
|
||||
"Forgot Password?",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
// Trigger login event
|
||||
loginBloc.add(
|
||||
LoginButtonPressed(
|
||||
username: _usernameController.text,
|
||||
password: _passwordController.text,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Login'),
|
||||
),
|
||||
],
|
||||
Widget _buildLoginForm(BuildContext context,AuthState state) {
|
||||
final loginBloc = BlocProvider.of<AuthBloc>(context);
|
||||
Size size = MediaQuery.of(context).size;
|
||||
late ScrollController _scrollController;
|
||||
_scrollController = ScrollController();
|
||||
void _scrollToCenter() {
|
||||
final double middlePosition = _scrollController.position.maxScrollExtent / 2;
|
||||
_scrollController.animateTo(
|
||||
middlePosition,
|
||||
duration: const Duration(seconds: 1),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToCenter();
|
||||
});
|
||||
return Stack(
|
||||
children: [
|
||||
FirstLayer(
|
||||
second: Center(
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(size.width*0.02) ,
|
||||
margin: EdgeInsets.all(size.width*0.09),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Center(
|
||||
child:Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: SvgPicture.asset(
|
||||
Assets.loginLogo,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
],
|
||||
)),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||
border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2))),
|
||||
child: Form(
|
||||
key: loginBloc.loginFormKey,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: size.width*0.02,
|
||||
vertical: size.width*0.003),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 40),
|
||||
Text(
|
||||
'Login',
|
||||
style:Theme.of(context).textTheme.headlineLarge),
|
||||
SizedBox(height: size.height*0.03),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10,),
|
||||
|
||||
SizedBox(
|
||||
child: DropdownButtonFormField<String>(
|
||||
padding: EdgeInsets.zero,
|
||||
value: loginBloc.regionList!.any((region) => region.id == loginBloc.regionUuid)
|
||||
? loginBloc.regionUuid
|
||||
: null,
|
||||
|
||||
validator: loginBloc.validateRegion,
|
||||
icon: const Icon(
|
||||
Icons.keyboard_arrow_down_outlined,
|
||||
),
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
errorStyle: const TextStyle(height: 0),
|
||||
hintText: null,
|
||||
),
|
||||
hint: SizedBox(
|
||||
width: size.width * 0.12,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Select your region/country',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
isDense: true,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
items: loginBloc.regionList!.map((RegionModel region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region.id,
|
||||
child: SizedBox(
|
||||
width: size.width*0.08,
|
||||
child: Text(region.name)),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? value) {
|
||||
loginBloc.add(CheckEnableEvent());
|
||||
loginBloc.add(SelectRegionEvent(val: value!));
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text("Email",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
onChanged: (value) {
|
||||
loginBloc.add(CheckEnableEvent());
|
||||
// print(loginBloc.checkEnable());
|
||||
},
|
||||
validator:loginBloc.loginValidateEmail ,
|
||||
controller:loginBloc.loginEmailController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
errorStyle: const TextStyle(height: 0), // Hide the error text space
|
||||
hintText: 'Enter your email address',
|
||||
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400)
|
||||
),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text("Password",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
onChanged: (value) {
|
||||
loginBloc.add(CheckEnableEvent());
|
||||
},
|
||||
validator:loginBloc.validatePassword,
|
||||
obscureText:loginBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller:loginBloc.loginPasswordController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400),
|
||||
suffixIcon: IconButton(onPressed: () {
|
||||
loginBloc.add(PasswordVisibleEvent(newValue: loginBloc.obscureText));
|
||||
},
|
||||
icon: SizedBox(
|
||||
child: SvgPicture.asset(
|
||||
loginBloc.obscureText?
|
||||
Assets.visiblePassword :
|
||||
Assets.invisiblePassword,
|
||||
height: 15,
|
||||
width: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
errorStyle: const TextStyle(height: 0), // Hide the error text space
|
||||
),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
SizedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ForgetPasswordPage(),));
|
||||
},
|
||||
child: Text(
|
||||
"Forgot Password?",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black,fontSize: 14,fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Transform.scale(
|
||||
scale: 1.2, // Adjust the scale as needed
|
||||
child: Checkbox(
|
||||
fillColor: MaterialStateProperty.all<Color>(Colors.white),
|
||||
activeColor: Colors.white,
|
||||
value:loginBloc.isChecked,
|
||||
checkColor: Colors.black,
|
||||
shape: const CircleBorder(),
|
||||
onChanged: (bool? newValue) {
|
||||
loginBloc.add(CheckBoxEvent(newValue: newValue));
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width:size.width * 0.14,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: 'Agree to ',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '(Terms of Service)',
|
||||
style: const TextStyle(
|
||||
color: Colors.black,),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/terms');
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: ' (Legal Statement)',
|
||||
style: const TextStyle(color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/legal');
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: ' (Privacy Statement)',
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/privacy');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width:size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
enabled: loginBloc.checkValidate,
|
||||
child:Text('Sign in',
|
||||
style: Theme.of(context).textTheme.labelLarge !.copyWith(
|
||||
fontSize: 14,
|
||||
color:
|
||||
loginBloc.checkValidate ?
|
||||
ColorsManager.whiteColors:ColorsManager.whiteColors.withOpacity(0.2),
|
||||
)
|
||||
),
|
||||
onPressed: () {
|
||||
if(loginBloc.loginFormKey.currentState!.validate() ){
|
||||
loginBloc.add(LoginButtonPressed(
|
||||
regionUuid:loginBloc.regionUuid,
|
||||
username: loginBloc.loginEmailController.text,
|
||||
password: loginBloc.loginPasswordController.text,
|
||||
));
|
||||
}else{
|
||||
loginBloc.add(ChangeValidateEvent());
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 15.0),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [ SizedBox(child: Text(loginBloc.validate,
|
||||
style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),)],)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
)),
|
||||
const Spacer(),
|
||||
],
|
||||
),),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (state is AuthLoading)
|
||||
const Center(child: CircularProgressIndicator())
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
75
lib/pages/common/custom_dialog.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
Future<void> showCustomDialog({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
String? title,
|
||||
String? iconPath,
|
||||
double? dialogHeight,
|
||||
double? iconHeight,
|
||||
double? iconWidth,
|
||||
VoidCallback? onOkPressed,
|
||||
bool barrierDismissible = false, required actions,
|
||||
}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
barrierDismissible: barrierDismissible,
|
||||
builder: (BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
return AlertDialog(
|
||||
alignment: Alignment.center,
|
||||
content: SizedBox(
|
||||
height: dialogHeight ?? size.height * 0.15,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (iconPath != null)
|
||||
SvgPicture.asset(
|
||||
iconPath,
|
||||
height: iconHeight ?? 35,
|
||||
width: iconWidth ?? 35,
|
||||
),
|
||||
if (title != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
message,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.black),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: onOkPressed ?? () => Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
'OK',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 16),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
221
lib/pages/common/custom_table.dart
Normal file
@ -0,0 +1,221 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class DynamicTable extends StatefulWidget {
|
||||
final List<String> headers;
|
||||
final List<List<dynamic>> data;
|
||||
final BoxDecoration? headerDecoration;
|
||||
final BoxDecoration? cellDecoration;
|
||||
final Size size;
|
||||
final bool withCheckBox;
|
||||
final bool isEmpty;
|
||||
final void Function(bool?)? selectAll;
|
||||
final void Function(int, bool?)? onRowCheckboxChanged;
|
||||
final List<String>? initialSelectedIds;
|
||||
|
||||
const DynamicTable({
|
||||
super.key,
|
||||
required this.headers,
|
||||
required this.data,
|
||||
required this.size,
|
||||
required this.isEmpty,
|
||||
required this.withCheckBox,
|
||||
this.headerDecoration,
|
||||
this.cellDecoration,
|
||||
this.selectAll,
|
||||
this.onRowCheckboxChanged,
|
||||
this.initialSelectedIds,
|
||||
});
|
||||
|
||||
@override
|
||||
_DynamicTableState createState() => _DynamicTableState();
|
||||
}
|
||||
|
||||
class _DynamicTableState extends State<DynamicTable> {
|
||||
late List<bool> _selected;
|
||||
bool _selectAll = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selected = List<bool>.generate(widget.data.length, (index) {
|
||||
return widget.initialSelectedIds != null &&
|
||||
widget.initialSelectedIds!.contains(widget.data[index][1]);
|
||||
});
|
||||
_selectAll = _selected.every((element) => element == true);
|
||||
}
|
||||
|
||||
void _toggleSelectAll(bool? value) {
|
||||
setState(() {
|
||||
_selectAll = value ?? false;
|
||||
_selected = List<bool>.filled(widget.data.length, _selectAll);
|
||||
if (widget.selectAll != null) {
|
||||
widget.selectAll!(_selectAll);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleRowSelection(int index, bool? value) {
|
||||
setState(() {
|
||||
_selected[index] = value ?? false;
|
||||
_selectAll = _selected.every((element) => element == true);
|
||||
if (widget.onRowCheckboxChanged != null) {
|
||||
widget.onRowCheckboxChanged!(index, _selected[index]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: widget.cellDecoration,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child:
|
||||
ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: widget.size.width,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: widget.headerDecoration ??
|
||||
BoxDecoration(color: Colors.grey[200]),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.withCheckBox)
|
||||
_buildSelectAllCheckbox(),
|
||||
...widget.headers
|
||||
.map((header) => _buildTableHeaderCell(header))
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
widget.isEmpty?
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.emptyTable
|
||||
),
|
||||
const SizedBox(height: 15,),
|
||||
Text('No Passwords',style: Theme.of(context).textTheme.bodySmall!.copyWith(color:ColorsManager.grayColor ),)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
):
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.data.length,
|
||||
itemBuilder: (context, index) {
|
||||
final row = widget.data[index];
|
||||
return Row(
|
||||
children: [
|
||||
if (widget.withCheckBox)
|
||||
_buildRowCheckbox(index,widget.size.height*0.10),
|
||||
...row.map((cell) =>
|
||||
_buildTableCell(cell.toString(),widget.size.height*0.10)).toList(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSelectAllCheckbox() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
||||
decoration: const BoxDecoration(
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(color: ColorsManager.boxDivider))),
|
||||
child: Checkbox(
|
||||
value: _selectAll,
|
||||
onChanged: _toggleSelectAll,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRowCheckbox(int index,size) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
||||
height:size ,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ColorsManager.boxDivider,
|
||||
width: 1.0,
|
||||
),
|
||||
)),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Center(child: Checkbox(
|
||||
value: _selected[index],
|
||||
onChanged: (bool? value) {
|
||||
_toggleRowSelection(index, value);
|
||||
},
|
||||
),)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
Widget _buildTableHeaderCell(String title) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(color: ColorsManager.boxDivider))),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(title, style: const TextStyle(fontWeight: FontWeight.w400,fontSize: 13,color: Color(0xFF999999))),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableCell(String content,size) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
height:size ,
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ColorsManager.boxDivider,
|
||||
width: 1.0,
|
||||
),
|
||||
)),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
content,
|
||||
style: const TextStyle(color: Colors.black, fontSize: 10,fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
83
lib/pages/common/custom_web_textfield.dart
Normal file
@ -0,0 +1,83 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
class CustomWebTextField extends StatelessWidget {
|
||||
const CustomWebTextField({
|
||||
super.key,
|
||||
required this.isRequired,
|
||||
required this.textFieldName,
|
||||
required this.controller,
|
||||
this.description,
|
||||
this.validator,
|
||||
});
|
||||
|
||||
final bool isRequired;
|
||||
final String textFieldName;
|
||||
final String? description;
|
||||
final TextEditingController? controller;
|
||||
final String? Function(String?)? validator;
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if(isRequired)
|
||||
Row(
|
||||
children: [
|
||||
Text('* ',
|
||||
style: Theme.of(context)
|
||||
.textTheme.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
Text(textFieldName, style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: Colors.black,fontSize: 13),),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10,),
|
||||
Expanded(
|
||||
child: Text(
|
||||
description??'',
|
||||
style: Theme.of(context)
|
||||
.textTheme.bodySmall!
|
||||
.copyWith(fontSize: 9,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.textGray),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 7,),
|
||||
Container(
|
||||
decoration: containerDecoration.copyWith(
|
||||
color: const Color(0xFFF5F6F7),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.3),
|
||||
spreadRadius:2,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(1, 1), // changes position of shadow
|
||||
),
|
||||
]
|
||||
),
|
||||
child: TextFormField(
|
||||
validator: validator,
|
||||
controller: controller,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
decoration: textBoxDecoration()!
|
||||
.copyWith(
|
||||
errorStyle: const TextStyle(height: 0), // Hide the error text space
|
||||
|
||||
hintText: 'Please enter'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
94
lib/pages/common/date_time_widget.dart
Normal file
@ -0,0 +1,94 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class DateTimeWebWidget extends StatelessWidget {
|
||||
const DateTimeWebWidget({
|
||||
super.key,
|
||||
required this.size,
|
||||
required this.isRequired,
|
||||
required this.title,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.firstString,
|
||||
required this.secondString,
|
||||
required this.icon,
|
||||
});
|
||||
|
||||
final Size size;
|
||||
final String title;
|
||||
final bool isRequired;
|
||||
final String firstString;
|
||||
final String secondString;
|
||||
final String icon;
|
||||
final Function()? startTime;
|
||||
final Function()? endTime;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if(isRequired)
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
Text(title??'' ,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: Colors.black,fontSize: 13),),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8,),
|
||||
Container(
|
||||
height:size.height * 0.055 ,
|
||||
padding: EdgeInsets.only(top: 10,bottom: 10,right: 30,left: 10),
|
||||
decoration: containerDecoration,
|
||||
child: FittedBox(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: startTime,
|
||||
child: FittedBox(
|
||||
child: Text(firstString,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400),),
|
||||
)
|
||||
),
|
||||
SizedBox(width: 30,),
|
||||
const Icon(Icons.arrow_right_alt),
|
||||
SizedBox(width: 30,),
|
||||
|
||||
InkWell(
|
||||
onTap:endTime,
|
||||
child: FittedBox(
|
||||
child: Text(secondString,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400),
|
||||
),
|
||||
)),
|
||||
SizedBox(width: 30,),
|
||||
|
||||
SvgPicture.asset(
|
||||
icon,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
99
lib/pages/common/default_button.dart
Normal file
@ -0,0 +1,99 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class DefaultButton extends StatelessWidget {
|
||||
const DefaultButton({
|
||||
super.key,
|
||||
this.enabled = true,
|
||||
this.onPressed,
|
||||
required this.child,
|
||||
this.isSecondary = false,
|
||||
this.isLoading = false,
|
||||
this.isDone = false,
|
||||
this.customTextStyle,
|
||||
this.customButtonStyle,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.borderRadius,
|
||||
this.height,
|
||||
this.padding,
|
||||
});
|
||||
final void Function()? onPressed;
|
||||
final Widget child;
|
||||
final double? height;
|
||||
final bool isSecondary;
|
||||
final double? borderRadius;
|
||||
final bool enabled;
|
||||
final double? padding;
|
||||
final bool isDone;
|
||||
final bool isLoading;
|
||||
final TextStyle? customTextStyle;
|
||||
final ButtonStyle? customButtonStyle;
|
||||
final Color? backgroundColor;
|
||||
final Color? foregroundColor;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ElevatedButton(
|
||||
onPressed: enabled ? onPressed : null,
|
||||
style: isSecondary
|
||||
? null
|
||||
: customButtonStyle ??
|
||||
ButtonStyle(
|
||||
textStyle: MaterialStateProperty.all(
|
||||
customTextStyle
|
||||
?? Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: foregroundColor,
|
||||
fontWeight: FontWeight.normal
|
||||
),
|
||||
),
|
||||
|
||||
foregroundColor: MaterialStateProperty.all(
|
||||
isSecondary
|
||||
? Colors.black
|
||||
: enabled
|
||||
? foregroundColor ?? Colors.white
|
||||
: Colors.black,
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) {
|
||||
return enabled
|
||||
? backgroundColor ?? ColorsManager.primaryColor
|
||||
: Colors.black.withOpacity(0.2);
|
||||
}),
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(borderRadius ?? 20),
|
||||
),
|
||||
),
|
||||
fixedSize: MaterialStateProperty.all(
|
||||
const Size.fromHeight(50),
|
||||
),
|
||||
padding: MaterialStateProperty.all(
|
||||
EdgeInsets.all(padding ?? 10),
|
||||
),
|
||||
minimumSize: MaterialStateProperty.all(
|
||||
const Size.fromHeight(50),
|
||||
),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: height ?? 50,
|
||||
child: Center(
|
||||
child: isLoading
|
||||
? const SizedBox.square(
|
||||
dimension: 24,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: isDone
|
||||
? const Icon(
|
||||
Icons.check_circle_outline,
|
||||
color: Colors.white,
|
||||
)
|
||||
: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
38
lib/pages/common/first_layer.dart
Normal file
@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class FirstLayer extends StatelessWidget {
|
||||
final Widget? second;
|
||||
const FirstLayer({super.key,this.second});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
height: MediaQuery.sizeOf(context).height,
|
||||
child: SvgPicture.asset(
|
||||
Assets.webBackground,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
height: MediaQuery.sizeOf(context).height,
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(Assets.vector),
|
||||
fit: BoxFit.cover,
|
||||
opacity: 0.9,
|
||||
),
|
||||
),
|
||||
),
|
||||
second!
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
92
lib/pages/common/hour_picker_dialog.dart
Normal file
@ -0,0 +1,92 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HourPickerDialog extends StatefulWidget {
|
||||
final TimeOfDay initialTime;
|
||||
const HourPickerDialog({super.key, required this.initialTime});
|
||||
|
||||
@override
|
||||
_HourPickerDialogState createState() => _HourPickerDialogState();
|
||||
}
|
||||
|
||||
class _HourPickerDialogState extends State<HourPickerDialog> {
|
||||
late int _selectedHour;
|
||||
bool _isPm = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedHour = widget.initialTime.hour > 12 ? widget.initialTime.hour - 12 : widget.initialTime.hour;
|
||||
_isPm = widget.initialTime.period == DayPeriod.pm;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Select Hour'),
|
||||
content: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
DropdownButton<int>(
|
||||
value: _selectedHour,
|
||||
items: List.generate(12, (index) {
|
||||
int displayHour = index + 1;
|
||||
return DropdownMenuItem(
|
||||
value: displayHour,
|
||||
child: Text(displayHour.toString()),
|
||||
);
|
||||
}),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_selectedHour = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
SizedBox(width: 16.0),
|
||||
DropdownButton<bool>(
|
||||
value: _isPm,
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
value: false,
|
||||
child: Text('AM'),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: true,
|
||||
child: Text('PM'),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_isPm = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(null),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
int hour = _isPm ? _selectedHour + 12 : _selectedHour;
|
||||
Navigator.of(context).pop(TimeOfDay(hour: hour, minute: 0));
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TimeOfDay?> showHourPicker({
|
||||
required BuildContext context,
|
||||
required TimeOfDay initialTime,
|
||||
}) {
|
||||
return showDialog<TimeOfDay>(
|
||||
context: context,
|
||||
builder: (context) => HourPickerDialog(initialTime: initialTime),
|
||||
);
|
||||
}
|
74
lib/pages/common/info_dialog.dart
Normal file
@ -0,0 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class InfoDialog extends StatelessWidget {
|
||||
final String title;
|
||||
final String content;
|
||||
final Size? size;
|
||||
final List<Widget>? actions;
|
||||
|
||||
InfoDialog({
|
||||
required this.title,
|
||||
required this.content,
|
||||
this.actions,
|
||||
this.size,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
alignment: Alignment.center,
|
||||
content: SizedBox(
|
||||
height: size!.height * 0.25,
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: SvgPicture.asset(
|
||||
Assets.deviceNoteIcon,
|
||||
height: 35,
|
||||
width: 35,
|
||||
),
|
||||
),
|
||||
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
Text(
|
||||
content,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 18),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
actions: actions ??
|
||||
<Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,23 +1,34 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:graphview/GraphView.dart';
|
||||
import 'package:syncrow_web/pages/access_management/view/access_management.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/user_model.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
|
||||
import 'package:syncrow_web/pages/home/home_model/home_item_model.dart';
|
||||
import 'package:syncrow_web/services/home_api.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
final Graph graph = Graph()..isTree = true;
|
||||
final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();
|
||||
List<Node> sourcesList = [];
|
||||
List<Node> destinationsList = [];
|
||||
static UserModel? user;
|
||||
|
||||
HomeBloc() : super((HomeInitial())) {
|
||||
on<CreateNewNode>(_createNode);
|
||||
fetchUserInfo();
|
||||
|
||||
}
|
||||
|
||||
void _createNode(CreateNewNode event, Emitter<HomeState> emit) async {
|
||||
emit(HomeInitial());
|
||||
sourcesList.add(event.sourceNode);
|
||||
destinationsList.add(event.destinationNode);
|
||||
|
||||
for (int i = 0; i < sourcesList.length; i++) {
|
||||
graph.addEdge(sourcesList[i], destinationsList[i]);
|
||||
}
|
||||
@ -27,7 +38,89 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
..levelSeparation = (150)
|
||||
..subtreeSeparation = (150)
|
||||
..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM);
|
||||
|
||||
emit(HomeUpdateTree(graph: graph, builder: builder));
|
||||
}
|
||||
|
||||
|
||||
|
||||
Future fetchUserInfo() async {
|
||||
try {
|
||||
var uuid =
|
||||
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
||||
user = await HomeApi().fetchUserInfo(uuid);
|
||||
emit(HomeUserInfoLoaded(user!)); // Emit state after fetching user info
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<HomeItemModel> homeItems = [
|
||||
HomeItemModel(
|
||||
title: 'Access',
|
||||
icon: Assets.accessIcon,
|
||||
active: true,
|
||||
onPress: (context) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => AccessManagementPage()),
|
||||
);
|
||||
},
|
||||
color: null,
|
||||
),
|
||||
HomeItemModel(
|
||||
title: 'Space Management',
|
||||
icon: Assets.spaseManagementIcon,
|
||||
active: true,
|
||||
onPress: (context) {
|
||||
},
|
||||
color: ColorsManager.primaryColor,
|
||||
),
|
||||
HomeItemModel(
|
||||
title: 'Devices',
|
||||
icon: Assets.devicesIcon,
|
||||
active: true,
|
||||
onPress: (context) {
|
||||
},
|
||||
color: ColorsManager.primaryColor,
|
||||
),
|
||||
HomeItemModel(
|
||||
title: 'Move in',
|
||||
icon: Assets.moveinIcon,
|
||||
active: false,
|
||||
onPress: (context) {
|
||||
},
|
||||
color: ColorsManager.primaryColor,
|
||||
),
|
||||
HomeItemModel(
|
||||
title: 'Construction',
|
||||
icon: Assets.constructionIcon,
|
||||
active: false,
|
||||
onPress: (context) {
|
||||
},
|
||||
color: ColorsManager.primaryColor,
|
||||
),
|
||||
HomeItemModel(
|
||||
title: 'Energy',
|
||||
icon: Assets.energyIcon,
|
||||
active: false,
|
||||
onPress: (context) {
|
||||
},
|
||||
color: ColorsManager.slidingBlueColor.withOpacity(0.2),
|
||||
),
|
||||
HomeItemModel(
|
||||
title: 'Integrations',
|
||||
icon: Assets.integrationsIcon,
|
||||
active: false,
|
||||
onPress: (context) {
|
||||
},
|
||||
color: ColorsManager.slidingBlueColor.withOpacity(0.2),
|
||||
),
|
||||
HomeItemModel(
|
||||
title: 'Asset',
|
||||
icon: Assets.assetIcon,
|
||||
active: false,
|
||||
onPress: (context) {
|
||||
},
|
||||
color: ColorsManager.slidingBlueColor.withOpacity(0.2),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:graphview/GraphView.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/user_model.dart';
|
||||
|
||||
abstract class HomeState extends Equatable {
|
||||
const HomeState();
|
||||
@ -24,3 +25,8 @@ class HomeUpdateTree extends HomeState {
|
||||
@override
|
||||
List<Object> get props => [graph, builder];
|
||||
}
|
||||
class HomeUserInfoLoaded extends HomeState {
|
||||
final UserModel user;
|
||||
|
||||
HomeUserInfoLoaded(this.user);
|
||||
}
|
||||
|
22
lib/pages/home/home_model/home_item_model.dart
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class HomeItemModel {
|
||||
final String? title;
|
||||
final String? icon;
|
||||
final Color? color;
|
||||
final bool? active;
|
||||
final void Function(BuildContext context) onPress;
|
||||
|
||||
|
||||
HomeItemModel({
|
||||
this.title,
|
||||
this.icon,
|
||||
this.color,
|
||||
this.active,
|
||||
required this.onPress,
|
||||
});
|
||||
}
|
74
lib/pages/home/view/home_card.dart
Normal file
@ -0,0 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class HomeCard extends StatelessWidget {
|
||||
final bool active;
|
||||
final String img;
|
||||
final int index;
|
||||
final String name;
|
||||
final Function()? onTap;
|
||||
const HomeCard({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.index,
|
||||
this.active = false,
|
||||
required this.img,
|
||||
required this.onTap,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool evenNumbers = index % 2 == 0;
|
||||
return InkWell(
|
||||
onTap: active ? onTap : null,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 10,right: 10,bottom: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: evenNumbers && active?
|
||||
ColorsManager.blueColor.withOpacity(0.8) :
|
||||
(active ?ColorsManager.blueColor
|
||||
: ColorsManager.blueColor.withOpacity(0.2)),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: SvgPicture.asset(
|
||||
img,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,30 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/view/tree_page.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:syncrow_web/pages/home/view/home_page_mobile.dart';
|
||||
import 'package:syncrow_web/pages/home/view/home_page_web.dart';
|
||||
import 'package:syncrow_web/utils/responsive_layout.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WebScaffold(
|
||||
appBarTitle: 'Space Management',
|
||||
appBarBody:[
|
||||
Text(
|
||||
'Community structure',
|
||||
style: appBarTextStyle,
|
||||
),
|
||||
Text(
|
||||
'Community ',
|
||||
style: appBarTextStyle
|
||||
),
|
||||
],
|
||||
scaffoldBody: BlocProvider(
|
||||
create: (context) => HomeBloc(),
|
||||
child: const TreeWidget(),
|
||||
),
|
||||
return ResponsiveLayout(
|
||||
desktopBody: HomeWebPage(),
|
||||
mobileBody:HomeMobilePage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
118
lib/pages/home/view/home_page_mobile.dart
Normal file
@ -0,0 +1,118 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/view/home_card.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
|
||||
class HomeMobilePage extends StatelessWidget {
|
||||
HomeMobilePage({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Size size = MediaQuery.of(context).size;
|
||||
return WebScaffold(
|
||||
enableMenuSideba: false,
|
||||
appBarTitle: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.loginLogo,
|
||||
width: 150,
|
||||
),
|
||||
],
|
||||
),
|
||||
scaffoldBody: BlocProvider(
|
||||
create: (context) => HomeBloc(),
|
||||
child: SizedBox(
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(height: size.height * 0.05),
|
||||
const Text(
|
||||
'ACCESS YOUR APPS',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: SizedBox(
|
||||
height: size.height * 0.6,
|
||||
width: size.width * 0.68,
|
||||
child: GridView.builder(
|
||||
itemCount: 8,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 20.0,
|
||||
mainAxisSpacing: 20.0,
|
||||
childAspectRatio: 1.5,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
return HomeCard(
|
||||
index: index,
|
||||
active: homeItems[index]['active'],
|
||||
name: homeItems[index]['title'],
|
||||
img: homeItems[index]['icon'],
|
||||
onTap: () {},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
dynamic homeItems = [
|
||||
{
|
||||
'title': 'Access',
|
||||
'icon': Assets.accessIcon,
|
||||
'active': true,
|
||||
},
|
||||
{
|
||||
'title': 'Space\nManagement',
|
||||
'icon': Assets.spaseManagementIcon,
|
||||
'color': ColorsManager.primaryColor,
|
||||
'active': true,
|
||||
},
|
||||
{
|
||||
'title': 'Devices',
|
||||
'icon': Assets.devicesIcon,
|
||||
'active': true,
|
||||
},
|
||||
{
|
||||
'title': 'Move in',
|
||||
'icon': Assets.moveinIcon,
|
||||
'active': false,
|
||||
},
|
||||
{
|
||||
'title': 'Construction',
|
||||
'icon': Assets.constructionIcon,
|
||||
'active': false,
|
||||
},
|
||||
{
|
||||
'title': 'Energy',
|
||||
'icon': Assets.energyIcon,
|
||||
'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
|
||||
'active': false,
|
||||
},
|
||||
{
|
||||
'title': 'Integrations',
|
||||
'icon': Assets.integrationsIcon,
|
||||
'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
|
||||
'active': false,
|
||||
},
|
||||
{
|
||||
'title': 'Asset',
|
||||
'icon': Assets.assetIcon,
|
||||
'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
|
||||
'active': false,
|
||||
},
|
||||
];
|
||||
}
|
79
lib/pages/home/view/home_page_web.dart
Normal file
@ -0,0 +1,79 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
|
||||
import 'package:syncrow_web/pages/home/view/home_card.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
|
||||
class HomeWebPage extends StatelessWidget {
|
||||
HomeWebPage({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Size size = MediaQuery.of(context).size;
|
||||
return WebScaffold(
|
||||
enableMenuSideba: false,
|
||||
appBarTitle: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.loginLogo,
|
||||
width: 150,
|
||||
),
|
||||
],
|
||||
),
|
||||
scaffoldBody: BlocProvider(
|
||||
create: (context) => HomeBloc(),
|
||||
child: BlocConsumer<HomeBloc, HomeState>(
|
||||
listener: (BuildContext context, state) {},
|
||||
builder: (context, state) {
|
||||
final homeBloc = BlocProvider.of<HomeBloc>(context);
|
||||
return SizedBox(
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(height: size.height * 0.1),
|
||||
Text(
|
||||
'ACCESS YOUR APPS',
|
||||
style: Theme.of(context)
|
||||
.textTheme.headlineLarge!
|
||||
.copyWith(color: Colors.black, fontSize: 40),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: SizedBox(
|
||||
height: size.height * 0.6,
|
||||
width: size.width * 0.68,
|
||||
child: GridView.builder(
|
||||
itemCount: 8,
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
crossAxisSpacing: 20.0,
|
||||
mainAxisSpacing: 20.0,
|
||||
childAspectRatio: 1.5,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
return HomeCard(
|
||||
index: index,
|
||||
active: homeBloc.homeItems[index].active!,
|
||||
name: homeBloc.homeItems[index].title!,
|
||||
img: homeBloc.homeItems[index].icon!,
|
||||
onTap: () => homeBloc.homeItems[index].onPress(context),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
497
lib/pages/visitor_password/bloc/visitor_password_bloc.dart
Normal file
@ -0,0 +1,497 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:syncrow_web/pages/common/custom_dialog.dart';
|
||||
import 'package:syncrow_web/pages/common/hour_picker_dialog.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart';
|
||||
import 'package:syncrow_web/services/access_mang_api.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
|
||||
class VisitorPasswordBloc
|
||||
extends Bloc<VisitorPasswordEvent, VisitorPasswordState> {
|
||||
VisitorPasswordBloc() : super(VisitorPasswordInitial()) {
|
||||
on<SelectUsageFrequency>(selectUsageFrequency);
|
||||
on<FetchDevice>(_onFetchDevice);
|
||||
on<SelectPasswordType>(selectAccessType);
|
||||
on<SelectTimeVisitorPassword>(selectTimeVisitorPassword);
|
||||
on<ToggleRepeatEvent>(toggleRepeat);
|
||||
on<ToggleDaySelectionEvent>(toggleDaySelection);
|
||||
on<SelectDeviceEvent>(selectDevice);
|
||||
on<UpdateFilteredDevicesEvent>(_onUpdateFilteredDevices);
|
||||
on<OnlineOneTimePasswordEvent>(postOnlineOneTimePassword);
|
||||
on<OnlineMultipleTimePasswordEvent>(postOnlineMultipleTimePassword);
|
||||
on<OfflineMultipleTimePasswordEvent>(postOfflineMultipleTimePassword);
|
||||
on<OfflineOneTimePasswordEvent>(postOfflineOneTimePassword);
|
||||
on<SelectTimeEvent>(selectTimeOfLinePassword);
|
||||
on<ChangeTimeEvent>(changeTime);
|
||||
}
|
||||
final TextEditingController userNameController = TextEditingController();
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
|
||||
final TextEditingController deviceNameController = TextEditingController();
|
||||
final TextEditingController deviceIdController = TextEditingController();
|
||||
final TextEditingController unitNameController = TextEditingController();
|
||||
final TextEditingController virtualAddressController =
|
||||
TextEditingController();
|
||||
List<String> selectedDevices = [];
|
||||
|
||||
List<DeviceModel> data = [];
|
||||
List<String> selectedDeviceIds = [];
|
||||
String effectiveTime = 'Start Time';
|
||||
String expirationTime = 'End Time';
|
||||
|
||||
final forgetFormKey = GlobalKey<FormState>();
|
||||
|
||||
String accessTypeSelected = 'Online Password';
|
||||
String usageFrequencySelected = 'One-Time';
|
||||
String passwordController = '';
|
||||
|
||||
bool repeat = false;
|
||||
|
||||
int? effectiveTimeTimeStamp;
|
||||
int? expirationTimeTimeStamp;
|
||||
|
||||
DateTime? startTime = DateTime.now();
|
||||
DateTime? endTime;
|
||||
|
||||
String startTimeAccess = 'Start Time';
|
||||
String endTimeAccess = 'End Time';
|
||||
|
||||
selectAccessType(
|
||||
SelectPasswordType event, Emitter<VisitorPasswordState> emit) {
|
||||
accessTypeSelected = event.type;
|
||||
emit(PasswordTypeSelected(event.type));
|
||||
}
|
||||
|
||||
selectUsageFrequency(
|
||||
SelectUsageFrequency event, Emitter<VisitorPasswordState> emit) {
|
||||
usageFrequencySelected = event.usageType;
|
||||
emit(UsageFrequencySelected(event.usageType));
|
||||
}
|
||||
|
||||
Future<void> selectTimeVisitorPassword(
|
||||
SelectTimeVisitorPassword event,
|
||||
Emitter<VisitorPasswordState> emit,
|
||||
) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: event.context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(2015, 8),
|
||||
lastDate: DateTime(3101),
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
final TimeOfDay? timePicked = await showTimePicker(
|
||||
context: event.context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: ColorsManager.primaryColor,
|
||||
onSurface: Colors.black,
|
||||
),
|
||||
buttonTheme: const ButtonThemeData(
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Colors.green,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (timePicked != null) {
|
||||
final selectedDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
picked.day,
|
||||
timePicked.hour,
|
||||
timePicked.minute,
|
||||
);
|
||||
|
||||
final selectedTimestamp =
|
||||
selectedDateTime.millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
if (event.isStart) {
|
||||
if (expirationTimeTimeStamp != null &&
|
||||
selectedTimestamp > expirationTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar(
|
||||
'Effective Time cannot be later than Expiration Time.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
effectiveTimeTimeStamp = selectedTimestamp;
|
||||
startTimeAccess = selectedDateTime.toString().split('.').first;
|
||||
} else {
|
||||
if (effectiveTimeTimeStamp != null &&
|
||||
selectedTimestamp < effectiveTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar(
|
||||
'Expiration Time cannot be earlier than Effective Time.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
expirationTimeTimeStamp = selectedTimestamp;
|
||||
endTimeAccess = selectedDateTime.toString().split('.').first;
|
||||
}
|
||||
emit(ChangeTimeState());
|
||||
emit(VisitorPasswordInitial());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool toggleRepeat(
|
||||
ToggleRepeatEvent event, Emitter<VisitorPasswordState> emit) {
|
||||
emit(LoadingInitialState());
|
||||
repeat = !repeat;
|
||||
emit(IsRepeatState(repeat: repeat));
|
||||
return repeat;
|
||||
}
|
||||
|
||||
List<Map<String, String>> days = [
|
||||
{"day": "Sun", "key": "Sun"},
|
||||
{"day": "Mon", "key": "Mon"},
|
||||
{"day": "Tue", "key": "Tue"},
|
||||
{"day": "Wed", "key": "Wed"},
|
||||
{"day": "Thu", "key": "Thu"},
|
||||
{"day": "Fri", "key": "Fri"},
|
||||
{"day": "Sat", "key": "Sat"},
|
||||
];
|
||||
|
||||
List<String> selectedDays = [];
|
||||
|
||||
Future<void> toggleDaySelection(
|
||||
ToggleDaySelectionEvent event,
|
||||
Emitter<VisitorPasswordState> emit,
|
||||
) async {
|
||||
emit(LoadingInitialState());
|
||||
if (selectedDays.contains(event.key)) {
|
||||
selectedDays.remove(event.key);
|
||||
} else {
|
||||
selectedDays.add(event.key);
|
||||
}
|
||||
emit(ChangeTimeState());
|
||||
}
|
||||
|
||||
Future<void> _onFetchDevice(
|
||||
FetchDevice event, Emitter<VisitorPasswordState> emit) async {
|
||||
try {
|
||||
emit(DeviceLoaded());
|
||||
data = await AccessMangApi().fetchDevices();
|
||||
emit(TableLoaded(data));
|
||||
} catch (e) {
|
||||
emit(FailedState(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
//online password
|
||||
Future<void> postOnlineOneTimePassword(OnlineOneTimePasswordEvent event,
|
||||
Emitter<VisitorPasswordState> emit) async {
|
||||
try {
|
||||
emit(LoadingInitialState());
|
||||
generate7DigitNumber();
|
||||
bool res = await AccessMangApi().postOnlineOneTime(
|
||||
email: event.email,
|
||||
password: passwordController,
|
||||
devicesUuid: selectedDevices,
|
||||
passwordName: event.passwordName,
|
||||
effectiveTime: effectiveTimeTimeStamp.toString(),
|
||||
invalidTime: expirationTimeTimeStamp.toString());
|
||||
if (res == true) {
|
||||
emit(SuccessState());
|
||||
} else {
|
||||
throw Exception('Failed to create password');
|
||||
}
|
||||
emit(TableLoaded(data));
|
||||
} catch (e) {
|
||||
emit(FailedState(e.toString()));
|
||||
Navigator.pop(event.context!);
|
||||
stateDialog(
|
||||
context: event.context!,
|
||||
message: e.toString(),
|
||||
title: 'Something Wrong');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> postOnlineMultipleTimePassword(
|
||||
OnlineMultipleTimePasswordEvent event,
|
||||
Emitter<VisitorPasswordState> emit) async {
|
||||
try {
|
||||
emit(LoadingInitialState());
|
||||
|
||||
await generate7DigitNumber();
|
||||
bool res = await AccessMangApi().postOnlineMultipleTime(
|
||||
scheduleList: [
|
||||
if (repeat)
|
||||
Schedule(
|
||||
effectiveTime: getTimeFromDateTimeString(expirationTime),
|
||||
invalidTime:
|
||||
getTimeFromDateTimeString(effectiveTime).toString(),
|
||||
workingDay: selectedDays,
|
||||
),
|
||||
],
|
||||
password: passwordController,
|
||||
invalidTime: expirationTimeTimeStamp.toString(),
|
||||
effectiveTime: effectiveTimeTimeStamp.toString(),
|
||||
email: event.email,
|
||||
devicesUuid: selectedDevices,
|
||||
passwordName: event.passwordName);
|
||||
if (res == true) {
|
||||
emit(SuccessState());
|
||||
}else {
|
||||
throw Exception('Failed to create password');
|
||||
}
|
||||
emit(TableLoaded(data));
|
||||
|
||||
} catch (e) {
|
||||
emit(FailedState(e.toString()));
|
||||
Navigator.pop(event.context!);
|
||||
stateDialog(
|
||||
context: event.context!,
|
||||
message: e.toString(),
|
||||
title: 'Something Wrong');
|
||||
}
|
||||
}
|
||||
|
||||
//offline password
|
||||
Future<void> postOfflineOneTimePassword(OfflineOneTimePasswordEvent event,
|
||||
Emitter<VisitorPasswordState> emit) async {
|
||||
try {
|
||||
emit(LoadingInitialState());
|
||||
await generate7DigitNumber();
|
||||
bool res = await AccessMangApi().postOffLineOneTime(
|
||||
email: event.email,
|
||||
devicesUuid: selectedDevices,
|
||||
passwordName: event.passwordName);
|
||||
if (res == true) {
|
||||
emit(SuccessState());
|
||||
}else {
|
||||
throw Exception('Failed to create password');
|
||||
}
|
||||
emit(TableLoaded(data));
|
||||
|
||||
} catch (e) {
|
||||
emit(FailedState(e.toString()));
|
||||
Navigator.pop(event.context!);
|
||||
stateDialog(
|
||||
context: event.context!,
|
||||
message: e.toString(),
|
||||
title: 'Something Wrong');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> postOfflineMultipleTimePassword(
|
||||
OfflineMultipleTimePasswordEvent event,
|
||||
Emitter<VisitorPasswordState> emit) async {
|
||||
try {
|
||||
emit(LoadingInitialState());
|
||||
await generate7DigitNumber();
|
||||
bool res = await AccessMangApi().postOffLineMultipleTime(
|
||||
email: event.email,
|
||||
devicesUuid: selectedDevices,
|
||||
passwordName: event.passwordName,
|
||||
invalidTime: expirationTimeTimeStamp.toString(),
|
||||
effectiveTime: effectiveTimeTimeStamp.toString(),
|
||||
);
|
||||
if (res == true) {
|
||||
emit(SuccessState());
|
||||
}else {
|
||||
throw Exception('Failed to create password');
|
||||
}
|
||||
emit(TableLoaded(data));
|
||||
|
||||
} catch (e) {
|
||||
emit(FailedState(e.toString()));
|
||||
Navigator.pop(event.context!);
|
||||
stateDialog(
|
||||
context: event.context!,
|
||||
message: e.toString(),
|
||||
title: 'Something Wrong'); }
|
||||
}
|
||||
|
||||
void selectDevice(
|
||||
SelectDeviceEvent event, Emitter<VisitorPasswordState> emit) {
|
||||
if (selectedDeviceIds.contains(event.deviceId)) {
|
||||
selectedDeviceIds.remove(event.deviceId);
|
||||
} else {
|
||||
selectedDeviceIds.add(event.deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
String? validate(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future generate7DigitNumber() async {
|
||||
passwordController = '';
|
||||
Random random = Random();
|
||||
int min = 1000000;
|
||||
int max = 9999999;
|
||||
passwordController = (min + random.nextInt(max - min + 1)).toString();
|
||||
return passwordController;
|
||||
}
|
||||
|
||||
String getTimeOnly(DateTime? dateTime) {
|
||||
if (dateTime == null) return '';
|
||||
return DateFormat('HH:mm').format(dateTime);
|
||||
}
|
||||
|
||||
void filterDevices() {
|
||||
final deviceName = deviceNameController.text.toLowerCase();
|
||||
final deviceId = deviceIdController.text.toLowerCase();
|
||||
final unitName = unitNameController.text.toLowerCase();
|
||||
final filteredData = data.where((device) {
|
||||
final matchesDeviceName = device.name.toLowerCase().contains(deviceName);
|
||||
final matchesDeviceId = device.uuid.toLowerCase().contains(deviceId);
|
||||
// final matchesUnitName = device.unitName.toLowerCase().contains(unitName); // Assuming unitName is a property of the device
|
||||
return matchesDeviceName && matchesDeviceId;
|
||||
}).toList();
|
||||
add(UpdateFilteredDevicesEvent(filteredData));
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<VisitorPasswordState> mapEventToState(
|
||||
VisitorPasswordEvent event) async* {
|
||||
if (event is FetchDevice) {
|
||||
} else if (event is UpdateFilteredDevicesEvent) {
|
||||
yield TableLoaded(event.filteredData);
|
||||
}
|
||||
}
|
||||
|
||||
void _onUpdateFilteredDevices(
|
||||
UpdateFilteredDevicesEvent event, Emitter<VisitorPasswordState> emit) {
|
||||
emit(TableLoaded(event.filteredData));
|
||||
}
|
||||
|
||||
addDeviceToList(context) {
|
||||
selectedDevices = selectedDeviceIds;
|
||||
Navigator.of(context).pop(selectedDevices);
|
||||
}
|
||||
|
||||
Future<void> selectTimeOfLinePassword(
|
||||
SelectTimeEvent event, Emitter<VisitorPasswordState> emit) async {
|
||||
emit(ChangeTimeState());
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: event.context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime.now(),
|
||||
lastDate: DateTime(3101),
|
||||
);
|
||||
if (picked != null) {
|
||||
final TimeOfDay? timePicked = await showHourPicker(
|
||||
context: event.context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
);
|
||||
if (timePicked != null) {
|
||||
final selectedDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
picked.day,
|
||||
timePicked.hour,
|
||||
0,
|
||||
);
|
||||
final selectedTimestamp = DateTime(
|
||||
selectedDateTime.year,
|
||||
selectedDateTime.month,
|
||||
selectedDateTime.day,
|
||||
selectedDateTime.hour,
|
||||
selectedDateTime.minute,
|
||||
).millisecondsSinceEpoch ~/
|
||||
1000; // Divide by 1000 to remove milliseconds
|
||||
if (event.isEffective) {
|
||||
if (expirationTimeTimeStamp != null &&
|
||||
selectedTimestamp > expirationTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar(
|
||||
'Effective Time cannot be later than Expiration Time.');
|
||||
} else {
|
||||
effectiveTime = selectedDateTime
|
||||
.toString()
|
||||
.split('.')
|
||||
.first; // Remove seconds and milliseconds
|
||||
effectiveTimeTimeStamp = selectedTimestamp;
|
||||
}
|
||||
} else {
|
||||
if (effectiveTimeTimeStamp != null &&
|
||||
selectedTimestamp < effectiveTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar(
|
||||
'Expiration Time cannot be earlier than Effective Time.');
|
||||
} else {
|
||||
expirationTime = selectedDateTime
|
||||
.toString()
|
||||
.split('.')
|
||||
.first; // Remove seconds and milliseconds
|
||||
expirationTimeTimeStamp = selectedTimestamp;
|
||||
}
|
||||
}
|
||||
emit(TimeSelectedState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeTime(ChangeTimeEvent event, Emitter<VisitorPasswordState> emit) {
|
||||
if (event.isStartEndTime == true) {
|
||||
startTime = event.val;
|
||||
} else {
|
||||
endTime = event.val;
|
||||
}
|
||||
}
|
||||
|
||||
DateTime? convertStringToDateTime(String dateTimeString) {
|
||||
try {
|
||||
final DateFormat inputFormat = DateFormat('yyyy-MM-dd HH:mm:ss');
|
||||
DateTime dateTime = inputFormat.parse(dateTimeString);
|
||||
return dateTime;
|
||||
} catch (e) {
|
||||
print("Error parsing date: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String getTimeFromDateTimeString(String dateTimeString) {
|
||||
DateTime? dateTime = convertStringToDateTime(dateTimeString);
|
||||
if (dateTime == null) return '';
|
||||
return DateFormat('HH:mm').format(dateTime);
|
||||
}
|
||||
|
||||
String? validateEmail(String? value) {
|
||||
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value!)) {
|
||||
return '';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> stateDialog({
|
||||
BuildContext? context,
|
||||
String? message,
|
||||
String? title,
|
||||
dynamic actions,
|
||||
}) {
|
||||
return showCustomDialog(
|
||||
context: context!,
|
||||
message: message!,
|
||||
iconPath: Assets.deviceNoteIcon,
|
||||
title: title,
|
||||
dialogHeight: 150,
|
||||
actions: actions ??
|
||||
<Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
137
lib/pages/visitor_password/bloc/visitor_password_event.dart
Normal file
@ -0,0 +1,137 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
|
||||
|
||||
abstract class VisitorPasswordEvent extends Equatable {
|
||||
const VisitorPasswordEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class SelectPasswordType extends VisitorPasswordEvent {
|
||||
final String type;
|
||||
|
||||
const SelectPasswordType(this.type);
|
||||
|
||||
@override
|
||||
List<Object> get props => [type];
|
||||
}
|
||||
|
||||
class SelectUsageFrequency extends VisitorPasswordEvent {
|
||||
final String usageType;
|
||||
|
||||
const SelectUsageFrequency(this.usageType);
|
||||
|
||||
@override
|
||||
List<Object> get props => [usageType];
|
||||
}
|
||||
class SelectTimeVisitorPassword extends VisitorPasswordEvent {
|
||||
final BuildContext context;
|
||||
final bool isStart;
|
||||
final bool isRepeat;
|
||||
|
||||
const SelectTimeVisitorPassword({ required this.context,required this.isStart,required this.isRepeat});
|
||||
|
||||
@override
|
||||
List<Object> get props => [context,isStart,isRepeat];
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ToggleDaySelectionEvent extends VisitorPasswordEvent {
|
||||
final String key;
|
||||
|
||||
const ToggleDaySelectionEvent({required this.key});
|
||||
@override
|
||||
List<Object> get props => [key];
|
||||
}
|
||||
|
||||
|
||||
class ToggleRepeatEvent extends VisitorPasswordEvent {}
|
||||
class GeneratePasswordEvent extends VisitorPasswordEvent {}
|
||||
|
||||
class FetchDevice extends VisitorPasswordEvent {
|
||||
}
|
||||
|
||||
//online password
|
||||
class OnlineOneTimePasswordEvent extends VisitorPasswordEvent {
|
||||
final String? email;
|
||||
final String? passwordName;
|
||||
final BuildContext? context;
|
||||
|
||||
const OnlineOneTimePasswordEvent({this.email,this.passwordName,this.context});
|
||||
|
||||
@override
|
||||
List<Object> get props => [email!,passwordName!,];
|
||||
}
|
||||
class OnlineMultipleTimePasswordEvent extends VisitorPasswordEvent {
|
||||
final String? email;
|
||||
final String? passwordName;
|
||||
final String? invalidTime;
|
||||
final String? effectiveTime;
|
||||
final BuildContext? context;
|
||||
const OnlineMultipleTimePasswordEvent({this.email,this.passwordName,this.invalidTime,this.effectiveTime,this.context});
|
||||
@override
|
||||
List<Object> get props => [email!,passwordName!,invalidTime!,effectiveTime!,context!];
|
||||
}
|
||||
|
||||
//offline password
|
||||
class OfflineOneTimePasswordEvent extends VisitorPasswordEvent {
|
||||
final BuildContext? context;
|
||||
final String? email;
|
||||
final String? passwordName;
|
||||
const OfflineOneTimePasswordEvent({this.email,this.passwordName,this.context});
|
||||
@override
|
||||
List<Object> get props => [email!,passwordName!,context!,];
|
||||
}
|
||||
|
||||
class OfflineMultipleTimePasswordEvent extends VisitorPasswordEvent {
|
||||
final String? email;
|
||||
final String? passwordName;
|
||||
final String? invalidTime;
|
||||
final String? effectiveTime;
|
||||
final BuildContext? context;
|
||||
|
||||
const OfflineMultipleTimePasswordEvent({this.context,this.email,this.passwordName,this.invalidTime,this.effectiveTime});
|
||||
|
||||
@override
|
||||
List<Object> get props => [email!,passwordName!,invalidTime!,effectiveTime!,context!];
|
||||
}
|
||||
|
||||
|
||||
class SelectDeviceEvent extends VisitorPasswordEvent {
|
||||
final String deviceId;
|
||||
const SelectDeviceEvent(this.deviceId);
|
||||
}
|
||||
|
||||
class FilterDataEvent extends VisitorPasswordEvent {
|
||||
final String? passwordName;
|
||||
final int? startTime;
|
||||
final int? endTime;
|
||||
|
||||
const FilterDataEvent({
|
||||
this.passwordName,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
});
|
||||
}
|
||||
class UpdateFilteredDevicesEvent extends VisitorPasswordEvent {
|
||||
final List<DeviceModel> filteredData;
|
||||
|
||||
UpdateFilteredDevicesEvent(this.filteredData);
|
||||
}class SelectTimeEvent extends VisitorPasswordEvent {
|
||||
final BuildContext context;
|
||||
final bool isEffective;
|
||||
const SelectTimeEvent({required this.context,required this.isEffective});
|
||||
@override
|
||||
List<Object> get props => [context,isEffective];
|
||||
}
|
||||
class ChangeTimeEvent extends VisitorPasswordEvent {
|
||||
final dynamic val;
|
||||
final bool isStartEndTime;
|
||||
|
||||
const ChangeTimeEvent({required this.val,required this.isStartEndTime});
|
||||
@override
|
||||
List<Object> get props => [val,isStartEndTime];
|
||||
}
|
65
lib/pages/visitor_password/bloc/visitor_password_state.dart
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
|
||||
|
||||
abstract class VisitorPasswordState extends Equatable {
|
||||
const VisitorPasswordState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class VisitorPasswordInitial extends VisitorPasswordState {}
|
||||
|
||||
|
||||
|
||||
class PasswordTypeSelected extends VisitorPasswordState {
|
||||
final String selectedType;
|
||||
const PasswordTypeSelected(this.selectedType);
|
||||
@override
|
||||
List<Object> get props => [selectedType];
|
||||
}
|
||||
|
||||
class UsageFrequencySelected extends VisitorPasswordState {
|
||||
final String selectedFrequency;
|
||||
|
||||
const UsageFrequencySelected(this.selectedFrequency);
|
||||
|
||||
@override
|
||||
List<Object> get props => [selectedFrequency];
|
||||
}
|
||||
|
||||
class IsRepeatState extends VisitorPasswordState {
|
||||
final bool repeat;
|
||||
const IsRepeatState({required this.repeat});
|
||||
|
||||
@override
|
||||
List<Object> get props => [repeat];
|
||||
|
||||
}
|
||||
|
||||
class LoadingInitialState extends VisitorPasswordState {}
|
||||
class ChangeTimeState extends VisitorPasswordState {}
|
||||
class TimeSelectedState extends VisitorPasswordState {}
|
||||
class DeviceLoaded extends VisitorPasswordState {}
|
||||
class SuccessState extends VisitorPasswordState {}
|
||||
|
||||
class FailedState extends VisitorPasswordState {
|
||||
final String message;
|
||||
const FailedState(this.message);
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
||||
class TableLoaded extends VisitorPasswordState {
|
||||
final List<DeviceModel> data;
|
||||
const TableLoaded(this.data);
|
||||
@override
|
||||
List<Object> get props => [data];
|
||||
}
|
||||
|
||||
class DeviceSelectionUpdated extends VisitorPasswordState {
|
||||
final List<String> selectedDeviceIds;
|
||||
const DeviceSelectionUpdated(this.selectedDeviceIds);
|
||||
}
|
101
lib/pages/visitor_password/model/device_model.dart
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
|
||||
import 'package:syncrow_web/utils/constants/const.dart';
|
||||
|
||||
class DeviceModel {
|
||||
dynamic productUuid;
|
||||
dynamic productType;
|
||||
dynamic activeTime;
|
||||
dynamic category;
|
||||
dynamic categoryName;
|
||||
dynamic createTime;
|
||||
dynamic gatewayId;
|
||||
dynamic icon;
|
||||
dynamic ip;
|
||||
dynamic lat;
|
||||
dynamic localKey;
|
||||
dynamic lon;
|
||||
dynamic model;
|
||||
dynamic name;
|
||||
DeviseStatus online;
|
||||
dynamic ownerId;
|
||||
dynamic sub;
|
||||
dynamic timeZone;
|
||||
dynamic updateTime;
|
||||
dynamic uuid;
|
||||
|
||||
DeviceModel({
|
||||
required this.productUuid,
|
||||
required this.productType,
|
||||
required this.activeTime,
|
||||
required this.category,
|
||||
required this.categoryName,
|
||||
required this.createTime,
|
||||
required this.gatewayId,
|
||||
required this.icon,
|
||||
required this.ip,
|
||||
required this.lat,
|
||||
required this.localKey,
|
||||
required this.lon,
|
||||
required this.model,
|
||||
required this.name,
|
||||
required this.online,
|
||||
required this.ownerId,
|
||||
required this.sub,
|
||||
required this.timeZone,
|
||||
required this.updateTime,
|
||||
required this.uuid,
|
||||
});
|
||||
|
||||
// Deserialize from JSON
|
||||
factory DeviceModel.fromJson(Map<String, dynamic> json) {
|
||||
return DeviceModel(
|
||||
productUuid: json['productUuid'] ,
|
||||
productType: json['productType'],
|
||||
activeTime: json['activeTime'],
|
||||
category: json['category'] ,
|
||||
categoryName: json['categoryName'] ,
|
||||
createTime: json['createTime'] ,
|
||||
gatewayId: json['gatewayId'],
|
||||
icon: json['icon'],
|
||||
ip: json['ip'] ,
|
||||
lat: json['lat'] ,
|
||||
localKey: json['localKey'] ,
|
||||
lon: json['lon'] ,
|
||||
model: json['model'] ,
|
||||
name: json['name'],
|
||||
online: OnlineTypeExtension.fromString(json['online']),
|
||||
ownerId: json['ownerId'] ,
|
||||
sub: json['sub'],
|
||||
timeZone: json['timeZone'],
|
||||
updateTime: json['updateTime'] ,
|
||||
uuid: json['uuid'],
|
||||
);
|
||||
}
|
||||
|
||||
// Serialize to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'productUuid': productUuid,
|
||||
'productType': productType,
|
||||
'activeTime': activeTime,
|
||||
'category': category,
|
||||
'categoryName': categoryName,
|
||||
'createTime': createTime,
|
||||
'gatewayId': gatewayId,
|
||||
'icon': icon,
|
||||
'ip': ip,
|
||||
'lat': lat,
|
||||
'localKey': localKey,
|
||||
'lon': lon,
|
||||
'model': model,
|
||||
'name': name,
|
||||
'online': online,
|
||||
'ownerId': ownerId,
|
||||
'sub': sub,
|
||||
'timeZone': timeZone,
|
||||
'updateTime': updateTime,
|
||||
'uuid': uuid,
|
||||
};
|
||||
}
|
||||
}
|
27
lib/pages/visitor_password/model/schedule_model.dart
Normal file
@ -0,0 +1,27 @@
|
||||
class Schedule {
|
||||
final String effectiveTime;
|
||||
final String invalidTime;
|
||||
final List<String> workingDay;
|
||||
|
||||
Schedule({
|
||||
required this.effectiveTime,
|
||||
required this.invalidTime,
|
||||
required this.workingDay,
|
||||
});
|
||||
|
||||
factory Schedule.fromJson(Map<String, dynamic> json) {
|
||||
return Schedule(
|
||||
effectiveTime: json['effectiveTime'],
|
||||
invalidTime: json['invalidTime'],
|
||||
workingDay: List<String>.from(json['workingDay']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'effectiveTime': effectiveTime,
|
||||
'invalidTime': invalidTime,
|
||||
'workingDay': workingDay,
|
||||
};
|
||||
}
|
||||
}
|
220
lib/pages/visitor_password/view/add_device_dialog.dart
Normal file
@ -0,0 +1,220 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/common/custom_table.dart';
|
||||
import 'package:syncrow_web/pages/common/custom_web_textfield.dart';
|
||||
import 'package:syncrow_web/pages/common/default_button.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/constants/const.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class AddDeviceDialog extends StatelessWidget {
|
||||
final List<String>? selectedDeviceIds;
|
||||
const AddDeviceDialog({super.key,this.selectedDeviceIds });
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Size size = MediaQuery.of(context).size;
|
||||
return BlocProvider(
|
||||
create: (context) => VisitorPasswordBloc()..add(FetchDevice()),
|
||||
child: BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>(
|
||||
builder: (BuildContext context, VisitorPasswordState state) {
|
||||
final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context);
|
||||
if (state is TableLoaded) {
|
||||
for (var device in selectedDeviceIds!) {
|
||||
if (selectedDeviceIds!.contains(device)) {
|
||||
visitorBloc.add(SelectDeviceEvent(device));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
title: Text('Add Accessible Device',
|
||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 24,
|
||||
color: Colors.black),),
|
||||
content: Container(
|
||||
height: MediaQuery.of(context).size.height/1.7,
|
||||
width: MediaQuery.of(context).size.width/2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: size.width,
|
||||
padding: EdgeInsets.all(15),
|
||||
decoration:containerDecoration.copyWith(
|
||||
color: ColorsManager.worningColor,
|
||||
border: Border.all(color: Color(0xffFFD22F)),
|
||||
boxShadow: []
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
child: SvgPicture.asset(
|
||||
Assets.deviceNoteIcon,
|
||||
height: 15,
|
||||
width: 15,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10,),
|
||||
Text('Only online accessible devices can be added',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12,
|
||||
color: ColorsManager.grayColor),),
|
||||
],
|
||||
)
|
||||
),
|
||||
const SizedBox(height: 20,),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: CustomWebTextField(
|
||||
controller: visitorBloc.deviceNameController,
|
||||
isRequired: true,
|
||||
textFieldName: 'Device Name',
|
||||
description: '',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: CustomWebTextField(
|
||||
controller: visitorBloc.deviceIdController,
|
||||
isRequired: true,
|
||||
textFieldName: 'Device ID',
|
||||
description: '',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: CustomWebTextField(
|
||||
controller: visitorBloc.unitNameController,
|
||||
isRequired: true,
|
||||
textFieldName: 'Unit Name',
|
||||
description: '',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
child: SizedBox(
|
||||
width: size.width * 0.06,
|
||||
child: Center(
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
visitorBloc.filterDevices();
|
||||
},
|
||||
borderRadius: 9,
|
||||
child: const Text('Search'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
width: size.width * 0.06,
|
||||
child: DefaultButton(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
borderRadius: 9,
|
||||
child: Text('Reset',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black),
|
||||
),
|
||||
onPressed: () {
|
||||
visitorBloc.deviceNameController.clear();
|
||||
visitorBloc.deviceIdController.clear();
|
||||
visitorBloc.unitNameController.clear();
|
||||
visitorBloc.add(FetchDevice()); // Reset to original list
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: state is TableLoaded
|
||||
? DynamicTable(
|
||||
initialSelectedIds:selectedDeviceIds ,
|
||||
cellDecoration: containerDecoration,
|
||||
isEmpty:visitorBloc.data.isEmpty,
|
||||
selectAll: (p0) {
|
||||
visitorBloc.selectedDeviceIds.clear();
|
||||
for (var item in state.data) {
|
||||
visitorBloc.add(SelectDeviceEvent(item.uuid));
|
||||
}
|
||||
},
|
||||
onRowCheckboxChanged: (index, isSelected) {
|
||||
final deviceId = state.data[index].uuid;
|
||||
visitorBloc.add(SelectDeviceEvent(deviceId));
|
||||
},
|
||||
withCheckBox: true,
|
||||
size: size*0.5,
|
||||
headers: const [ 'Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status'],
|
||||
data: state.data.map((item) {
|
||||
return [
|
||||
item.name.toString(),
|
||||
item.uuid.toString(),
|
||||
item.productType.toString(),
|
||||
'',
|
||||
item.online.value.toString(),
|
||||
];
|
||||
}).toList(),
|
||||
)
|
||||
: const Center(child: CircularProgressIndicator()))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
actions: <Widget>[
|
||||
Container(
|
||||
decoration: containerDecoration,
|
||||
width: size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
borderRadius: 8,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // Close the dialog
|
||||
},
|
||||
backgroundColor: Colors.white,
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: Theme.of(context).textTheme.bodyMedium!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: containerDecoration,
|
||||
width: size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
visitorBloc.addDeviceToList(context);
|
||||
},
|
||||
borderRadius: 8,
|
||||
child: Text('Ok'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
86
lib/pages/visitor_password/view/repeat_widget.dart
Normal file
@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/date_time_widget.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class RepeatWidget extends StatelessWidget {
|
||||
const RepeatWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Size size = MediaQuery.of(context).size;
|
||||
return BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>(
|
||||
builder: (context, state) {
|
||||
final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context);
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// Wrap the Row in a SingleChildScrollView to handle overflow
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: visitorBloc.days.map((day) {
|
||||
return Container(
|
||||
width: 70, // Adjust width as needed
|
||||
margin: EdgeInsets.all(5),
|
||||
child: CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
day['day']!,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: visitorBloc.selectedDays.contains(day['key'])
|
||||
? Colors.black
|
||||
: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
value: visitorBloc.selectedDays.contains(day['key']),
|
||||
onChanged: (bool? value) {
|
||||
if (value != null) {
|
||||
visitorBloc.add(ToggleDaySelectionEvent(key: day['key']!));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: DateTimeWebWidget(
|
||||
icon: Assets.timeIcon,
|
||||
isRequired: false,
|
||||
title: '',
|
||||
size: size,
|
||||
endTime: () {
|
||||
visitorBloc.add(SelectTimeEvent(
|
||||
context: context,
|
||||
isEffective: false));
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
visitorBloc.add(ChangeTimeEvent(val: visitorBloc.endTime, isStartEndTime: true));
|
||||
});
|
||||
},
|
||||
startTime: () {
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
visitorBloc.add(ChangeTimeEvent(val: visitorBloc.endTime, isStartEndTime: true));
|
||||
});
|
||||
visitorBloc.add(SelectTimeEvent(context: context, isEffective: true));
|
||||
},
|
||||
firstString: visitorBloc.effectiveTime,
|
||||
secondString: visitorBloc.expirationTime,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
538
lib/pages/visitor_password/view/visitor_password_dialog.dart
Normal file
@ -0,0 +1,538 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/common/custom_web_textfield.dart';
|
||||
import 'package:syncrow_web/pages/common/date_time_widget.dart';
|
||||
import 'package:syncrow_web/pages/common/default_button.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/view/add_device_dialog.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/view/repeat_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class VisitorPasswordDialog extends StatelessWidget {
|
||||
const VisitorPasswordDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Size size = MediaQuery.of(context).size;
|
||||
var text = Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: Colors.black,fontSize: 13);
|
||||
return BlocProvider(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
child: BlocListener<VisitorPasswordBloc, VisitorPasswordState>(
|
||||
listener: (context, state) {
|
||||
final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context);
|
||||
if (state is SuccessState) {
|
||||
visitorBloc.stateDialog(
|
||||
context: context,
|
||||
message: 'Password Created Successfully',
|
||||
title: 'Send Success',
|
||||
);
|
||||
} else if (state is FailedState) {
|
||||
visitorBloc.stateDialog(
|
||||
context: context,
|
||||
message: state.message,
|
||||
title: 'Something Wrong',
|
||||
);
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>(
|
||||
builder: (BuildContext context, VisitorPasswordState state) {
|
||||
final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context);
|
||||
bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat;
|
||||
return AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
title: Text(
|
||||
'Create visitor password',
|
||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 24,
|
||||
color: Colors.black),
|
||||
),
|
||||
content:
|
||||
state is LoadingInitialState ?const Center(child: CircularProgressIndicator()):
|
||||
SingleChildScrollView(
|
||||
child: Form(
|
||||
key: visitorBloc.forgetFormKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: CustomWebTextField(
|
||||
validator: visitorBloc.validate,
|
||||
controller: visitorBloc.userNameController,
|
||||
isRequired: true,
|
||||
textFieldName: 'Name',
|
||||
description: '',
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: CustomWebTextField(
|
||||
validator: visitorBloc.validateEmail,
|
||||
controller: visitorBloc.emailController,
|
||||
isRequired: true,
|
||||
textFieldName: 'Email Address',
|
||||
description:
|
||||
'The password will be sent to the visitor’s email address.',
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('* ',
|
||||
style: Theme.of(context).textTheme
|
||||
.bodyMedium!.copyWith(color: Colors.red),
|
||||
),
|
||||
Text('Access Type',
|
||||
style:text ),
|
||||
],
|
||||
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: RadioListTile<String>(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Online Password',
|
||||
style: text,
|
||||
),
|
||||
value: 'Online Password',
|
||||
groupValue: (state is PasswordTypeSelected)
|
||||
? state.selectedType
|
||||
: visitorBloc.accessTypeSelected,
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
context.read<VisitorPasswordBloc>()
|
||||
.add(SelectPasswordType(value));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: RadioListTile<String>(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
|
||||
title: Text('Offline Password',
|
||||
style:text ),
|
||||
value: 'Offline Password',
|
||||
groupValue: (state is PasswordTypeSelected)
|
||||
? state.selectedType
|
||||
: visitorBloc.accessTypeSelected,
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
context.read<VisitorPasswordBloc>().add(SelectPasswordType(value));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: RadioListTile<String>(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
|
||||
title: Text('Dynamic Password',
|
||||
style: text,),
|
||||
value: 'Dynamic Password',
|
||||
groupValue: (state is PasswordTypeSelected)
|
||||
? state.selectedType
|
||||
: visitorBloc.accessTypeSelected,
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
context.read<VisitorPasswordBloc>()
|
||||
.add(SelectPasswordType(value));
|
||||
visitorBloc.usageFrequencySelected = '';
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.grayColor,fontSize: 9),),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
)
|
||||
],
|
||||
),
|
||||
visitorBloc.accessTypeSelected == 'Dynamic Password'
|
||||
? const SizedBox()
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('* ',
|
||||
style: Theme.of(context).textTheme.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
Text('Usage Frequency',style:text ,),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: RadioListTile<String>(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('One-Time',
|
||||
style:text ,),
|
||||
value: 'One-Time',
|
||||
groupValue:
|
||||
(state is UsageFrequencySelected)
|
||||
? state.selectedFrequency
|
||||
: visitorBloc.usageFrequencySelected,
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
context.read<VisitorPasswordBloc>()
|
||||
.add(SelectUsageFrequency(value));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: RadioListTile<String>(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Periodic',
|
||||
style: text),
|
||||
value: 'Periodic',
|
||||
groupValue: (state is UsageFrequencySelected)
|
||||
? state.selectedFrequency
|
||||
: visitorBloc.usageFrequencySelected,
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
context.read<VisitorPasswordBloc>()
|
||||
.add(SelectUsageFrequency(value));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text('Within the validity period, each device can be unlocked only once.',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,fontSize: 9),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
if ((visitorBloc.usageFrequencySelected != 'One-Time' ||
|
||||
visitorBloc.accessTypeSelected != 'Offline Password') &&
|
||||
(visitorBloc.usageFrequencySelected != ''))
|
||||
DateTimeWebWidget(
|
||||
isRequired: true,
|
||||
title: 'Access Period',
|
||||
size: size,
|
||||
endTime: () {
|
||||
visitorBloc.add(SelectTimeVisitorPassword(
|
||||
context: context,
|
||||
isStart: false,
|
||||
isRepeat: false));
|
||||
},
|
||||
startTime: () {
|
||||
visitorBloc.add(SelectTimeVisitorPassword(
|
||||
context: context,
|
||||
isStart: true,
|
||||
isRepeat: false));
|
||||
},
|
||||
firstString: visitorBloc.startTimeAccess.toString(),
|
||||
secondString: visitorBloc.endTimeAccess.toString(),
|
||||
icon: Assets.calendarIcon
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('* ',
|
||||
style: Theme.of(context).textTheme.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
Text('Access Devices',
|
||||
style:text ,),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'Within the validity period, each device can be unlocked only once.',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.grayColor,fontSize: 9),),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||
visitorBloc.accessTypeSelected == 'Online Password')
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: Column(
|
||||
children: [
|
||||
Text('Repeat',
|
||||
style:text),
|
||||
Transform.scale(
|
||||
scale: .8,
|
||||
child: CupertinoSwitch(
|
||||
value: visitorBloc.repeat,
|
||||
onChanged: (value) {
|
||||
visitorBloc.add(ToggleRepeatEvent());
|
||||
},
|
||||
applyTheme: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
),
|
||||
),
|
||||
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||
visitorBloc.accessTypeSelected == 'Online Password')
|
||||
isRepeat ? const RepeatWidget() : const SizedBox(),
|
||||
Container(
|
||||
decoration: containerDecoration,
|
||||
width: size.width / 9,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AddDeviceDialog(selectedDeviceIds: visitorBloc.selectedDevices,);
|
||||
},
|
||||
).then((listDevice) {
|
||||
if(listDevice!=null){
|
||||
visitorBloc.selectedDevices = listDevice;
|
||||
}
|
||||
});
|
||||
},
|
||||
borderRadius: 8,
|
||||
child: Text('+ Add Device',style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.whiteColors,fontSize: 12),),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
actions: <Widget>[
|
||||
Container(
|
||||
decoration: containerDecoration,
|
||||
width: size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
borderRadius: 8,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
backgroundColor: Colors.white,
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.blackColor,fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: containerDecoration,
|
||||
width: size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
if (visitorBloc.forgetFormKey.currentState!.validate()) {
|
||||
if(visitorBloc.selectedDevices.isNotEmpty){
|
||||
if(visitorBloc.effectiveTimeTimeStamp!=null&&visitorBloc.expirationTimeTimeStamp!=null) {
|
||||
setPasswordFunction(context, size, visitorBloc);
|
||||
}
|
||||
else{
|
||||
visitorBloc.stateDialog(context:
|
||||
context,message: 'Please select Access Period to continue',title: 'Access Period');
|
||||
}
|
||||
}else{
|
||||
visitorBloc.stateDialog(context:
|
||||
context,message: 'Please select devices to continue',title: 'Select Devices');
|
||||
}
|
||||
}
|
||||
},
|
||||
borderRadius: 8,
|
||||
child: Text('Ok', style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.whiteColors,fontSize: 16),),
|
||||
),
|
||||
),
|
||||
],
|
||||
); },
|
||||
),
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setPasswordFunction(
|
||||
BuildContext context,
|
||||
Size size,
|
||||
VisitorPasswordBloc visitorBloc,
|
||||
) {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>(
|
||||
builder: (context, state) {
|
||||
if (state is LoadingInitialState) {
|
||||
// Show loading indicator while loading
|
||||
return AlertDialog(
|
||||
alignment: Alignment.center,
|
||||
content: SizedBox(
|
||||
height: size.height * 0.25,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(), // Display a loading spinner
|
||||
),
|
||||
),
|
||||
);
|
||||
}else{
|
||||
return AlertDialog(
|
||||
alignment: Alignment.center,
|
||||
content: SizedBox(
|
||||
height: size.height * 0.25,
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: SvgPicture.asset(
|
||||
Assets.deviceNoteIcon,
|
||||
height: 35,
|
||||
width: 35,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Set Password',
|
||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Text(
|
||||
'This action will update all of the selected\n door locks passwords in the property.\n\nAre you sure you want to continue?',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
actions: <Widget>[
|
||||
Container(
|
||||
decoration: containerDecoration,
|
||||
width: size.width * 0.1,
|
||||
child: DefaultButton(
|
||||
borderRadius: 8,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
backgroundColor: Colors.white,
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: containerDecoration,
|
||||
width: size.width * 0.1,
|
||||
child: DefaultButton(
|
||||
borderRadius: 8,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
||||
visitorBloc.accessTypeSelected == 'Online Password') {
|
||||
visitorBloc.add(OnlineOneTimePasswordEvent(
|
||||
context: context,
|
||||
passwordName: visitorBloc.userNameController.text,
|
||||
email: visitorBloc.emailController.text,
|
||||
));
|
||||
} else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||
visitorBloc.accessTypeSelected == 'Online Password') {
|
||||
visitorBloc.add(OnlineMultipleTimePasswordEvent(
|
||||
passwordName: visitorBloc.userNameController.text,
|
||||
email: visitorBloc.emailController.text,
|
||||
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
|
||||
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
|
||||
));
|
||||
} else if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
||||
visitorBloc.accessTypeSelected == 'Offline Password') {
|
||||
visitorBloc.add(OfflineOneTimePasswordEvent(
|
||||
context: context,
|
||||
passwordName: visitorBloc.userNameController.text,
|
||||
email: visitorBloc.emailController.text,
|
||||
));
|
||||
} else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||
visitorBloc.accessTypeSelected == 'Offline Password') {
|
||||
visitorBloc.add(OfflineMultipleTimePasswordEvent(
|
||||
passwordName: visitorBloc.userNameController.text,
|
||||
email: visitorBloc.emailController.text,
|
||||
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
|
||||
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
|
||||
));
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
'Ok',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.whiteColors,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
189
lib/services/access_mang_api.dart
Normal file
@ -0,0 +1,189 @@
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:syncrow_web/pages/access_management/model/password_model.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class AccessMangApi{
|
||||
|
||||
Future<List<PasswordModel>> fetchVisitorPassword() async {
|
||||
try {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.visitorPassword,
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
List<dynamic> jsonData = json;
|
||||
List<PasswordModel> passwordList = jsonData.map((jsonItem) {
|
||||
return PasswordModel.fromJson(jsonItem);
|
||||
}).toList();
|
||||
return passwordList;
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error fetching visitor passwords: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future fetchDevices() async {
|
||||
try {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getDevices,
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
List<dynamic> jsonData = json;
|
||||
List<DeviceModel> passwordList = jsonData.map((jsonItem) {
|
||||
return DeviceModel.fromJson(jsonItem);
|
||||
}).toList();
|
||||
return passwordList;
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error fetching $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> postOnlineOneTime({
|
||||
String? email,
|
||||
String? passwordName,
|
||||
String? password,
|
||||
String? effectiveTime,
|
||||
String? invalidTime,
|
||||
List<String>? devicesUuid}) async {
|
||||
try {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.sendOnlineOneTime,
|
||||
body: jsonEncode({
|
||||
"email": email,
|
||||
"passwordName": passwordName,
|
||||
"password": password,
|
||||
"devicesUuid": devicesUuid,
|
||||
"effectiveTime":effectiveTime ,
|
||||
"invalidTime": invalidTime
|
||||
}),
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
if(json['statusCode'].toString()=='201'){
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
debugPrint('Error: ${e.message}');
|
||||
debugPrint('Error fetching ${e.response!.statusMessage}');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future postOnlineMultipleTime({
|
||||
String? effectiveTime,
|
||||
String? invalidTime,
|
||||
String? email,
|
||||
String? password,
|
||||
String? passwordName,
|
||||
List<Schedule>? scheduleList,
|
||||
List<String>? devicesUuid}) async {
|
||||
try {
|
||||
Map<String, dynamic> body = {
|
||||
"email": email,
|
||||
"devicesUuid": devicesUuid,
|
||||
"passwordName": passwordName,
|
||||
"password": password,
|
||||
"effectiveTime": effectiveTime,
|
||||
"invalidTime": invalidTime,
|
||||
};
|
||||
if (scheduleList != null) {
|
||||
body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList();
|
||||
}
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.sendOnlineMultipleTime,
|
||||
body: jsonEncode(body),
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
if(json['data']['successOperations'][0]['success'].toString()=='true'){
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} on DioException catch (e){
|
||||
debugPrint('Error fetching ${e.type.name}');
|
||||
debugPrint('Error fetching ${e.response!.statusMessage}');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// OffLine One Time Password
|
||||
|
||||
Future postOffLineOneTime({String? email,String? passwordName,List<String>? devicesUuid}) async {
|
||||
try {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.sendOffLineOneTime,
|
||||
body: jsonEncode({
|
||||
"email": email,
|
||||
"passwordName": passwordName,
|
||||
"devicesUuid": devicesUuid
|
||||
}),
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
if (json['data']['successOperations'][0]['success'].toString() ==
|
||||
'true') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error fetching $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future postOffLineMultipleTime({
|
||||
String? email,
|
||||
String? passwordName,
|
||||
String? effectiveTime,
|
||||
String? invalidTime,
|
||||
List<String>? devicesUuid
|
||||
|
||||
}) async {
|
||||
try {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.sendOffLineOneTime,
|
||||
body: jsonEncode({
|
||||
"email": email,
|
||||
"devicesUuid":devicesUuid,
|
||||
"passwordName": passwordName,
|
||||
"effectiveTime": effectiveTime,
|
||||
"invalidTime": invalidTime
|
||||
}),
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
if (json['data']['successOperations'][0]['success'].toString() ==
|
||||
'true') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error fetching $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/token.dart';
|
||||
import 'dart:async';
|
||||
import 'package:syncrow_web/services/api/network_exception.dart';
|
||||
import 'dart:async';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
|
||||
@ -12,6 +13,8 @@ class HTTPInterceptor extends InterceptorsWrapper {
|
||||
|
||||
List<String> headerExclusionListOfAddedParameters = [
|
||||
ApiEndpoints.login,
|
||||
ApiEndpoints.getRegion,
|
||||
ApiEndpoints.sendOtp
|
||||
];
|
||||
|
||||
@override
|
||||
@ -30,7 +33,7 @@ class HTTPInterceptor extends InterceptorsWrapper {
|
||||
if (checkHeaderExclusionListOfAddedParameters(options.path)) {
|
||||
options.headers.putIfAbsent(HttpHeaders.authorizationHeader, () => "Bearer $token");
|
||||
}
|
||||
options.headers['Authorization'] = 'Bearer ${token!}';
|
||||
// options.headers['Authorization'] = 'Bearer ${'${token!}123'}';
|
||||
super.onRequest(options, handler);
|
||||
}
|
||||
|
||||
|
@ -1,29 +1,114 @@
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/region_model.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/token.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
import 'api/http_service.dart';
|
||||
|
||||
class AuthenticationAPI {
|
||||
|
||||
|
||||
static Future<Token> loginWithEmail({required var model}) async {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.login,
|
||||
body: model.toJson(),
|
||||
showServerMessage: false,
|
||||
expectedResponseModel: (json) => Token.fromJson(json['data']));
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
return Token.fromJson(json['data']);
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// static Future<bool> signUp({required SignUpModel model}) async {
|
||||
// final response = await HTTPService().post(
|
||||
// path: ApiEndpoints.signUp,
|
||||
// body: model.toJson(),
|
||||
// showServerMessage: false,
|
||||
// expectedResponseModel: (json) => json['statusCode'] == 201);
|
||||
// return response;
|
||||
// }
|
||||
static Future forgetPassword(
|
||||
{required var email, required var password,}) async {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.forgetPassword,
|
||||
body: {
|
||||
"email": email,
|
||||
"password": password
|
||||
},
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {});
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
static Future<int?> sendOtp({required String email, required String regionUuid}) async {
|
||||
try {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.sendOtp,
|
||||
body: {
|
||||
"email": email,
|
||||
"type": "PASSWORD",
|
||||
"regionUuid": regionUuid
|
||||
},
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
return 30;
|
||||
}
|
||||
);
|
||||
return 30;
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null) {
|
||||
if (e.response!.statusCode == 400) {
|
||||
final errorData = e.response!.data;
|
||||
String errorMessage = errorData['message'];
|
||||
if(errorMessage=='User not found'){
|
||||
return 1;
|
||||
}else{
|
||||
int cooldown = errorData['data']['cooldown'] ?? 1;
|
||||
return cooldown;
|
||||
}
|
||||
} else {
|
||||
debugPrint('Error: ${e.response!.statusCode} - ${e.response!.statusMessage}');
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
debugPrint('Error: ${e.message}');
|
||||
return 1;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Unexpected Error: $e');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static Future verifyOtp(
|
||||
{required String email, required String otpCode}) async {
|
||||
try{
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.verifyOtp,
|
||||
body: {"email": email, "type": "PASSWORD", "otpCode": otpCode},
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
if (json['message'] == 'Otp Verified Successfully') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return response;
|
||||
}on DioException catch (e){
|
||||
if (e.response != null) {
|
||||
if (e.response!.statusCode == 400) {
|
||||
final errorData = e.response!.data;
|
||||
String errorMessage = errorData['message'];
|
||||
return errorMessage;
|
||||
}
|
||||
} else {
|
||||
debugPrint('Error: ${e.message}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<RegionModel>> fetchRegion() async {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getRegion,
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
return (json as List).map((zone) => RegionModel.fromJson(zone)).toList();
|
||||
}
|
||||
);
|
||||
return response as List<RegionModel>;
|
||||
}
|
||||
|
||||
}
|
||||
|
16
lib/services/home_api.dart
Normal file
@ -0,0 +1,16 @@
|
||||
import 'package:syncrow_web/pages/auth/model/user_model.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class HomeApi{
|
||||
Future fetchUserInfo(userId) async {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!),
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
return UserModel.fromJson(json);
|
||||
}
|
||||
);
|
||||
return response;
|
||||
}
|
||||
}
|
@ -18,13 +18,18 @@ abstract class ColorsManager {
|
||||
static const Color dozeColor = Color(0xFFFEC258);
|
||||
static const Color relaxColor = Color(0xFFFBD288);
|
||||
static const Color readingColor = Color(0xFFF7D69C);
|
||||
static const Color worningColor = Color(0xFFFFF3C8);
|
||||
static const Color energizingColor = Color(0xFFEDEDED);
|
||||
static const Color dividerColor = Color(0xFFEBEBEB);
|
||||
static const Color slidingBlueColor = Color(0x99023DFE);
|
||||
static const Color blackColor = Color(0xFF000000);
|
||||
static const Color lightGreen = Color(0xFF00FF0A);
|
||||
static const Color grayColor = Color(0xFF999999);
|
||||
static const Color red = Colors.red;
|
||||
static const Color red = Color(0xFFFF0000);
|
||||
static const Color graysColor = Color(0xffEBEBEB);
|
||||
static const Color textGray = Color(0xffD5D5D5);
|
||||
static const Color btnColor = Color(0xFF00008B);
|
||||
static const Color blueColor = Color(0xFF0036E6);
|
||||
static const Color boxColor = Color(0xFFF5F6F7);
|
||||
static const Color boxDivider = Color(0xFFE0E0E0);
|
||||
}
|
||||
|
@ -1,8 +1,25 @@
|
||||
abstract class ApiEndpoints {
|
||||
static const String baseUrl = 'https://syncrow-staging.azurewebsites.net';
|
||||
static const String baseUrl = 'https://syncrow-dev.azurewebsites.net';
|
||||
// static const String baseUrl = 'http://100.107.182.63:4001'; //Localhost
|
||||
//https://syncrow-staging.azurewebsites.net
|
||||
////////////////////////////////////// Authentication ///////////////////////////////
|
||||
static const String signUp = 'authentication/user/signup';
|
||||
static const String login = 'authentication/user/login';
|
||||
static const String signUp = '$baseUrl/authentication/user/signup';
|
||||
static const String login = '$baseUrl/authentication/user/login';
|
||||
static const String forgetPassword = '$baseUrl/authentication/user/forget-password';
|
||||
static const String sendOtp = '$baseUrl/authentication/user/send-otp';
|
||||
static const String verifyOtp = '$baseUrl/authentication/user/verify-otp';
|
||||
static const String getRegion = '$baseUrl/region';
|
||||
static const String visitorPassword = '$baseUrl/visitor-password';
|
||||
static const String getDevices = '$baseUrl/visitor-password/devices';
|
||||
|
||||
|
||||
static const String sendOnlineOneTime = '$baseUrl/visitor-password/temporary-password/online/one-time';
|
||||
static const String sendOnlineMultipleTime = '$baseUrl/visitor-password/temporary-password/online/multiple-time';
|
||||
|
||||
//offline Password
|
||||
static const String sendOffLineOneTime = '$baseUrl/visitor-password/temporary-password/offline/one-time';
|
||||
static const String sendOffLineMultipleTime = '$baseUrl/visitor-password/temporary-password/offline/multiple-time';
|
||||
|
||||
|
||||
static const String getUser = '$baseUrl/user/{userUuid}';
|
||||
}
|
||||
|
@ -9,4 +9,22 @@ class Assets {
|
||||
static const String loginLogo = "assets/images/login_logo.svg";
|
||||
static const String whiteLogo = "assets/images/white-logo.png";
|
||||
static const String window = "assets/images/Window.png";
|
||||
static const String liftLine = "assets/images/lift_line.png";
|
||||
static const String rightLine = "assets/images/right_line.png";
|
||||
static const String google = "assets/images/google.svg";
|
||||
static const String facebook = "assets/images/facebook.svg";
|
||||
static const String invisiblePassword = "assets/images/Password_invisible.svg";
|
||||
static const String visiblePassword = "assets/images/Password_visible.svg";
|
||||
static const String accessIcon = "assets/images/access_icon.svg";
|
||||
static const String spaseManagementIcon = "assets/images/spase_management_icon.svg";
|
||||
static const String devicesIcon = "assets/images/devices_icon.svg";
|
||||
static const String moveinIcon = "assets/images/movein_icon.svg";
|
||||
static const String constructionIcon = "assets/images/construction_icon.svg";
|
||||
static const String energyIcon = "assets/images/energy_icon.svg";
|
||||
static const String integrationsIcon = "assets/images/Integrations_icon.svg";
|
||||
static const String assetIcon = "assets/images/asset_icon.svg";
|
||||
static const String calendarIcon = "assets/images/calendar_icon.svg";
|
||||
static const String deviceNoteIcon = "assets/images/device_note.svg";
|
||||
static const String timeIcon = "assets/images/time_icon.svg";
|
||||
static const String emptyTable = "assets/images/empty_table.svg";
|
||||
}
|
||||
|
109
lib/utils/constants/const.dart
Normal file
@ -0,0 +1,109 @@
|
||||
|
||||
enum AccessType {
|
||||
onlineOnetime,
|
||||
onlineMultiple,
|
||||
offlineOnetime,
|
||||
offlineMultiple,
|
||||
}
|
||||
|
||||
extension AccessTypeExtension on AccessType {
|
||||
String get value {
|
||||
switch (this) {
|
||||
case AccessType.onlineOnetime:
|
||||
return "Online Password";
|
||||
case AccessType.onlineMultiple:
|
||||
return "online Multiple Password";
|
||||
case AccessType.offlineOnetime:
|
||||
return "Offline Onetime Password";
|
||||
case AccessType.offlineMultiple:
|
||||
return "Offline Multiple Password";
|
||||
}
|
||||
}
|
||||
|
||||
static AccessType fromString(String value) {
|
||||
switch (value) {
|
||||
case "ONLINE_ONETIME":
|
||||
return AccessType.onlineOnetime;
|
||||
case "ONLINE_MULTIPLE":
|
||||
return AccessType.onlineMultiple;
|
||||
case "OFFLINE_ONETIME":
|
||||
return AccessType.offlineOnetime;
|
||||
case "OFFLINE_MULTIPLE":
|
||||
return AccessType.offlineMultiple;
|
||||
default:
|
||||
throw ArgumentError("Invalid access type: $value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
enum DeviseStatus {
|
||||
online,
|
||||
offline,
|
||||
}
|
||||
|
||||
extension OnlineTypeExtension on DeviseStatus {
|
||||
String get value {
|
||||
switch (this) {
|
||||
case DeviseStatus.online:
|
||||
return "Online";
|
||||
case DeviseStatus.offline:
|
||||
return "Offline";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static DeviseStatus fromString(bool value) {
|
||||
switch (value) {
|
||||
case false:
|
||||
return DeviseStatus.offline;
|
||||
case true:
|
||||
return DeviseStatus.online;
|
||||
default:
|
||||
throw ArgumentError("Invalid access type: $value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum AccessStatus {
|
||||
expired ,
|
||||
effective ,
|
||||
toBeEffective,
|
||||
}
|
||||
|
||||
extension AccessStatusExtension on AccessStatus {
|
||||
String get value {
|
||||
switch (this) {
|
||||
case AccessStatus.expired:
|
||||
return "Expired";
|
||||
case AccessStatus.effective:
|
||||
return "Effective" ;
|
||||
case AccessStatus.toBeEffective:
|
||||
return "To be effective";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static AccessStatus fromString(String value) {
|
||||
switch (value) {
|
||||
case "EXPIRED" :
|
||||
return AccessStatus.expired;
|
||||
case "EFFECTIVE" :
|
||||
return AccessStatus.effective;
|
||||
case "TO_BE_EFFECTIVE":
|
||||
return AccessStatus.toBeEffective;
|
||||
default:
|
||||
throw ArgumentError("Invalid access type: $value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
43
lib/utils/constants/strings_manager.dart
Normal file
@ -0,0 +1,43 @@
|
||||
class StringsManager {
|
||||
static const noRouteFound = 'No route found';
|
||||
static const noInternetConnection = 'No internet connection';
|
||||
static const String dashboard = 'Dashboard';
|
||||
static const String devices = 'Devices';
|
||||
static const String routine = 'Routines';
|
||||
static const String tapToRunRoutine = 'Tap to run routine';
|
||||
static const String wizard = 'Wizard';
|
||||
static const String active = 'Active';
|
||||
static const String current = 'Current';
|
||||
static const String frequency = 'Frequency';
|
||||
static const String energyUsage = 'Energy Usage';
|
||||
static const String totalConsumption = 'Total Consumption';
|
||||
static const String ACConsumption = 'AC Consumption';
|
||||
static const String units = 'Units';
|
||||
static const String emissions = 'Emissions';
|
||||
static const String reductions = 'Reductions';
|
||||
static const String winter = 'Winter';
|
||||
static const String winterMode = 'Winter Mode';
|
||||
static const String summer = 'Summer';
|
||||
static const String summerMode = 'Summer Mode';
|
||||
static const String on = 'ON';
|
||||
static const String off = 'OFF';
|
||||
static const String timer = 'Timer';
|
||||
static const String dimmerAndColor = "Dimmer & color";
|
||||
static const String recentlyUsed = "Recently used colors";
|
||||
static const String lightingModes = "Lighting modes";
|
||||
static const String doze = "Doze";
|
||||
static const String relax = "Relax";
|
||||
static const String reading = "Reading";
|
||||
static const String energizing = "Energizing";
|
||||
static const String createScene = 'Create Scene';
|
||||
static const String tapToRun = 'Launch: Tap - To - Run';
|
||||
static const String turnOffAllLights =
|
||||
'Example: turn off all lights in the with one tap.';
|
||||
static const String whenDeviceStatusChanges = 'When device status changes';
|
||||
static const String whenUnusualActivityIsDetected =
|
||||
'Example: when an unusual activity is detected.';
|
||||
static const String functions = "Functions";
|
||||
static const String firstLaunch = "firstLaunch";
|
||||
static const String deleteScene = 'Delete Scene';
|
||||
static const String deleteAutomation = 'Delete Automation';
|
||||
}
|
58
lib/utils/helpers/shared_preferences_helper.dart
Normal file
@ -0,0 +1,58 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SharedPreferencesHelper {
|
||||
static saveStringToSP(String key, String value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(key, value);
|
||||
}
|
||||
|
||||
static saveBoolToSP(String key, bool value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(key, value);
|
||||
}
|
||||
|
||||
static saveIntToSP(String key, int value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(key, value);
|
||||
}
|
||||
|
||||
static saveDoubleToSP(String key, double value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setDouble(key, value);
|
||||
}
|
||||
|
||||
static saveStringListToSP(String key, List<String> value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setStringList(key, value);
|
||||
}
|
||||
|
||||
static Future<String> readStringFromSP(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String value = prefs.getString(key) ?? '';
|
||||
return value;
|
||||
}
|
||||
|
||||
static Future<bool?> readBoolFromSP(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
bool? value = prefs.getBool(key);
|
||||
return value;
|
||||
}
|
||||
|
||||
static Future<int> readIntFromSP(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
int value = prefs.getInt(key) ?? 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
static Future<List<String>> readStringListFromSP(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
List<String>? value = prefs.getStringList(key) ?? [];
|
||||
return value;
|
||||
}
|
||||
|
||||
static Future<bool> removeValueFromSP(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(key);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ class CustomSnackBar {
|
||||
BuildContext? currentContext = key?.currentContext;
|
||||
if (key != null && currentContext != null) {
|
||||
final snackBar = SnackBar(
|
||||
|
||||
padding: const EdgeInsets.all(16),
|
||||
backgroundColor: Colors.green,
|
||||
content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
|
@ -6,21 +6,41 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration
|
||||
suffixIcon:suffixIcon? const Icon(Icons.search):null,
|
||||
hintText: 'Search',
|
||||
filled: true, // Enable background filling
|
||||
fillColor: Colors.grey.shade200, // Set the background color
|
||||
fillColor: const Color(0xffF5F6F7), // Set the background color
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(15), // Add border radius
|
||||
borderRadius: BorderRadius.circular(8), // Add border radius
|
||||
borderSide: BorderSide.none, // Remove the underline
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(15), // Add border radius
|
||||
borderRadius: BorderRadius.circular(8), // Add border radius
|
||||
borderSide: BorderSide.none, // Remove the underline
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(15), // Add border radius
|
||||
borderRadius: BorderRadius.circular(8), // Add border radius
|
||||
borderSide: BorderSide.none, // Remove the underline
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.red, width: 2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.red, width: 2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
TextStyle appBarTextStyle =
|
||||
const TextStyle(fontSize: 20, color: ColorsManager.whiteColors);
|
||||
BoxDecoration containerDecoration = BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
spreadRadius: 5,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0,
|
||||
3), // changes position of shadow
|
||||
),
|
||||
],
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)));
|
||||
|
||||
|
||||
|
@ -1,27 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class WebAppBar extends StatelessWidget {
|
||||
final String? title;
|
||||
final List<Widget>? body;
|
||||
const WebAppBar({super.key,this.title,this.body});
|
||||
final Widget? title;
|
||||
final List<Widget>? body;
|
||||
const WebAppBar({super.key, this.title, this.body});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 120,
|
||||
decoration: const BoxDecoration(color:ColorsManager.secondaryColor ),
|
||||
return BlocBuilder<HomeBloc, HomeState>(builder: (context, state) {
|
||||
return Container(
|
||||
height: 100,
|
||||
decoration: const BoxDecoration(color: ColorsManager.secondaryColor),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title!,style: const TextStyle(
|
||||
fontSize: 30,
|
||||
color: Colors.white),)
|
||||
),
|
||||
Expanded(
|
||||
child: title!,
|
||||
),
|
||||
if (body != null)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
@ -32,9 +33,9 @@ class WebAppBar extends StatelessWidget {
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(onPressed: () {},
|
||||
icon: const Icon(Icons.apps_sharp,color: Colors.white,)),
|
||||
const SizedBox(width: 10,),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
const SizedBox.square(
|
||||
dimension: 40,
|
||||
child: CircleAvatar(
|
||||
@ -48,13 +49,20 @@ class WebAppBar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10,),
|
||||
const Text('mohamamd alnemer ',style: TextStyle(fontSize: 16,color: Colors.white),),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
if(HomeBloc.user!=null)
|
||||
Text(
|
||||
'${HomeBloc.user!.firstName.toString() ?? ''} ${HomeBloc.user!.lastName.toString() ?? ''} ',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
) ,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,12 @@ import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/web_layout/web_app_bar.dart';
|
||||
import 'menu_sidebar.dart';
|
||||
|
||||
class WebScaffold extends StatelessWidget {
|
||||
final bool enableMenuSideba;
|
||||
final String? appBarTitle;
|
||||
final Widget? appBarTitle;
|
||||
final List<Widget>? appBarBody;
|
||||
final Widget? scaffoldBody;
|
||||
const WebScaffold({super.key,this.appBarTitle,this.appBarBody,this.scaffoldBody,this.enableMenuSideba=true});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -24,11 +22,12 @@ class WebScaffold extends StatelessWidget {
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Container(color: Colors.white.withOpacity(0.7),),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 0.6,
|
||||
opacity: 0.7,
|
||||
child: WebAppBar(
|
||||
title: appBarTitle,
|
||||
body: appBarBody,
|
||||
|
@ -7,8 +7,10 @@ import Foundation
|
||||
|
||||
import flutter_secure_storage_macos
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
88
pubspec.lock
@ -65,6 +65,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
data_table_2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: data_table_2
|
||||
sha256: f02ec9b24f44420816a87370ff4f4e533e15b274f6267e4c9a88a585ad1a0473
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.15"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -105,6 +113,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -224,6 +240,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -324,10 +348,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e"
|
||||
sha256: e84c8a53fe1510ef4582f118c7b4bdf15b03002b51d7c2b66983c65843d61193
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.7"
|
||||
version: "2.2.8"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -392,6 +416,62 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: c3f888ba2d659f3e75f4686112cc1e71f46177f74452d40d8307edc332296ead
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -497,10 +577,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "0.5.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -42,8 +42,9 @@ dependencies:
|
||||
dio: ^5.5.0+1
|
||||
get_it: ^7.6.7
|
||||
flutter_secure_storage: ^9.2.2
|
||||
|
||||
|
||||
shared_preferences: ^2.3.0
|
||||
data_table_2: ^2.5.15
|
||||
intl: ^0.19.0
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@ -69,6 +70,7 @@ flutter:
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/images/
|
||||
- assets/
|
||||
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
|
@ -1,30 +0,0 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:syncrow_web/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|