mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-09 22:57:21 +00:00
Merge pull request #5 from SyncrowIOT/access_managment
Access management and region
This commit is contained in:
13
assets/images/calendar_icon.svg
Normal file
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 |
5
assets/images/device_note.svg
Normal file
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 |
23
assets/images/empty_table.svg
Normal file
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 |
4
assets/images/time_icon.svg
Normal file
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
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,8 +1,11 @@
|
||||
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';
|
||||
|
||||
@ -23,7 +26,13 @@ class MyApp extends StatelessWidget {
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (context) => HomeBloc()),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),)
|
||||
],
|
||||
child: MaterialApp(
|
||||
debugShowCheckedModeBanner: false, // Hide debug banner
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
dragDevices: {
|
||||
@ -33,10 +42,10 @@ class MyApp extends StatelessWidget {
|
||||
PointerDeviceKind.unknown,
|
||||
},
|
||||
),
|
||||
|
||||
theme: ThemeData(
|
||||
textTheme: const TextTheme(
|
||||
bodySmall: TextStyle(
|
||||
fontSize: 13, color: ColorsManager.whiteColors, fontWeight: FontWeight.bold),
|
||||
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),
|
||||
@ -47,10 +56,11 @@ class MyApp extends StatelessWidget {
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme
|
||||
useMaterial3: true, // Enable Material 3
|
||||
),
|
||||
home: isLoggedIn == 'Success' ? const HomePage() : const LoginPage(),
|
||||
);
|
||||
home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
229
lib/pages/access_management/bloc/access_bloc.dart
Normal file
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
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
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
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
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()),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,26 +23,34 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
on<UpdateTimerEvent>(_onUpdateTimer);
|
||||
on<PasswordVisibleEvent>(_passwordVisible);
|
||||
on<RegionInitialEvent>(_fetchRegion);
|
||||
on<SelectRegionEvent>(selectRegion);
|
||||
on<CheckEnableEvent>(checkEnable);
|
||||
on<ChangeValidateEvent>(changeValidate);
|
||||
}
|
||||
|
||||
////////////////////////////// forget password //////////////////////////////////
|
||||
////////////////////////////// forget password //////////////////////////////////
|
||||
final TextEditingController forgetEmailController = TextEditingController();
|
||||
final TextEditingController forgetPasswordController = 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;
|
||||
List<RegionModel>? regionList = [RegionModel(name: 'name', id: 'id')];
|
||||
|
||||
Future<void> _onStartTimer(StartTimerEvent event, Emitter<AuthState> emit) async {
|
||||
Future<void> _onStartTimer(
|
||||
StartTimerEvent event, Emitter<AuthState> emit) async {
|
||||
if (_validateInputs(emit)) return;
|
||||
if (_timer != null && _timer!.isActive) {
|
||||
return;
|
||||
}
|
||||
_remainingTime = 60;
|
||||
_remainingTime = 1;
|
||||
add(UpdateTimerEvent(
|
||||
remainingTime: _remainingTime, isButtonEnabled: false));
|
||||
await AuthenticationAPI.sendOtp(email: forgetEmailController.text);
|
||||
_remainingTime = (await AuthenticationAPI.sendOtp(
|
||||
email: forgetEmailController.text, regionUuid: regionUuid))!;
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_remainingTime--;
|
||||
if (_remainingTime <= 0) {
|
||||
@ -50,7 +58,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
|
||||
} else {
|
||||
add(UpdateTimerEvent(
|
||||
remainingTime: _remainingTime, isButtonEnabled: false));
|
||||
remainingTime: _remainingTime,
|
||||
isButtonEnabled: false));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -64,7 +73,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||
try {
|
||||
emit(LoadingForgetState());
|
||||
bool response = await AuthenticationAPI.verifyOtp(
|
||||
var response = await AuthenticationAPI.verifyOtp(
|
||||
email: forgetEmailController.text, otpCode: forgetOtp.text);
|
||||
if (response == true) {
|
||||
await AuthenticationAPI.forgetPassword(
|
||||
@ -72,12 +81,27 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
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());
|
||||
}
|
||||
emit(SuccessForgetState());
|
||||
} catch (failure) {
|
||||
emit(FailureForgetState(error: failure.toString()));
|
||||
// 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(
|
||||
@ -85,10 +109,6 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
remainingTime: event.remainingTime));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////// login /////////////////////////////////////
|
||||
final TextEditingController loginEmailController = TextEditingController();
|
||||
final TextEditingController loginPasswordController = TextEditingController();
|
||||
@ -99,12 +119,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
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) {
|
||||
@ -114,14 +137,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
}
|
||||
token = await AuthenticationAPI.loginWithEmail(
|
||||
model: LoginWithEmailModel(
|
||||
email: event.username,
|
||||
password: event.password,
|
||||
),
|
||||
email: event.username,
|
||||
password: event.password,
|
||||
regionUuid: event.regionUuid),
|
||||
);
|
||||
} catch (failure) {
|
||||
validate='Something went wrong';
|
||||
emit(const LoginFailure(error: 'Something went wrong'));
|
||||
// emit(LoginFailure(error: failure.toString()));
|
||||
validate = 'Invalid Credentials!';
|
||||
emit(AuthInitialState());
|
||||
// emit(const LoginFailure(error: 'Something went wrong'));
|
||||
return;
|
||||
}
|
||||
if (token.accessTokenIsNotEmpty) {
|
||||
@ -139,6 +162,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
emit(const LoginFailure(error: 'Something went wrong'));
|
||||
}
|
||||
} else {
|
||||
|
||||
emit(const LoginFailure(error: 'Accept terms and condition'));
|
||||
}
|
||||
}
|
||||
@ -146,13 +170,13 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
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);
|
||||
await AuthenticationAPI.verifyOtp(email: forgetEmailController.text, otpCode: forgetOtp.text);
|
||||
emit(SuccessForgetState());
|
||||
}
|
||||
|
||||
@ -162,18 +186,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
emit(PasswordVisibleState());
|
||||
}
|
||||
|
||||
void launchURL(String url) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
void launchURL(String url) {}
|
||||
|
||||
/////////////////////////////////////VALIDATORS/////////////////////////////////////
|
||||
String? validatePassword(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Password is required';
|
||||
} else if (value.length < 8) {
|
||||
return 'Password must be at least 8 characters';
|
||||
return '';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -183,16 +201,21 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
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;
|
||||
}
|
||||
|
||||
String? validateCode(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Code is required';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
bool _validateInputs(Emitter<AuthState> emit) {
|
||||
emit(LoadingForgetState());
|
||||
@ -274,25 +297,17 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
return '$maskedLocalPart@$domainPart';
|
||||
}
|
||||
|
||||
final List<String> regions = [
|
||||
'North America',
|
||||
'South America',
|
||||
'Europe',
|
||||
'Asia',
|
||||
'Africa',
|
||||
'Australia',
|
||||
'Antarctica',
|
||||
];
|
||||
|
||||
|
||||
static Future<String> getTokenAndValidate() async {
|
||||
static Future<String> getTokenAndValidate() async {
|
||||
try {
|
||||
const storage = FlutterSecureStorage();
|
||||
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
|
||||
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
|
||||
StringsManager.firstLaunch) ??
|
||||
true;
|
||||
if (firstLaunch) {
|
||||
storage.deleteAll();
|
||||
}
|
||||
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
|
||||
await SharedPreferencesHelper.saveBoolToSP(
|
||||
StringsManager.firstLaunch, false);
|
||||
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
|
||||
if (value.isEmpty) {
|
||||
return 'Token not found';
|
||||
@ -318,15 +333,58 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
try {
|
||||
emit(AuthLoading());
|
||||
regionList = await AuthenticationAPI.fetchRegion();
|
||||
emit(LoginSuccess());
|
||||
emit(AuthInitialState());
|
||||
} catch (e) {
|
||||
emit( LoginFailure(error: e.toString()));
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class AuthEvent extends Equatable {
|
||||
const AuthEvent();
|
||||
@ -10,11 +11,12 @@ abstract class AuthEvent extends Equatable {
|
||||
class LoginButtonPressed extends AuthEvent {
|
||||
final String username;
|
||||
final String password;
|
||||
final String regionUuid;
|
||||
|
||||
const LoginButtonPressed({required this.username, required this.password});
|
||||
const LoginButtonPressed({required this.username, required this.password, required this.regionUuid, });
|
||||
|
||||
@override
|
||||
List<Object> get props => [username, password];
|
||||
List<Object> get props => [username, password,regionUuid];
|
||||
}
|
||||
|
||||
class CheckBoxEvent extends AuthEvent {
|
||||
@ -51,5 +53,13 @@ class PasswordVisibleEvent extends AuthEvent{
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
class SelectRegionEvent extends AuthEvent {}
|
||||
|
@ -12,6 +12,7 @@ class LoginInitial extends AuthState {}
|
||||
class AuthTokenLoading extends AuthState {}
|
||||
|
||||
class AuthLoading extends AuthState {}
|
||||
class AuthInitialState extends AuthState {}
|
||||
|
||||
class LoginSuccess extends AuthState {}
|
||||
|
||||
@ -73,3 +74,12 @@ class AuthTokenError extends AuthError {
|
||||
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,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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -6,19 +6,17 @@ 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,
|
||||
@ -29,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'],
|
||||
@ -46,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,
|
||||
@ -58,7 +58,8 @@ class UserModel {
|
||||
return {
|
||||
'id': uuid,
|
||||
'email': email,
|
||||
'name': name,
|
||||
'firstName': firstName,
|
||||
'lastName': lastName,
|
||||
'photoUrl': photoUrl,
|
||||
'phoneNumber': phoneNumber,
|
||||
'isEmailVerified': isEmailVerified,
|
||||
|
@ -12,7 +12,7 @@ class ForgetPasswordPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return const ResponsiveLayout(
|
||||
desktopBody: ForgetPasswordWebPage(),
|
||||
mobileBody:ForgetPasswordMobilePage()
|
||||
mobileBody:ForgetPasswordWebPage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,24 +4,30 @@ 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(),
|
||||
create: (context) => AuthBloc()..add(RegionInitialEvent()),
|
||||
child: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is SuccessForgetState){
|
||||
if (state is SuccessForgetState) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Change Password Successfully '),
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
else if (state is FailureForgetState) {
|
||||
} else if (state is FailureForgetState) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.error),
|
||||
@ -30,11 +36,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is LoadingForgetState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return _buildForm(context, state);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -45,263 +47,317 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
late ScrollController _scrollController;
|
||||
_scrollController = ScrollController();
|
||||
void _scrollToCenter() {
|
||||
final double middlePosition = _scrollController.position.maxScrollExtent / 2;
|
||||
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;
|
||||
Size size = MediaQuery.of(context).size;
|
||||
return FirstLayer(
|
||||
second: Center(
|
||||
child: 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)),
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
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.11,
|
||||
child: const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Select your region/country',
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
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!,
|
||||
));
|
||||
},
|
||||
),
|
||||
isDense: true,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
items: forgetBloc.regions.map((String region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region,
|
||||
child: Text(region),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? value) {
|
||||
print(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: 20),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text("Account",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
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: 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,),
|
||||
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: () {
|
||||
BlocProvider.of<AuthBloc>(context).add(StartTimerEvent());
|
||||
},
|
||||
child: Text(
|
||||
'Get Code ${state is TimerState && !state.isButtonEnabled ? "(${state.remainingTime.toString()})" : ""}',
|
||||
style: TextStyle(
|
||||
color: state is TimerState && !state.isButtonEnabled
|
||||
? Colors.grey
|
||||
: ColorsManager.btnColor,
|
||||
],
|
||||
),
|
||||
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),
|
||||
),
|
||||
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,),
|
||||
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(
|
||||
if (forgetBloc.forgetValidate != '') // Check if there is a validation message
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
"Do you have an account? ",
|
||||
style: TextStyle(color: Colors.white),
|
||||
)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Flexible(
|
||||
child: Text(
|
||||
"Sign in",
|
||||
)),
|
||||
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(),
|
||||
],
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ 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';
|
||||
@ -147,15 +148,13 @@ class LoginMobilePage extends StatelessWidget {
|
||||
),
|
||||
isDense: true,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
items:
|
||||
loginBloc.regions.map((String region) {
|
||||
items:loginBloc.regionList!.map((RegionModel region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region,
|
||||
child: Text(region),
|
||||
value: region.name,
|
||||
child: Text(region.name),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? value) {
|
||||
print(value);
|
||||
},
|
||||
),
|
||||
)
|
||||
@ -296,14 +295,12 @@ class LoginMobilePage extends StatelessWidget {
|
||||
: ColorsManager.grayColor,
|
||||
child: const Text('Sign in'),
|
||||
onPressed: () {
|
||||
if (loginBloc.loginFormKey.currentState!
|
||||
.validate()) {
|
||||
if (loginBloc.loginFormKey.currentState!.validate()) {
|
||||
loginBloc.add(
|
||||
LoginButtonPressed(
|
||||
username:
|
||||
loginBloc.loginEmailController.text,
|
||||
password:
|
||||
loginBloc.loginPasswordController.text,
|
||||
regionUuid:'' ,
|
||||
username: loginBloc.loginEmailController.text,
|
||||
password: loginBloc.loginPasswordController.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
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';
|
||||
|
||||
@ -11,7 +10,7 @@ class LoginPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return const ResponsiveLayout(
|
||||
desktopBody: LoginWebPage(),
|
||||
mobileBody:LoginMobilePage()
|
||||
mobileBody:LoginWebPage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ 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';
|
||||
@ -28,7 +29,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: BlocProvider(
|
||||
create: (BuildContext context) => AuthBloc(),
|
||||
create: (BuildContext context) => AuthBloc()..add(RegionInitialEvent()),
|
||||
child: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is LoginSuccess) {
|
||||
@ -47,11 +48,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is AuthLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return _buildLoginForm(context,state);
|
||||
}
|
||||
return _buildLoginForm(context,state);
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -74,268 +71,324 @@ class _LoginWebPageState extends State<LoginWebPage> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToCenter();
|
||||
});
|
||||
return 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(),
|
||||
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(
|
||||
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(),
|
||||
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,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 10,),
|
||||
SizedBox(
|
||||
child: DropdownButtonFormField<String>(
|
||||
validator:loginBloc.validateRegion ,
|
||||
icon: const Icon(
|
||||
Icons.keyboard_arrow_down_outlined,
|
||||
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),
|
||||
),
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: null,),
|
||||
hint: SizedBox(
|
||||
width: size.width * 0.11,
|
||||
child: const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
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(
|
||||
'Select your region/country',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
"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');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
isDense: true,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
items:loginBloc.regions.map((String region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region,
|
||||
child: Text(region),
|
||||
);
|
||||
}).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,
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
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,),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator:loginBloc.validatePassword,
|
||||
obscureText:loginBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller:loginBloc.loginPasswordController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
suffixIcon: IconButton(onPressed: () {
|
||||
loginBloc.add(PasswordVisibleEvent(newValue: loginBloc.obscureText));
|
||||
},
|
||||
icon: SizedBox(
|
||||
child: SvgPicture.asset(
|
||||
loginBloc.obscureText?
|
||||
Assets.visiblePassword :
|
||||
Assets.invisiblePassword,
|
||||
height: 15,
|
||||
width: 15,
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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');
|
||||
},
|
||||
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),
|
||||
)
|
||||
),
|
||||
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');
|
||||
},
|
||||
),
|
||||
],
|
||||
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 SizedBox(height: 20.0),
|
||||
SizedBox(
|
||||
width:size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
backgroundColor: loginBloc.isChecked?
|
||||
ColorsManager.btnColor:ColorsManager.grayColor,
|
||||
child: const Text('Sign in'),
|
||||
onPressed: () {
|
||||
if (loginBloc.loginFormKey.currentState!.validate()) {
|
||||
loginBloc.add(LoginButtonPressed(
|
||||
username: loginBloc.loginEmailController.text,
|
||||
password: loginBloc.loginPasswordController.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
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(),
|
||||
],
|
||||
),),
|
||||
),
|
||||
)
|
||||
)),
|
||||
const Spacer(),
|
||||
],
|
||||
),),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (state is AuthLoading)
|
||||
const Center(child: CircularProgressIndicator())
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
75
lib/pages/common/custom_dialog.dart
Normal file
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
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
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
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@ class DefaultButton extends StatelessWidget {
|
||||
this.height,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
final void Function()? onPressed;
|
||||
final Widget child;
|
||||
final double? height;
|
||||
@ -28,15 +27,10 @@ class DefaultButton extends StatelessWidget {
|
||||
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(
|
||||
@ -65,7 +59,7 @@ class DefaultButton extends StatelessWidget {
|
||||
(Set<MaterialState> states) {
|
||||
return enabled
|
||||
? backgroundColor ?? ColorsManager.primaryColor
|
||||
: Colors.grey;
|
||||
: Colors.black.withOpacity(0.2);
|
||||
}),
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
|
92
lib/pages/common/hour_picker_dialog.dart
Normal file
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
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
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,
|
||||
});
|
||||
}
|
@ -37,14 +37,16 @@ class HomeCard extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -2,8 +2,8 @@ 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/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
|
||||
@ -13,107 +13,67 @@ class HomeWebPage extends StatelessWidget {
|
||||
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.1),
|
||||
const Text(
|
||||
'ACCESS YOUR APPS',
|
||||
style: TextStyle(fontSize: 40, 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: 4,
|
||||
crossAxisSpacing: 20.0,
|
||||
mainAxisSpacing: 20.0,
|
||||
childAspectRatio: 1.5,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
return HomeCard(
|
||||
index:index,
|
||||
active:ceilingSensorButtons[index]['active'],
|
||||
name: ceilingSensorButtons[index]['title'],
|
||||
img:ceilingSensorButtons[index]['icon'] ,
|
||||
onTap: () {
|
||||
|
||||
},);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
dynamic ceilingSensorButtons =
|
||||
[
|
||||
{
|
||||
'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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
497
lib/pages/visitor_password/bloc/visitor_password_bloc.dart
Normal file
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
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
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
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
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
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
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
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
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 [];
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,8 @@ class HTTPInterceptor extends InterceptorsWrapper {
|
||||
|
||||
List<String> headerExclusionListOfAddedParameters = [
|
||||
ApiEndpoints.login,
|
||||
ApiEndpoints.getRegion
|
||||
ApiEndpoints.getRegion,
|
||||
ApiEndpoints.sendOtp
|
||||
];
|
||||
|
||||
@override
|
||||
|
@ -1,12 +1,12 @@
|
||||
import 'dart:convert';
|
||||
|
||||
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';
|
||||
|
||||
class AuthenticationAPI {
|
||||
|
||||
static Future<Token> loginWithEmail({required var model}) async {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.login,
|
||||
@ -19,38 +19,85 @@ class AuthenticationAPI {
|
||||
}
|
||||
|
||||
static Future forgetPassword(
|
||||
{required var email, required var password}) async {
|
||||
{required var email, required var password,}) async {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.forgetPassword,
|
||||
body: {"email": email, "password": password},
|
||||
body: {
|
||||
"email": email,
|
||||
"password": password
|
||||
},
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {});
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future sendOtp({required var email}) async {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.sendOtp,
|
||||
body: {"email": email, "type": "VERIFICATION"},
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {});
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future<bool> verifyOtp(
|
||||
{required String email, required String otpCode}) async {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.verifyOtp,
|
||||
body: {"email": email, "type": "VERIFICATION", "otpCode": otpCode},
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
if (json['message'] == 'Otp Verified Successfully') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
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 response;
|
||||
);
|
||||
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 {
|
||||
|
16
lib/services/home_api.dart
Normal file
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,15 +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);
|
||||
}
|
||||
|
@ -9,4 +9,17 @@ abstract class ApiEndpoints {
|
||||
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}';
|
||||
}
|
||||
|
@ -23,4 +23,8 @@ class Assets {
|
||||
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
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 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
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)));
|
||||
|
||||
|
||||
Decoration containerDecoration = const BoxDecoration(color: Colors.white,borderRadius: BorderRadius.all(Radius.circular(20)));
|
@ -1,4 +1,7 @@
|
||||
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 {
|
||||
@ -8,8 +11,9 @@ class WebAppBar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 120,
|
||||
return BlocBuilder<HomeBloc, HomeState>(builder: (context, state) {
|
||||
return Container(
|
||||
height: 100,
|
||||
decoration: const BoxDecoration(color: ColorsManager.secondaryColor),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Expanded(
|
||||
@ -48,8 +52,9 @@ class WebAppBar extends StatelessWidget {
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
if(HomeBloc.user!=null)
|
||||
Text(
|
||||
'mohamamd alnemer ',
|
||||
'${HomeBloc.user!.firstName.toString() ?? ''} ${HomeBloc.user!.lastName.toString() ?? ''} ',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
@ -58,5 +63,6 @@ class WebAppBar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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 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(
|
||||
@ -29,7 +27,7 @@ class WebScaffold extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 0.6,
|
||||
opacity: 0.7,
|
||||
child: WebAppBar(
|
||||
title: appBarTitle,
|
||||
body: appBarBody,
|
||||
|
16
pubspec.lock
16
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:
|
||||
@ -232,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:
|
||||
|
@ -43,7 +43,8 @@ dependencies:
|
||||
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
|
||||
|
Reference in New Issue
Block a user