Compare commits

..

40 Commits

Author SHA1 Message Date
340ac35b3e display multiple error 2024-09-19 13:25:21 +04:00
af31d33778 display long error message in two line 2024-09-19 13:11:04 +04:00
26833cf215 Merge pull request #50 from SyncrowIOT/SP-465
Sp 465
2024-09-18 14:21:12 +03:00
6165c2ceee Merge remote-tracking branch 'origin/dev' into SP-465
# Conflicts:
#	lib/generated/assets.dart
2024-09-18 14:16:56 +03:00
65bb388daf schedule for one gang and two gang and three gange 2024-09-18 14:04:18 +03:00
b7f59902cd schedule for one gang and two gang and three gange 2024-09-18 13:19:43 +03:00
740ca4e4ba schedule for one gang and two gang and three gange 2024-09-18 13:01:15 +03:00
d9cd24e7f0 schedule for one gang and two gang and three gange 2024-09-18 13:00:46 +03:00
42b5ff105f schedule for one gang and two gang and three gange 2024-09-18 12:59:28 +03:00
ddaf36797d two gang schedule 2024-09-15 16:48:16 +03:00
ad47e34fa2 Implement Two-Gang & One-Gang 2024-09-15 11:05:06 +03:00
46662b5bac Implement Two-Gang & One-Gang 2024-09-15 10:15:37 +03:00
b5842194ff Implement Two-Gang & One-Gang 2024-09-12 16:59:03 +03:00
ba4ebd07e1 Merge pull request #49 from SyncrowIOT/bugfix/SP-292
Bugfix/SP-292
2024-09-09 12:22:30 +04:00
21c336360c revert 2024-09-06 10:40:45 +04:00
fee34703bd revert back 2024-09-06 10:33:39 +04:00
a92676d6a4 revert back to dev 2024-09-06 10:32:02 +04:00
8c5cf2c04e dialog 2024-09-06 10:25:54 +04:00
1f02f66916 changed stack to column 2024-09-06 10:20:52 +04:00
3ea089425c revert back change 2024-09-06 10:07:14 +04:00
76be98354b Bug fixes in the doorlock real time 2024-09-03 00:34:43 +03:00
3418fbe7b4 Merge pull request #48 from SyncrowIOT/sp-231
Sp 231
2024-09-02 15:05:31 +03:00
ea3bc4b5e1 curtain changes 2024-09-02 15:04:16 +03:00
77d2f54352 revert back dev 2024-09-02 13:38:49 +04:00
cff66bb653 revert back to dev 2024-09-02 13:35:25 +04:00
022ba53f5d Revert back to dev 2024-09-02 13:34:16 +04:00
bf69399af2 Merge remote-tracking branch 'origin/dev' into bugfix/SP-292 2024-09-02 13:27:08 +04:00
a72fc0b466 Added dialogue for success message 2024-09-02 13:25:58 +04:00
df13840a65 asset added of success icon 2024-09-02 13:25:33 +04:00
e63bf2a2c2 Merge pull request #47 from SyncrowIOT/bugfix/SP-286
fix: Shows only one error at a time for password validation error
2024-09-02 09:05:03 +04:00
bf944a6121 curtain changes 2024-09-01 10:21:04 +03:00
eb5aa56536 curtain changes 2024-08-29 16:57:15 +03:00
1e35bb8736 curtain changes 2024-08-29 16:56:19 +03:00
9c23ab1399 curtain changes 2024-08-29 16:54:43 +03:00
b3bb0b9eea fix: Correct error message and improve consistency in passwordValidator 2024-08-29 16:37:49 +04:00
032a28d47a setting up the dependencies 2024-08-29 15:54:27 +04:00
dcc98445d7 Implemented smart door animation 2024-08-26 16:45:50 +03:00
611c515173 Updated icons and bug fixes 2024-08-21 16:10:57 +03:00
e733dd9230 Updated the build number 2024-08-12 17:39:02 +03:00
8e104aeea7 Read devices status from Firebase realtime database 2024-08-12 12:32:20 +03:00
111 changed files with 5015 additions and 1072 deletions

5
assets/icons/1gang.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M38.0142 39.2553L35.3682 40H20.9308H19.9999H19.0691H1.24111C0.555643 40 0 39.4444 0 38.7589V1.24111C0 0.555643 0.555643 0 1.24111 0H19.0682H20.1226H20.9543H35.4255L38.2625 1.24111C38.9479 1.24111 39.5036 1.79675 39.5036 2.48221L39.2553 38.0142C39.2553 38.6997 38.6997 39.2553 38.0142 39.2553Z" fill="#E9E9E9"/>
<path d="M38.7589 0H35.0356C35.721 0 36.2767 0.555643 36.2767 1.24111V38.7589C36.2767 39.4444 35.721 40 35.0356 40H38.7589C39.4444 40 40 39.4444 40 38.7589V1.24111C40 0.555643 39.4444 0 38.7589 0Z" fill="#D1D1D1"/>
<path opacity="0.6" d="M21.375 31.8319V33.3213C21.375 34.0067 20.9553 34.5624 20.4375 34.5624H16.3125C15.7947 34.5624 15.375 34.0067 15.375 33.3213V31.8319C15.375 31.1465 15.7947 30.5908 16.3125 30.5908H20.4375C20.9553 30.5908 21.375 31.1465 21.375 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 933 B

7
assets/icons/2gang.svg Normal file
View File

@ -0,0 +1,7 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M38.0142 39.2553L35.3682 40H20.9308H19.9999H19.0691H1.24111C0.555643 40 0 39.4444 0 38.7589V1.24111C0 0.555643 0.555643 0 1.24111 0H19.0682H20.1226H20.9543H35.4255L38.2625 1.24111C38.9479 1.24111 39.5036 1.79675 39.5036 2.48221L39.2553 38.0142C39.2553 38.6997 38.6997 39.2553 38.0142 39.2553Z" fill="#E9E9E9"/>
<path d="M38.7589 0H35.0356C35.721 0 36.2767 0.555643 36.2767 1.24111V38.7589C36.2767 39.4444 35.721 40 35.0356 40H38.7589C39.4444 40 40 39.4444 40 38.7589V1.24111C40 0.555643 39.4444 0 38.7589 0Z" fill="#D1D1D1"/>
<path opacity="0.6" d="M12.0284 31.8319V33.3213C12.0284 34.0067 11.6087 34.5624 11.0909 34.5624H6.96594C6.44816 34.5624 6.02844 34.0067 6.02844 33.3213V31.8319C6.02844 31.1465 6.44816 30.5908 6.96594 30.5908H11.0909C11.6087 30.5908 12.0284 31.1465 12.0284 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
<path opacity="0.6" d="M26.0285 31.8319V33.3213C26.0285 34.0067 26.4482 34.5624 26.966 34.5624H31.091C31.6088 34.5624 32.0285 34.0067 32.0285 33.3213V31.8319C32.0285 31.1465 31.6088 30.5908 31.091 30.5908H26.966C26.4482 30.5908 26.0285 31.1465 26.0285 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
<path d="M19.0691 0H20.9308V40H19.0691V0Z" fill="#D1D1D1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,15 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5419 20.2425C11.1182 19.3486 10.0545 18.9679 9.16647 19.3936L1.38334 23.1269C0.591819 23.3099 0 24.0198 0 24.8727V33.8466C0 34.8381 0.797668 35.6414 1.78229 35.6414C2.76668 35.6414 3.56412 34.8381 3.56412 33.8466V27.6645L9.16647 30.3517C9.41376 30.4705 9.67449 30.5264 9.93112 30.5264C10.5974 30.5264 11.2359 30.1484 11.5419 29.5026C11.9643 28.6076 11.5875 27.5367 10.699 27.1104L6.03295 24.8725L10.699 22.6347C11.5877 22.2085 11.9643 21.1376 11.5419 20.2425Z" fill="#D5D5D5"/>
<path d="M20.6084 17.6893C20.8655 17.6893 21.1257 17.6334 21.3737 17.5145L32.0481 12.395C32.9366 11.9684 33.3141 10.8973 32.891 10.0024C32.4682 9.10855 31.4052 8.72804 30.5156 9.1535L19.8408 14.2735C18.9522 14.6999 18.5754 15.7705 18.998 16.6657C19.3038 17.3111 19.9423 17.6893 20.6084 17.6893Z" fill="#D5D5D5"/>
<path d="M42.7227 7.27445L48.1841 4.65449V10.9114C48.1841 11.9027 48.9818 12.7061 49.9659 12.7061C50.9503 12.7061 51.7478 11.9027 51.7478 10.9114V4.62192L57.2779 7.27445C57.5259 7.39303 57.7862 7.44922 58.0433 7.44922C58.7091 7.44922 59.3478 7.07124 59.6534 6.42537C60.0763 5.53041 59.6994 4.45953 58.8106 4.03316L50.7663 0.174427C50.2813 -0.0581424 49.7189 -0.0581424 49.2338 0.174427L41.19 4.03316C40.3015 4.45953 39.9244 5.53018 40.3472 6.42537C40.771 7.32101 41.8362 7.70198 42.7227 7.27445Z" fill="#D5D5D5"/>
<path d="M67.9533 12.3945L78.6281 17.5147C78.875 17.6335 79.1361 17.6895 79.3932 17.6895C80.059 17.6895 80.6978 17.3115 81.0034 16.6656C81.426 15.7707 81.0494 14.6998 80.1606 14.2734L69.4856 9.15299C68.5982 8.7273 67.5345 9.10735 67.1105 10.0018C66.6875 10.8968 67.0645 11.9677 67.9533 12.3945Z" fill="#D5D5D5"/>
<path d="M81.539 34.8098C82.4275 34.3836 82.8046 33.3128 82.3815 32.4176C81.9587 31.5231 80.8946 31.1423 80.0065 31.5694L73.167 34.8508L51.7477 24.577V16.5765C51.7477 15.5845 50.9503 14.7817 49.9659 14.7817C48.9817 14.7817 48.1841 15.5847 48.1841 16.5765V24.6095L26.8346 34.8499L20.6999 31.9073C19.8093 31.4809 18.7482 31.8612 18.3244 32.7563C17.9016 33.6513 18.2787 34.7224 19.1672 35.1488L25.8031 38.3318V61.6684L19.0593 64.903C18.1707 65.3294 17.7939 66.4 18.2165 67.2955C18.5221 67.9411 19.1606 68.3191 19.8266 68.3191C20.0837 68.3191 20.3447 68.2631 20.5917 68.1443L26.8349 65.1498L48.1841 75.3902V81.5074C48.1841 82.4992 48.9817 83.3022 49.9659 83.3022C50.9503 83.3022 51.7477 82.4989 51.7477 81.5074V75.4232L73.1657 65.15L79.6524 68.2615C79.9002 68.3801 80.1607 68.4363 80.4175 68.4363C81.0834 68.4363 81.7221 68.0583 82.0277 67.4124C82.4508 66.5175 82.0735 65.4466 81.1849 65.02L74.1977 61.6686V38.3318L81.539 34.8098ZM70.634 62.3879L51.7479 71.4466V49.0993L70.634 40.0401V62.3879ZM29.4788 61.8938C29.4472 61.8269 29.4053 61.7691 29.3668 61.708V40.0403L48.1841 49.0663V71.414L29.6409 62.5197C29.6239 62.3085 29.5745 62.0963 29.4788 61.8938ZM50.0003 27.715L69.0207 36.8382L50.0003 45.961L30.9799 36.8382L50.0003 27.715Z" fill="#D5D5D5"/>
<path d="M78.6271 82.4859L67.9525 87.6061C67.0639 88.0327 66.6864 89.1036 67.1095 89.9979C67.4153 90.644 68.054 91.0215 68.7199 91.0215C68.9767 91.0215 69.2379 90.9655 69.485 90.8477L80.1598 85.7272C81.0483 85.3009 81.4252 84.2295 81.0025 83.3346C80.579 82.4401 79.5149 82.0616 78.6271 82.4859Z" fill="#D5D5D5"/>
<path d="M57.2782 92.7267L51.748 95.3794V88.2271C51.748 87.2358 50.9506 86.4324 49.9662 86.4324C48.982 86.4324 48.1844 87.2358 48.1844 88.2271V95.3469L42.723 92.7267C41.8335 92.3024 40.7713 92.6808 40.3475 93.576C39.9246 94.4709 40.3015 95.5414 41.1903 95.9677L49.2346 99.826C49.4769 99.9418 49.7387 100 50.0008 100C50.2629 100 50.5248 99.9418 50.7671 99.826L58.8114 95.9677C59.6999 95.5414 60.077 94.4709 59.6541 93.576C59.2304 92.6808 58.166 92.3033 57.2782 92.7267Z" fill="#D5D5D5"/>
<path d="M32.0476 87.6063L21.3728 82.4861C20.4863 82.0606 19.4217 82.4395 18.9975 83.3345C18.5749 84.2294 18.9517 85.3008 19.8403 85.7271L30.5153 90.8476C30.7622 90.9654 31.0233 91.0214 31.2802 91.0214C31.946 91.0214 32.5847 90.6439 32.8903 89.9978C33.3132 89.1037 32.9361 88.0329 32.0476 87.6063Z" fill="#D5D5D5"/>
<path d="M11.5419 70.4973C11.1182 69.6028 10.0545 69.2239 9.16647 69.6487L3.56412 72.3354V65.6177C3.56412 64.6264 2.76668 63.823 1.78229 63.823C0.797668 63.823 0 64.6264 0 65.6177V74.5918C0 74.8162 0.0457694 75.0283 0.120686 75.2262C0.156892 75.8783 0.53193 76.465 1.12215 76.7485L9.16647 80.6072C9.41376 80.7254 9.67449 80.7818 9.93112 80.7818C10.5974 80.7818 11.2359 80.4038 11.5419 79.7581C11.9643 78.8632 11.5875 77.7923 10.699 77.3655L6.03295 75.1281L10.699 72.8902C11.5877 72.4636 11.9643 71.3923 11.5419 70.4973Z" fill="#D5D5D5"/>
<path d="M1.78229 56.8224C2.76668 56.8224 3.56412 56.0192 3.56412 55.0273V44.4373C3.56412 43.446 2.76668 42.6426 1.78229 42.6426C0.797668 42.6426 0 43.446 0 44.4373V55.0273C0.00022771 56.0192 0.797896 56.8224 1.78229 56.8224Z" fill="#D5D5D5"/>
<path d="M98.6173 23.1267L90.8342 19.3935C89.9443 18.9666 88.8825 19.3478 88.4589 20.2423C88.0363 21.1377 88.4132 22.2084 89.3017 22.635L93.9677 24.8729L89.3017 27.1107C88.4132 27.5371 88.0363 28.6077 88.4589 29.5029C88.7645 30.1488 89.4032 30.5268 90.0691 30.5268C90.3262 30.5268 90.5873 30.4708 90.8342 30.352L96.4365 27.6648V33.847C96.4365 34.8385 97.234 35.6417 98.2184 35.6417C99.2027 35.6417 100 34.8385 100 33.847V24.8731C100 24.0196 99.4088 23.3098 98.6173 23.1267Z" fill="#D5D5D5"/>
<path d="M98.2184 42.6421C97.2337 42.6421 96.4365 43.4455 96.4365 44.4368V55.0268C96.4365 56.0187 97.234 56.8215 98.2184 56.8215C99.2027 56.8215 100 56.0185 100 55.0268V44.4368C100 43.4455 99.203 42.6421 98.2184 42.6421Z" fill="#D5D5D5"/>
<path d="M98.2184 63.8228C97.2337 63.8228 96.4365 64.6262 96.4365 65.6175V72.3352L90.8342 69.6485C89.9443 69.223 88.8825 69.6021 88.4589 70.4971C88.0363 71.392 88.4132 72.4634 89.3017 72.8898L93.9677 75.1276L89.3017 77.365C88.4132 77.7918 88.0363 78.8627 88.4589 79.7577C88.7645 80.4033 89.4032 80.7813 90.0691 80.7813C90.3262 80.7813 90.5873 80.7249 90.8342 80.6068L98.8785 76.748C99.4687 76.4645 99.8438 75.8778 99.88 75.2258C99.9546 75.0278 100 74.8157 100 74.5914V65.6173C100 64.6262 99.203 63.8228 98.2184 63.8228Z" fill="#D5D5D5"/>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,14 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 284">
<g id="Group">
<g id="Group_2">
<path id="Vector" d="M37.8033 16.3571C37.2313 15.7851 36.3039 15.785 35.7318 16.3572L21.5532 30.5356L14.2681 23.2508C13.6962 22.6788 12.7686 22.6787 12.1966 23.2509C11.6246 23.8229 11.6246 24.7504 12.1966 25.3224L20.5174 33.643C20.8034 33.9291 21.1783 34.072 21.5531 34.072C21.928 34.072 22.3029 33.9291 22.5888 33.6429L37.8033 18.4287C38.3754 17.8567 38.3754 16.9292 37.8033 16.3571Z" fill="white"/>
</g>
</g>
<g id="Group_3">
<g id="Group_4">
<path id="Vector_2" d="M42.6776 7.32236C37.9558 2.60049 31.6776 0 25 0C18.3223 0 12.0442 2.60049 7.32236 7.32236C2.60039 12.0443 0 18.3224 0 25C0 31.6778 2.60039 37.9559 7.32236 42.6777C12.0441 47.3996 18.3223 50 25 50C31.6777 50 37.9558 47.3996 42.6776 42.6777C47.3995 37.9559 50 31.6778 50 25C50 18.3224 47.3995 12.0443 42.6776 7.32236ZM25 47.0703C12.8304 47.0703 2.92969 37.1696 2.92969 25C2.92969 12.8304 12.8304 2.92969 25 2.92969C37.1696 2.92969 47.0703 12.8304 47.0703 25C47.0703 37.1696 37.1696 47.0703 25 47.0703Z" fill="white"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,46 +1,54 @@
PODS:
- device_info_plus (0.0.1):
- Flutter
- Firebase/Analytics (10.20.0):
- Firebase/Analytics (10.25.0):
- Firebase/Core
- Firebase/Core (10.20.0):
- Firebase/Core (10.25.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 10.20.0)
- Firebase/CoreOnly (10.20.0):
- FirebaseCore (= 10.20.0)
- Firebase/Crashlytics (10.20.0):
- FirebaseAnalytics (~> 10.25.0)
- Firebase/CoreOnly (10.25.0):
- FirebaseCore (= 10.25.0)
- Firebase/Crashlytics (10.25.0):
- Firebase/CoreOnly
- FirebaseCrashlytics (~> 10.20.0)
- FirebaseCrashlytics (~> 10.25.0)
- Firebase/Database (10.25.0):
- Firebase/CoreOnly
- FirebaseDatabase (~> 10.25.0)
- firebase_analytics (10.8.7):
- Firebase/Analytics (= 10.20.0)
- Firebase/Analytics (= 10.25.0)
- firebase_core
- Flutter
- firebase_core (2.25.5):
- Firebase/CoreOnly (= 10.20.0)
- firebase_core (2.32.0):
- Firebase/CoreOnly (= 10.25.0)
- Flutter
- firebase_crashlytics (3.4.16):
- Firebase/Crashlytics (= 10.20.0)
- Firebase/Crashlytics (= 10.25.0)
- firebase_core
- Flutter
- FirebaseAnalytics (10.20.0):
- FirebaseAnalytics/AdIdSupport (= 10.20.0)
- firebase_database (10.5.7):
- Firebase/Database (= 10.25.0)
- firebase_core
- Flutter
- FirebaseAnalytics (10.25.0):
- FirebaseAnalytics/AdIdSupport (= 10.25.0)
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30910.0, >= 2.30908.0)
- FirebaseAnalytics/AdIdSupport (10.20.0):
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseAnalytics/AdIdSupport (10.25.0):
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleAppMeasurement (= 10.20.0)
- GoogleAppMeasurement (= 10.25.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30910.0, >= 2.30908.0)
- FirebaseCore (10.20.0):
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseAppCheckInterop (10.29.0)
- FirebaseCore (10.25.0):
- FirebaseCoreInternal (~> 10.0)
- GoogleUtilities/Environment (~> 7.12)
- GoogleUtilities/Logger (~> 7.12)
@ -48,19 +56,27 @@ PODS:
- FirebaseCore (~> 10.0)
- FirebaseCoreInternal (10.29.0):
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- FirebaseCrashlytics (10.20.0):
- FirebaseCrashlytics (10.25.0):
- FirebaseCore (~> 10.5)
- FirebaseInstallations (~> 10.0)
- FirebaseRemoteConfigInterop (~> 10.23)
- FirebaseSessions (~> 10.5)
- GoogleDataTransport (~> 9.2)
- GoogleUtilities/Environment (~> 7.8)
- nanopb (< 2.30910.0, >= 2.30908.0)
- nanopb (< 2.30911.0, >= 2.30908.0)
- PromisesObjC (~> 2.1)
- FirebaseDatabase (10.25.0):
- FirebaseAppCheckInterop (~> 10.17)
- FirebaseCore (~> 10.0)
- FirebaseSharedSwift (~> 10.0)
- GoogleUtilities/UserDefaults (~> 7.13)
- leveldb-library (~> 1.22)
- FirebaseInstallations (10.29.0):
- FirebaseCore (~> 10.0)
- GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/UserDefaults (~> 7.8)
- PromisesObjC (~> 2.1)
- FirebaseRemoteConfigInterop (10.29.0)
- FirebaseSessions (10.29.0):
- FirebaseCore (~> 10.5)
- FirebaseCoreExtension (~> 10.0)
@ -70,31 +86,32 @@ PODS:
- GoogleUtilities/UserDefaults (~> 7.13)
- nanopb (< 2.30911.0, >= 2.30908.0)
- PromisesSwift (~> 2.1)
- FirebaseSharedSwift (10.29.0)
- Flutter (1.0.0)
- flutter_localization (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0):
- Flutter
- GoogleAppMeasurement (10.20.0):
- GoogleAppMeasurement/AdIdSupport (= 10.20.0)
- GoogleAppMeasurement (10.25.0):
- GoogleAppMeasurement/AdIdSupport (= 10.25.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30910.0, >= 2.30908.0)
- GoogleAppMeasurement/AdIdSupport (10.20.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 10.20.0)
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleAppMeasurement/AdIdSupport (10.25.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 10.25.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30910.0, >= 2.30908.0)
- GoogleAppMeasurement/WithoutAdIdSupport (10.20.0):
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleAppMeasurement/WithoutAdIdSupport (10.25.0):
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30910.0, >= 2.30908.0)
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleDataTransport (9.4.1):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30911.0, >= 2.30908.0)
@ -129,11 +146,12 @@ PODS:
- GoogleUtilities/Privacy
- image_picker_ios (0.0.1):
- Flutter
- nanopb (2.30909.1):
- nanopb/decode (= 2.30909.1)
- nanopb/encode (= 2.30909.1)
- nanopb/decode (2.30909.1)
- nanopb/encode (2.30909.1)
- leveldb-library (1.22.5)
- nanopb (2.30910.0):
- nanopb/decode (= 2.30910.0)
- nanopb/encode (= 2.30910.0)
- nanopb/decode (2.30910.0)
- nanopb/encode (2.30910.0)
- onesignal_flutter (5.2.0):
- Flutter
- OneSignalXCFramework (= 5.2.0)
@ -207,6 +225,7 @@ DEPENDENCIES:
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
- firebase_database (from `.symlinks/plugins/firebase_database/ios`)
- Flutter (from `Flutter`)
- flutter_localization (from `.symlinks/plugins/flutter_localization/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
@ -223,15 +242,20 @@ SPEC REPOS:
trunk:
- Firebase
- FirebaseAnalytics
- FirebaseAppCheckInterop
- FirebaseCore
- FirebaseCoreExtension
- FirebaseCoreInternal
- FirebaseCrashlytics
- FirebaseDatabase
- FirebaseInstallations
- FirebaseRemoteConfigInterop
- FirebaseSessions
- FirebaseSharedSwift
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleUtilities
- leveldb-library
- nanopb
- OneSignalXCFramework
- PromisesObjC
@ -246,6 +270,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/firebase_core/ios"
firebase_crashlytics:
:path: ".symlinks/plugins/firebase_crashlytics/ios"
firebase_database:
:path: ".symlinks/plugins/firebase_database/ios"
Flutter:
:path: Flutter
flutter_localization:
@ -271,25 +297,31 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
Firebase: 10c8cb12fb7ad2ae0c09ffc86cd9c1ab392a0031
firebase_analytics: 2c1c3057d5da3bd3aab819f7e6ee153a4e46c59e
firebase_core: c8628c7ce80f79439149549052bff22f6784fbf5
firebase_crashlytics: 012078b4eec6fc9716f97ba3da0f0e44a04e95b1
FirebaseAnalytics: a2731bf3670747ce8f65368b118d18aa8e368246
FirebaseCore: 28045c1560a2600d284b9c45a904fe322dc890b6
Firebase: 0312a2352584f782ea56f66d91606891d4607f06
firebase_analytics: 3a9263fedec72970e6bd30a7132bbdd386de2c14
firebase_core: a626d00494efa398e7c54f25f1454a64c8abf197
firebase_crashlytics: 0b7cb41f5fb3b6889d0fb408cfce3cc7a4247061
firebase_database: 2713033e426b176d4fe5e7195f3d19aa1b549a91
FirebaseAnalytics: ec00fe8b93b41dc6fe4a28784b8e51da0647a248
FirebaseAppCheckInterop: 6a1757cfd4067d8e00fccd14fcc1b8fd78cfac07
FirebaseCore: 7ec4d0484817f12c3373955bc87762d96842d483
FirebaseCoreExtension: 705ca5b14bf71d2564a0ddc677df1fc86ffa600f
FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934
FirebaseCrashlytics: 81530595edb6d99f1918f723a6c33766a24a4c86
FirebaseCrashlytics: 4b96efb0ce73b38b2a85e8b8bd1bd8f63f09d015
FirebaseDatabase: faa489a42f5f868d23a55dd442d6e2099348458e
FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd
FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d
FirebaseSessions: dbd14adac65ce996228652c1fc3a3f576bdf3ecc
FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
GoogleAppMeasurement: bb3c564c3efb933136af0e94899e0a46167466a8
GoogleAppMeasurement: 9abf64b682732fed36da827aa2a68f0221fd2356
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
leveldb-library: e8eadf9008a61f9e1dde3978c086d2b6d9b9dc28
nanopb: 438bc412db1928dac798aa6fd75726007be04262
onesignal_flutter: 5ce68a29861960168e81101cb1bd685d264361de
OneSignalXCFramework: bdf74fdc06888f9466dc21e826fe1549ed143095
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c

View File

@ -514,8 +514,8 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = 48V27SBR8J;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Syncrow;
@ -524,7 +524,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
MARKETING_VERSION = 1.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.example.syncrow.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -706,8 +706,8 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = 48V27SBR8J;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Syncrow;
@ -716,7 +716,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
MARKETING_VERSION = 1.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.example.syncrow.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -736,8 +736,8 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = 48V27SBR8J;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Syncrow;
@ -746,7 +746,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
MARKETING_VERSION = 1.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.example.syncrow.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -1,115 +1,115 @@
{
"images" : [
{
"filename" : "logo-20@2x.png",
"filename" : "Syncrow Icon-20@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "logo-20@3x.png",
"filename" : "Syncrow Icon-20@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "logo-29.png",
"filename" : "Syncrow Icon-29.png",
"idiom" : "iphone",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "logo-29@2x.png",
"filename" : "Syncrow Icon-29@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "logo-29@3x.png",
"filename" : "Syncrow Icon-29@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "logo-40@2x.png",
"filename" : "Syncrow Icon-40@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "logo-40@3x.png",
"filename" : "Syncrow Icon-40@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "logo-60@2x.png",
"filename" : "Syncrow Icon-60@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "logo-60@3x.png",
"filename" : "Syncrow Icon-60@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "logo-20.png",
"filename" : "Syncrow Icon-20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "logo-20@2x 1.png",
"filename" : "Syncrow Icon-20@2x 1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "logo-29 1.png",
"filename" : "Syncrow Icon-29 1.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "logo-29@2x 1.png",
"filename" : "Syncrow Icon-29@2x 1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "logo-40.png",
"filename" : "Syncrow Icon-40.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "logo-40@2x 1.png",
"filename" : "Syncrow Icon-40@2x 1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "logo-76.png",
"filename" : "Syncrow Icon-76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "logo-76@2x.png",
"filename" : "Syncrow Icon-76@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "logo-83.5@2x.png",
"filename" : "Syncrow Icon-83.5@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "logo-1024.png",
"filename" : "Syncrow Icon-1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -11,10 +11,7 @@ import 'package:syncrow_app/features/app_layout/view/widgets/app_bar_home_dropdo
import 'package:syncrow_app/features/auth/model/user_model.dart';
import 'package:syncrow_app/features/dashboard/view/dashboard_view.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/room_model.dart';
import 'package:syncrow_app/features/devices/model/status_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_view.dart';
import 'package:syncrow_app/features/devices/view/widgets/devices_view_body.dart';
import 'package:syncrow_app/features/menu/view/menu_view.dart';
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
@ -22,10 +19,7 @@ import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_b
import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart';
import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart';
import 'package:syncrow_app/features/scene/model/create_automation_model.dart';
import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart';
import 'package:syncrow_app/features/scene/view/create_scene_view.dart';
import 'package:syncrow_app/features/scene/view/scene_tasks_view.dart';
import 'package:syncrow_app/features/scene/view/scene_view.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/navigation/navigation_service.dart';
@ -33,10 +27,8 @@ import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:syncrow_app/services/api/devices_api.dart';
import 'package:syncrow_app/services/api/profile_api.dart';
import 'package:syncrow_app/services/api/spaces_api.dart';
import 'package:syncrow_app/utils/helpers/custom_page_route.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
part 'home_state.dart';
@ -161,7 +153,7 @@ class HomeCubit extends Cubit<HomeState> {
}
_sendSubscriptionId() async {
String? subscriptionId = OneSignal.User.pushSubscription.id ?? '';
// String? subscriptionId = OneSignal.User.pushSubscription.id ?? '';
//TODO send the subscription id to BE
}

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -59,19 +58,39 @@ class AuthCubit extends Cubit<AuthState> {
/////////////////////////////////////VALIDATORS/////////////////////////////////////
String? passwordValidator(String? value) {
if (value != null) {
if (value.isEmpty) {
return 'Please enter your password';
}
if (value.isNotEmpty) {
if (!RegExp(
r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!"#$%&()*+,-./:;<=>?@[\]^_`{|}~])[A-Za-z\d!"#$%&()*+,-./:;<=>?@[\]^_`{|}~]{8,}$')
.hasMatch(value)) {
return 'Password must contain at least:\n - one uppercase letter.\n - one lowercase letter.\n - one number. \n - special character';
}
}
List<String> errors = [];
if (value == null || value.isEmpty) {
errors.add("Please enter your password");
}
return null;
if (value != null && value.length < 8) {
errors.add('Password must be at least 8 characters long');
}
if (value != null && !RegExp(r'[a-z]').hasMatch(value)) {
errors.add('Password must contain at least one lowercase letter');
}
if (value != null && !RegExp(r'[A-Z]').hasMatch(value)) {
errors.add('Password must contain at least one uppercase letter');
}
if (value != null && !RegExp(r'\d').hasMatch(value)) {
errors.add('Password must contain at least one number');
}
if (value != null &&
!RegExp(r'[!"#$%&()*+,-./:;<=>?@[\]^_`{|}~]').hasMatch(value)) {
errors.add('Password must contain at least one special character');
}
if (errors.isNotEmpty) {
return errors
.join('\n'); // Join the errors with new lines to show all at once
}
return null; // No errors
}
String? fullNameValidator(String? value) {
@ -182,13 +201,10 @@ class AuthCubit extends Cubit<AuthState> {
debugPrint('token: ${token.accessToken}');
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(
key: Token.loginAccessTokenKey,
value: token.accessToken
);
key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString()
);
value: Token.decodeToken(token.accessToken)['uuid'].toString());
user = UserModel.fromToken(token);
emailController.clear();
passwordController.clear();
@ -225,20 +241,24 @@ class AuthCubit extends Cubit<AuthState> {
sendOtp() async {
try {
emit(AuthLoading());
await AuthenticationAPI.sendOtp(body: {'email': email, 'type': 'VERIFICATION'});
await AuthenticationAPI.sendOtp(
body: {'email': email, 'type': 'VERIFICATION'});
emit(AuthSignUpSuccess());
} catch (_) {
emit(AuthLoginError(message: 'Something went wrong'));
}
}
reSendOtp() async {
Future<bool> reSendOtp() async {
try {
emit(AuthLoading());
await AuthenticationAPI.sendOtp(body: {'email': email, 'type': 'VERIFICATION'});
await AuthenticationAPI.sendOtp(
body: {'email': email, 'type': 'VERIFICATION'});
emit(ResendOtpSuccess());
return true;
} catch (_) {
emit(AuthLoginError(message: 'Something went wrong'));
return false;
}
}
@ -282,13 +302,16 @@ class AuthCubit extends Cubit<AuthState> {
try {
emit(AuthTokenLoading());
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) {
@ -315,7 +338,6 @@ class AuthCubit extends Cubit<AuthState> {
}
}
sendToForgetPassword({required String password}) async {
try {
emit(AuthForgetPassLoading());
@ -325,8 +347,4 @@ class AuthCubit extends Cubit<AuthState> {
emit(AuthForgetPassError(message: 'Something went wrong'));
}
}
}

View File

@ -8,6 +8,7 @@ import 'package:pin_code_fields/pin_code_fields.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/auth/view/create_new_password.dart';
import 'package:syncrow_app/features/shared_widgets/default_button.dart';
import 'package:syncrow_app/features/shared_widgets/success_dialog.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/navigation/routing_constants.dart';
@ -40,13 +41,19 @@ class _OtpViewState extends State<OtpView> {
_lifecycleEventHandler = LifecycleEventHandler(
resumeCallBack: () async {
SharedPreferencesHelper.saveBoolToSP('timeStampSaved', false);
String timeStampInBackground = await SharedPreferencesHelper.readStringFromSP('timeStamp');
int savedCounter = await SharedPreferencesHelper.readIntFromSP('savedCounter');
String timeStampInBackground =
await SharedPreferencesHelper.readStringFromSP('timeStamp');
int savedCounter =
await SharedPreferencesHelper.readIntFromSP('savedCounter');
DateTime currentTime = DateTime.now();
int differenceInSeconds = timeStampInBackground.isNotEmpty
? currentTime.difference(DateTime.parse(timeStampInBackground)).inSeconds
? currentTime
.difference(DateTime.parse(timeStampInBackground))
.inSeconds
: 0;
remainingSec = differenceInSeconds > savedCounter ? 0 : savedCounter - differenceInSeconds;
remainingSec = differenceInSeconds > savedCounter
? 0
: savedCounter - differenceInSeconds;
timerStarted = true;
startTimer(remainingSec ?? 0);
return;
@ -75,7 +82,8 @@ class _OtpViewState extends State<OtpView> {
}
handleTimerOnBackground() async {
bool timeStampSaved = await SharedPreferencesHelper.readBoolFromSP('timeStampSaved') ?? false;
bool timeStampSaved =
await SharedPreferencesHelper.readBoolFromSP('timeStampSaved') ?? false;
if (!timeStampSaved) {
final dateInString = DateTime.now().toString();
SharedPreferencesHelper.saveIntToSP('savedCounter', remainingSec ?? 0);
@ -115,7 +123,8 @@ class _OtpViewState extends State<OtpView> {
@override
Widget build(BuildContext context) {
String maskedEmail = AuthCubit.get(context).maskEmail(AuthCubit.get(context).email);
String maskedEmail =
AuthCubit.get(context).maskEmail(AuthCubit.get(context).email);
return BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) {
if (state is AuthOtpSuccess) {
@ -199,7 +208,10 @@ class _OtpViewState extends State<OtpView> {
child: RichText(
text: TextSpan(
text: 'We have sent the verification code to',
style: Theme.of(context).textTheme.titleSmall!.copyWith(
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(
color: Colors.white,
fontWeight: FontsManager.regular,
fontSize: 14,
@ -207,7 +219,10 @@ class _OtpViewState extends State<OtpView> {
children: [
TextSpan(
text: ' $maskedEmail',
style: Theme.of(context).textTheme.titleSmall!.copyWith(
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(
color: Colors.black,
fontWeight: FontsManager.bold,
fontSize: 14,
@ -215,7 +230,10 @@ class _OtpViewState extends State<OtpView> {
),
TextSpan(
text: ' change email?',
style: Theme.of(context).textTheme.titleSmall!.copyWith(
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(
color: const Color(0xFF87C7FF),
fontWeight: FontsManager.regular,
fontSize: 14,
@ -239,7 +257,8 @@ class _OtpViewState extends State<OtpView> {
keyboardType: TextInputType.number,
autoFocus: true,
backgroundColor: Colors.transparent,
animationDuration: const Duration(milliseconds: 30),
animationDuration:
const Duration(milliseconds: 30),
beforeTextPaste: (text) {
// Allow pasting only if all characters are numeric
return int.tryParse(text!) != null;
@ -262,18 +281,27 @@ class _OtpViewState extends State<OtpView> {
errorBorderWidth: 1,
borderWidth: 1,
errorBorderColor: Colors.red,
activeColor: state is AuthLoginError ? Colors.red : Colors.white,
inactiveColor:
state is AuthLoginError ? Colors.red : Colors.white,
activeFillColor:
state is AuthLoginError ? Colors.red : Colors.white,
inactiveFillColor:
state is AuthLoginError ? Colors.red : Colors.white,
selectedFillColor:
state is AuthLoginError ? Colors.red : Colors.white,
activeColor: state is AuthLoginError
? Colors.red
: Colors.white,
inactiveColor: state is AuthLoginError
? Colors.red
: Colors.white,
activeFillColor: state is AuthLoginError
? Colors.red
: Colors.white,
inactiveFillColor: state is AuthLoginError
? Colors.red
: Colors.white,
selectedFillColor: state is AuthLoginError
? Colors.red
: Colors.white,
disabledColor: Colors.white,
fieldHeight: 56,
fieldWidth: MediaQuery.sizeOf(context).width > 340 ? 40 : 20,
fieldWidth:
MediaQuery.sizeOf(context).width > 340
? 40
: 20,
// fieldWidth: 40,
selectedColor: Colors.white,
shape: PinCodeFieldShape.box,
@ -295,10 +323,12 @@ class _OtpViewState extends State<OtpView> {
isDone: state is AuthLoginSuccess,
isLoading: state is AuthLoading,
customButtonStyle: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
backgroundColor:
MaterialStateProperty.all(
Colors.black.withOpacity(.25),
),
foregroundColor: MaterialStateProperty.all(
foregroundColor:
MaterialStateProperty.all(
Colors.white,
),
),
@ -307,7 +337,8 @@ class _OtpViewState extends State<OtpView> {
),
onPressed: () {
if ((state is! AuthLoading)) {
AuthCubit.get(context).verifyOtp(widget.isForgetPage);
AuthCubit.get(context)
.verifyOtp(widget.isForgetPage);
FocusScope.of(context).unfocus();
}
},
@ -321,10 +352,12 @@ class _OtpViewState extends State<OtpView> {
isDone: state is AuthLoginSuccess,
isLoading: state is AuthLoading,
customButtonStyle: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
backgroundColor:
MaterialStateProperty.all(
Colors.black.withOpacity(.25),
),
foregroundColor: MaterialStateProperty.all(
foregroundColor:
MaterialStateProperty.all(
Colors.white,
),
),
@ -341,8 +374,20 @@ class _OtpViewState extends State<OtpView> {
return;
}
if ((state is! AuthLoading)) {
await AuthCubit.get(context).reSendOtp();
bool success = await AuthCubit.get(context)
.reSendOtp();
FocusScope.of(context).unfocus();
if(success){
showDialog(
context: context,
builder: (_) => SuccessDialog(
key: ValueKey('SuccessDialog'),
message: 'New OTP sent!',));
}
Future.delayed(Duration(seconds: 2),
() {
Navigator.of(context).pop();
});
}
},
),

View File

@ -105,8 +105,8 @@ class SignUpView extends StatelessWidget {
autocorrect: false,
autofillHints: const [AutofillHints.name],
// controller: AuthCubit.get(context).fullNameController,
validator: (value) =>
AuthCubit.get(context).fullNameValidator(value),
validator: (value) => AuthCubit.get(context)
.fullNameValidator(value),
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
@ -114,7 +114,11 @@ class SignUpView extends StatelessWidget {
AuthCubit.get(context).fullName = value;
},
onTap: () {},
decoration: defaultInputDecoration(context, hint: "Full Name"),
decoration: defaultInputDecoration(context,
hint: "Full Name")
.copyWith(
errorMaxLines: 2,
),
),
const SizedBox(height: 16),
const BodyMedium(
@ -129,15 +133,16 @@ class SignUpView extends StatelessWidget {
autocorrect: false,
enableSuggestions: false,
autofillHints: const [AutofillHints.email],
validator: AuthCubit.get(context).emailAddressValidator,
validator: AuthCubit.get(context)
.emailAddressValidator,
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
onChanged: (value) {
AuthCubit.get(context).email = value;
},
decoration:
defaultInputDecoration(context, hint: "Example@email.com"),
decoration: defaultInputDecoration(context,
hint: "Example@email.com"),
),
const SizedBox(height: 16),
const BodyMedium(
@ -150,17 +155,25 @@ class SignUpView extends StatelessWidget {
keyboardType: TextInputType.text,
scrollPadding: EdgeInsets.zero,
autocorrect: false,
autofillHints: const [AutofillHints.password],
validator: AuthCubit.get(context).passwordValidator,
autofillHints: const [
AutofillHints.password
],
validator: AuthCubit.get(context)
.passwordValidator,
onChanged: (value) {
AuthCubit.get(context).signUpPassword = value;
AuthCubit.get(context).signUpPassword =
value;
},
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
obscureText: !AuthCubit.get(context).isPasswordVisible,
obscureText: !AuthCubit.get(context)
.isPasswordVisible,
decoration: defaultInputDecoration(context,
hint: "At least 8 characters"),
hint: "At least 8 characters")
.copyWith(
errorMaxLines: 8,
),
),
const SizedBox(height: 16),
const BodyMedium(
@ -174,13 +187,17 @@ class SignUpView extends StatelessWidget {
scrollPadding: EdgeInsets.zero,
autocorrect: false,
enableSuggestions: false,
autofillHints: const [AutofillHints.password],
autofillHints: const [
AutofillHints.password
],
onChanged: (value) {},
validator: AuthCubit.get(context).reEnterPasswordCheck,
validator: AuthCubit.get(context)
.reEnterPasswordCheck,
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
obscureText: !AuthCubit.get(context).isPasswordVisible,
obscureText: !AuthCubit.get(context)
.isPasswordVisible,
decoration: defaultInputDecoration(context,
hint: "At least 8 characters"),
),
@ -193,10 +210,12 @@ class SignUpView extends StatelessWidget {
isDone: state is AuthLoginSuccess,
isLoading: state is AuthLoading,
customButtonStyle: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
backgroundColor:
MaterialStateProperty.all(
Colors.black.withOpacity(.25),
),
foregroundColor: MaterialStateProperty.all(
foregroundColor:
MaterialStateProperty.all(
Colors.white,
),
),
@ -204,11 +223,14 @@ class SignUpView extends StatelessWidget {
'Sign up',
),
onPressed: () {
AuthCubit.get(context).showValidationMessage = true;
if (formKey.currentState!.validate()) {
AuthCubit.get(context)
.showValidationMessage = true;
if (formKey.currentState!
.validate()) {
if ((state is! AuthLoading)) {
AuthCubit.get(context).signUp();
FocusScope.of(context).unfocus();
FocusScope.of(context)
.unfocus();
}
}
},

View File

@ -1,5 +1,5 @@
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart';
@ -40,6 +40,7 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
on<ChangeAllSwitch>(_changeAllAcSwitch);
on<IncreaseAllTemp>(_increaseAllTemp);
on<DecreaseAllTemp>(_decreaseAllTemp);
on<AcUpdated>(_onAcUpdated);
}
void _fetchAcsStatus(AcsInitial event, Emitter<AcsState> emit) async {
@ -62,6 +63,8 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
}
deviceStatus = AcStatusModel.fromJson(response['productUuid'], statusModelList);
emit(GetAcStatusState(acStatusModel: deviceStatus));
Future.delayed(const Duration(milliseconds: 500));
_listenToChanges();
}
} catch (e) {
emit(AcsFailedState(errorMessage: e.toString()));
@ -69,6 +72,29 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
}
}
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$acId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList.add(StatusModel(code: element['code'], value: element['value']));
});
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
add(AcUpdated());
});
} catch (_) {}
}
_onAcUpdated(AcUpdated event, Emitter<AcsState> emit) {
emit(GetAcStatusState(acStatusModel: deviceStatus));
}
_getAllAcs() async {
deviceStatusList = [];
devicesList = [];
@ -164,7 +190,7 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
deviceStatus.childLock = lockValue;
emit(AcModifyingState(acStatusModel: deviceStatus));
_runDeBouncerForOneDevice(deviceId: acId, code: 'child_lock', value: lockValue);
await _runDeBouncerForOneDevice(deviceId: acId, code: 'child_lock', value: lockValue);
}
void _increaseCoolTo(IncreaseCoolToTemp event, Emitter<AcsState> emit) async {

View File

@ -20,6 +20,8 @@ class AcSwitch extends AcsEvent {
List<Object> get props => [acSwitch, deviceId, productId];
}
class AcUpdated extends AcsEvent {}
class AcsInitial extends AcsEvent {
final bool allAcs;
const AcsInitial({required this.allAcs});

View File

@ -1,3 +1,4 @@
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_event.dart';
import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_state.dart';
@ -15,6 +16,7 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
CeilingSensorBloc({required this.deviceId}) : super(InitialState()) {
on<InitialEvent>(_fetchCeilingSensorStatus);
on<ChangeValueEvent>(_changeValue);
on<CeilingSensorUpdated>(_onCeilingSensorUpdated);
}
void _fetchCeilingSensorStatus(InitialEvent event, Emitter<CeilingSensorState> emit) async {
@ -27,12 +29,36 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
}
deviceStatus = CeilingSensorModel.fromJson(statusModelList);
emit(UpdateState(ceilingSensorModel: deviceStatus));
_listenToChanges();
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList.add(StatusModel(code: element['code'], value: element['value']));
});
deviceStatus = CeilingSensorModel.fromJson(statusList);
add(CeilingSensorUpdated());
});
} catch (_) {}
}
_onCeilingSensorUpdated(CeilingSensorUpdated event, Emitter<CeilingSensorState> emit) {
emit(UpdateState(ceilingSensorModel: deviceStatus));
}
void _changeValue(ChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
emit(LoadingNewSate(ceilingSensorModel: deviceStatus));
try {

View File

@ -11,6 +11,8 @@ class LoadingEvent extends CeilingSensorEvent {}
class InitialEvent extends CeilingSensorEvent {}
class CeilingSensorUpdated extends CeilingSensorEvent {}
class ChangeValueEvent extends CeilingSensorEvent {
final int value;
final String code;

View File

@ -0,0 +1,184 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_event.dart';
import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_state.dart';
import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/status_model.dart';
import 'package:syncrow_app/services/api/devices_api.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
double curtainWidth = 270;
double curtainOpeningSpace = 195;
double blindHeight = 310;
double blindOpeningSpace = 245;
double openPercentage = 0;
bool isMoving = false;
final String curtainId;
CurtainBloc(
this.curtainId,
) : super(CurtainInitial()) {
on<InitCurtain>(_fetchStatus);
on<OpenCurtain>(_onOpenCurtain);
on<CloseCurtain>(_onCloseCurtain);
on<PauseCurtain>(_onPauseCurtain);
}
Future<void> _onOpenCurtain(
OpenCurtain event,
Emitter<CurtainState> emit) async {
isMoving = true;
while (openPercentage < 100.0) {
if (state is CurtainsClosing) {
_pauseCurtain(emit);
break;
}
emit(CurtainsOpening(
curtainWidth: curtainWidth,
blindHeight: blindHeight,
openPercentage: openPercentage,
));
if (isMoving) {
await Future.delayed(const Duration(milliseconds: 200), () async {
openPercentage += 10.0;
event.deviceType == DeviceType.Curtain
? curtainWidth -= curtainOpeningSpace / 10
: blindHeight -= blindOpeningSpace / 10;
if (openPercentage >= 100.0) {
_pauseCurtain(emit);
}
});
if (openPercentage >=100.0) {
await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: curtainId,
code: 'control',
value: 'close',
),
curtainId,
);
await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: curtainId,
code: 'percent_control',
value: 100,
),
curtainId,
);
}
} else {
_pauseCurtain(emit);
break;
}
}
}
Future<void> _onCloseCurtain(
CloseCurtain event, Emitter<CurtainState> emit) async {
isMoving = true;
while (openPercentage > 0.0) {
if (state is CurtainsOpening) {
_pauseCurtain(emit);
break;
}
emit(CurtainsClosing(
curtainWidth: curtainWidth,
blindHeight: blindHeight,
openPercentage: openPercentage,
));
if (isMoving) {
await Future.delayed(const Duration(milliseconds: 200), () async {
openPercentage -= 10.0;
event.deviceType == DeviceType.Curtain
? curtainWidth += curtainOpeningSpace / 10
: blindHeight += blindOpeningSpace / 10;
if (openPercentage <= 0.0) {
_pauseCurtain(emit);
}
});
if (openPercentage == 0.0) {
await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: curtainId,
code: 'percent_control',
value: 0,
),
curtainId,
);
await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: curtainId,
code: 'control',
value: 'open',
),
curtainId,
);
}
} else {
_pauseCurtain(emit);
break;
}
}
}
Future<void> _onPauseCurtain(
PauseCurtain event, Emitter<CurtainState> emit) async {
_pauseCurtain(emit);
await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: curtainId,
code: 'control',
value: 'stop',
),
curtainId,
);
await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: curtainId,
code: 'percent_control',
value: openPercentage.ceil(),
),
curtainId,
);
}
Future<void> _pauseCurtain(Emitter<CurtainState> emit) async {
isMoving = false;
emit(CurtainsPaused(
curtainWidth: curtainWidth,
blindHeight: blindHeight,
openPercentage: openPercentage,
));
}
void _fetchStatus(InitCurtain event, Emitter<CurtainState> emit) async {
try {
emit(CurtainLoadingState());
// Fetch the status from the API
var response = await DevicesAPI.getDeviceStatus(curtainId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
// Get the open percentage from the response
openPercentage = double.tryParse(statusModelList[1].value.toString())!;
// Calculate curtain width and blind height based on the open percentage
if (openPercentage != null) {
curtainWidth = 270 - (openPercentage / 100) * curtainOpeningSpace;
blindHeight = 310 - (openPercentage / 100) * blindOpeningSpace;
}
emit(CurtainsOpening(
curtainWidth: curtainWidth,
blindHeight: blindHeight,
openPercentage: openPercentage,
));
} catch (e) {
emit(FailedState());
return;
}
}
}

View File

@ -0,0 +1,34 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
abstract class CurtainEvent extends Equatable {
const CurtainEvent();
@override
List<Object?> get props => [];
}
class OpenCurtain extends CurtainEvent {
final DeviceType deviceType;
const OpenCurtain(this.deviceType);
@override
List<Object?> get props => [deviceType];
}
class CloseCurtain extends CurtainEvent {
final DeviceType deviceType;
const CloseCurtain(this.deviceType);
@override
List<Object?> get props => [deviceType];
}
class InitCurtain extends CurtainEvent {}
class PauseCurtain extends CurtainEvent {}
class useCurtainEvent extends CurtainEvent {}

View File

@ -0,0 +1,76 @@
// curtain_state.dart
import 'package:equatable/equatable.dart';
abstract class CurtainState extends Equatable {
const CurtainState();
@override
List<Object?> get props => [];
}
class CurtainInitial extends CurtainState {}
class UpdateCurtain extends CurtainState {
final double curtainWidth;
final double blindHeight;
final double openPercentage;
const UpdateCurtain({
required this.curtainWidth,
required this.blindHeight,
required this.openPercentage,
});
@override
List<Object?> get props => [curtainWidth, blindHeight, openPercentage];
}
class FailedState extends CurtainState {}
class CurtainLoadingState extends CurtainState {}
class CurtainsOpening extends CurtainState {
final double curtainWidth;
final double blindHeight;
final double openPercentage;
const CurtainsOpening({
required this.curtainWidth,
required this.blindHeight,
required this.openPercentage,
});
@override
List<Object?> get props => [curtainWidth, blindHeight, openPercentage];
}
class CurtainsClosing extends CurtainState {
final double curtainWidth;
final double blindHeight;
final double openPercentage;
const CurtainsClosing({
required this.curtainWidth,
required this.blindHeight,
required this.openPercentage,
});
@override
List<Object?> get props => [curtainWidth, blindHeight, openPercentage];
}
class CurtainsPaused extends CurtainState {
final double curtainWidth;
final double blindHeight;
final double openPercentage;
const CurtainsPaused({
required this.curtainWidth,
required this.blindHeight,
required this.openPercentage,
});
@override
List<Object?> get props => [curtainWidth, blindHeight, openPercentage];
}

View File

@ -87,10 +87,10 @@ class DevicesCubit extends Cubit<DevicesState> {
return const DoorsListView();
case DeviceType.Curtain:
return const CurtainListView();
// case DeviceType.ThreeGang:
// return const ThreeGangSwitchesView();
// case DeviceType.Gateway:
// return const GateWayView();
// case DeviceType.ThreeGang:
// return const ThreeGangSwitchesView();
// case DeviceType.Gateway:
// return const GateWayView();
default:
return null;
}
@ -308,10 +308,10 @@ class DevicesCubit extends Cubit<DevicesState> {
emitSafe(GetDevicesLoading());
int roomIndex =
HomeCubit.getInstance().selectedSpace!.rooms!.indexWhere((element) => element.id == roomId);
HomeCubit.getInstance().selectedSpace!.rooms!.indexWhere((element) => element.id == roomId);
try {
HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices =
await DevicesAPI.getDevicesByRoomId(roomId);
await DevicesAPI.getDevicesByRoomId(roomId);
} catch (e) {
emitSafe(GetDevicesError(e.toString()));
return;
@ -397,87 +397,7 @@ class DevicesCubit extends Cubit<DevicesState> {
// }
// }
//////////////////////////////////CURTAINS//////////////////////////////////////
double curtainWidth = 270;
double curtainOpeningSpace = 195;
double blindWindowHight = 310;
double blindOpeningSpace = 245;
double _openPercentage = 0;
bool isMoving = false;
openCurtain(DeviceType type) async {
if (state is CurtainsIsOpening) {
return;
}
isMoving = true;
while (_openPercentage < 100.0) {
if (state is CurtainsIsClosing) {
//listen to interruption by the closing process
pauseCurtain();
break;
}
emit(CurtainsIsOpening());
if (isMoving) {
await Future.delayed(const Duration(milliseconds: 200), () {
_openPercentage += 10.0;
//25.5 is the 10% of the curtain opening space, its used to update the
// animated container of thecurtain
type == DeviceType.Curtain
? curtainWidth -= curtainOpeningSpace / 10
: blindWindowHight -= blindOpeningSpace / 10;
if (_openPercentage >= 100.0) {
pauseCurtain();
}
});
} else {
pauseCurtain();
break;
}
}
}
closeCurtain(DeviceType type) async {
if (state is CurtainsIsClosing) {
return;
}
isMoving = true;
while (_openPercentage > 0.0) {
if (state is CurtainsIsOpening) {
// interrupted by the opening process
pauseCurtain();
break;
}
emit(CurtainsIsClosing());
if (isMoving) {
await Future.delayed(const Duration(milliseconds: 200), () {
_openPercentage -= 10.0;
//25.5 is the 10% of the curtain opening space, its used to update the
type == DeviceType.Curtain
? curtainWidth += curtainOpeningSpace / 10
: blindWindowHight += 24.5;
if (_openPercentage <= 0.0) {
pauseCurtain();
}
});
} else {
pauseCurtain();
break;
}
}
}
pauseCurtain() {
isMoving = false;
emit(CurtainsStopped());
}
}
enum LightMode {

View File

@ -0,0 +1,326 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/one_gang_model.dart';
import 'package:syncrow_app/features/devices/model/schedule_model.dart';
import 'package:syncrow_app/features/devices/model/status_model.dart';
import 'package:syncrow_app/services/api/devices_api.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
import 'one_gang_event.dart';
class OneGangBloc extends Bloc<OneGangEvent, OneGangState> {
final String oneGangId;
final String switchCode;
OneGangModel deviceStatus = OneGangModel(
firstSwitch: false,
firstCountDown: 0,
);
Timer? _timer;
bool oneGangGroup = false;
List<DeviceModel> devicesList = [];
OneGangBloc({required this.oneGangId,required this.switchCode}) : super(InitialState()) {
on<InitialEvent>(_fetchOneGangStatus);
on<OneGangUpdated>(_oneGangUpdated);
on<ChangeFirstSwitchStatusEvent>(_changeFirstSwitch);
on<ChangeSlidingSegment>(_changeSliding);
on<SetCounterValue>(_setCounterValue);
on<GetCounterEvent>(_getCounterValue);
on<TickTimer>(_onTickTimer);
on<OnClose>(_onClose);
on<ToggleDaySelectionEvent>(toggleDaySelection);
on<ThreeGangSave>(saveSchedule);
on<GetScheduleEvent>(getSchedule);
on<ToggleScheduleEvent>(toggleChange);
on<DeleteScheduleEvent>(deleteSchedule);
}
void _fetchOneGangStatus(InitialEvent event, Emitter<OneGangState> emit) async {
emit(LoadingInitialState());
try {
var response = await DevicesAPI.getDeviceStatus(oneGangId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = OneGangModel.fromJson(statusModelList);
emit(UpdateState(oneGangModel: deviceStatus));
_listenToChanges();
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$oneGangId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) async {
if (_timer != null) {
await Future.delayed(const Duration(seconds: 2));
}
Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList.add(StatusModel(code: element['code'], value: element['value']));
});
deviceStatus = OneGangModel.fromJson(statusList);
if (!isClosed) {
add(OneGangUpdated());
}
});
} catch (_) {}
}
_oneGangUpdated(OneGangUpdated event, Emitter<OneGangState> emit) {
emit(UpdateState(oneGangModel: deviceStatus));
}
void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter<OneGangState> emit) async {
emit(LoadingNewSate(oneGangModel: deviceStatus));
try {
deviceStatus.firstSwitch = !event.value;
emit(UpdateState(oneGangModel: deviceStatus));
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: oneGangGroup ? event.deviceId : oneGangId,
code: 'switch_1',
value: !event.value),
oneGangGroup ? event.deviceId : oneGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: oneGangGroup));
}
});
} catch (_) {
add(InitialEvent(groupScreen: oneGangGroup));
}
}
void _changeSliding(ChangeSlidingSegment event, Emitter<OneGangState> emit) async {
emit(ChangeSlidingSegmentState(value: event.value));
}
void _setCounterValue(SetCounterValue event, Emitter<OneGangState> emit) async {
emit(LoadingNewSate(oneGangModel: deviceStatus));
int seconds = 0;
try {
seconds = event.duration.inSeconds;
final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: oneGangId, code: event.deviceCode, value: seconds),
oneGangId);
if (response['success'] ?? false) {
if (event.deviceCode == 'countdown_1') {
deviceStatus.firstCountDown = seconds;
}
} else {
emit(const FailedState(error: 'Something went wrong'));
return;
}
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
if (seconds > 0) {
_onStartTimer(seconds);
} else {
_timer?.cancel();
emit(TimerRunComplete());
}
}
void _getCounterValue(GetCounterEvent event, Emitter<OneGangState> emit) async {
emit(LoadingInitialState());
try {
var response = await DevicesAPI.getDeviceStatus(oneGangId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = OneGangModel.fromJson(statusModelList);
if (event.deviceCode == 'countdown_1') {
deviceStatus.firstCountDown > 0
? _onStartTimer(deviceStatus.firstCountDown)
: emit(UpdateTimerState(seconds: deviceStatus.firstCountDown));
}
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
void _onClose(OnClose event, Emitter<OneGangState> emit) {
_timer?.cancel();
}
void _onStartTimer(int seconds) {
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
add(TickTimer(seconds - timer.tick));
});
}
void _onTickTimer(TickTimer event, Emitter<OneGangState> emit) {
if (event.remainingTime > 0) {
emit(TimerRunInProgress(event.remainingTime));
} else {
_timer?.cancel();
emit(TimerRunComplete());
}
}
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"},
];
Future<void> saveSchedule(ThreeGangSave event, Emitter<OneGangState> emit,) async {
try {
if(selectedDays.isNotEmpty) {
emit(LoadingInitialState());
final response = await DevicesAPI.postSchedule(
category: switchCode,
deviceId: oneGangId,
time: getTimeStampWithoutSeconds(selectedTime).toString(),
code: switchCode,
value: toggleSchedule,
days: selectedDays);
CustomSnackBar.displaySnackBar('Save Successfully');
add(GetScheduleEvent());
emit(ThreeGangSaveSchedule());
toggleCreateSchedule();
}else{
CustomSnackBar.displaySnackBar('Please select days');
}
} catch (e) {
emit(FailedState(error: e.toString()));
}
}
Future<void> getSchedule(GetScheduleEvent event, Emitter<OneGangState> emit,) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.getSchedule(
category: switchCode,
deviceId: oneGangId ,
);
List<dynamic> jsonData = response;
listSchedule = jsonData.map((item) => ScheduleModel.fromJson(item)).toList();
emit(InitialState());
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
if (dateTime == null) return null;
DateTime dateTimeWithoutSeconds = DateTime(dateTime.year, dateTime.month,
dateTime.day, dateTime.hour, dateTime.minute);
return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000;
}
Future toggleChange(
ToggleScheduleEvent event, Emitter<OneGangState> emit) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.changeSchedule(
scheduleId: event.id, deviceUuid: oneGangId, enable: event.toggle);
if (response == true) {
add(GetScheduleEvent());
toggleSchedule = event.toggle;
return toggleSchedule;
}
emit(IsToggleState(onOff: toggleSchedule));
add(const ChangeSlidingSegment(value: 1));
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
Future deleteSchedule(
DeleteScheduleEvent event, Emitter<OneGangState> emit) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.deleteSchedule(
scheduleId: event.id,
deviceUuid: oneGangId, );
if (response == true) {
add(GetScheduleEvent());
return toggleSchedule;
}
emit(IsToggleState(onOff: toggleSchedule));
add(const ChangeSlidingSegment(value: 1));
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
void toggleCreateSchedule() {
emit(LoadingInitialState());
createSchedule = !createSchedule;
selectedDays.clear();
selectedTime=DateTime.now();
emit(UpdateCreateScheduleState(createSchedule));
emit(ChangeSlidingSegmentState(value: 1));
}
bool toggleSchedule = true;
List<String> selectedDays = [];
bool createSchedule = false;
List<ScheduleModel> listSchedule = [];
DateTime? selectedTime=DateTime.now();
Future<void> toggleDaySelection(
ToggleDaySelectionEvent event,
Emitter<OneGangState> emit,
) async {
emit(LoadingInitialState());
if (selectedDays.contains(event.key)) {
selectedDays.remove(event.key);
} else {
selectedDays.add(event.key);
}
emit(ChangeTimeState());
add(ChangeSlidingSegment(value: 1));
}
int selectedTabIndex = 0;
void toggleSelectedIndex(index) {
emit(LoadingInitialState());
selectedTabIndex = index;
emit(ChangeSlidingSegmentState(value: selectedTabIndex));
}
}

View File

@ -0,0 +1,116 @@
import 'package:equatable/equatable.dart';
abstract class OneGangEvent extends Equatable {
const OneGangEvent();
@override
List<Object> get props => [];
}
class LoadingEvent extends OneGangEvent {}
class OneGangUpdated extends OneGangEvent {}
class InitialEvent extends OneGangEvent {
final bool groupScreen;
const InitialEvent({required this.groupScreen});
@override
List<Object> get props => [groupScreen];
}
class ChangeFirstSwitchStatusEvent extends OneGangEvent {
final bool value;
final String deviceId;
const ChangeFirstSwitchStatusEvent({required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}
class ChangeSecondSwitchStatusEvent extends OneGangEvent {
final bool value;
final String deviceId;
const ChangeSecondSwitchStatusEvent({required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}
class AllOffEvent extends OneGangEvent {}
class AllOnEvent extends OneGangEvent {}
class GroupAllOnEvent extends OneGangEvent {}
class GroupAllOffEvent extends OneGangEvent {}
class ChangeSlidingSegment extends OneGangEvent {
final int value;
const ChangeSlidingSegment({required this.value});
@override
List<Object> get props => [value];
}
class GetCounterEvent extends OneGangEvent {
final String deviceCode;
const GetCounterEvent({required this.deviceCode});
@override
List<Object> get props => [deviceCode];
}
class SetCounterValue extends OneGangEvent {
final Duration duration;
final String deviceCode;
const SetCounterValue({required this.duration, required this.deviceCode});
@override
List<Object> get props => [duration, deviceCode];
}
class StartTimer extends OneGangEvent {
final int duration;
const StartTimer(this.duration);
@override
List<Object> get props => [duration];
}
class TickTimer extends OneGangEvent {
final int remainingTime;
const TickTimer(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class StopTimer extends OneGangEvent {}
class OnClose extends OneGangEvent {}
//------------------- Schedule ----------=---------
class GetScheduleEvent extends OneGangEvent {}
class ThreeGangSave extends OneGangEvent {}
class ToggleScheduleEvent extends OneGangEvent {
final String id;
final bool toggle;
const ToggleScheduleEvent({required this.toggle,required this.id});
@override
List<Object> get props => [toggle,id];
}
class ToggleDaySelectionEvent extends OneGangEvent {
final String key;
const ToggleDaySelectionEvent({required this.key});
@override
List<Object> get props => [key];
}
class DeleteScheduleEvent extends OneGangEvent {
final String id;
const DeleteScheduleEvent({required this.id});
@override
List<Object> get props => [id];
}

View File

@ -0,0 +1,89 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart';
import 'package:syncrow_app/features/devices/model/one_gang_model.dart';
import 'package:syncrow_app/features/devices/model/two_gang_model.dart';
class OneGangState extends Equatable {
const OneGangState();
@override
List<Object> get props => [];
}
class InitialState extends OneGangState {}
class LoadingInitialState extends OneGangState {}
class UpdateState extends OneGangState {
final OneGangModel oneGangModel;
const UpdateState({required this.oneGangModel});
@override
List<Object> get props => [TwoGangModel];
}
class LoadingNewSate extends OneGangState {
final OneGangModel oneGangModel;
const LoadingNewSate({required this.oneGangModel});
@override
List<Object> get props => [OneGangModel];
}
class UpdateGroupState extends OneGangState {
final List<GroupTwoGangModel> twoGangList;
final bool allSwitches;
const UpdateGroupState({required this.twoGangList, required this.allSwitches});
@override
List<Object> get props => [twoGangList, allSwitches];
}
class FailedState extends OneGangState {
final String error;
const FailedState({required this.error});
@override
List<Object> get props => [error];
}
class ChangeSlidingSegmentState extends OneGangState {
final int value;
const ChangeSlidingSegmentState({required this.value});
@override
List<Object> get props => [value];
}
class UpdateTimerState extends OneGangState {
final int seconds;
const UpdateTimerState({required this.seconds});
@override
List<Object> get props => [seconds];
}
class TimerRunInProgress extends OneGangState {
final int remainingTime;
const TimerRunInProgress(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class TimerRunComplete extends OneGangState {}
class ThreeGangSaveSchedule extends OneGangState {}
class IsToggleState extends OneGangState {
final bool? onOff;
const IsToggleState({this.onOff});
}
class ChangeTimeState extends OneGangState {}
class UpdateCreateScheduleState extends OneGangState {
final bool createSchedule;
UpdateCreateScheduleState(this.createSchedule);
}

View File

@ -1,12 +1,11 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart';
import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart';
import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/offline_password_model.dart';
import 'package:syncrow_app/features/devices/model/offline_temporary_password.dart';
import 'package:syncrow_app/features/devices/model/smart_door_model.dart';
@ -25,6 +24,7 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
SmartDoorBloc({required this.deviceId}) : super(InitialState()) {
on<InitialEvent>(_fetchSmartDoorStatus);
on<DoorLockUpdated>(_doorLockUpdated);
on<InitialPasswordsPage>(getTemporaryPasswords);
on<InitialOneTimePassword>(getOneTimePasswords);
on<InitialTimeLimitPassword>(getTimeLimitPasswords);
@ -54,6 +54,7 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
bool isStartEndTime = true;
DateTime? startTime;
DateTime? endTime;
int unlockRequest = 0;
List<TemporaryPassword>? temporaryPasswords = [];
List<OfflinePasswordModel>? oneTimePasswords = [];
List<OfflinePasswordModel>? timeLimitPasswords = [];
@ -69,23 +70,24 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
return passwordController.text;
}
Future generateAndSavePasswordOneTime (GenerateAndSavePasswordOneTimeEvent event, Emitter<SmartDoorState> emit) async {
Future generateAndSavePasswordOneTime(
GenerateAndSavePasswordOneTimeEvent event, Emitter<SmartDoorState> emit) async {
try {
if (isSavingPassword) return;
isSavingPassword = true;
emit(LoadingInitialState());
var res = await DevicesAPI.generateOneTimePassword(deviceId: deviceId);
ApiResponse pass= ApiResponse.fromJson(res);
passwordController.text =pass.data.offlineTempPassword;
passwordId=pass.data.offlineTempPasswordId;
var res = await DevicesAPI.generateOneTimePassword(deviceId: deviceId);
ApiResponse pass = ApiResponse.fromJson(res);
passwordController.text = pass.data.offlineTempPassword;
passwordId = pass.data.offlineTempPasswordId;
Future.delayed(const Duration(seconds: 1), () {
Clipboard.setData(ClipboardData(text: passwordController.text));
Clipboard.setData(ClipboardData(text: passwordController.text));
});
emit(const GeneratePasswordOneTimestate(generated: true));
} catch (_) {
emit(FailedState(errorMessage: _.toString()));
}finally {
} finally {
isSavingPassword = false;
}
}
@ -100,20 +102,42 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
}
deviceStatus = SmartDoorModel.fromJson(statusModelList);
emit(UpdateState(smartDoorModel: deviceStatus));
_listenToChanges();
} catch (e) {
emit(FailedState(errorMessage: e.toString()));
return;
}
}
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList.add(StatusModel(code: element['code'], value: element['value']));
});
deviceStatus = SmartDoorModel.fromJson(statusList);
add(DoorLockUpdated());
});
} catch (_) {}
}
_doorLockUpdated(DoorLockUpdated event, Emitter<SmartDoorState> emit) {
unlockRequest = deviceStatus.unlockRequest;
emit(UpdateState(smartDoorModel: deviceStatus));
}
void _renamePassword(RenamePasswordEvent event, Emitter<SmartDoorState> emit) async {
try {
emit(LoadingInitialState());
var response = await DevicesAPI.renamePass(
name:passwordNameController.text ,
doorLockUuid:deviceId ,
passwordId:passwordId
);
await DevicesAPI.renamePass(
name: passwordNameController.text, doorLockUuid: deviceId, passwordId: passwordId);
add(InitialOneTimePassword());
add(InitialTimeLimitPassword());
emit(UpdateState(smartDoorModel: deviceStatus));
@ -126,11 +150,14 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
void getTemporaryPasswords(InitialPasswordsPage event, Emitter<SmartDoorState> emit) async {
try {
emit(LoadingInitialState());
var response = await DevicesAPI.getTemporaryPasswords(deviceId, );
var response = await DevicesAPI.getTemporaryPasswords(
deviceId,
);
if (response is List) {
temporaryPasswords = response.map((item) => TemporaryPassword.fromJson(item)).toList();
} else if (response is Map && response.containsKey('data')) {
temporaryPasswords = (response['data'] as List).map((item) => TemporaryPassword.fromJson(item)).toList();
temporaryPasswords =
(response['data'] as List).map((item) => TemporaryPassword.fromJson(item)).toList();
}
emit(TemporaryPasswordsLoadedState(temporaryPassword: temporaryPasswords!));
} catch (e) {
@ -183,25 +210,26 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
bool setStartEndTime(SetStartEndTimeEvent event, Emitter<SmartDoorState> emit) {
emit(LoadingInitialState());
isStartEndTime = event.val;
emit(IsStartEndState(isStartEndTime:isStartEndTime));
emit(IsStartEndState(isStartEndTime: isStartEndTime));
return isStartEndTime;
}
void _updateLock(UpdateLockEvent event, Emitter<SmartDoorState> emit) async {
emit(LoadingNewSate(smartDoorModel: deviceStatus));
try {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: deviceId, code: 'normal_open_switch', value: !event.value),
deviceId);
// final response = await DevicesAPI.controlDevice(
// DeviceControlModel(deviceId: deviceId, code: 'normal_open_switch', value: !event.value),
// deviceId);
if (response['success'] ?? false) {
final response = await DevicesAPI.openDoorLock(deviceId);
if (response) {
deviceStatus.normalOpenSwitch = !event.value;
}
} catch (_) {}
emit(UpdateState(smartDoorModel: deviceStatus));
}
Future<void> selectTimeOfLinePassword(SelectTimeEvent event, Emitter<SmartDoorState> emit) async {
emit(ChangeTimeState());
final DateTime? picked = await showDatePicker(
@ -224,24 +252,28 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
0,
);
final selectedTimestamp = DateTime(
selectedDateTime.year,
selectedDateTime.month,
selectedDateTime.day,
selectedDateTime.hour,
selectedDateTime.minute,
).millisecondsSinceEpoch ~/ 1000; // Divide by 1000 to remove milliseconds
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
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.');
CustomSnackBar.displaySnackBar(
'Expiration Time cannot be earlier than Effective Time.');
} else {
expirationTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds
expirationTime =
selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds
expirationTimeTimeStamp = selectedTimestamp;
}
}
@ -250,7 +282,8 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
}
}
Future<void> selectTimeOnlinePassword(SelectTimeOnlinePasswordEvent event, Emitter<SmartDoorState> emit) async {
Future<void> selectTimeOnlinePassword(
SelectTimeOnlinePasswordEvent event, Emitter<SmartDoorState> emit) async {
emit(ChangeTimeState());
final DateTime? picked = await showDatePicker(
context: event.context,
@ -288,12 +321,12 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
timePicked.minute,
);
final selectedTimestamp = DateTime(
selectedDateTime.year,
selectedDateTime.month,
selectedDateTime.day,
selectedDateTime.hour,
selectedDateTime.minute,
).millisecondsSinceEpoch ~/
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!) {
@ -305,7 +338,8 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
}
} else {
if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) {
CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.');
CustomSnackBar.displaySnackBar(
'Expiration Time cannot be earlier than Effective Time.');
} else {
expirationTime =
selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds
@ -322,7 +356,7 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
try {
isSavingPassword = true;
emit(LoadingSaveState());
var res = await DevicesAPI.createPassword(
await DevicesAPI.createPassword(
deviceId: deviceId,
effectiveTime: effectiveTimeTimeStamp.toString(),
invalidTime: expirationTimeTimeStamp.toString(),
@ -341,25 +375,25 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
CustomSnackBar.displaySnackBar('Save Successfully');
emit(SaveState());
} catch (_) {
}finally {
} finally {
isSavingPassword = false;
}
}
Future<void> generateAndSavePasswordTimeLimited (GenerateAndSavePasswordTimeLimitEvent event, Emitter<SmartDoorState> emit) async {
Future<void> generateAndSavePasswordTimeLimited(
GenerateAndSavePasswordTimeLimitEvent event, Emitter<SmartDoorState> emit) async {
if (timeLimitValidate() || isSavingPassword) return;
try {
isSavingPassword = true;
emit(LoadingInitialState());
var res = await DevicesAPI.generateMultiTimePassword(
var res = await DevicesAPI.generateMultiTimePassword(
deviceId: deviceId,
effectiveTime: effectiveTimeTimeStamp.toString(),
invalidTime: expirationTimeTimeStamp.toString(),
);
ApiResponse pass= ApiResponse.fromJson(res);
passwordController.text =pass.data.offlineTempPassword;
passwordId=pass.data.offlineTempPasswordId;
ApiResponse pass = ApiResponse.fromJson(res);
passwordController.text = pass.data.offlineTempPassword;
passwordId = pass.data.offlineTempPasswordId;
CustomSnackBar.displaySnackBar('Save Successfully');
add(InitialTimeLimitPassword());
Future.delayed(const Duration(seconds: 1), () {
@ -368,8 +402,7 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
emit(const GeneratePasswordOneTimestate(generated: true));
} catch (_) {
add(InitialPasswordsPage());
}
finally {
} finally {
isSavingPassword = false;
}
}
@ -377,7 +410,6 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
Future<void> deletePassword(DeletePasswordEvent event, Emitter<SmartDoorState> emit) async {
try {
emit(LoadingInitialState());
var response =
await DevicesAPI.deletePassword(deviceId: deviceId, passwordId: event.passwordId)
.then((value) async {
add(InitialPasswordsPage());
@ -388,7 +420,7 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
}
bool _validateInputs() {
if (passwordController.text.length < 7 ) {
if (passwordController.text.length < 7) {
CustomSnackBar.displaySnackBar('Password less than 7');
return true;
}
@ -397,7 +429,7 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
CustomSnackBar.displaySnackBar('Password required');
return true;
}
if (passwordNameController.text.isEmpty ) {
if (passwordNameController.text.isEmpty) {
CustomSnackBar.displaySnackBar('Password name required');
return true;
}
@ -412,7 +444,7 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
return true;
}
if (repeat == true && (endTime == null || startTime == null || selectedDays == null)) {
if (repeat == true && (endTime == null || startTime == null)) {
CustomSnackBar.displaySnackBar('Start Time and End time and the days required ');
return true;
}
@ -420,7 +452,6 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
}
bool timeLimitValidate() {
if (effectiveTime == 'Select Time' || effectiveTimeTimeStamp == null) {
CustomSnackBar.displaySnackBar('Select effective time');
return true;
@ -444,7 +475,10 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
List<String> selectedDays = [];
Future<void> toggleDaySelection(ToggleDaySelectionEvent event, Emitter<SmartDoorState> emit,)async {
Future<void> toggleDaySelection(
ToggleDaySelectionEvent event,
Emitter<SmartDoorState> emit,
) async {
emit(LoadingInitialState());
if (selectedDays.contains(event.key)) {
selectedDays.remove(event.key);
@ -459,8 +493,3 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
return DateFormat('HH:mm').format(dateTime);
}
}

View File

@ -9,10 +9,15 @@ abstract class SmartDoorEvent extends Equatable {
}
class InitialEvent extends SmartDoorEvent {}
class InitialPasswordsPage extends SmartDoorEvent {}
class InitialOneTimePassword extends SmartDoorEvent {}
class InitialTimeLimitPassword extends SmartDoorEvent {}
class DoorLockUpdated extends SmartDoorEvent {}
class UpdateLockEvent extends SmartDoorEvent {
final bool value;
const UpdateLockEvent({required this.value});
@ -26,6 +31,7 @@ class SavePasswordEvent extends SmartDoorEvent {
@override
List<Object> get props => [context];
}
class GenerateAndSavePasswordTimeLimitEvent extends SmartDoorEvent {
final BuildContext context;
const GenerateAndSavePasswordTimeLimitEvent({required this.context});
@ -40,28 +46,26 @@ class GenerateAndSavePasswordOneTimeEvent extends SmartDoorEvent {
List<Object> get props => [context];
}
class GeneratePasswordEvent extends SmartDoorEvent {
}
class GeneratePasswordEvent extends SmartDoorEvent {}
class SelectTimeEvent extends SmartDoorEvent {
final BuildContext context;
final BuildContext context;
final bool isEffective;
const SelectTimeEvent({required this.context,required this.isEffective});
const SelectTimeEvent({required this.context, required this.isEffective});
@override
List<Object> get props => [context,isEffective];
List<Object> get props => [context, isEffective];
}
class SelectTimeOnlinePasswordEvent extends SmartDoorEvent {
final BuildContext context;
final BuildContext context;
final bool isEffective;
const SelectTimeOnlinePasswordEvent({required this.context,required this.isEffective});
const SelectTimeOnlinePasswordEvent({required this.context, required this.isEffective});
@override
List<Object> get props => [context,isEffective];
List<Object> get props => [context, isEffective];
}
class ToggleRepeatEvent extends SmartDoorEvent {}
class SetStartEndTimeEvent extends SmartDoorEvent {
final bool val;
const SetStartEndTimeEvent({required this.val});
@ -70,7 +74,7 @@ class SetStartEndTimeEvent extends SmartDoorEvent {
}
class DeletePasswordEvent extends SmartDoorEvent {
final String passwordId;
final String passwordId;
const DeletePasswordEvent({required this.passwordId});
@override
@ -78,7 +82,7 @@ class DeletePasswordEvent extends SmartDoorEvent {
}
class ToggleDaySelectionEvent extends SmartDoorEvent {
final String key;
final String key;
const ToggleDaySelectionEvent({required this.key});
@override
@ -89,12 +93,9 @@ class ChangeTimeEvent extends SmartDoorEvent {
final dynamic val;
final bool isStartEndTime;
const ChangeTimeEvent({required this.val,required this.isStartEndTime});
const ChangeTimeEvent({required this.val, required this.isStartEndTime});
@override
List<Object> get props => [val,isStartEndTime];
List<Object> get props => [val, isStartEndTime];
}
class RenamePasswordEvent extends SmartDoorEvent {
}
class RenamePasswordEvent extends SmartDoorEvent {}

View File

@ -1,4 +1,6 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart';
@ -6,12 +8,16 @@ import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_sta
import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/group_three_gang_model.dart';
import 'package:syncrow_app/features/devices/model/schedule_model.dart';
import 'package:syncrow_app/features/devices/model/status_model.dart';
import 'package:syncrow_app/features/devices/model/three_gang_model.dart';
import 'package:syncrow_app/services/api/devices_api.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
final String threeGangId;
final String switchCode;
ThreeGangModel deviceStatus = ThreeGangModel(
firstSwitch: false,
secondSwitch: false,
@ -20,13 +26,18 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
secondCountDown: 0,
thirdCountDown: 0);
Timer? _timer;
// Timer? _firstSwitchTimer;
// Timer? _secondSwitchTimer;
// Timer? _thirdSwitchTimer;
bool threeGangGroup = false;
List<DeviceModel> devicesList = [];
List<GroupThreeGangModel> groupThreeGangList = [];
bool allSwitchesOn = true;
ThreeGangBloc({required this.threeGangId}) : super(InitialState()) {
ThreeGangBloc({required this.threeGangId,required this.switchCode}) : super(InitialState()) {
on<InitialEvent>(_fetchThreeGangStatus);
on<ThreeGangUpdated>(_threeGangUpdated);
on<ChangeFirstSwitchStatusEvent>(_changeFirstSwitch);
on<ChangeSecondSwitchStatusEvent>(_changeSecondSwitch);
on<ChangeThirdSwitchStatusEvent>(_changeThirdSwitch);
@ -39,6 +50,14 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
on<OnClose>(_onClose);
on<GroupAllOnEvent>(_groupAllOn);
on<GroupAllOffEvent>(_groupAllOff);
on<ToggleDaySelectionEvent>(toggleDaySelection);
on<ThreeGangSave>(saveSchedule);
on<GetScheduleEvent>(getSchedule);
on<ToggleScheduleEvent>(toggleChange);
on<DeleteScheduleEvent>(deleteSchedule);
}
void _fetchThreeGangStatus(InitialEvent event, Emitter<ThreeGangState> emit) async {
@ -85,6 +104,7 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
}
deviceStatus = ThreeGangModel.fromJson(statusModelList);
emit(UpdateState(threeGangModel: deviceStatus));
_listenToChanges();
}
} catch (e) {
emit(FailedState(error: e.toString()));
@ -92,6 +112,34 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
}
}
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$threeGangId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) async {
if (_timer != null) {
await Future.delayed(const Duration(seconds: 2));
}
Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList.add(StatusModel(code: element['code'], value: element['value']));
});
deviceStatus = ThreeGangModel.fromJson(statusList);
if (!isClosed) {
add(ThreeGangUpdated());
}
});
} catch (_) {}
}
_threeGangUpdated(ThreeGangUpdated event, Emitter<ThreeGangState> emit) {
emit(UpdateState(threeGangModel: deviceStatus));
}
void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter<ThreeGangState> emit) async {
emit(LoadingNewSate(threeGangModel: deviceStatus));
try {
@ -111,16 +159,22 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
emit(UpdateState(threeGangModel: deviceStatus));
}
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: threeGangGroup ? event.deviceId : threeGangId,
code: 'switch_1',
value: !event.value),
threeGangGroup ? event.deviceId : threeGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: threeGangGroup));
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: threeGangGroup ? event.deviceId : threeGangId,
code: 'switch_1',
value: !event.value),
threeGangGroup ? event.deviceId : threeGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: threeGangGroup));
}
});
} catch (_) {
add(InitialEvent(groupScreen: threeGangGroup));
}
@ -146,16 +200,21 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
emit(UpdateState(threeGangModel: deviceStatus));
}
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: threeGangGroup ? event.deviceId : threeGangId,
code: 'switch_2',
value: !event.value),
threeGangGroup ? event.deviceId : threeGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: threeGangGroup));
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: threeGangGroup ? event.deviceId : threeGangId,
code: 'switch_2',
value: !event.value),
threeGangGroup ? event.deviceId : threeGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: threeGangGroup));
}
});
} catch (_) {
add(InitialEvent(groupScreen: threeGangGroup));
}
@ -180,16 +239,22 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
emit(UpdateState(threeGangModel: deviceStatus));
}
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: threeGangGroup ? event.deviceId : threeGangId,
code: 'switch_3',
value: !event.value),
threeGangGroup ? event.deviceId : threeGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: threeGangGroup));
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: threeGangGroup ? event.deviceId : threeGangId,
code: 'switch_3',
value: !event.value),
threeGangGroup ? event.deviceId : threeGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: threeGangGroup));
}
});
} catch (_) {
add(InitialEvent(groupScreen: threeGangGroup));
}
@ -424,4 +489,143 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
emit(TimerRunComplete());
}
}
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"},
];
Future<void> toggleDaySelection(
ToggleDaySelectionEvent event,
Emitter<ThreeGangState> emit,
) async {
emit(LoadingInitialState());
if (selectedDays.contains(event.key)) {
selectedDays.remove(event.key);
} else {
selectedDays.add(event.key);
}
emit(ChangeTimeState());
add(ChangeSlidingSegment(value: 1));
}
Future<void> saveSchedule(ThreeGangSave event, Emitter<ThreeGangState> emit,) async {
try {
if(selectedDays.isNotEmpty) {
emit(LoadingInitialState());
final response = await DevicesAPI.postSchedule(
category: switchCode,
deviceId: threeGangId,
time: getTimeStampWithoutSeconds(selectedTime).toString(),
code: switchCode,
value: toggleSchedule,
days: selectedDays);
CustomSnackBar.displaySnackBar('Save Successfully');
add(GetScheduleEvent());
emit(ThreeGangSaveSchedule());
toggleCreateSchedule();
}else{
CustomSnackBar.displaySnackBar('Please select days');
}
} catch (e) {
emit(FailedState(error: e.toString()));
}
}
Future<void> getSchedule(GetScheduleEvent event, Emitter<ThreeGangState> emit,) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.getSchedule(
category: switchCode,
deviceId: threeGangId ,
);
List<dynamic> jsonData = response;
listSchedule = jsonData.map((item) => ScheduleModel.fromJson(item)).toList();
emit(InitialState());
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
if (dateTime == null) return null;
DateTime dateTimeWithoutSeconds = DateTime(dateTime.year, dateTime.month,
dateTime.day, dateTime.hour, dateTime.minute);
return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000;
}
Future toggleChange(
ToggleScheduleEvent event, Emitter<ThreeGangState> emit) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.changeSchedule(
scheduleId: event.id, deviceUuid: threeGangId, enable: event.toggle);
if (response == true) {
add(GetScheduleEvent());
toggleSchedule = event.toggle;
return toggleSchedule;
}
emit(IsToggleState(onOff: toggleSchedule));
add(const ChangeSlidingSegment(value: 1));
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
Future deleteSchedule(
DeleteScheduleEvent event, Emitter<ThreeGangState> emit) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.deleteSchedule(
scheduleId: event.id,
deviceUuid: threeGangId, );
if (response == true) {
add(GetScheduleEvent());
return toggleSchedule;
}
emit(IsToggleState(onOff: toggleSchedule));
add(const ChangeSlidingSegment(value: 1));
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
void toggleCreateSchedule() {
emit(LoadingInitialState());
createSchedule = !createSchedule;
selectedDays.clear();
selectedTime=DateTime.now();
emit(UpdateCreateScheduleState(createSchedule));
emit(ChangeSlidingSegmentState(value: 1));
}
int selectedTabIndex = 0;
void toggleSelectedIndex(index) {
emit(LoadingInitialState());
selectedTabIndex = index;
emit(ChangeSlidingSegmentState(value: selectedTabIndex));
}
bool toggleSchedule = true;
List<String> selectedDays = [];
bool createSchedule = false;
List<ScheduleModel> listSchedule = [];
DateTime? selectedTime=DateTime.now();
}

View File

@ -9,6 +9,8 @@ abstract class ThreeGangEvent extends Equatable {
class LoadingEvent extends ThreeGangEvent {}
class ThreeGangUpdated extends ThreeGangEvent {}
class InitialEvent extends ThreeGangEvent {
final bool groupScreen;
const InitialEvent({required this.groupScreen});
@ -91,3 +93,29 @@ class TickTimer extends ThreeGangEvent {
class StopTimer extends ThreeGangEvent {}
class OnClose extends ThreeGangEvent {}
//------------------- Schedule ----------=---------
class GetScheduleEvent extends ThreeGangEvent {}
class ThreeGangSave extends ThreeGangEvent {}
class ToggleScheduleEvent extends ThreeGangEvent {
final String id;
final bool toggle;
const ToggleScheduleEvent({required this.toggle,required this.id});
@override
List<Object> get props => [toggle,id];
}
class ToggleDaySelectionEvent extends ThreeGangEvent {
final String key;
const ToggleDaySelectionEvent({required this.key});
@override
List<Object> get props => [key];
}
class DeleteScheduleEvent extends ThreeGangEvent {
final String id;
const DeleteScheduleEvent({required this.id});
@override
List<Object> get props => [id];
}

View File

@ -75,3 +75,15 @@ class TimerRunInProgress extends ThreeGangState {
}
class TimerRunComplete extends ThreeGangState {}
class ThreeGangSaveSchedule extends ThreeGangState {}
class IsToggleState extends ThreeGangState {
final bool? onOff;
const IsToggleState({this.onOff});
}
class ChangeTimeState extends ThreeGangState {}
class UpdateCreateScheduleState extends ThreeGangState {
final bool createSchedule;
UpdateCreateScheduleState(this.createSchedule);
}

View File

@ -0,0 +1,553 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_event.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart';
import 'package:syncrow_app/features/devices/model/schedule_model.dart';
import 'package:syncrow_app/features/devices/model/status_model.dart';
import 'package:syncrow_app/features/devices/model/two_gang_model.dart';
import 'package:syncrow_app/services/api/devices_api.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
class TwoGangBloc extends Bloc<TwoGangEvent, TwoGangState> {
final String twoGangId;
final String switchCode;
TwoGangModel deviceStatus = TwoGangModel(
firstSwitch: false,
secondSwitch: false,
firstCountDown: 0,
secondCountDown: 0,
);
Timer? _timer;
bool twoGangGroup = false;
List<DeviceModel> devicesList = [];
List<GroupTwoGangModel> groupTwoGangList = [];
bool allSwitchesOn = true;
bool toggleSchedule = true;
List<String> selectedDays = [];
bool createSchedule = false;
List<ScheduleModel> listSchedule = [];
TwoGangBloc({required this.twoGangId,required this.switchCode}) : super(InitialState()) {
on<InitialEvent>(_fetchTwoGangStatus);
on<TwoGangUpdated>(_twoGangUpdated);
on<ChangeFirstSwitchStatusEvent>(_changeFirstSwitch);
on<ChangeSecondSwitchStatusEvent>(_changeSecondSwitch);
on<AllOffEvent>(_allOff);
on<AllOnEvent>(_allOn);
on<ChangeSlidingSegment>(_changeSliding);
on<SetCounterValue>(_setCounterValue);
on<GetCounterEvent>(_getCounterValue);
on<TickTimer>(_onTickTimer);
on<OnClose>(_onClose);
on<GroupAllOnEvent>(_groupAllOn);
on<GroupAllOffEvent>(_groupAllOff);
on<ToggleDaySelectionEvent>(toggleDaySelection);
on<TwoGangSave>(saveSchedule);
on<GetScheduleEvent>(getSchedule);
on<ToggleScheduleEvent>(toggleRepeat);
on<DeleteScheduleEvent>(deleteSchedule);
}
DateTime? selectedTime = DateTime.now();
void toggleCreateSchedule() {
emit(LoadingInitialState());
createSchedule = !createSchedule;
selectedDays.clear();
selectedTime=DateTime.now();
emit(UpdateCreateScheduleState(createSchedule));
emit(ChangeSlidingSegmentState(value: 1));
}
int selectedTabIndex = 0;
void toggleSelectedIndex(index) {
emit(LoadingInitialState());
selectedTabIndex = index;
emit(ChangeSlidingSegmentState(value: selectedTabIndex));
}
void _fetchTwoGangStatus(InitialEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingInitialState());
try {
twoGangGroup = event.groupScreen;
if (twoGangGroup) {
devicesList = [];
groupTwoGangList = [];
allSwitchesOn = true;
devicesList = await DevicesAPI.getDeviceByGroupName(
HomeCubit.getInstance().selectedSpace?.id ?? '', '2G');
for (int i = 0; i < devicesList.length; i++) {
var response =
await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? '');
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = TwoGangModel.fromJson(statusModelList);
groupTwoGangList.add(GroupTwoGangModel(
deviceId: devicesList[i].uuid ?? '',
deviceName: devicesList[i].name ?? '',
firstSwitch: deviceStatus.firstSwitch,
secondSwitch: deviceStatus.secondSwitch,
));
}
if (groupTwoGangList.isNotEmpty) {
groupTwoGangList.firstWhere((element) {
if (!element.firstSwitch || !element.secondSwitch) {
allSwitchesOn = false;
}
return true;
});
}
emit(UpdateGroupState(
twoGangList: groupTwoGangList, allSwitches: allSwitchesOn));
} else {
var response = await DevicesAPI.getDeviceStatus(twoGangId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = TwoGangModel.fromJson(statusModelList);
emit(UpdateState(twoGangModel: deviceStatus));
_listenToChanges();
}
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
_listenToChanges() {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$twoGangId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) async {
if (_timer != null) {
await Future.delayed(const Duration(seconds: 2));
}
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(StatusModel(code: element['code'], value: element['value']));
});
deviceStatus = TwoGangModel.fromJson(statusList);
if (!isClosed) {
add(TwoGangUpdated());
}
});
} catch (_) {}
}
_twoGangUpdated(TwoGangUpdated event, Emitter<TwoGangState> emit) {
emit(UpdateState(twoGangModel: deviceStatus));
}
void _changeFirstSwitch(
ChangeFirstSwitchStatusEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
deviceStatus.firstSwitch = !event.value;
emit(UpdateState(twoGangModel: deviceStatus));
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangGroup ? event.deviceId : twoGangId,
code: 'switch_1',
value: !event.value),
twoGangGroup ? event.deviceId : twoGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: twoGangGroup));
}
});
} catch (_) {
add(InitialEvent(groupScreen: twoGangGroup));
}
}
void _changeSecondSwitch(
ChangeSecondSwitchStatusEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
deviceStatus.secondSwitch = !event.value;
emit(UpdateState(twoGangModel: deviceStatus));
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangGroup ? event.deviceId : twoGangId,
code: 'switch_2',
value: !event.value),
twoGangGroup ? event.deviceId : twoGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: twoGangGroup));
}
});
} catch (_) {
add(InitialEvent(groupScreen: twoGangGroup));
}
}
void _allOff(AllOffEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
deviceStatus.firstSwitch = false;
deviceStatus.secondSwitch = false;
emit(UpdateState(twoGangModel: deviceStatus));
final response = await Future.wait([
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangId,
code: 'switch_1',
value: deviceStatus.firstSwitch),
twoGangId),
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangId,
code: 'switch_2',
value: deviceStatus.secondSwitch),
twoGangId),
]);
if (response.every((element) => !element['success'])) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: false));
}
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: false));
}
}
void _allOn(AllOnEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
deviceStatus.firstSwitch = true;
deviceStatus.secondSwitch = true;
emit(UpdateState(twoGangModel: deviceStatus));
final response = await Future.wait([
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangId,
code: 'switch_1',
value: deviceStatus.firstSwitch),
twoGangId),
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangId,
code: 'switch_2',
value: deviceStatus.secondSwitch),
twoGangId),
]);
if (response.every((element) => !element['success'])) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: false));
}
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: false));
}
}
void _groupAllOn(GroupAllOnEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
for (int i = 0; i < groupTwoGangList.length; i++) {
groupTwoGangList[i].firstSwitch = true;
groupTwoGangList[i].secondSwitch = true;
}
emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: true));
for (int i = 0; i < groupTwoGangList.length; i++) {
final response = await Future.wait([
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: groupTwoGangList[i].deviceId,
code: 'switch_1',
value: true),
groupTwoGangList[i].deviceId),
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: groupTwoGangList[i].deviceId,
code: 'switch_2',
value: true),
groupTwoGangList[i].deviceId),
]);
if (response.every((element) => !element['success'])) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
break;
}
}
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
}
}
void _groupAllOff(GroupAllOffEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
for (int i = 0; i < groupTwoGangList.length; i++) {
groupTwoGangList[i].firstSwitch = false;
groupTwoGangList[i].secondSwitch = false;
}
emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: false));
for (int i = 0; i < groupTwoGangList.length; i++) {
final response = await Future.wait([
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: groupTwoGangList[i].deviceId,
code: 'switch_1',
value: false),
groupTwoGangList[i].deviceId),
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: groupTwoGangList[i].deviceId,
code: 'switch_2',
value: false),
groupTwoGangList[i].deviceId),
]);
if (response.every((element) => !element['success'])) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
break;
}
}
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
}
}
void _changeSliding(
ChangeSlidingSegment event, Emitter<TwoGangState> emit) async {
emit(ChangeSlidingSegmentState(value: event.value));
}
void _setCounterValue(
SetCounterValue event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
int seconds = 0;
try {
seconds = event.duration.inSeconds;
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangId,
code: event.deviceCode,
value: seconds),
twoGangId);
if (response['success'] ?? false) {
if (event.deviceCode == 'countdown_1') {
deviceStatus.firstCountDown = seconds;
}
else if (event.deviceCode == 'countdown_2') {
deviceStatus.secondCountDown = seconds;
}
} else {
emit(const FailedState(error: 'Something went wrong'));
return;
}
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
if (seconds > 0) {
_onStartTimer(seconds);
} else {
_timer?.cancel();
emit(TimerRunComplete());
}
}
void _getCounterValue(
GetCounterEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingInitialState());
try {
add(GetScheduleEvent());
var response = await DevicesAPI.getDeviceStatus(twoGangId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = TwoGangModel.fromJson(statusModelList);
if (event.deviceCode == 'countdown_1') {
deviceStatus.firstCountDown > 0
? _onStartTimer(deviceStatus.firstCountDown)
: emit(UpdateTimerState(seconds: deviceStatus.firstCountDown));
} else if (event.deviceCode == 'countdown_2') {
deviceStatus.secondCountDown > 0
? _onStartTimer(deviceStatus.secondCountDown)
: emit(UpdateTimerState(seconds: deviceStatus.secondCountDown));
}
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
void _onClose(OnClose event, Emitter<TwoGangState> emit) {
_timer?.cancel();
}
void _onStartTimer(int seconds) {
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
add(TickTimer(seconds - timer.tick));
});
}
void _onTickTimer(TickTimer event, Emitter<TwoGangState> emit) {
if (event.remainingTime > 0) {
emit(TimerRunInProgress(event.remainingTime));
} else {
_timer?.cancel();
emit(TimerRunComplete());
}
}
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"},
];
Future<void> toggleDaySelection(
ToggleDaySelectionEvent event,
Emitter<TwoGangState> emit,
) async {
emit(LoadingInitialState());
if (selectedDays.contains(event.key)) {
selectedDays.remove(event.key);
} else {
selectedDays.add(event.key);
}
emit(ChangeTimeState());
add(ChangeSlidingSegment(value: 1));
}
Future<void> saveSchedule(TwoGangSave event, Emitter<TwoGangState> emit,) async {
try {
if(selectedDays.isNotEmpty) {
emit(LoadingInitialState());
final response = await DevicesAPI.postSchedule(
category: switchCode,
deviceId: twoGangId,
time: getTimeStampWithoutSeconds(selectedTime).toString(),
code: switchCode,
value: toggleSchedule,
days: selectedDays);
CustomSnackBar.displaySnackBar('Save Successfully');
add(GetScheduleEvent());
emit(TwoGangSaveSchedule());
toggleCreateSchedule();
}else{
CustomSnackBar.displaySnackBar('Please select days');
}
} catch (e) {
emit(FailedState(error: e.toString()));
}
}
Future<void> getSchedule(GetScheduleEvent event, Emitter<TwoGangState> emit,) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.getSchedule(
category: switchCode,
deviceId: twoGangId,
);
List<dynamic> jsonData = response;
listSchedule = jsonData.map((item) => ScheduleModel.fromJson(item)).toList();
emit(InitialState());
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
if (dateTime == null) return null;
DateTime dateTimeWithoutSeconds = DateTime(dateTime.year, dateTime.month,
dateTime.day, dateTime.hour, dateTime.minute);
return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000;
}
Future toggleRepeat(
ToggleScheduleEvent event, Emitter<TwoGangState> emit) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.changeSchedule(
scheduleId: event.id,
deviceUuid: twoGangId,
enable: event.toggle);
if (response == true) {
add(GetScheduleEvent());
toggleSchedule = event.toggle;
return toggleSchedule;
}
emit(IsToggleState(onOff: toggleSchedule));
add(const ChangeSlidingSegment(value: 1));
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
Future deleteSchedule(
DeleteScheduleEvent event, Emitter<TwoGangState> emit) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.deleteSchedule(
scheduleId: event.id,
deviceUuid: twoGangId, );
if (response == true) {
add(GetScheduleEvent());
return toggleSchedule;
}
emit(IsToggleState(onOff: toggleSchedule));
add(const ChangeSlidingSegment(value: 1));
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
}

View File

@ -0,0 +1,130 @@
import 'package:equatable/equatable.dart';
abstract class TwoGangEvent extends Equatable {
const TwoGangEvent();
@override
List<Object> get props => [];
}
class LoadingEvent extends TwoGangEvent {}
class TwoGangUpdated extends TwoGangEvent {}
class TwoGangSave extends TwoGangEvent {}
class ToggleScheduleEvent extends TwoGangEvent {
final String id;
final bool toggle;
const ToggleScheduleEvent({required this.toggle,required this.id});
@override
List<Object> get props => [toggle,id];
}
class errorMessage extends TwoGangEvent {}
class ToggleDaySelectionEvent extends TwoGangEvent {
final String key;
const ToggleDaySelectionEvent({required this.key});
@override
List<Object> get props => [key];
}
class InitialEvent extends TwoGangEvent {
final bool groupScreen;
const InitialEvent({required this.groupScreen});
@override
List<Object> get props => [groupScreen];
}
class ChangeFirstSwitchStatusEvent extends TwoGangEvent {
final bool value;
final String deviceId;
const ChangeFirstSwitchStatusEvent({required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}
class ChangeSecondSwitchStatusEvent extends TwoGangEvent {
final bool value;
final String deviceId;
const ChangeSecondSwitchStatusEvent({required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}
class AllOffEvent extends TwoGangEvent {}
class AllOnEvent extends TwoGangEvent {}
class GroupAllOnEvent extends TwoGangEvent {}
class GroupAllOffEvent extends TwoGangEvent {}
// two_gang_event.dart
class ToggleCreateScheduleEvent extends TwoGangEvent {}
class ChangeSlidingSegment extends TwoGangEvent {
final int value;
const ChangeSlidingSegment({required this.value});
@override
List<Object> get props => [value];
}
class GetCounterEvent extends TwoGangEvent {
final String deviceCode;
const GetCounterEvent({required this.deviceCode});
@override
List<Object> get props => [deviceCode];
}
class SetCounterValue extends TwoGangEvent {
final Duration duration;
final String deviceCode;
const SetCounterValue({required this.duration, required this.deviceCode});
@override
List<Object> get props => [duration, deviceCode];
}
class StartTimer extends TwoGangEvent {
final int duration;
const StartTimer(this.duration);
@override
List<Object> get props => [duration];
}
class TickTimer extends TwoGangEvent {
final int remainingTime;
const TickTimer(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class StopTimer extends TwoGangEvent {}
class OnClose extends TwoGangEvent {}
class GetScheduleEvent extends TwoGangEvent {}
class DeleteScheduleEvent extends TwoGangEvent {
final String id;
const DeleteScheduleEvent({required this.id});
@override
List<Object> get props => [id];
}
class TabChangedEvent extends TwoGangEvent {
final int index;
TabChangedEvent({required this.index});
}

View File

@ -0,0 +1,92 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart';
import 'package:syncrow_app/features/devices/model/two_gang_model.dart';
class TwoGangState extends Equatable {
const TwoGangState();
@override
List<Object> get props => [];
}
class InitialState extends TwoGangState {}
class TwoGangSaveSchedule extends TwoGangState {}
class ChangeTimeState extends TwoGangState {}
class LoadingInitialState extends TwoGangState {}
class UpdateState extends TwoGangState {
final TwoGangModel twoGangModel;
const UpdateState({required this.twoGangModel});
@override
List<Object> get props => [TwoGangModel];
}
class LoadingNewSate extends TwoGangState {
final TwoGangModel twoGangModel;
const LoadingNewSate({required this.twoGangModel});
@override
List<Object> get props => [TwoGangModel];
}
class UpdateGroupState extends TwoGangState {
final List<GroupTwoGangModel> twoGangList;
final bool allSwitches;
const UpdateGroupState({required this.twoGangList, required this.allSwitches});
@override
List<Object> get props => [twoGangList, allSwitches];
}
class FailedState extends TwoGangState {
final String error;
const FailedState({required this.error});
@override
List<Object> get props => [error];
}
class ChangeSlidingSegmentState extends TwoGangState {
final int value;
const ChangeSlidingSegmentState({required this.value});
@override
List<Object> get props => [value];
}
class UpdateTimerState extends TwoGangState {
final int seconds;
const UpdateTimerState({required this.seconds});
@override
List<Object> get props => [seconds];
}
class TimerRunInProgress extends TwoGangState {
final int remainingTime;
const TimerRunInProgress(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class TimerRunComplete extends TwoGangState {}
class IsToggleState extends TwoGangState {
final bool? onOff;
const IsToggleState({this.onOff});
}
// two_gang_state.dart
class UpdateCreateScheduleState extends TwoGangState {
final bool createSchedule;
UpdateCreateScheduleState(this.createSchedule);
}

View File

@ -1,3 +1,4 @@
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/wall_sensor_bloc/wall_sensor_state.dart';
import 'package:syncrow_app/features/devices/bloc/wall_sensor_bloc/wall_sensor_event.dart';
@ -16,6 +17,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
on<InitialEvent>(_fetchCeilingSensorStatus);
on<ChangeIndicatorEvent>(_changeIndicator);
on<ChangeValueEvent>(_changeValue);
on<WallSensorUpdatedEvent>(_wallSensorUpdated);
}
void _fetchCeilingSensorStatus(InitialEvent event, Emitter<WallSensorState> emit) async {
@ -28,12 +30,36 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
}
deviceStatus = WallSensorModel.fromJson(statusModelList);
emit(UpdateState(wallSensorModel: deviceStatus));
_listenToChanges();
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList.add(StatusModel(code: element['code'], value: element['value']));
});
deviceStatus = WallSensorModel.fromJson(statusList);
add(WallSensorUpdatedEvent());
});
} catch (_) {}
}
_wallSensorUpdated(WallSensorUpdatedEvent event, Emitter<WallSensorState> emit) {
emit(UpdateState(wallSensorModel: deviceStatus));
}
void _changeIndicator(ChangeIndicatorEvent event, Emitter<WallSensorState> emit) async {
emit(LoadingNewSate(wallSensorModel: deviceStatus));
try {

View File

@ -11,6 +11,8 @@ class LoadingEvent extends WallSensorEvent {}
class InitialEvent extends WallSensorEvent {}
class WallSensorUpdatedEvent extends WallSensorEvent {}
class ChangeIndicatorEvent extends WallSensorEvent {
final bool value;
const ChangeIndicatorEvent({required this.value});

View File

@ -4,28 +4,47 @@ class CeilingSensorModel {
String presenceState;
int sensitivity;
String checkingResult;
int presenceRange;
int sportsPara;
String bodyMovement;
CeilingSensorModel({
required this.presenceState,
required this.sensitivity,
required this.checkingResult,
});
CeilingSensorModel(
{required this.presenceState,
required this.sensitivity,
required this.checkingResult,
required this.presenceRange,
required this.sportsPara,
required this.bodyMovement});
factory CeilingSensorModel.fromJson(List<StatusModel> jsonList) {
late String _presenceState;
late int _sensitivity;
late String _checkingResult;
int _presenceRange = 1;
int _sportsPara = 1;
String _bodyMovement = 'none';
for (int i = 0; i < jsonList.length; i++) {
if (jsonList[i].code == 'presence_state') {
_presenceState = jsonList[i].value ?? 'none';
} else if (jsonList[i].code == 'sensitivity') {
_sensitivity = jsonList[i].value ?? false;
_sensitivity = jsonList[i].value ?? 1;
} else if (jsonList[i].code == 'checking_result') {
_checkingResult = jsonList[i].value ?? false;
_checkingResult = jsonList[i].value ?? '';
} else if (jsonList[i].code == 'presence_range') {
_presenceRange = jsonList[i].value ?? 0;
} else if (jsonList[i].code == 'sports_para') {
_sportsPara = jsonList[i].value ?? 0;
} else if (jsonList[i].code == 'body_movement') {
_bodyMovement = jsonList[i].value ?? '';
}
}
return CeilingSensorModel(
presenceState: _presenceState, sensitivity: _sensitivity, checkingResult: _checkingResult);
presenceState: _presenceState,
sensitivity: _sensitivity,
checkingResult: _checkingResult,
presenceRange: _presenceRange,
sportsPara: _sportsPara,
bodyMovement: _bodyMovement);
}
}

View File

@ -58,6 +58,10 @@ class DeviceModel {
tempIcon = Assets.assetsIcons3GangSwitch;
} else if (type == DeviceType.Gateway) {
tempIcon = Assets.assetsIconsGateway;
} else if (type == DeviceType.OneGang) {
tempIcon = Assets.oneGang;
} else if (type == DeviceType.TwoGang) {
tempIcon = Assets.twoGang;
} else {
tempIcon = Assets.assetsIconsLogo;
}

View File

@ -0,0 +1,13 @@
class GroupTwoGangModel {
final String deviceId;
final String deviceName;
bool firstSwitch;
bool secondSwitch;
GroupTwoGangModel({
required this.deviceId,
required this.deviceName,
required this.firstSwitch,
required this.secondSwitch,
});
}

View File

@ -0,0 +1,30 @@
import 'package:syncrow_app/features/devices/model/status_model.dart';
class OneGangModel {
bool firstSwitch;
int firstCountDown;
OneGangModel(
{required this.firstSwitch,
required this.firstCountDown,
});
factory OneGangModel.fromJson(List<StatusModel> jsonList) {
late bool _switch;
late int _count;
for (int i = 0; i < jsonList.length; i++) {
if (jsonList[i].code == 'switch_1') {
_switch = jsonList[i].value ?? false;
} else if (jsonList[i].code == 'countdown_1') {
_count = jsonList[i].value ?? 0;
}
}
return OneGangModel(
firstSwitch: _switch,
firstCountDown: _count,
);
}
}

View File

@ -0,0 +1,83 @@
import 'package:intl/intl.dart';
class ScheduleModel {
String category;
bool enable;
ScheduleFunctionData function;
String time;
String scheduleId;
String timezoneId;
List<String> days;
ScheduleModel({
required this.category,
required this.enable,
required this.function,
required this.time,
required this.scheduleId,
required this.timezoneId,
required this.days,
});
// Factory method to create an instance from JSON
factory ScheduleModel.fromJson(Map<String, dynamic> json) {
return ScheduleModel(
category: json['category'],
enable: json['enable'],
function: ScheduleFunctionData.fromJson(json['function']),
time:getTimeIn12HourFormat( json['time']),
scheduleId: json['scheduleId'],
timezoneId: json['timezoneId'],
days: List<String>.from(json['days']),
);
}
// Method to convert an instance into JSON
Map<String, dynamic> toJson() {
return {
'category': category,
'enable': enable,
'function': function.toJson(),
'time': time,
'scheduleId': scheduleId,
'timezoneId': timezoneId,
'days': days,
};
}
}
class ScheduleFunctionData {
String code;
bool value;
ScheduleFunctionData({
required this.code,
required this.value,
});
// Factory method to create an instance from JSON
factory ScheduleFunctionData.fromJson(Map<String, dynamic> json) {
return ScheduleFunctionData(
code: json['code'],
value: json['value'],
);
}
// Method to convert an instance into JSON
Map<String, dynamic> toJson() {
return {
'code': code,
'value': value,
};
}
}
String getTimeIn12HourFormat(time) {
// Parse the time string (in HH:mm format)
final DateFormat inputFormat = DateFormat("HH:mm");
final DateFormat outputFormat = DateFormat("h:mm a"); // 12-hour format with AM/PM
// Convert the time string
DateTime parsedTime = inputFormat.parse(time);
return outputFormat.format(parsedTime);
}

View File

@ -0,0 +1,40 @@
import 'package:syncrow_app/features/devices/model/status_model.dart';
class TwoGangModel {
bool firstSwitch;
bool secondSwitch;
int firstCountDown;
int secondCountDown;
TwoGangModel(
{required this.firstSwitch,
required this.secondSwitch,
required this.firstCountDown,
required this.secondCountDown,
});
factory TwoGangModel.fromJson(List<StatusModel> jsonList) {
late bool _firstSwitch;
late bool _secondSwitch;
late int _firstCount;
late int _secondCount;
for (int i = 0; i < jsonList.length; i++) {
if (jsonList[i].code == 'switch_1') {
_firstSwitch = jsonList[i].value ?? false;
} else if (jsonList[i].code == 'switch_2') {
_secondSwitch = jsonList[i].value ?? false;
}else if (jsonList[i].code == 'countdown_1') {
_firstCount = jsonList[i].value ?? 0;
} else if (jsonList[i].code == 'countdown_2') {
_secondCount = jsonList[i].value ?? 0;
}
}
return TwoGangModel(
firstSwitch: _firstSwitch,
secondSwitch: _secondSwitch,
firstCountDown: _firstCount,
secondCountDown: _secondCount,
);
}
}

View File

@ -35,9 +35,9 @@ class WallSensorModel {
if (jsonList[i].code == 'presence_state') {
_presenceState = jsonList[i].value ?? 'none';
} else if (jsonList[i].code == 'far_detection') {
_farDetection = jsonList[i].value ?? false;
_farDetection = jsonList[i].value ?? 0;
} else if (jsonList[i].code == 'presence_time') {
_presenceTime = jsonList[i].value ?? false;
_presenceTime = jsonList[i].value ?? 0;
} else if (jsonList[i].code == 'motion_sensitivity_value') {
_motionSensitivity = jsonList[i].value ?? 0;
} else if (jsonList[i].code == 'motionless_sensitivity') {
@ -47,7 +47,7 @@ class WallSensorModel {
} else if (jsonList[i].code == 'illuminance_value') {
_illuminance = jsonList[i].value ?? 0;
} else if (jsonList[i].code == 'indicator') {
_indicator = jsonList[i].value ?? 0;
_indicator = jsonList[i].value ?? false;
}
}
return WallSensorModel(

View File

@ -34,8 +34,13 @@ class CeilingSensorInterface extends StatelessWidget {
create: (context) =>
CeilingSensorBloc(deviceId: ceilingSensor.uuid ?? '')..add(InitialEvent()),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(builder: (context, state) {
CeilingSensorModel ceilingSensorModel =
CeilingSensorModel(presenceState: 'none', sensitivity: 1, checkingResult: '');
CeilingSensorModel ceilingSensorModel = CeilingSensorModel(
presenceState: 'none',
sensitivity: 1,
checkingResult: '',
presenceRange: 1,
sportsPara: 1,
bodyMovement: 'none');
if (state is UpdateState) {
ceilingSensorModel = state.ceilingSensorModel;
} else if (state is LoadingNewSate) {
@ -178,7 +183,8 @@ class CeilingSensorInterface extends StatelessWidget {
children: [
const BodySmall(text: 'Sports Para'),
BodyLarge(
text: '0',
text: ceilingSensorModel.sportsPara
.toString(),
style: context.bodyLarge.copyWith(
fontWeight: FontsManager.bold,
),
@ -204,7 +210,8 @@ class CeilingSensorInterface extends StatelessWidget {
textOverflow: TextOverflow.ellipsis,
),
BodyLarge(
text: '0.0M',
text:
'${ceilingSensorModel.presenceRange}M',
textOverflow: TextOverflow.ellipsis,
style: context.bodyLarge.copyWith(
fontWeight: FontsManager.bold,
@ -231,7 +238,7 @@ class CeilingSensorInterface extends StatelessWidget {
textOverflow: TextOverflow.ellipsis,
),
BodyLarge(
text: 'none',
text: ceilingSensorModel.bodyMovement,
textOverflow: TextOverflow.ellipsis,
style: context.bodyLarge.copyWith(
fontWeight: FontsManager.bold,

View File

@ -1,163 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
class BlindsView extends StatelessWidget {
const BlindsView({
super.key,
this.blind,
});
final DeviceModel? blind;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DevicesCubit.getInstance(),
child: BlocBuilder<DevicesCubit, DevicesState>(
builder: (context, state) {
return DefaultScaffold(
title: blind?.name ?? 'Blinds',
child: Column(
children: [
Column(
children: [
Stack(
alignment: Alignment.topCenter,
children: [
Container(
height: 340,
width: 365,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesWindow,
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 15, bottom: 10),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
height: DevicesCubit.get(context).blindWindowHight,
width: 270,
child: Stack(
children: List.generate(
25,
(index) {
double spacing = DevicesCubit.get(context)
.blindWindowHight /
24;
double topPosition = index * spacing;
return AnimatedPositioned(
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
top: topPosition,
child: SizedBox(
height: 10,
width: 270,
child: Image.asset(
Assets.assetsImagesHorizintalBlade,
fit: BoxFit.fill,
),
),
);
},
),
),
),
),
],
),
const SizedBox(height: 80),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DecoratedBox(
decoration:
BoxDecoration(shape: BoxShape.circle, boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(3, 3),
),
]),
child: InkWell(
overlayColor:
MaterialStateProperty.all(Colors.transparent),
onTap: () {
// DevicesCubit.get(context).setHight(false);
DevicesCubit.get(context).openCurtain(
blind?.productType! ?? DeviceType.Blind);
},
child: Image.asset(
Assets.assetsImagesUp,
width: 60,
height: 60,
),
),
),
DecoratedBox(
decoration:
BoxDecoration(shape: BoxShape.circle, boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(3, 3),
),
]),
child: InkWell(
overlayColor:
MaterialStateProperty.all(Colors.transparent),
onTap: () {
DevicesCubit.get(context).pauseCurtain();
},
child: Image.asset(
Assets.assetsImagesPause,
width: 60,
height: 60,
),
),
),
DecoratedBox(
decoration:
BoxDecoration(shape: BoxShape.circle, boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(3, 3),
),
]),
child: InkWell(
overlayColor:
MaterialStateProperty.all(Colors.transparent),
onTap: () {
DevicesCubit.get(context).closeCurtain(
blind?.productType! ?? DeviceType.Blind);
},
child: Image.asset(
Assets.assetsImagesDown,
width: 60,
height: 60,
),
),
),
],
),
],
),
],
),
);
},
),
);
}
}

View File

@ -1,83 +0,0 @@
// import 'package:flutter/material.dart';
// import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
// import 'package:syncrow_app/generated2/assets.dart';
// class BlindsBottons extends StatelessWidget {
// const BlindsBottons({
// super.key,
// });
// @override
// Widget build(BuildContext context) {
// return Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// DecoratedBox(
// decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [
// BoxShadow(
// color: Colors.grey.withOpacity(0.5),
// spreadRadius: 1,
// blurRadius: 5,
// offset: const Offset(3, 3),
// ),
// ]),
// child: InkWell(
// overlayColor: MaterialStateProperty.all(Colors.transparent),
// onTap: () {
// // DevicesCubit.get(context).setHight(false);
// DevicesCubit.get(context).closeCurtain();
// },
// child: Image.asset(
// Assets.assetsImagesUp,
// width: 60,
// height: 60,
// ),
// ),
// ),
// DecoratedBox(
// decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [
// BoxShadow(
// color: Colors.grey.withOpacity(0.5),
// spreadRadius: 1,
// blurRadius: 5,
// offset: const Offset(3, 3),
// ),
// ]),
// child: InkWell(
// overlayColor: MaterialStateProperty.all(Colors.transparent),
// onTap: () {
// // DevicesCubit.get(context).setHight(false);
// },
// child: Image.asset(
// Assets.assetsImagesPause,
// width: 60,
// height: 60,
// ),
// ),
// ),
// DecoratedBox(
// decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [
// BoxShadow(
// color: Colors.grey.withOpacity(0.5),
// spreadRadius: 1,
// blurRadius: 5,
// offset: const Offset(3, 3),
// ),
// ]),
// child: InkWell(
// overlayColor: MaterialStateProperty.all(Colors.transparent),
// onTap: () {
// // DevicesCubit.get(context).setHight(true);
// DevicesCubit.get(context).openCurtain();
// },
// child: Image.asset(
// Assets.assetsImagesDown,
// width: 60,
// height: 60,
// ),
// ),
// ),
// ],
// );
// }
// }

View File

@ -1,114 +1,73 @@
part of "curtain_view.dart";
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_event.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/generated/assets.dart';
class CurtainButtons extends StatelessWidget {
const CurtainButtons({super.key, required this.curtain});
final DeviceModel curtain;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Stack(
alignment: Alignment.center,
children: [
DecoratedBox(
decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(3, 3),
),
]),
child: InkWell(
overlayColor: MaterialStateProperty.all(Colors.transparent),
onTap: () {
DevicesCubit.get(context).openCurtain(curtain.productType!);
},
child: const SizedBox.square(
dimension: 60,
),
),
),
Padding(
padding: const EdgeInsets.only(right: 5, bottom: 5),
child: InkWell(
overlayColor: MaterialStateProperty.all(Colors.transparent),
onTap: () {
DevicesCubit.get(context).openCurtain(curtain.productType!);
},
child: SvgPicture.asset(
Assets.assetsIconsCurtainsIconOpenCurtain,
width: 110,
height: 110,
),
),
)
],
_buildButton(
onTap: () => context.read<CurtainBloc>().add(OpenCurtain(curtain.productType!)),
iconPath: Assets.assetsIconsCurtainsIconOpenCurtain,
),
_buildButton(
onTap: () => context.read<CurtainBloc>().add(PauseCurtain()),
iconPath: Assets.assetsImagesPause,
isSvg: false,
),
_buildButton(
onTap: () => context.read<CurtainBloc>().add(CloseCurtain(curtain.productType!)),
iconPath: Assets.assetsIconsCurtainsIconCloseCurtain,
),
],
);
}
Widget _buildButton({
required VoidCallback onTap,
required String iconPath,
bool isSvg = true,
}) {
return Stack(
alignment: Alignment.center,
children: [
DecoratedBox(
decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(3, 3),
),
]),
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(3, 3),
),
],
),
child: InkWell(
overlayColor: MaterialStateProperty.all(Colors.transparent),
onTap: () {
DevicesCubit.get(context).pauseCurtain();
},
child: Image.asset(
Assets.assetsImagesPause,
width: 60,
height: 60,
),
onTap: onTap,
child: const SizedBox.square(dimension: 60),
),
),
Stack(
alignment: Alignment.center,
children: [
DecoratedBox(
decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(3, 3),
),
]),
child: const SizedBox.square(
dimension: 60,
),
// InkWell(
// overlayColor: MaterialStateProperty.all(Colors.transparent),
// onTap: () {
// DevicesCubit.get(context).closeCurtain(curtain.productType!);
// },
// child: SvgPicture.asset(
// Assets.assetsIconsCurtainsIconCloseCurtain,
// width: 60,
// height: 60,
// ),
// ),
),
Padding(
padding: const EdgeInsets.only(right: 5, bottom: 5),
child: InkWell(
overlayColor: MaterialStateProperty.all(Colors.transparent),
onTap: () {
DevicesCubit.get(context).closeCurtain(curtain.productType!);
},
child: SvgPicture.asset(
Assets.assetsIconsCurtainsIconCloseCurtain,
width: 110,
height: 110,
),
),
)
],
Padding(
padding: const EdgeInsets.only(right: 5, bottom: 5),
child: InkWell(
overlayColor: MaterialStateProperty.all(Colors.transparent),
onTap: onTap,
child: isSvg
? SvgPicture.asset(iconPath, width: 110, height: 110)
: Image.asset(iconPath, width: 60, height: 60),
),
),
],
);

View File

@ -1,86 +1,99 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_event.dart';
import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_buttons.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/generated/assets.dart';
part "curtain_buttons.dart";
class CurtainView extends StatelessWidget {
const CurtainView({super.key, required this.curtain});
final DeviceModel curtain;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DevicesCubit.getInstance(),
child: BlocBuilder<DevicesCubit, DevicesState>(
builder: (context, state) => DefaultScaffold(
title: curtain.name,
child: Column(
children: [
Stack(
alignment: Alignment.centerLeft,
children: [
Container(
height: 340,
width: 365,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesWindow,
create: (context) => CurtainBloc(curtain.uuid!)..add(InitCurtain()),
child: BlocBuilder<CurtainBloc, CurtainState>(
builder: (context, state) {
double curtainWidth = 270;
double blindHeight = 310;
if (state is CurtainsOpening) {
curtainWidth = state.curtainWidth;
blindHeight = state.blindHeight;
} else if (state is CurtainsClosing) {
curtainWidth = state.curtainWidth;
blindHeight = state.blindHeight;
} else if (state is CurtainsPaused) {
curtainWidth = state.curtainWidth;
blindHeight = state.blindHeight;
}
return DefaultScaffold(
title: curtain.name,
child: Column(
children: [
Stack(
alignment: Alignment.centerLeft,
children: [
Container(
height: 340,
width: 365,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesWindow,
),
),
),
),
),
Padding(
padding: const EdgeInsets.all(40),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
height: 310,
width: DevicesCubit.getInstance().curtainWidth,
child: Stack(
children: List.generate(
10,
(index) {
double spacing =
DevicesCubit.getInstance().curtainWidth / 9;
double leftMostPosition = index * spacing;
return AnimatedPositioned(
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
left: leftMostPosition,
child: SizedBox(
height: 320,
width: 32,
child: SvgPicture.asset(
Assets.assetsIconsCurtainsIconVerticalBlade,
fit: BoxFit.fill,
Padding(
padding: const EdgeInsets.all(40),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
height: 310,
width: curtainWidth,
child: Stack(
children: List.generate(
10, (index) {
double spacing = curtainWidth / 9;
double leftMostPosition = index * spacing;
return AnimatedPositioned(
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
left: leftMostPosition,
child: SizedBox(
height: 320,
width: 32,
child: SvgPicture.asset(
Assets.assetsIconsCurtainsIconVerticalBlade,
fit: BoxFit.fill,
),
),
),
);
},
);
},
),
),
),
),
),
Positioned(
Positioned(
top: 27,
left: 43,
child: SvgPicture.asset(
Assets.assetsIconsCurtainsIconCurtainHolder)),
],
),
const SizedBox(height: 80),
CurtainButtons(
curtain: curtain,
),
],
),
),
Assets.assetsIconsCurtainsIconCurtainHolder,
),
),
],
),
const SizedBox(height: 80),
CurtainButtons(curtain: curtain),
],
),
);
},
),
);
}

View File

@ -66,6 +66,7 @@ class DevicesViewBody extends StatelessWidget {
const SizedBox(
height: 10,
),
Expanded(
child: PageView(
controller: HomeCubit.getInstance().devicesPageController,
@ -78,8 +79,7 @@ class DevicesViewBody extends StatelessWidget {
),
if (HomeCubit.getInstance().selectedSpace != null)
if (HomeCubit.getInstance().selectedSpace!.rooms != null)
...HomeCubit.getInstance().selectedSpace!.rooms!.map(
(room) {
...HomeCubit.getInstance().selectedSpace!.rooms!.map((room) {
return RoomPage(
room: room,
);

View File

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_screen.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_screen.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class OneGangInterface extends StatelessWidget {
const OneGangInterface({super.key, this.gangSwitch});
final DeviceModel? gangSwitch;
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: ColorsManager.backgroundColor,
extendBodyBehindAppBar: true,
extendBody: true,
appBar: gangSwitch != null
? DeviceAppbar(
deviceName: gangSwitch!.name!,
deviceUuid: gangSwitch!.uuid!,
)
: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: BodyLarge(
text: gangSwitch?.name ?? 'Lights',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
),
body: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesBackground,
),
fit: BoxFit.cover,
opacity: 0.4,
),
),
child: SafeArea(
child: Padding(
padding: EdgeInsets.only(
left: Constants.defaultPadding,
right: Constants.defaultPadding,
bottom: Constants.bottomNavBarHeight,
),
child: OneGangScreen(device: gangSwitch),
),
),
),
),
);
}
}

View File

@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/one_gang_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_timer_screen.dart';
import 'package:syncrow_app/features/devices/view/widgets/three_gang/gang_switch.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import '../../../bloc/one_gang_bloc/one_gang_event.dart';
class OneGangScreen extends StatelessWidget {
const OneGangScreen({super.key, this.device});
final DeviceModel? device;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => OneGangBloc(
switchCode: 'switch_1',
oneGangId : device?.uuid ?? '')
..add(const InitialEvent(groupScreen:false)),
child: BlocBuilder<OneGangBloc, OneGangState>(
builder: (context, state) {
OneGangModel oneGangModel = OneGangModel(
firstSwitch: false,
firstCountDown: 0,
);
if (state is LoadingNewSate) {
oneGangModel = state.oneGangModel;
} else if (state is UpdateState) {
oneGangModel = state.oneGangModel;
}
return state is LoadingInitialState ?
const Center(
child:
DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()),
):
RefreshIndicator(
onRefresh: () async {
BlocProvider.of<OneGangBloc>(context)
.add(InitialEvent(groupScreen: device != null ? false : true));
},
child: ListView(
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Expanded(child: SizedBox.shrink()),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
children: [
GangSwitch(
threeGangSwitch: device!,
value: oneGangModel.firstSwitch,
action: () {
BlocProvider.of<OneGangBloc>(context).add(
ChangeFirstSwitchStatusEvent(
value: oneGangModel.firstSwitch));
},
),
const SizedBox(height: 20),
const SizedBox(
width: 70,
child: BodySmall(
text:" Entrance Light",
fontColor: ColorsManager.textPrimaryColor,
textAlign: TextAlign.center,
),
),
],
),
],
),
),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
width: 20,
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => TimerScheduleScreen(
switchCode:'switch_1' ,
device: device!,
deviceCode: 'countdown_1',
)));
},
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(100),
),
),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(100),
),
child: Center(
child: Icon(
Icons.access_time,
color:
ColorsManager.primaryColorWithOpacity,
size: 25,
),
),
),
],
),
),
),
const SizedBox(height: 10),
BodySmall(
text: "Timer",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
),
),
],
),
],
),
),
Expanded(child: SizedBox())
],
),
),
],
),
);
},
),
);
}
}

View File

@ -0,0 +1,258 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_event.dart';
import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/shared_widgets/create_schedule.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/schedule_list.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class TimerScheduleScreen extends StatelessWidget {
final DeviceModel device;
final String deviceCode;
final String switchCode;
const TimerScheduleScreen(
{required this.device,
required this.deviceCode,
required this.switchCode,
super.key});
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: BlocProvider(
create: (context) =>
OneGangBloc(switchCode: switchCode, oneGangId: device.uuid ?? '')
..add(GetCounterEvent(deviceCode: deviceCode))
..add(GetScheduleEvent()),
child: BlocBuilder<OneGangBloc, OneGangState>(
builder: (context, state) {
final oneGangBloc = BlocProvider.of<OneGangBloc>(context);
Duration duration = Duration.zero;
int countNum = 0;
if (state is UpdateTimerState) {
countNum = state.seconds;
} else if (state is TimerRunInProgress) {
countNum = state.remainingTime;
} else if (state is TimerRunComplete) {
countNum = 0;
} else if (state is LoadingNewSate) {
countNum = 0;
}
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (!didPop) {
oneGangBloc.add(OnClose());
Navigator.pop(context);
}
},
child: DefaultTabController(
length: 2,
child: DefaultScaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: const BodyLarge(
text: 'Schedule',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
actions: [
oneGangBloc.createSchedule == true ?
TextButton(
onPressed: () {
oneGangBloc.add(ThreeGangSave());
},
child: const Text('Save')
) :
oneGangBloc.selectedTabIndex==1? IconButton(
onPressed: () {
oneGangBloc.toggleCreateSchedule();
},
icon: const Icon(Icons.add),
):SizedBox(),
],
),
child:
state is LoadingInitialState?
const Center(child: CircularProgressIndicator()):
Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
decoration: const ShapeDecoration(
color: ColorsManager.onPrimaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(30)),
),
),
child: TabBar(
onTap: (value) {
if(value==0){
if(oneGangBloc.createSchedule == true){
oneGangBloc.toggleCreateSchedule();
}
oneGangBloc.toggleSelectedIndex(0);
}else{
oneGangBloc.toggleSelectedIndex(1);
}
},
indicatorColor: Colors.white, // Customize the indicator
dividerHeight: 0,
indicatorSize: TabBarIndicatorSize.tab,
indicator: const ShapeDecoration(
color: ColorsManager.slidingBlueColor,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(20)),
),
),
tabs: [
Tab(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 10),
child: BodySmall(
text: 'Countdown',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
),
Tab(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
'Schedule',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
),
],
),
),
Expanded(
child: TabBarView(
children: [
Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
countNum > 0
? BodyLarge(
text: _formatDuration(countNum),
fontColor:
ColorsManager.slidingBlueColor,
fontSize: 40,
)
: CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm,
onTimerDurationChanged:
(Duration newDuration) {
duration = newDuration;
},
),
GestureDetector(
onTap: () {
if (state is LoadingNewSate) {
return;
}
if (countNum > 0) {
oneGangBloc.add(SetCounterValue(
deviceCode: deviceCode,
duration: Duration.zero));
} else if (duration != Duration.zero) {
oneGangBloc.add(SetCounterValue(
deviceCode: deviceCode,
duration: duration));
}
},
child: SvgPicture.asset(countNum > 0
? Assets.pauseIcon
: Assets.playIcon)),
],
),
),
),
Column(
mainAxisAlignment:oneGangBloc.listSchedule.isNotEmpty?
MainAxisAlignment.start:MainAxisAlignment.center,
children: [
SizedBox(
child: oneGangBloc.createSchedule == true ?
CreateSchedule(
onToggleChanged: (bool isOn) {
oneGangBloc.toggleSchedule = isOn;
},
onDateTimeChanged: (DateTime dateTime) {
oneGangBloc.selectedTime=dateTime;
},
days: oneGangBloc.days,
selectDays: (List<String> selectedDays) {
oneGangBloc.selectedDays = selectedDays;
},
)
:
Padding(
padding: const EdgeInsets.only(top: 10),
child: ScheduleListView(
listSchedule: oneGangBloc.listSchedule, // Pass the schedule list here
onDismissed: (scheduleId) {
oneGangBloc.listSchedule.removeWhere((schedule) => schedule.scheduleId == scheduleId);
oneGangBloc.add(DeleteScheduleEvent(id: scheduleId));
},
onToggleSchedule: (scheduleId, isEnabled) {
oneGangBloc.add(ToggleScheduleEvent(
id: scheduleId,
toggle: isEnabled,
));
},
),
),
),
],
),
],
),
),
],
),
))
);
},
),
),
);
}
String _formatDuration(int seconds) {
final hours = (seconds ~/ 3600).toString().padLeft(2, '0');
final minutes = ((seconds % 3600) ~/ 60).toString().padLeft(2, '0');
final secs = (seconds % 60).toString().padLeft(2, '0');
return '$hours:$minutes:$secs';
}
}

View File

@ -8,8 +8,13 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/ACs/acs_view.dart';
import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_view.dart';
import 'package:syncrow_app/features/devices/view/widgets/gateway/gateway_view.dart';
import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_Interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_screen.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_Interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_screen.dart';
import 'package:syncrow_app/features/devices/view/widgets/wall_sensor/wall_sensor_interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_interface.dart';
@ -102,6 +107,11 @@ void showDeviceInterface(DeviceModel device, BuildContext context) {
// navigateToInterface(CeilingSensorInterface(ceilingSensor: device), context);
break;
case DeviceType.Curtain:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
CurtainView(curtain: device,)));
break;
case DeviceType.Blind:
break;
@ -120,13 +130,24 @@ void showDeviceInterface(DeviceModel device, BuildContext context) {
break;
case DeviceType.LightBulb:
navigateToInterface(LightInterface(light: device), context);
case DeviceType.OneGang:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
OneGangInterface(gangSwitch: device)));
case DeviceType.TwoGang:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
TwoGangInterface(gangSwitch: device)));
case DeviceType.ThreeGang:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
ThreeGangInterface(gangSwitch: device)));
// navigateToInterface(ThreeGangInterface(gangSwitch: device), context);
break;
default:
}

View File

@ -22,6 +22,7 @@ class RoomsSlider extends StatelessWidget {
onPageChanged: (index) {
HomeCubit.getInstance().roomSliderPageChanged(index);
},
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),

View File

@ -1,14 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
// import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart';
// import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart';
// import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/smart_door_model.dart';
// import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_status_bar.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
@ -35,40 +31,36 @@ class _DoorLockButtonState extends State<DoorLockButton> with SingleTickerProvid
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
value: context.read<SmartDoorBloc>().unlockRequest > 0 ? 1 : 0,
duration: Duration(seconds: context.read<SmartDoorBloc>().unlockRequest),
);
_animation = Tween<double>(begin: 0, end: 1.0).animate(_animationController)
if (context.read<SmartDoorBloc>().unlockRequest > 0) {
_animationController.reverse();
}
_animation = Tween<double>(begin: 0, end: 1).animate(_animationController)
..addListener(() {
if (_animation.status == AnimationStatus.completed) {
// if (widget.doorLock.status
// .firstWhere((element) => element.code == 'normal_open_switch')
// .value !=
// true) {
// DevicesCubit.getInstance().deviceControl(
// DeviceControlModel(
// deviceId: widget.doorLock.uuid, code: 'normal_open_switch', value: true),
// widget.doorLock.uuid ?? "");
// }
BlocProvider.of<SmartDoorBloc>(context)
.add(UpdateLockEvent(value: smartDoorModel.normalOpenSwitch));
_animationController.reverse();
} else if (_animation.status == AnimationStatus.dismissed) {
// if (widget.doorLock.status
// .firstWhere((element) => element.code == 'normal_open_switch')
// .value !=
// false) {
// DevicesCubit.getInstance().deviceControl(
// DeviceControlModel(
// deviceId: widget.doorLock.uuid, code: 'normal_open_switch', value: false),
// widget.doorLock.uuid ?? "");
// }
}
setState(() {});
});
}
@override
void didUpdateWidget(DoorLockButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (_animationController.status == AnimationStatus.dismissed) {
if (context.read<SmartDoorBloc>().unlockRequest > 0) {
_animationController.value = 1;
_animationController.duration =
Duration(seconds: context.read<SmartDoorBloc>().unlockRequest);
_animationController.reverse();
}
}
}
@override
void dispose() {
_animationController.dispose();
@ -88,14 +80,18 @@ class _DoorLockButtonState extends State<DoorLockButton> with SingleTickerProvid
WidgetStateProperty.all(ColorsManager.primaryColorWithOpacity.withOpacity(0.1)),
borderRadius: BorderRadius.circular(999),
onTapDown: (details) {
if (_animationController.status == AnimationStatus.dismissed) {
_animationController.forward();
} else if (_animationController.status == AnimationStatus.completed) {
_animationController.reverse();
} else if (_animationController.status == AnimationStatus.forward) {
_animationController.reverse();
} else if (_animationController.status == AnimationStatus.reverse) {
_animationController.forward();
// if (_animationController.status == AnimationStatus.dismissed) {
// _animationController.forward();
// } else if (_animationController.status == AnimationStatus.completed) {
// _animationController.reverse();
// } else if (_animationController.status == AnimationStatus.forward) {
// _animationController.reverse();
// } else if (_animationController.status == AnimationStatus.reverse) {
// _animationController.forward();
// }
if (context.read<SmartDoorBloc>().unlockRequest > 0) {
BlocProvider.of<SmartDoorBloc>(context)
.add(UpdateLockEvent(value: smartDoorModel.normalOpenSwitch));
}
},
onTapUp: (details) {

View File

@ -7,27 +7,22 @@ import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_sta
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/smart_door_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/popup_menu_widget.dart';
import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_button.dart';
import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_grid.dart';
import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_status_bar.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class DoorInterface extends StatelessWidget {
DoorInterface({super.key, required this.doorLock});
const DoorInterface({super.key, required this.doorLock});
final DeviceModel doorLock;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SmartDoorBloc(deviceId: doorLock.uuid ?? '')..add(InitialEvent()),
child: BlocConsumer<SmartDoorBloc, SmartDoorState>(
listener: (context, state) {
child: BlocConsumer<SmartDoorBloc, SmartDoorState>(listener: (context, state) {
if (state is FailedState) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@ -63,32 +58,32 @@ class DoorInterface extends StatelessWidget {
}
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: ColorsManager.backgroundColor,
extendBodyBehindAppBar: true,
extendBody: true,
appBar: DeviceAppbar(
deviceName: doorLock.name!,
deviceUuid: doorLock.uuid!,
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
body: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesBackground,
),
fit: BoxFit.cover,
opacity: 0.4,
),
child: Scaffold(
backgroundColor: ColorsManager.backgroundColor,
extendBodyBehindAppBar: true,
extendBody: true,
appBar: DeviceAppbar(
deviceName: doorLock.name!,
deviceUuid: doorLock.uuid!,
),
child: SafeArea(
child: Padding(
body: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesBackground,
),
fit: BoxFit.cover,
opacity: 0.4,
),
),
child: SafeArea(
child: Padding(
padding: EdgeInsets.only(
top: Constants.appBarHeight,
left: Constants.defaultPadding,
@ -98,38 +93,35 @@ class DoorInterface extends StatelessWidget {
? const Center(
child: RefreshProgressIndicator(),
)
:
RefreshIndicator(
onRefresh: () async {
BlocProvider.of<SmartDoorBloc>(context).add(InitialEvent());
},
child:
ListView(
children: [
DoorLockStatusBar(
smartDoorModel: smartDoorModel,
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DoorLockButton(
doorLock: doorLock,
smartDoorModel: smartDoorModel,
),
DoorLockGrid( uuid: doorLock.uuid!, ),
],
)
],
)),
: RefreshIndicator(
onRefresh: () async {
BlocProvider.of<SmartDoorBloc>(context).add(InitialEvent());
},
child: ListView(
children: [
DoorLockStatusBar(
smartDoorModel: smartDoorModel,
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DoorLockButton(
doorLock: doorLock,
smartDoorModel: smartDoorModel,
),
DoorLockGrid(
uuid: doorLock.uuid!,
),
],
)
],
)),
),
),
),
),
),
));
));
}),
);
}
}

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
// import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart';
// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart';
// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/two_gang_bloc.dart';
// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/two_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';

View File

@ -63,7 +63,8 @@ class ScheduleScreen extends StatelessWidget {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => TimerScreen(
pageBuilder: (context, animation1, animation2) => TimerScheduleScreen(
switchCode :"switch_1",
device: device,
deviceCode: 'countdown_1',
)));
@ -99,10 +100,11 @@ class ScheduleScreen extends StatelessWidget {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => TimerScreen(
device: device,
deviceCode: 'countdown_2',
)));
pageBuilder: (context, animation1, animation2) => TimerScheduleScreen(
switchCode :"switch_2",
device: device,
deviceCode: 'countdown_2',
)));
},
child: Container(
padding:
@ -135,8 +137,9 @@ class ScheduleScreen extends StatelessWidget {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => TimerScreen(
device: device,
pageBuilder: (context, animation1, animation2) => TimerScheduleScreen(
switchCode :"switch_3",
device: device,
deviceCode: 'countdown_3',
)));
},

View File

@ -22,7 +22,9 @@ class ThreeGangScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => ThreeGangBloc(threeGangId: device?.uuid ?? '')
create: (context) => ThreeGangBloc(
switchCode: '',
threeGangId: device?.uuid ?? '')
..add(InitialEvent(groupScreen: device != null ? false : true)),
child: BlocBuilder<ThreeGangBloc, ThreeGangState>(
builder: (context, state) {

View File

@ -7,7 +7,10 @@ import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_blo
import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart';
import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/shared_widgets/create_schedule.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/schedule_list.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/generated/assets.dart';
@ -15,10 +18,17 @@ import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class TimerScreen extends StatelessWidget {
class TimerScheduleScreen extends StatelessWidget {
final DeviceModel device;
final String deviceCode;
const TimerScreen({required this.device, required this.deviceCode, super.key});
final String switchCode;
const TimerScheduleScreen(
{required this.device,
required this.deviceCode,
required this.switchCode,
super.key});
@override
Widget build(BuildContext context) {
@ -27,150 +37,217 @@ class TimerScreen extends StatelessWidget {
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: ColorsManager.backgroundColor,
extendBodyBehindAppBar: true,
extendBody: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: const BodyLarge(
text: 'Schedule',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
),
body: SafeArea(
child: BlocProvider(
create: (context) => ThreeGangBloc(threeGangId: device.uuid ?? '')
..add(GetCounterEvent(deviceCode: deviceCode)),
child: BlocBuilder<ThreeGangBloc, ThreeGangState>(
builder: (context, state) {
Duration duration = Duration.zero;
int countNum = 0;
int tabNum = 0;
if (state is UpdateTimerState) {
countNum = state.seconds;
} else if (state is TimerRunInProgress) {
countNum = state.remainingTime;
} else if (state is TimerRunComplete) {
countNum = 0;
} else if (state is LoadingNewSate) {
countNum = 0;
}
if (state is ChangeSlidingSegmentState) {
tabNum = state.value;
}
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (!didPop) {
BlocProvider.of<ThreeGangBloc>(context).add(OnClose());
Navigator.pop(context);
}
},
child: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
padding: const EdgeInsets.all(24),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesBackground,
),
fit: BoxFit.cover,
opacity: 0.4,
),
child: BlocProvider(
create: (context) =>
ThreeGangBloc(switchCode: switchCode, threeGangId: device.uuid ?? '')
..add(GetCounterEvent(deviceCode: deviceCode))
..add(GetScheduleEvent()),
child: BlocBuilder<ThreeGangBloc, ThreeGangState>(
builder: (context, state) {
final threeGangBloc = BlocProvider.of<ThreeGangBloc>(context);
Duration duration = Duration.zero;
int countNum = 0;
if (state is UpdateTimerState) {
countNum = state.seconds;
} else if (state is TimerRunInProgress) {
countNum = state.remainingTime;
} else if (state is TimerRunComplete) {
countNum = 0;
} else if (state is LoadingNewSate) {
countNum = 0;
}
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (!didPop) {
threeGangBloc.add(OnClose());
Navigator.pop(context);
}
},
child: DefaultTabController(
length: 2,
child: DefaultScaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: const BodyLarge(
text: 'Schedule',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
child: state is LoadingInitialState || state is LoadingNewSate
? const Center(
child: DefaultContainer(
width: 50, height: 50, child: CircularProgressIndicator()),
)
: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: MediaQuery.sizeOf(context).width,
decoration: const ShapeDecoration(
color: ColorsManager.slidingBlueColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
actions: [
threeGangBloc.createSchedule == true ?
TextButton(
onPressed: () {
threeGangBloc.add(ThreeGangSave());
},
child: const Text('Save')
) :
threeGangBloc.selectedTabIndex==1? IconButton(
onPressed: () {
threeGangBloc.toggleCreateSchedule();
},
icon: const Icon(Icons.add),
):SizedBox(),
],
),
child:
state is LoadingInitialState?
const Center(child: CircularProgressIndicator()):
Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
decoration: const ShapeDecoration(
color: ColorsManager.onPrimaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(30)),
),
),
child: TabBar(
onTap: (value) {
if(value==0){
if(threeGangBloc.createSchedule == true){
threeGangBloc.toggleCreateSchedule();
}
threeGangBloc.toggleSelectedIndex(0);
}else{
threeGangBloc.toggleSelectedIndex(1);
}
},
indicatorColor: Colors.white, // Customize the indicator
dividerHeight: 0,
indicatorSize: TabBarIndicatorSize.tab,
indicator: const ShapeDecoration(
color: ColorsManager.slidingBlueColor,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(20)),
),
),
tabs: [
Tab(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 10),
child: BodySmall(
text: 'Countdown',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
child: CupertinoSlidingSegmentedControl<int>(
thumbColor: ColorsManager.slidingBlueColor,
onValueChanged: (value) {
BlocProvider.of<ThreeGangBloc>(context)
.add(ChangeSlidingSegment(value: value ?? 0));
},
groupValue:
state is ChangeSlidingSegmentState ? state.value : 0,
backgroundColor: Colors.white,
children: <int, Widget>{
0: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: BodySmall(
text: 'Countdown',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
1: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
'Schedule',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
},
),
),
Tab(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
'Schedule',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
if (tabNum == 0)
countNum > 0
? BodyLarge(
text: _formatDuration(countNum),
fontColor: ColorsManager.slidingBlueColor,
fontSize: 40,
)
: CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm,
onTimerDurationChanged: (Duration newDuration) {
duration = newDuration;
},
),
if (tabNum == 0)
GestureDetector(
onTap: () {
if (state is LoadingNewSate) {
return;
}
if (countNum > 0) {
BlocProvider.of<ThreeGangBloc>(context).add(
SetCounterValue(
),
],
),
),
Expanded(
child: TabBarView(
children: [
Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
countNum > 0
? BodyLarge(
text: _formatDuration(countNum),
fontColor:
ColorsManager.slidingBlueColor,
fontSize: 40,
)
: CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm,
onTimerDurationChanged:
(Duration newDuration) {
duration = newDuration;
},
),
GestureDetector(
onTap: () {
if (state is LoadingNewSate) {
return;
}
if (countNum > 0) {
threeGangBloc.add(SetCounterValue(
deviceCode: deviceCode,
duration: Duration.zero));
} else if (duration != Duration.zero) {
BlocProvider.of<ThreeGangBloc>(context).add(
SetCounterValue(
deviceCode: deviceCode, duration: duration));
}
} else if (duration != Duration.zero) {
threeGangBloc.add(SetCounterValue(
deviceCode: deviceCode,
duration: duration));
}
},
child: SvgPicture.asset(countNum > 0
? Assets.pauseIcon
: Assets.playIcon)),
],
),
),
),
Column(
mainAxisAlignment:threeGangBloc.listSchedule.isNotEmpty?
MainAxisAlignment.start:MainAxisAlignment.center,
children: [
SizedBox(
child: threeGangBloc.createSchedule == true ?
CreateSchedule(
onToggleChanged: (bool isOn) {
threeGangBloc.toggleSchedule = isOn;
},
child: SvgPicture.asset(
countNum > 0 ? Assets.pauseIcon : Assets.playIcon))
],
)));
},
),
),
onDateTimeChanged: (DateTime dateTime) {
threeGangBloc.selectedTime=dateTime;
},
days: threeGangBloc.days,
selectDays: (List<String> selectedDays) {
threeGangBloc.selectedDays = selectedDays;
},
)
:
Padding(
padding: const EdgeInsets.only(top: 10),
child: ScheduleListView(
listSchedule: threeGangBloc.listSchedule, // Pass the schedule list here
onDismissed: (scheduleId) {
threeGangBloc.listSchedule.removeWhere((schedule) => schedule.scheduleId == scheduleId);
threeGangBloc.add(DeleteScheduleEvent(id: scheduleId));
},
onToggleSchedule: (scheduleId, isEnabled) {
threeGangBloc.add(ToggleScheduleEvent(
id: scheduleId,
toggle: isEnabled,
));
},
),
),
),
],
),
],
),
),
],
),
))
);
},
),
),
);

View File

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_screen.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class TwoGangInterface extends StatelessWidget {
const TwoGangInterface({super.key, this.gangSwitch});
final DeviceModel? gangSwitch;
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: ColorsManager.backgroundColor,
extendBodyBehindAppBar: true,
extendBody: true,
appBar: gangSwitch != null
? DeviceAppbar(
deviceName: gangSwitch!.name!,
deviceUuid: gangSwitch!.uuid!,
)
: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: BodyLarge(
text: gangSwitch?.name ?? 'Lights',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
),
body: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesBackground,
),
fit: BoxFit.cover,
opacity: 0.4,
),
),
child: SafeArea(
child: Padding(
padding: EdgeInsets.only(
left: Constants.defaultPadding,
right: Constants.defaultPadding,
bottom: Constants.bottomNavBarHeight,
),
child: TwoGangScreen(device: gangSwitch),
),
),
),
),
);
}
}

View File

@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart';
import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart';
import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart';
import 'package:syncrow_app/features/devices/model/group_three_gang_model.dart';
import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
class TwoGangList extends StatelessWidget {
const TwoGangList({super.key, required this.twoGangList, required this.allSwitches});
final List<GroupTwoGangModel> twoGangList;
final bool allSwitches;
@override
Widget build(BuildContext context) {
return BlocBuilder<ThreeGangBloc, ThreeGangState>(
builder: (context, state) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 10),
const BodySmall(text: 'All Lights'),
const SizedBox(height: 5),
DevicesDefaultSwitch(
switchValue: allSwitches,
action: () {
BlocProvider.of<ThreeGangBloc>(context).add(GroupAllOnEvent());
},
secondAction: () {
BlocProvider.of<ThreeGangBloc>(context).add(GroupAllOffEvent());
},
),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(0),
itemCount: twoGangList.length,
itemBuilder: (context, index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
BodySmall(text: '${twoGangList[index].deviceName} beside light'),
const SizedBox(height: 5),
DevicesDefaultSwitch(
switchValue: twoGangList[index].firstSwitch,
action: () {
BlocProvider.of<ThreeGangBloc>(context).add(ChangeFirstSwitchStatusEvent(
value: twoGangList[index].firstSwitch,
deviceId: twoGangList[index].deviceId));
},
),
const SizedBox(height: 10),
BodySmall(text: '${twoGangList[index].deviceName} ceiling light'),
const SizedBox(height: 5),
DevicesDefaultSwitch(
switchValue: twoGangList[index].secondSwitch,
action: () {
BlocProvider.of<ThreeGangBloc>(context).add(ChangeSecondSwitchStatusEvent(
value: twoGangList[index].secondSwitch,
deviceId: twoGangList[index].deviceId));
},
),
const SizedBox(height: 10),
BodySmall(text: '${twoGangList[index].deviceName} spotlight'),
const SizedBox(height: 5),
],
);
},
),
],
),
);
},
);
}
}

View File

@ -0,0 +1,339 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_event.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart';
import 'package:syncrow_app/features/devices/model/two_gang_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/three_gang/gang_switch.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_list.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_schedule_screen.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class TwoGangScreen extends StatelessWidget {
const TwoGangScreen({super.key, this.device, this.switchCode});
final DeviceModel? device;
final String? switchCode;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => TwoGangBloc(
switchCode: switchCode??'',
twoGangId: device?.uuid ?? '')
..add(InitialEvent(groupScreen: device != null ? false : true)),
child: BlocBuilder<TwoGangBloc, TwoGangState>(
builder: (context, state) {
TwoGangModel twoGangModel = TwoGangModel(
firstSwitch: false,
secondSwitch: false,
firstCountDown: 0,
secondCountDown: 0,
);
List<GroupTwoGangModel> groupTwoGangModel = [];
bool allSwitchesOn = false;
if (state is LoadingNewSate) {
twoGangModel = state.twoGangModel;
} else if (state is UpdateState) {
twoGangModel = state.twoGangModel;
} else if (state is UpdateGroupState) {
groupTwoGangModel = state.twoGangList;
allSwitchesOn = state.allSwitches;
}
return state is LoadingInitialState
? const Center(
child: DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()),)
: device == null
? TwoGangList(
twoGangList: groupTwoGangModel,
allSwitches: allSwitchesOn,
)
: RefreshIndicator(
onRefresh: () async {
BlocProvider.of<TwoGangBloc>(context)
.add(InitialEvent(groupScreen: device != null ? false : true));
},
child: ListView(
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Expanded(child: SizedBox.shrink()),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
children: [
GangSwitch(
threeGangSwitch: device!,
value: twoGangModel.firstSwitch,
action: () {
BlocProvider.of<TwoGangBloc>(context).add(
ChangeFirstSwitchStatusEvent(
value: twoGangModel.firstSwitch));
},
),
const SizedBox(height: 20),
const SizedBox(
width: 77,
child: BodySmall(
text: "Cove Light",
fontColor: ColorsManager.textPrimaryColor,
textAlign: TextAlign.center,
),
),
],
),
Column(
children: [
GangSwitch(
threeGangSwitch: device!,
value: twoGangModel.secondSwitch,
action: () {
BlocProvider.of<TwoGangBloc>(context).add(
ChangeSecondSwitchStatusEvent(
value: twoGangModel.secondSwitch));
},
),
const SizedBox(height: 20),
const SizedBox(
width: 77,
child: BodySmall(
text: "Chandelier",
fontColor: ColorsManager.textPrimaryColor,
textAlign: TextAlign.center,
),
),
],
),
],
),
),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: GestureDetector(
onTap: () {
BlocProvider.of<TwoGangBloc>(context)
.add(AllOnEvent());
},
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(100),
),
),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(100),
),
child: Center(
child: BodySmall(
text: "On",
style: context.bodyMedium.copyWith(
color: ColorsManager
.primaryColorWithOpacity,
fontWeight: FontWeight.bold),
),
),
),
],
),
),
),
const SizedBox(height: 10),
BodySmall(
text: "All On",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
),
),
],
),
const SizedBox(
width: 20,
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder:
(context, animation1, animation2) =>
TwoGangScheduleScreen(
device: device!,
)));
},
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(100),
),
),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(100),
),
child: Center(
child: Icon(
Icons.access_time,
color:
ColorsManager.primaryColorWithOpacity,
size: 25,
),
),
),
],
),
),
),
const SizedBox(height: 10),
BodySmall(
text: "Timer",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
),
),
],
),
const SizedBox(
width: 20,
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: GestureDetector(
onTap: () {
// DevicesCubit.getInstance().deviceControl(
// DeviceControlModel(
// deviceId: device.uuid,
// code: 'switch_1',
// value: false,
// ),
// device.uuid!,
// );
// DevicesCubit.getInstance().deviceControl(
// DeviceControlModel(
// deviceId: device.uuid,
// code: 'switch_2',
// value: false,
// ),
// device.uuid!,
// );
// DevicesCubit.getInstance().deviceControl(
// DeviceControlModel(
// deviceId: device.uuid,
// code: 'switch_3',
// value: false,
// ),
// device.uuid!,
// );
BlocProvider.of<TwoGangBloc>(context)
.add(AllOffEvent());
},
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(100),
),
),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(100),
),
child: Center(
child: BodySmall(
text: "Off",
style: context.bodyMedium.copyWith(
color: ColorsManager
.primaryColorWithOpacity,
fontWeight: FontWeight.bold),
),
),
),
],
),
),
),
const SizedBox(height: 10),
BodySmall(
text: "All Off",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
),
),
],
),
],
),
),
Expanded(child: SizedBox())
],
),
),
],
),
);
},
),
);
}
}

View File

@ -0,0 +1,141 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_timer_screen.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class TwoGangScheduleScreen extends StatelessWidget {
final DeviceModel device;
const TwoGangScheduleScreen({required this.device, super.key});
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: ColorsManager.backgroundColor,
extendBodyBehindAppBar: true,
extendBody: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: const BodyLarge(
text: 'Schedule',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
),
body: SafeArea(
child: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesBackground,
),
fit: BoxFit.cover,
opacity: 0.4,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 48,
),
DefaultContainer(
width: MediaQuery.sizeOf(context).width,
child: Column(
children: [
InkWell(
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => TimerScheduleScreen(
device: device,
deviceCode: 'countdown_1',
switchCode:'switch_1' ,
)));
},
child: Container(
padding:
const EdgeInsets.only(left: 25, right: 15, top: 20, bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodySmall(
text: "Cove Light",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
fontSize: 15,
fontWeight: FontWeight.w400,
),
),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
size: 18,
)
],
),
),
),
const Divider(
color: ColorsManager.dividerColor,
),
InkWell(
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => TimerScheduleScreen(
device: device,
deviceCode: 'countdown_2',
switchCode:'switch_2' ,
)));
},
child: Container(
padding:
const EdgeInsets.only(left: 25, right: 15, top: 20, bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodySmall(
text: "Chandelier",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
fontSize: 15,
fontWeight: FontWeight.w400,
),
),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
size: 18,
)
],
),
),
),
],
)),
],
),
),
),
));
}
}

View File

@ -0,0 +1,259 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/shared_widgets/create_schedule.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/schedule_list.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_event.dart';
class TimerScheduleScreen extends StatelessWidget {
final DeviceModel device;
final String deviceCode;
final String switchCode;
const TimerScheduleScreen(
{required this.device,
required this.deviceCode,
required this.switchCode,
super.key});
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: BlocProvider(
create: (context) =>
TwoGangBloc(switchCode: switchCode, twoGangId: device.uuid ?? '')
..add(GetCounterEvent(deviceCode: deviceCode))
..add(GetScheduleEvent()),
child: BlocBuilder<TwoGangBloc, TwoGangState>(
builder: (context, state) {
final twoGangBloc = BlocProvider.of<TwoGangBloc>(context);
Duration duration = Duration.zero;
int countNum = 0;
if (state is UpdateTimerState) {
countNum = state.seconds;
} else if (state is TimerRunInProgress) {
countNum = state.remainingTime;
} else if (state is TimerRunComplete) {
countNum = 0;
} else if (state is LoadingNewSate) {
countNum = 0;
}
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (!didPop) {
twoGangBloc.add(OnClose());
Navigator.pop(context);
}
},
child: DefaultTabController(
length: 2,
child: DefaultScaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: const BodyLarge(
text: 'Schedule',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
actions: [
twoGangBloc.createSchedule == true ?
TextButton(
onPressed: () {
twoGangBloc.add(TwoGangSave());
},
child: const Text('Save')
) :
twoGangBloc.selectedTabIndex==1? IconButton(
onPressed: () {
twoGangBloc.toggleCreateSchedule();
},
icon: const Icon(Icons.add),
):SizedBox(),
],
),
child:
state is LoadingInitialState?
const Center(child: CircularProgressIndicator()):
Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
decoration: const ShapeDecoration(
color: ColorsManager.onPrimaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(30)),
),
),
child: TabBar(
onTap: (value) {
print(value);
if(value==0){
if(twoGangBloc.createSchedule == true){
twoGangBloc.toggleCreateSchedule();
}
twoGangBloc.toggleSelectedIndex(0);
}else{
twoGangBloc.toggleSelectedIndex(1);
}
},
indicatorColor: Colors.white, // Customize the indicator
dividerHeight: 0,
indicatorSize: TabBarIndicatorSize.tab,
indicator: const ShapeDecoration(
color: ColorsManager.slidingBlueColor,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(20)),
),
),
tabs: [
Tab(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 10),
child: BodySmall(
text: 'Countdown',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
),
Tab(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
'Schedule',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
),
],
),
),
Expanded(
child: TabBarView(
children: [
Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
countNum > 0
? BodyLarge(
text: _formatDuration(countNum),
fontColor:
ColorsManager.slidingBlueColor,
fontSize: 40,
)
: CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm,
onTimerDurationChanged:
(Duration newDuration) {
duration = newDuration;
},
),
GestureDetector(
onTap: () {
if (state is LoadingNewSate) {
return;
}
if (countNum > 0) {
twoGangBloc.add(SetCounterValue(
deviceCode: deviceCode,
duration: Duration.zero));
} else if (duration != Duration.zero) {
twoGangBloc.add(SetCounterValue(
deviceCode: deviceCode,
duration: duration));
}
},
child: SvgPicture.asset(countNum > 0
? Assets.pauseIcon
: Assets.playIcon)),
],
),
),
),
Column(
mainAxisAlignment:twoGangBloc.listSchedule.isNotEmpty?
MainAxisAlignment.start:MainAxisAlignment.center,
children: [
SizedBox(
child: twoGangBloc.createSchedule == true ?
CreateSchedule(
onToggleChanged: (bool isOn) {
twoGangBloc.toggleSchedule = isOn;
},
onDateTimeChanged: (DateTime dateTime) {
twoGangBloc.selectedTime=dateTime;
},
days: twoGangBloc.days,
selectDays: (List<String> selectedDays) {
twoGangBloc.selectedDays = selectedDays;
},
)
:
Padding(
padding: const EdgeInsets.only(top: 10),
child: ScheduleListView(
listSchedule: twoGangBloc.listSchedule, // Pass the schedule list here
onDismissed: (scheduleId) {
twoGangBloc.listSchedule.removeWhere((schedule) => schedule.scheduleId == scheduleId);
twoGangBloc.add(DeleteScheduleEvent(id: scheduleId));
},
onToggleSchedule: (scheduleId, isEnabled) {
twoGangBloc.add(ToggleScheduleEvent(
id: scheduleId,
toggle: isEnabled,
));
},
),
),
),
],
),
],
),
),
],
),
))
);
},
),
),
);
}
String _formatDuration(int seconds) {
final hours = (seconds ~/ 3600).toString().padLeft(2, '0');
final minutes = ((seconds % 3600) ~/ 60).toString().padLeft(2, '0');
final secs = (seconds % 60).toString().padLeft(2, '0');
return '$hours:$minutes:$secs';
}
}

View File

@ -18,7 +18,7 @@ class ManageHomeView extends StatelessWidget {
title: 'Manage Your Home',
child: spaces == null
? const Center(
child: CircularProgressIndicator(),
child: BodyMedium(text: 'No spaces found'),
)
: Column(
children: [
@ -30,8 +30,7 @@ class ManageHomeView extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children:
List.generate(
children: List.generate(
spaces.length,
(index) {
if (index == spaces.length - 1) {
@ -43,12 +42,10 @@ class ManageHomeView extends StatelessWidget {
)));
},
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodyMedium(
text: StringHelpers.toTitleCase(
spaces[index].name ?? "")),
text: StringHelpers.toTitleCase(spaces[index].name ?? "")),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
@ -76,14 +73,10 @@ class ManageHomeView extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodyMedium(
text: HomeCubit.getInstance()
.spaces![index]
.name ??
""),
text: HomeCubit.getInstance().spaces![index].name ?? ""),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
@ -92,8 +85,7 @@ class ManageHomeView extends StatelessWidget {
],
),
Container(
margin:
const EdgeInsets.symmetric(vertical: 15),
margin: const EdgeInsets.symmetric(vertical: 15),
height: 1,
color: ColorsManager.greyColor,
),

Some files were not shown because too many files have changed in this diff Show More