Pulled latest changes

This commit is contained in:
Abdullah Alassaf
2025-03-11 23:42:42 +03:00
134 changed files with 4206 additions and 1588 deletions

View File

@ -1,5 +1,9 @@
plugins {
id "com.android.application"
// START: FlutterFire Configuration
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
// END: FlutterFire Configuration
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}

View File

@ -0,0 +1,68 @@
{
"project_info": {
"project_number": "427332280600",
"firebase_url": "https://test2-8a3d2-default-rtdb.firebaseio.com",
"project_id": "test2-8a3d2",
"storage_bucket": "test2-8a3d2.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:427332280600:android:550f67441246cb1a0c7e6d",
"android_client_info": {
"package_name": "com.example.syncrow.app"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyA5qOErxdm0zJmoHIB0TixfebYEsNRpwV0"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:427332280600:android:bb6047adeeb80fb00c7e6d",
"android_client_info": {
"package_name": "com.example.syncrow_application"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyA5qOErxdm0zJmoHIB0TixfebYEsNRpwV0"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:427332280600:android:2bc36fbe82994a3e0c7e6d",
"android_client_info": {
"package_name": "com.example.syncrow_web"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyA5qOErxdm0zJmoHIB0TixfebYEsNRpwV0"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@ -20,6 +20,10 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
// START: FlutterFire Configuration
id "com.google.gms.google-services" version "4.3.15" apply false
id "com.google.firebase.crashlytics" version "2.8.1" apply false
// END: FlutterFire Configuration
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}

View File

@ -0,0 +1,21 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_6702_36698)">
<circle cx="20" cy="20" r="15" fill="#F4F4F4"/>
<path d="M26.4 13.1094H23.7333V12.582C23.7333 11.7097 23.0156 11 22.1333 11H17.8667C16.9844 11 16.2667 11.7097 16.2667 12.582V13.1094H13.6C12.7178 13.1094 12 13.8191 12 14.6914C12 15.392 12.4631 15.9873 13.1024 16.1947L14.0537 27.5493C14.1222 28.3628 14.8226 29 15.6481 29H24.3519C25.1775 29 25.8778 28.3628 25.9464 27.5491L26.8976 16.1947C27.5369 15.9873 28 15.392 28 14.6914C28 13.8191 27.2822 13.1094 26.4 13.1094ZM17.3333 12.582C17.3333 12.2913 17.5726 12.0547 17.8667 12.0547H22.1333C22.4274 12.0547 22.6667 12.2913 22.6667 12.582V13.1094H17.3333V12.582ZM24.8833 27.4618C24.8605 27.7329 24.6271 27.9453 24.3519 27.9453H15.6481C15.373 27.9453 15.1395 27.7329 15.1167 27.462L14.1793 16.2734H25.8207L24.8833 27.4618ZM26.4 15.2188H13.6C13.3059 15.2188 13.0667 14.9822 13.0667 14.6914C13.0667 14.4006 13.3059 14.1641 13.6 14.1641H26.4C26.6941 14.1641 26.9333 14.4006 26.9333 14.6914C26.9333 14.9822 26.6941 15.2188 26.4 15.2188Z" fill="#999999"/>
<path d="M17.8656 26.3307L17.3323 17.8229C17.314 17.5322 17.0596 17.3111 16.767 17.3292C16.473 17.3472 16.2494 17.5974 16.2676 17.8881L16.801 26.396C16.8185 26.6756 17.0533 26.8907 17.3328 26.8907C17.6416 26.8907 17.8846 26.6335 17.8656 26.3307Z" fill="#999999"/>
<path d="M20.0001 17.3281C19.7056 17.3281 19.4668 17.5642 19.4668 17.8555V26.3633C19.4668 26.6545 19.7056 26.8906 20.0001 26.8906C20.2947 26.8906 20.5335 26.6545 20.5335 26.3633V17.8555C20.5335 17.5642 20.2947 17.3281 20.0001 17.3281Z" fill="#999999"/>
<path d="M23.233 17.3292C22.9396 17.3111 22.6859 17.5321 22.6677 17.8229L22.1343 26.3307C22.1162 26.6213 22.3397 26.8716 22.6337 26.8896C22.9278 26.9076 23.1808 26.6865 23.199 26.3959L23.7323 17.8881C23.7505 17.5974 23.527 17.3472 23.233 17.3292Z" fill="#999999"/>
</g>
<defs>
<filter id="filter0_d_6702_36698" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6702_36698"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6702_36698" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,25 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_6702_36714)">
<circle cx="20" cy="20" r="15" fill="#F4F4F4"/>
<path d="M21.1979 12.3395L18.26 15.2772C17.7228 15.8146 17.3477 16.4536 17.1345 17.1328C16.437 17.3525 15.798 17.7393 15.2772 18.26L12.3395 21.1979C10.5536 22.9837 10.5534 25.8744 12.3395 27.6605C14.1253 29.4464 17.0161 29.4466 18.8022 27.6605L21.7399 24.7227C22.2773 24.1855 22.6522 23.5465 22.8655 22.8671C23.5631 22.6475 24.202 22.2607 24.7227 21.7399L27.6605 18.8022C29.4464 17.0162 29.4467 14.1256 27.6605 12.3395C25.8746 10.5536 22.984 10.5534 21.1979 12.3395ZM17.266 20.2484C17.4886 20.7914 17.8199 21.2998 18.26 21.7399C18.6877 22.1674 19.1965 22.5054 19.7513 22.7343L16.8137 25.6721C16.1284 26.3572 15.0133 26.3574 14.328 25.6721C13.6427 24.9867 13.6427 23.8717 14.328 23.1864L17.2657 20.2485C17.2659 20.2485 17.2659 20.2485 17.266 20.2484ZM21.2428 24.2256L18.3049 27.1633C16.7939 28.6745 14.3479 28.6747 12.8366 27.1633C11.3254 25.6523 11.3253 23.2062 12.8366 21.695L15.7744 18.7571C16.1166 18.4149 16.5191 18.1412 16.9579 17.9488C16.8924 18.4845 16.9223 19.014 17.0364 19.5199C16.9419 19.5905 16.8524 19.6676 16.7686 19.7514L13.8309 22.6892C12.8715 23.6487 12.8715 25.2097 13.8309 26.1691C14.7903 27.1285 16.3513 27.1285 17.3108 26.1691L20.2485 23.2313C21.2089 22.2709 21.209 20.7119 20.2485 19.7514C19.7355 19.2383 19.6105 18.4924 19.8554 17.8663C20.3734 18.059 20.8488 18.3631 21.2428 18.7571C22.7504 20.2647 22.7505 22.7179 21.2428 24.2256ZM27.1633 18.3049L24.2256 21.2428C23.8834 21.585 23.4809 21.8587 23.0421 22.0511C23.1076 21.5154 23.0777 20.986 22.9637 20.4801C23.058 20.4095 23.1477 20.3323 23.2313 20.2485L26.1692 17.3108C27.1286 16.3514 27.1286 14.7903 26.1692 13.8309C25.3077 12.9695 23.9604 12.8807 22.9987 13.5684C22.8408 13.6813 22.8044 13.9009 22.9173 14.0588C23.0301 14.2168 23.2497 14.2533 23.4077 14.1403C24.1093 13.6386 25.0615 13.7174 25.6721 14.328C26.3574 15.0133 26.3574 16.1283 25.6721 16.8137L22.7342 19.7514C22.7342 19.7514 22.7342 19.7514 22.7341 19.7515C22.5113 19.2085 22.1801 18.7002 21.7399 18.26C21.3124 17.8325 20.8035 17.4945 20.2487 17.2656L21.4465 16.0678C21.5837 15.9306 21.5837 15.708 21.4465 15.5707C21.3091 15.4335 21.0867 15.4335 20.9493 15.5707L19.7514 16.7686C18.7911 17.729 18.7909 19.2879 19.7514 20.2485C20.2645 20.7615 20.3894 21.5076 20.1446 22.1337C19.6266 21.941 19.1511 21.6368 18.7571 21.2427C17.2497 19.7352 17.2495 17.282 18.7571 15.7744L21.695 12.8366C23.2061 11.3254 25.6522 11.3252 27.1633 12.8366C28.6745 14.3476 28.6747 16.7937 27.1633 18.3049Z" fill="#999999"/>
<path d="M22.5443 14.8262C22.5443 15.0204 22.3869 15.1777 22.1929 15.1777C21.9987 15.1777 21.8413 15.0204 21.8413 14.8262C21.8413 14.632 21.9987 14.4746 22.1929 14.4746C22.3869 14.4746 22.5443 14.632 22.5443 14.8262Z" fill="#999999"/>
<path d="M15.7755 15.774C15.9128 15.6368 15.9128 15.4142 15.7755 15.2769L14.2841 13.7855C14.1468 13.6483 13.9243 13.6483 13.787 13.7855C13.6498 13.9228 13.6498 14.1455 13.787 14.2828L15.2784 15.7742C15.4158 15.9114 15.6383 15.9114 15.7755 15.774Z" fill="#999999"/>
<path d="M12.3378 16.9062C12.1437 16.9062 11.9863 17.0636 11.9863 17.2577C11.9863 17.4519 12.1437 17.6092 12.3378 17.6092H14.447C14.641 17.6092 14.7984 17.4519 14.7984 17.2577C14.7984 17.0636 14.641 16.9062 14.447 16.9062H12.3378Z" fill="#999999"/>
<path d="M16.9062 12.2314V14.3405C16.9062 14.5346 17.0636 14.6921 17.2577 14.6921C17.4519 14.6921 17.6092 14.5346 17.6092 14.3405V12.2314C17.6092 12.0373 17.4519 11.8799 17.2577 11.8799C17.0636 11.8799 16.9062 12.0373 16.9062 12.2314Z" fill="#999999"/>
<path d="M24.227 24.2259C24.0897 24.3631 24.0897 24.5857 24.227 24.7231L25.7184 26.2145C25.7871 26.2831 25.877 26.3175 25.967 26.3175C26.2772 26.3175 26.4377 25.9397 26.2155 25.7173L24.7242 24.2259C24.5868 24.0887 24.3643 24.0887 24.227 24.2259Z" fill="#999999"/>
<path d="M23.0605 27.7673V25.6581C23.0605 25.464 22.903 25.3066 22.709 25.3066C22.5148 25.3066 22.3574 25.464 22.3574 25.6581V27.7673C22.3574 27.9614 22.5148 28.1187 22.709 28.1187C22.903 28.1187 23.0605 27.9614 23.0605 27.7673Z" fill="#999999"/>
<path d="M27.7693 23.0586C27.9633 23.0586 28.1207 22.9011 28.1207 22.707C28.1207 22.5128 27.9633 22.3555 27.7693 22.3555H25.66C25.466 22.3555 25.3086 22.5128 25.3086 22.707C25.3086 22.9011 25.466 23.0586 25.66 23.0586H27.7693Z" fill="#999999"/>
</g>
<defs>
<filter id="filter0_d_6702_36714" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6702_36714"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6702_36714" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,3 @@
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M43.2614 20.4808C44.1769 21.3963 44.1769 22.8804 43.2614 23.7955L27.5381 39.5192C26.6226 40.4343 25.139 40.4343 24.2235 39.5192L16.7386 32.0338C15.8231 31.1188 15.8231 29.6347 16.7386 28.7196C17.6537 27.8041 19.1377 27.8041 20.0528 28.7196L25.8806 34.5474L39.9467 20.4808C40.8623 19.5657 42.3463 19.5657 43.2614 20.4808ZM60 30C60 46.5825 46.5802 60 30 60C13.4175 60 0 46.5802 0 30C0 13.4175 13.4198 0 30 0C46.5825 0 60 13.4198 60 30ZM55.3125 30C55.3125 16.0085 43.9897 4.6875 30 4.6875C16.0085 4.6875 4.6875 16.0103 4.6875 30C4.6875 43.9915 16.0103 55.3125 30 55.3125C43.9915 55.3125 55.3125 43.9897 55.3125 30Z" fill="#023DFE" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 761 B

View File

@ -0,0 +1,21 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_6702_36698)">
<circle cx="20" cy="20" r="15" fill="#F4F4F4"/>
<path d="M26.4 13.1094H23.7333V12.582C23.7333 11.7097 23.0156 11 22.1333 11H17.8667C16.9844 11 16.2667 11.7097 16.2667 12.582V13.1094H13.6C12.7178 13.1094 12 13.8191 12 14.6914C12 15.392 12.4631 15.9873 13.1024 16.1947L14.0537 27.5493C14.1222 28.3628 14.8226 29 15.6481 29H24.3519C25.1775 29 25.8778 28.3628 25.9464 27.5491L26.8976 16.1947C27.5369 15.9873 28 15.392 28 14.6914C28 13.8191 27.2822 13.1094 26.4 13.1094ZM17.3333 12.582C17.3333 12.2913 17.5726 12.0547 17.8667 12.0547H22.1333C22.4274 12.0547 22.6667 12.2913 22.6667 12.582V13.1094H17.3333V12.582ZM24.8833 27.4618C24.8605 27.7329 24.6271 27.9453 24.3519 27.9453H15.6481C15.373 27.9453 15.1395 27.7329 15.1167 27.462L14.1793 16.2734H25.8207L24.8833 27.4618ZM26.4 15.2188H13.6C13.3059 15.2188 13.0667 14.9822 13.0667 14.6914C13.0667 14.4006 13.3059 14.1641 13.6 14.1641H26.4C26.6941 14.1641 26.9333 14.4006 26.9333 14.6914C26.9333 14.9822 26.6941 15.2188 26.4 15.2188Z" fill="#999999"/>
<path d="M17.8656 26.3307L17.3323 17.8229C17.314 17.5322 17.0596 17.3111 16.767 17.3292C16.473 17.3472 16.2494 17.5974 16.2676 17.8881L16.801 26.396C16.8185 26.6756 17.0533 26.8907 17.3328 26.8907C17.6416 26.8907 17.8846 26.6335 17.8656 26.3307Z" fill="#999999"/>
<path d="M20.0001 17.3281C19.7056 17.3281 19.4668 17.5642 19.4668 17.8555V26.3633C19.4668 26.6545 19.7056 26.8906 20.0001 26.8906C20.2947 26.8906 20.5335 26.6545 20.5335 26.3633V17.8555C20.5335 17.5642 20.2947 17.3281 20.0001 17.3281Z" fill="#999999"/>
<path d="M23.233 17.3292C22.9396 17.3111 22.6859 17.5321 22.6677 17.8229L22.1343 26.3307C22.1162 26.6213 22.3397 26.8716 22.6337 26.8896C22.9278 26.9076 23.1808 26.6865 23.199 26.3959L23.7323 17.8881C23.7505 17.5974 23.527 17.3472 23.233 17.3292Z" fill="#999999"/>
</g>
<defs>
<filter id="filter0_d_6702_36698" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6702_36698"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6702_36698" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,25 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_6702_36714)">
<circle cx="20" cy="20" r="15" fill="#F4F4F4"/>
<path d="M21.1979 12.3395L18.26 15.2772C17.7228 15.8146 17.3477 16.4536 17.1345 17.1328C16.437 17.3525 15.798 17.7393 15.2772 18.26L12.3395 21.1979C10.5536 22.9837 10.5534 25.8744 12.3395 27.6605C14.1253 29.4464 17.0161 29.4466 18.8022 27.6605L21.7399 24.7227C22.2773 24.1855 22.6522 23.5465 22.8655 22.8671C23.5631 22.6475 24.202 22.2607 24.7227 21.7399L27.6605 18.8022C29.4464 17.0162 29.4467 14.1256 27.6605 12.3395C25.8746 10.5536 22.984 10.5534 21.1979 12.3395ZM17.266 20.2484C17.4886 20.7914 17.8199 21.2998 18.26 21.7399C18.6877 22.1674 19.1965 22.5054 19.7513 22.7343L16.8137 25.6721C16.1284 26.3572 15.0133 26.3574 14.328 25.6721C13.6427 24.9867 13.6427 23.8717 14.328 23.1864L17.2657 20.2485C17.2659 20.2485 17.2659 20.2485 17.266 20.2484ZM21.2428 24.2256L18.3049 27.1633C16.7939 28.6745 14.3479 28.6747 12.8366 27.1633C11.3254 25.6523 11.3253 23.2062 12.8366 21.695L15.7744 18.7571C16.1166 18.4149 16.5191 18.1412 16.9579 17.9488C16.8924 18.4845 16.9223 19.014 17.0364 19.5199C16.9419 19.5905 16.8524 19.6676 16.7686 19.7514L13.8309 22.6892C12.8715 23.6487 12.8715 25.2097 13.8309 26.1691C14.7903 27.1285 16.3513 27.1285 17.3108 26.1691L20.2485 23.2313C21.2089 22.2709 21.209 20.7119 20.2485 19.7514C19.7355 19.2383 19.6105 18.4924 19.8554 17.8663C20.3734 18.059 20.8488 18.3631 21.2428 18.7571C22.7504 20.2647 22.7505 22.7179 21.2428 24.2256ZM27.1633 18.3049L24.2256 21.2428C23.8834 21.585 23.4809 21.8587 23.0421 22.0511C23.1076 21.5154 23.0777 20.986 22.9637 20.4801C23.058 20.4095 23.1477 20.3323 23.2313 20.2485L26.1692 17.3108C27.1286 16.3514 27.1286 14.7903 26.1692 13.8309C25.3077 12.9695 23.9604 12.8807 22.9987 13.5684C22.8408 13.6813 22.8044 13.9009 22.9173 14.0588C23.0301 14.2168 23.2497 14.2533 23.4077 14.1403C24.1093 13.6386 25.0615 13.7174 25.6721 14.328C26.3574 15.0133 26.3574 16.1283 25.6721 16.8137L22.7342 19.7514C22.7342 19.7514 22.7342 19.7514 22.7341 19.7515C22.5113 19.2085 22.1801 18.7002 21.7399 18.26C21.3124 17.8325 20.8035 17.4945 20.2487 17.2656L21.4465 16.0678C21.5837 15.9306 21.5837 15.708 21.4465 15.5707C21.3091 15.4335 21.0867 15.4335 20.9493 15.5707L19.7514 16.7686C18.7911 17.729 18.7909 19.2879 19.7514 20.2485C20.2645 20.7615 20.3894 21.5076 20.1446 22.1337C19.6266 21.941 19.1511 21.6368 18.7571 21.2427C17.2497 19.7352 17.2495 17.282 18.7571 15.7744L21.695 12.8366C23.2061 11.3254 25.6522 11.3252 27.1633 12.8366C28.6745 14.3476 28.6747 16.7937 27.1633 18.3049Z" fill="#999999"/>
<path d="M22.5443 14.8262C22.5443 15.0204 22.3869 15.1777 22.1929 15.1777C21.9987 15.1777 21.8413 15.0204 21.8413 14.8262C21.8413 14.632 21.9987 14.4746 22.1929 14.4746C22.3869 14.4746 22.5443 14.632 22.5443 14.8262Z" fill="#999999"/>
<path d="M15.7755 15.774C15.9128 15.6368 15.9128 15.4142 15.7755 15.2769L14.2841 13.7855C14.1468 13.6483 13.9243 13.6483 13.787 13.7855C13.6498 13.9228 13.6498 14.1455 13.787 14.2828L15.2784 15.7742C15.4158 15.9114 15.6383 15.9114 15.7755 15.774Z" fill="#999999"/>
<path d="M12.3378 16.9062C12.1437 16.9062 11.9863 17.0636 11.9863 17.2577C11.9863 17.4519 12.1437 17.6092 12.3378 17.6092H14.447C14.641 17.6092 14.7984 17.4519 14.7984 17.2577C14.7984 17.0636 14.641 16.9062 14.447 16.9062H12.3378Z" fill="#999999"/>
<path d="M16.9062 12.2314V14.3405C16.9062 14.5346 17.0636 14.6921 17.2577 14.6921C17.4519 14.6921 17.6092 14.5346 17.6092 14.3405V12.2314C17.6092 12.0373 17.4519 11.8799 17.2577 11.8799C17.0636 11.8799 16.9062 12.0373 16.9062 12.2314Z" fill="#999999"/>
<path d="M24.227 24.2259C24.0897 24.3631 24.0897 24.5857 24.227 24.7231L25.7184 26.2145C25.7871 26.2831 25.877 26.3175 25.967 26.3175C26.2772 26.3175 26.4377 25.9397 26.2155 25.7173L24.7242 24.2259C24.5868 24.0887 24.3643 24.0887 24.227 24.2259Z" fill="#999999"/>
<path d="M23.0605 27.7673V25.6581C23.0605 25.464 22.903 25.3066 22.709 25.3066C22.5148 25.3066 22.3574 25.464 22.3574 25.6581V27.7673C22.3574 27.9614 22.5148 28.1187 22.709 28.1187C22.903 28.1187 23.0605 27.9614 23.0605 27.7673Z" fill="#999999"/>
<path d="M27.7693 23.0586C27.9633 23.0586 28.1207 22.9011 28.1207 22.707C28.1207 22.5128 27.9633 22.3555 27.7693 22.3555H25.66C25.466 22.3555 25.3086 22.5128 25.3086 22.707C25.3086 22.9011 25.466 23.0586 25.66 23.0586H27.7693Z" fill="#999999"/>
</g>
<defs>
<filter id="filter0_d_6702_36714" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6702_36714"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6702_36714" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,3 @@
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M43.2614 20.4808C44.1769 21.3963 44.1769 22.8804 43.2614 23.7955L27.5381 39.5192C26.6226 40.4343 25.139 40.4343 24.2235 39.5192L16.7386 32.0338C15.8231 31.1188 15.8231 29.6347 16.7386 28.7196C17.6537 27.8041 19.1377 27.8041 20.0528 28.7196L25.8806 34.5474L39.9467 20.4808C40.8623 19.5657 42.3463 19.5657 43.2614 20.4808ZM60 30C60 46.5825 46.5802 60 30 60C13.4175 60 0 46.5802 0 30C0 13.4175 13.4198 0 30 0C46.5825 0 60 13.4198 60 30ZM55.3125 30C55.3125 16.0085 43.9897 4.6875 30 4.6875C16.0085 4.6875 4.6875 16.0103 4.6875 30C4.6875 43.9915 16.0103 55.3125 30 55.3125C43.9915 55.3125 55.3125 43.9897 55.3125 30Z" fill="#023DFE" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 761 B

1
firebase.json Normal file
View File

@ -0,0 +1 @@
{"flutter":{"platforms":{"android":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:android:2bc36fbe82994a3e0c7e6d","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:14346b200780dc760c7e6d","uploadDebugSymbols":true,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:14346b200780dc760c7e6d","uploadDebugSymbols":true,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"test2-8a3d2","configurations":{"android":"1:427332280600:android:2bc36fbe82994a3e0c7e6d","ios":"1:427332280600:ios:14346b200780dc760c7e6d","macos":"1:427332280600:ios:14346b200780dc760c7e6d","web":"1:427332280600:web:ad50516a87a35a1a0c7e6d","windows":"1:427332280600:web:f7a25537ccd5a7bd0c7e6d"}}}}}}

View File

@ -15,6 +15,7 @@
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
E44A9405B1EB1B638DD05A58 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ABF0EC746A2D686A0ED574F /* Pods_RunnerTests.framework */; };
F2A3345EC3021060731668D3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B14AB50E8716720E10D074BD /* GoogleService-Info.plist */; };
FF49F60EC38658783D8D66DA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AFAE479A87ECDEBD5D6EB30 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
@ -64,6 +65,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B14AB50E8716720E10D074BD /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
D3AD250AADBF93406007C9EB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -138,6 +140,7 @@
331C8082294A63A400263BE5 /* RunnerTests */,
1454C118FFCECEEDF59152D2 /* Pods */,
20A3C64D2B1CFED5A81C3251 /* Frameworks */,
B14AB50E8716720E10D074BD /* GoogleService-Info.plist */,
);
sourceTree = "<group>";
};
@ -199,6 +202,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
33590C9CD073D3D5EBA02CDE /* [CP] Embed Pods Frameworks */,
7A77858F6F15CB76D2D3A872 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */,
);
buildRules = (
);
@ -264,6 +268,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
F2A3345EC3021060731668D3 /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -303,6 +308,24 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
7A77858F6F15CB76D2D3A872 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\"";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:$HOME/.pub-cache/bin\"\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PODS_ROOT/FirebaseCrashlytics/upload-symbols\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw</string>
<key>GCM_SENDER_ID</key>
<string>427332280600</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.example.syncrowWeb</string>
<key>PROJECT_ID</key>
<string>test2-8a3d2</string>
<key>STORAGE_BUCKET</key>
<string>test2-8a3d2.firebasestorage.app</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:427332280600:ios:14346b200780dc760c7e6d</string>
<key>DATABASE_URL</key>
<string>https://test2-8a3d2-default-rtdb.firebaseio.com</string>
</dict>
</plist>

View File

@ -1,35 +1,38 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DialogTextfieldDropdown extends StatefulWidget {
final List<String> items;
final ValueChanged<String> onSelected;
final String? initialValue;
class TagDialogTextfieldDropdown extends StatefulWidget {
final List<Tag> items;
final ValueChanged<Tag> onSelected;
final Tag? initialValue;
final String product;
const DialogTextfieldDropdown({
const TagDialogTextfieldDropdown({
Key? key,
required this.items,
required this.onSelected,
this.initialValue,
required this.product,
}) : super(key: key);
@override
_DialogTextfieldDropdownState createState() =>
_DialogTextfieldDropdownState();
_DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState();
}
class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
bool _isOpen = false;
OverlayEntry? _overlayEntry;
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
List<String> _filteredItems = [];
List<Tag> _filteredItems = [];
@override
void initState() {
super.initState();
_controller.text = widget.initialValue ?? '';
_filteredItems = List.from(widget.items);
_controller.text = widget.initialValue?.tag ?? '';
_filterItems();
_focusNode.addListener(() {
if (!_focusNode.hasFocus) {
@ -38,6 +41,12 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
});
}
void _filterItems() {
setState(() {
_filteredItems = widget.items.where((tag) => tag.product?.uuid == widget.product).toList();
});
}
void _toggleDropdown() {
if (_isOpen) {
_closeDropdown();
@ -87,7 +96,7 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
shrinkWrap: true,
itemCount: _filteredItems.length,
itemBuilder: (context, index) {
final item = _filteredItems[index];
final tag = _filteredItems[index];
return Container(
decoration: const BoxDecoration(
@ -99,19 +108,16 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
),
),
child: ListTile(
title: Text(item,
title: Text(tag.tag ?? '',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager
.textPrimaryColor)),
?.copyWith(color: ColorsManager.textPrimaryColor)),
onTap: () {
_controller.text = item;
widget.onSelected(item);
_controller.text = tag.tag ?? '';
widget.onSelected(tag);
setState(() {
_filteredItems
.remove(item); // Remove selected item
_filteredItems.remove(tag);
});
_closeDropdown();
},
@ -150,11 +156,14 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
controller: _controller,
focusNode: _focusNode,
onFieldSubmitted: (value) {
widget.onSelected(value);
final selectedTag = _filteredItems.firstWhere((tag) => tag.tag == value,
orElse: () => Tag(tag: value));
widget.onSelected(selectedTag);
_closeDropdown();
},
onTapOutside: (event) {
widget.onSelected(_controller.text);
widget.onSelected(_filteredItems.firstWhere((tag) => tag.tag == _controller.text,
orElse: () => Tag(tag: _controller.text)));
_closeDropdown();
},
style: Theme.of(context).textTheme.bodyMedium,

View File

@ -56,24 +56,6 @@ class CustomExpansionTileState extends State<CustomExpansionTile> {
children: [
Row(
children: [
// Checkbox with independent state management
Checkbox(
value: false,
onChanged: (bool? value) {
setState(() {});
},
side: WidgetStateBorderSide.resolveWith((states) {
return const BorderSide(color: ColorsManager.grayBorder);
}),
fillColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return ColorsManager.grayBorder;
} else {
return ColorsManager.checkBoxFillColor;
}
}),
checkColor: ColorsManager.whiteColors,
),
// Expand/collapse icon, now wrapped in a GestureDetector for specific onTap
if (widget.children != null && widget.children!.isNotEmpty)
GestureDetector(
@ -84,7 +66,9 @@ class CustomExpansionTileState extends State<CustomExpansionTile> {
});
},
child: Icon(
_isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right,
_isExpanded
? Icons.keyboard_arrow_down
: Icons.keyboard_arrow_right,
color: ColorsManager.lightGrayColor,
size: 16.0, // Adjusted size for better alignment
),
@ -101,8 +85,10 @@ class CustomExpansionTileState extends State<CustomExpansionTile> {
_capitalizeFirstLetter(widget.title),
style: TextStyle(
color: widget.isSelected
? ColorsManager.blackColor // Change color to black when selected
: ColorsManager.lightGrayColor, // Gray when not selected
? ColorsManager
.blackColor // Change color to black when selected
: ColorsManager
.lightGrayColor, // Gray when not selected
fontWeight: FontWeight.w400,
),
),
@ -111,9 +97,11 @@ class CustomExpansionTileState extends State<CustomExpansionTile> {
],
),
// The expanded section (children) that shows when the tile is expanded
if (_isExpanded && widget.children != null && widget.children!.isNotEmpty)
if (_isExpanded &&
widget.children != null &&
widget.children!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(left: 48.0), // Indented children
padding: const EdgeInsets.only(left: 24.0), // Indented children
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: widget.children!,

View File

@ -0,0 +1,93 @@
// File generated by FlutterFire CLI.
// ignore_for_file: type=lint
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptionsDev {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
case TargetPlatform.windows:
return windows;
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyCVEvKsJYzhWDFM-9Od68FE0nPpP933st0',
appId: '1:427332280600:web:ad50516a87a35a1a0c7e6d',
messagingSenderId: '427332280600',
projectId: 'test2-8a3d2',
authDomain: 'test2-8a3d2.firebaseapp.com',
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
storageBucket: 'test2-8a3d2.firebasestorage.app',
measurementId: 'G-Z1RTTTV5H9',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyA5qOErxdm0zJmoHIB0TixfebYEsNRpwV0',
appId: '1:427332280600:android:2bc36fbe82994a3e0c7e6d',
messagingSenderId: '427332280600',
projectId: 'test2-8a3d2',
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
storageBucket: 'test2-8a3d2.firebasestorage.app',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw',
appId: '1:427332280600:ios:14346b200780dc760c7e6d',
messagingSenderId: '427332280600',
projectId: 'test2-8a3d2',
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
storageBucket: 'test2-8a3d2.firebasestorage.app',
iosBundleId: 'com.example.syncrowWeb',
);
static const FirebaseOptions macos = FirebaseOptions(
apiKey: 'AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw',
appId: '1:427332280600:ios:14346b200780dc760c7e6d',
messagingSenderId: '427332280600',
projectId: 'test2-8a3d2',
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
storageBucket: 'test2-8a3d2.firebasestorage.app',
iosBundleId: 'com.example.syncrowWeb',
);
static const FirebaseOptions windows = FirebaseOptions(
apiKey: 'AIzaSyDizKjPC5rdkEjDxwXjM-RU5unB0Ziq3iw',
appId: '1:427332280600:web:f7a25537ccd5a7bd0c7e6d',
messagingSenderId: '427332280600',
projectId: 'test2-8a3d2',
authDomain: 'test2-8a3d2.firebaseapp.com',
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
storageBucket: 'test2-8a3d2.firebasestorage.app',
measurementId: 'G-4LFVXEXWKY',
);
}

View File

@ -0,0 +1,77 @@
// File generated by FlutterFire CLI.
// ignore_for_file: type=lint
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptionsStaging {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for macos - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyDP9GpYfLE8gHTj3kZ1hW8fx_FkJqOqSQk',
appId: '1:786692570726:android:0ef7079c2b978d4417b7a7',
messagingSenderId: '786692570726',
projectId: 'syncrow-staging',
databaseURL: 'https://syncrow-staging-default-rtdb.firebaseio.com',
storageBucket: 'syncrow-staging.appspot.com',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyAWlRiuJ75FMlf2_UDdri1voWKvkaSHtRg',
appId: '1:786692570726:ios:455a6fcff77e130f17b7a7',
messagingSenderId: '786692570726',
projectId: 'syncrow-staging',
databaseURL: 'https://syncrow-staging-default-rtdb.firebaseio.com',
storageBucket: 'syncrow-staging.appspot.com',
iosBundleId: 'com.example.syncrow.app',
);
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyDyGaQ3sZhb4meaY6sGke-YglhdhJ2is8Q',
appId: '1:786692570726:web:93c931e6701797b317b7a7',
messagingSenderId: '786692570726',
projectId: 'syncrow-staging',
authDomain: 'syncrow-staging.firebaseapp.com',
databaseURL: 'https://syncrow-staging-default-rtdb.firebaseio.com',
storageBucket: 'syncrow-staging.appspot.com',
measurementId: 'G-CZ3J3G6LMQ',
);
}

View File

@ -1,8 +1,10 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options_prod.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
@ -18,9 +20,13 @@ import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async {
try {
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
const environment =
String.fromEnvironment('FLAVOR', defaultValue: 'production');
await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptionsStaging.currentPlatform,
);
initialSetup();
} catch (_) {}
runApp(MyApp());
@ -49,7 +55,8 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),

84
lib/main_dev.dart Normal file
View File

@ -0,0 +1,84 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options_dev.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async {
try {
const environment =
String.fromEnvironment('FLAVOR', defaultValue: 'development');
await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptionsDev.currentPlatform,
);
initialSetup();
} catch (_) {}
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({
super.key,
});
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(),
redirect: (context, state) async {
String checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc()..add(InitialEvent()),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
theme: myTheme,
routerConfig: _router,
));
}
}

View File

@ -8,9 +8,6 @@ import 'package:syncrow_web/pages/common/hour_picker_dialog.dart';
import 'package:syncrow_web/services/access_mang_api.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
class AccessBloc extends Bloc<AccessEvent, AccessState> {

View File

@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/pages/common/custom_table.dart';
@ -18,6 +17,7 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
@ -33,11 +33,9 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
return WebScaffold(
enableMenuSidebar: false,
appBarTitle: FittedBox(
child: Text(
'Access Management',
style: Theme.of(context).textTheme.headlineLarge,
),
appBarTitle: Text(
'Access Management',
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocProvider(

View File

@ -15,7 +15,6 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/services/auth_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {

View File

@ -8,6 +8,8 @@ class ForgetPasswordPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const ResponsiveLayout(
desktopBody: ForgetPasswordWebPage(), mobileBody: ForgetPasswordWebPage());
tablet: ForgetPasswordWebPage(),
desktopBody: ForgetPasswordWebPage(),
mobileBody: ForgetPasswordWebPage());
}
}

View File

@ -9,6 +9,8 @@ class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const ResponsiveLayout(
desktopBody: LoginWebPage(), mobileBody: LoginWebPage());
tablet: LoginWebPage(),
desktopBody: LoginWebPage(),
mobileBody: LoginWebPage());
}
}

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
@ -19,6 +20,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
on<AcControlEvent>(_onAcControl);
on<AcBatchControlEvent>(_onAcBatchControl);
on<AcFactoryResetEvent>(_onFactoryReset);
on<AcStatusUpdated>(_onAcStatusUpdated);
}
FutureOr<void> _onFetchAcStatus(
@ -28,12 +30,64 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(ACStatusLoaded(deviceStatus));
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
}
_listenToChanges(deviceId) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return;
if (_timer != null) {
await Future.delayed(const Duration(seconds: 1));
}
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(AcStatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) {
deviceStatus = event.deviceStatus;
emit(ACStatusLoaded(deviceStatus));
}
// Future<void> testFirebaseConnection() async {
// // Reference to a test node in your database
// final testRef = FirebaseDatabase.instance.ref("test");
// // Write a test value
// await testRef.set("Hello, Firebase!");
// // Listen for changes on the test node
// testRef.onValue.listen((DatabaseEvent event) {
// final data = event.snapshot.value;
// print("Data from Firebase: $data");
// // If you see "Hello, Firebase!" printed in your console, it means the connection works.
// });
// }
FutureOr<void> _onAcControl(
AcControlEvent event, Emitter<AcsState> emit) async {
final oldValue = _getValueByCode(event.code);

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
sealed class AcsEvent extends Equatable {
@ -7,6 +8,7 @@ sealed class AcsEvent extends Equatable {
@override
List<Object> get props => [];
}
class AcUpdated extends AcsEvent {}
class AcFetchDeviceStatusEvent extends AcsEvent {
final String deviceId;
@ -16,7 +18,10 @@ class AcFetchDeviceStatusEvent extends AcsEvent {
@override
List<Object> get props => [deviceId];
}
class AcStatusUpdated extends AcsEvent {
final AcStatusModel deviceStatus;
AcStatusUpdated(this.deviceStatus);
}
class AcFetchBatchStatusEvent extends AcsEvent {
final List<String> devicesIds;

View File

@ -24,7 +24,8 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => AcBloc(deviceId: device.uuid!)..add(AcFetchDeviceStatusEvent(device.uuid!)),
create: (context) => AcBloc(deviceId: device.uuid!)
..add(AcFetchDeviceStatusEvent(device.uuid!)),
child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) {
if (state is ACStatusLoaded) {
@ -98,7 +99,8 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
),
Text(
'h',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor),
),
Text(
'30',
@ -107,7 +109,9 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
fontWeight: FontWeight.bold,
),
),
Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)),
Text('m',
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor)),
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {},

View File

@ -5,9 +5,6 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
part 'device_managment_event.dart';
part 'device_managment_state.dart';

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
@ -10,6 +9,7 @@ import 'package:syncrow_web/pages/routines/view/routines_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
@ -25,13 +25,12 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
),
],
child: WebScaffold(
appBarTitle: FittedBox(
child: Text(
'Device Management',
style: Theme.of(context).textTheme.headlineLarge,
),
appBarTitle: Text(
'Device Management',
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
centerBody: BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) {
centerBody:
BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
@ -48,8 +47,11 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
child: Text(
'Devices',
style: context.textTheme.titleMedium?.copyWith(
color: !state.routineTab ? ColorsManager.whiteColors : ColorsManager.grayColor,
fontWeight: !state.routineTab ? FontWeight.w700 : FontWeight.w400,
color: !state.routineTab
? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight:
!state.routineTab ? FontWeight.w700 : FontWeight.w400,
),
),
),
@ -58,13 +60,18 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
backgroundColor: null,
),
onPressed: () {
context.read<RoutineBloc>().add(const TriggerSwitchTabsEvent(isRoutineTab: true));
context
.read<RoutineBloc>()
.add(const TriggerSwitchTabsEvent(isRoutineTab: true));
},
child: Text(
'Routines',
style: context.textTheme.titleMedium?.copyWith(
color: state.routineTab ? ColorsManager.whiteColors : ColorsManager.grayColor,
fontWeight: state.routineTab ? FontWeight.w700 : FontWeight.w400,
color: state.routineTab
? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight:
state.routineTab ? FontWeight.w700 : FontWeight.w400,
),
),
),
@ -72,7 +79,8 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
);
}),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) {
scaffoldBody:
BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) {
if (state.routineTab) {
return const RoutinesView();
}
@ -87,7 +95,8 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
} else if (deviceState is DeviceManagementLoaded) {
return DeviceManagementBody(devices: deviceState.devices);
} else if (deviceState is DeviceManagementFiltered) {
return DeviceManagementBody(devices: deviceState.filteredDevices);
return DeviceManagementBody(
devices: deviceState.filteredDevices);
} else {
return const Center(child: Text('Error fetching Devices'));
}

View File

@ -8,7 +8,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -95,7 +94,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
const DeviceSearchFilters(),
const SizedBox(height: 12),
Container(
height: 45,
// height: 45,
width: 125,
decoration: containerDecoration,
child: Center(

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
@ -22,42 +23,55 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
on<ShowCeilingDescriptionEvent>(_showDescription);
on<BackToCeilingGridViewEvent>(_backToGridView);
on<CeilingFactoryResetEvent>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
void _fetchCeilingSensorStatus(
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingInitialState());
try {
var response = await DevicesManagementApi().getDeviceStatus(event.deviceId);
var response =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
// _listenToChanges();
_listenToChanges(event.deviceId);
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
return;
}
}
// _listenToChanges() {
// try {
// DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
// Stream<DatabaseEvent> stream = ref.onValue;
_listenToChanges(deviceId) {
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 = [];
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
// usersMap['status'].forEach((element) {
// statusList.add(StatusModel(code: element['code'], value: element['value']));
// });
List<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
// deviceStatus = WallSensorModel.fromJson(statusList);
// add(WallSensorUpdatedEvent());
// });
// } catch (_) {}
// }
deviceStatus = CeilingSensorModel.fromJson(statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _changeValue(CeilingChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
void _onStatusUpdated(StatusUpdated event, Emitter<CeilingSensorState> emit) {
deviceStatus = event.deviceStatus;
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
}
void _changeValue(
CeilingChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
if (event.code == 'sensitivity') {
deviceStatus.sensitivity = event.value;
@ -122,7 +136,8 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
try {
late bool response;
if (isBatch) {
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
@ -143,8 +158,8 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
});
}
FutureOr<void> _getDeviceReports(
GetCeilingDeviceReportsEvent event, Emitter<CeilingSensorState> emit) async {
FutureOr<void> _getDeviceReports(GetCeilingDeviceReportsEvent event,
Emitter<CeilingSensorState> emit) async {
if (event.code.isEmpty) {
emit(ShowCeilingDescriptionState(description: reportString));
return;
@ -155,7 +170,8 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
try {
// await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString())
await DevicesManagementApi.getDeviceReports(deviceId, event.code).then((value) {
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
.then((value) {
emit(CeilingReportsState(deviceReport: value));
});
} catch (e) {
@ -165,19 +181,23 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
}
}
void _showDescription(ShowCeilingDescriptionEvent event, Emitter<CeilingSensorState> emit) {
void _showDescription(
ShowCeilingDescriptionEvent event, Emitter<CeilingSensorState> emit) {
emit(ShowCeilingDescriptionState(description: event.description));
}
void _backToGridView(BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
void _backToGridView(
BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
}
FutureOr<void> _fetchCeilingSensorBatchControl(
CeilingFetchDeviceStatusEvent event, Emitter<CeilingSensorState> emit) async {
CeilingFetchDeviceStatusEvent event,
Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingInitialState());
try {
var response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
var response =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
} catch (e) {

View File

@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
abstract class CeilingSensorEvent extends Equatable {
const CeilingSensorEvent();
@ -83,3 +84,12 @@ class CeilingFactoryResetEvent extends CeilingSensorEvent {
@override
List<Object> get props => [devicesId, factoryResetModel];
}
class StatusUpdated extends CeilingSensorEvent {
final CeilingSensorModel deviceStatus;
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
@ -16,6 +17,7 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
on<CurtainControl>(_onCurtainControl);
on<CurtainBatchControl>(_onCurtainBatchControl);
on<CurtainFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
FutureOr<void> _onFetchDeviceStatus(
@ -24,7 +26,7 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId);
deviceStatus = _checkStatus(status.status[0].value);
emit(CurtainStatusLoaded(deviceStatus));
@ -33,6 +35,48 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
}
}
void _listenToChanges(String deviceId) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) {
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
List<Status> statusList = [];
if (data['status'] != null) {
for (var element in data['status']) {
statusList.add(
Status(
code: element['code'].toString(),
value: element['value'].toString(),
),
);
}
}
if (statusList.isNotEmpty) {
bool newStatus = _checkStatus(statusList[0].value);
if (newStatus != deviceStatus) {
deviceStatus = newStatus;
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
}
}
});
} catch (e) {
emit(CurtainError('Failed to listen to changes: $e'));
}
}
void _onStatusUpdated(StatusUpdated event, Emitter<CurtainState> emit) {
emit(CurtainStatusLoading());
deviceStatus = event.deviceStatus;
emit(CurtainStatusLoaded(deviceStatus));
}
FutureOr<void> _onCurtainControl(
CurtainControl event, Emitter<CurtainState> emit) async {
final oldValue = deviceStatus;

View File

@ -60,3 +60,7 @@ class CurtainFactoryReset extends CurtainEvent {
@override
List<Object> get props => [deviceId, factoryReset];
}
class StatusUpdated extends CurtainEvent {
final bool deviceStatus;
const StatusUpdated(this.deviceStatus);
}

View File

@ -1,6 +1,7 @@
// ignore_for_file: invalid_use_of_visible_for_testing_member
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_event.dart';
@ -18,6 +19,39 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
//on<DoorLockControl>(_onDoorLockControl);
on<UpdateLockEvent>(_updateLock);
on<DoorLockFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
_listenToChanges(deviceId) {
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<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
DoorLockStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(StatusUpdated event, Emitter<DoorLockState> emit) {
emit(DoorLockStatusLoading());
deviceStatus = event.deviceStatus;
emit(DoorLockStatusLoaded(deviceStatus));
}
FutureOr<void> _onFetchDeviceStatus(
@ -28,6 +62,8 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
DoorLockStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(DoorLockStatusLoaded(deviceStatus));
} catch (e) {
emit(DoorLockControlError(e.toString()));

View File

@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart';
sealed class DoorLockEvent extends Equatable {
const DoorLockEvent();
@ -51,3 +52,10 @@ class DoorLockFactoryReset extends DoorLockEvent {
@override
List<Object> get props => [deviceId, factoryReset];
}
class StatusUpdated extends DoorLockEvent {
final DoorLockStatusModel deviceStatus;
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
@ -39,31 +40,68 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
on<GarageDoorFetchBatchStatusEvent>(_onFetchBatchStatus);
on<GarageDoorFactoryResetEvent>(_onFactoryReset);
on<EditGarageDoorScheduleEvent>(_onEditSchedule);
on<StatusUpdated>(_onStatusUpdated);
}
_listenToChanges(deviceId) {
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<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
GarageDoorStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _fetchGarageDoorStatus(GarageDoorInitialEvent event, Emitter<GarageDoorState> emit) async {
void _onStatusUpdated(StatusUpdated event, Emitter<GarageDoorState> emit) {
deviceStatus = event.deviceStatus;
emit(GarageDoorLoadedState(status: deviceStatus));
}
void _fetchGarageDoorStatus(
GarageDoorInitialEvent event, Emitter<GarageDoorState> emit) async {
emit(GarageDoorLoadingState());
try {
var response = await DevicesManagementApi().getDeviceStatus(event.deviceId);
var response =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status);
_listenToChanges(deviceId);
emit(GarageDoorLoadedState(status: deviceStatus));
} catch (e) {
emit(GarageDoorErrorState(message: e.toString()));
}
}
Future<void> _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event,
Emitter<GarageDoorState> emit) async {
emit(GarageDoorLoadingState());
try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status);
final status =
await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus =
GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status);
emit(GarageDoorBatchStatusLoaded(deviceStatus));
} catch (e) {
emit(GarageDoorBatchControlError(e.toString()));
}
}
Future<void> _addSchedule(AddGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _addSchedule(
AddGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
try {
ScheduleEntry newSchedule = ScheduleEntry(
category: event.category,
@ -71,9 +109,11 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
function: Status(code: 'switch_1', value: event.functionOn),
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
);
bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId);
bool success =
await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId);
if (success) {
add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'));
add(FetchGarageDoorSchedulesEvent(
deviceId: deviceId, category: 'switch_1'));
} else {
emit(GarageDoorLoadedState(status: deviceStatus));
}
@ -82,16 +122,19 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
}
}
void _onUpdateCountdownAlarm(UpdateCountdownAlarmEvent event, Emitter<GarageDoorState> emit) {
void _onUpdateCountdownAlarm(
UpdateCountdownAlarmEvent event, Emitter<GarageDoorState> emit) {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(
status: currentState.status.copyWith(countdownAlarm: event.countdownAlarm),
status:
currentState.status.copyWith(countdownAlarm: event.countdownAlarm),
));
}
}
void _onUpdateTrTimeCon(UpdateTrTimeConEvent event, Emitter<GarageDoorState> emit) {
void _onUpdateTrTimeCon(
UpdateTrTimeConEvent event, Emitter<GarageDoorState> emit) {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(
@ -100,7 +143,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
}
}
Future<void> _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _updateSchedule(UpdateGarageDoorScheduleEvent event,
Emitter<GarageDoorState> emit) async {
try {
final updatedSchedules = deviceStatus.schedules?.map((schedule) {
if (schedule.scheduleId == event.scheduleId) {
@ -127,12 +171,15 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
}
}
Future<void> _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _deleteSchedule(DeleteGarageDoorScheduleEvent event,
Emitter<GarageDoorState> emit) async {
try {
bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId);
bool success = await DevicesManagementApi()
.deleteScheduleRecord(deviceStatus.uuid, event.scheduleId);
if (success) {
final updatedSchedules =
deviceStatus.schedules?.where((schedule) => schedule.scheduleId != event.scheduleId).toList();
final updatedSchedules = deviceStatus.schedules
?.where((schedule) => schedule.scheduleId != event.scheduleId)
.toList();
deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules);
emit(GarageDoorLoadedState(status: deviceStatus));
} else {
@ -143,11 +190,12 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
}
}
Future<void> _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _fetchSchedules(FetchGarageDoorSchedulesEvent event,
Emitter<GarageDoorState> emit) async {
emit(ScheduleGarageLoadingState());
try {
List<ScheduleModel> schedules =
await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category);
List<ScheduleModel> schedules = await DevicesManagementApi()
.getDeviceSchedules(deviceStatus.uuid, event.category);
deviceStatus = deviceStatus.copyWith(schedules: schedules);
emit(
GarageDoorLoadedState(
@ -165,30 +213,37 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
}
}
Future<void> _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _updateSelectedTime(
UpdateSelectedTimeEvent event, Emitter<GarageDoorState> emit) async {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(selectedTime: event.selectedTime));
}
}
Future<void> _updateSelectedDay(UpdateSelectedDayEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _updateSelectedDay(
UpdateSelectedDayEvent event, Emitter<GarageDoorState> emit) async {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
List<bool> updatedDays = List.from(currentState.selectedDays);
updatedDays[event.dayIndex] = event.isSelected;
emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime));
emit(currentState.copyWith(
selectedDays: updatedDays, selectedTime: currentState.selectedTime));
}
}
Future<void> _updateFunctionOn(UpdateFunctionOnEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _updateFunctionOn(
UpdateFunctionOnEvent event, Emitter<GarageDoorState> emit) async {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(functionOn: event.functionOn, selectedTime: currentState.selectedTime));
emit(currentState.copyWith(
functionOn: event.functionOn,
selectedTime: currentState.selectedTime));
}
}
Future<void> _initializeAddSchedule(InitializeAddScheduleEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _initializeAddSchedule(
InitializeAddScheduleEvent event, Emitter<GarageDoorState> emit) async {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(
@ -200,20 +255,25 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
}
}
Future<void> _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _fetchRecords(
FetchGarageDoorRecordsEvent event, Emitter<GarageDoorState> emit) async {
emit(GarageDoorReportsLoadingState());
try {
final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
final from = DateTime.now()
.subtract(const Duration(days: 30))
.millisecondsSinceEpoch;
final to = DateTime.now().millisecondsSinceEpoch;
final DeviceReport records =
await DevicesManagementApi.getDeviceReportsByDate(event.deviceId, 'switch_1', from.toString(), to.toString());
await DevicesManagementApi.getDeviceReportsByDate(
event.deviceId, 'switch_1', from.toString(), to.toString());
emit(GarageDoorReportsState(deviceReport: records));
} catch (e) {
emit(GarageDoorReportsFailedState(error: e.toString()));
}
}
Future<void> _onBatchControl(GarageDoorBatchControlEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _onBatchControl(
GarageDoorBatchControlEvent event, Emitter<GarageDoorState> emit) async {
final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false;
_updateLocalValue(event.code, event.value);
@ -233,11 +293,13 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
}
}
void _backToGridView(BackToGarageDoorGridViewEvent event, Emitter<GarageDoorState> emit) {
void _backToGridView(
BackToGarageDoorGridViewEvent event, Emitter<GarageDoorState> emit) {
emit(GarageDoorLoadedState(status: deviceStatus));
}
void _handleUpdate(GarageDoorUpdatedEvent event, Emitter<GarageDoorState> emit) {
void _handleUpdate(
GarageDoorUpdatedEvent event, Emitter<GarageDoorState> emit) {
emit(GarageDoorLoadedState(status: deviceStatus));
}
@ -253,9 +315,11 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
late bool status;
await Future.delayed(const Duration(milliseconds: 500));
if (isBatch) {
status = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
status = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else {
status = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value));
status = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!status) {
@ -270,10 +334,12 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
}
}
Future<void> _onFactoryReset(GarageDoorFactoryResetEvent event, Emitter<GarageDoorState> emit) async {
Future<void> _onFactoryReset(
GarageDoorFactoryResetEvent event, Emitter<GarageDoorState> emit) async {
emit(GarageDoorLoadingState());
try {
final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId);
final response = await DevicesManagementApi()
.factoryReset(event.factoryReset, event.deviceId);
if (!response) {
emit(const GarageDoorErrorState(message: 'Failed to reset device'));
} else {
@ -284,34 +350,47 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
}
}
void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter<GarageDoorState> emit) async {
void _increaseDelay(
IncreaseGarageDoorDelayEvent event, Emitter<GarageDoorState> emit) async {
// if (deviceStatus.countdown1 != 0) {
try {
deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10));
deviceStatus = deviceStatus.copyWith(
delay: deviceStatus.delay + Duration(minutes: 10));
emit(GarageDoorLoadedState(status: deviceStatus));
add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1'));
add(GarageDoorControlEvent(
deviceId: event.deviceId,
value: deviceStatus.delay.inSeconds,
code: 'countdown_1'));
} catch (e) {
emit(GarageDoorErrorState(message: e.toString()));
}
// }
}
void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter<GarageDoorState> emit) async {
void _decreaseDelay(
DecreaseGarageDoorDelayEvent event, Emitter<GarageDoorState> emit) async {
// if (deviceStatus.countdown1 != 0) {
try {
if (deviceStatus.delay.inMinutes > 10) {
deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10));
deviceStatus = deviceStatus.copyWith(
delay: deviceStatus.delay - Duration(minutes: 10));
}
emit(GarageDoorLoadedState(status: deviceStatus));
add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1'));
add(GarageDoorControlEvent(
deviceId: event.deviceId,
value: deviceStatus.delay.inSeconds,
code: 'countdown_1'));
} catch (e) {
emit(GarageDoorErrorState(message: e.toString()));
}
//}
}
void _garageDoorControlEvent(GarageDoorControlEvent event, Emitter<GarageDoorState> emit) async {
final oldValue = event.code == 'countdown_1' ? deviceStatus.countdown1 : deviceStatus.switch1;
void _garageDoorControlEvent(
GarageDoorControlEvent event, Emitter<GarageDoorState> emit) async {
final oldValue = event.code == 'countdown_1'
? deviceStatus.countdown1
: deviceStatus.switch1;
_updateLocalValue(event.code, event.value);
emit(GarageDoorLoadedState(status: deviceStatus));
final success = await _runDeBouncer(
@ -327,7 +406,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
}
}
void _revertValue(String code, dynamic oldValue, Emitter<GarageDoorState> emit) {
void _revertValue(
String code, dynamic oldValue, Emitter<GarageDoorState> emit) {
switch (code) {
case 'switch_1':
if (oldValue is bool) {
@ -336,7 +416,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
break;
case 'countdown_1':
if (oldValue is int) {
deviceStatus = deviceStatus.copyWith(countdown1: oldValue, delay: Duration(seconds: oldValue));
deviceStatus = deviceStatus.copyWith(
countdown1: oldValue, delay: Duration(seconds: oldValue));
}
break;
// Add other cases if needed
@ -358,7 +439,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
break;
case 'countdown_1':
if (value is int) {
deviceStatus = deviceStatus.copyWith(countdown1: value, delay: Duration(seconds: value));
deviceStatus = deviceStatus.copyWith(
countdown1: value, delay: Duration(seconds: value));
}
break;
case 'countdown_alarm':
@ -401,7 +483,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
return super.close();
}
FutureOr<void> _onEditSchedule(EditGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
FutureOr<void> _onEditSchedule(
EditGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
try {
ScheduleEntry newSchedule = ScheduleEntry(
scheduleId: event.scheduleId,
@ -410,9 +493,11 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
function: Status(code: 'switch_1', value: event.functionOn),
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
);
bool success = await DevicesManagementApi().editScheduleRecord(deviceId, newSchedule);
bool success = await DevicesManagementApi()
.editScheduleRecord(deviceId, newSchedule);
if (success) {
add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'));
add(FetchGarageDoorSchedulesEvent(
deviceId: deviceId, category: 'switch_1'));
} else {
emit(GarageDoorLoadedState(status: deviceStatus));
}

View File

@ -3,6 +3,7 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
abstract class GarageDoorEvent extends Equatable {
const GarageDoorEvent();
@ -25,7 +26,8 @@ class GarageDoorControlEvent extends GarageDoorEvent {
final dynamic value;
final String code;
const GarageDoorControlEvent({required this.deviceId, required this.value, required this.code});
const GarageDoorControlEvent(
{required this.deviceId, required this.value, required this.code});
@override
List<Object?> get props => [deviceId, value];
@ -121,7 +123,8 @@ class FetchGarageDoorRecordsEvent extends GarageDoorEvent {
final String deviceId;
final String code;
const FetchGarageDoorRecordsEvent({required this.deviceId, required this.code});
const FetchGarageDoorRecordsEvent(
{required this.deviceId, required this.code});
@override
List<Object?> get props => [deviceId, code];
@ -232,3 +235,10 @@ class GarageDoorFactoryResetEvent extends GarageDoorEvent {
@override
List<Object?> get props => [factoryReset, deviceId];
}
class StatusUpdated extends GarageDoorEvent {
final GarageDoorStatusModel deviceStatus;
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
@ -16,6 +17,7 @@ class MainDoorSensorBloc
on<MainDoorSensorFetchBatchEvent>(_onFetchBatchStatus);
on<MainDoorSensorReportsEvent>(_fetchReports);
on<MainDoorSensorFactoryReset>(_factoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
late MainDoorSensorStatusModel deviceStatus;
@ -28,7 +30,7 @@ class MainDoorSensorBloc
final status = await DevicesManagementApi()
.getDeviceStatus(event.deviceId)
.then((value) => value.status);
_listenToChanges(event.deviceId);
deviceStatus = MainDoorSensorStatusModel.fromJson(event.deviceId, status);
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
} catch (e) {
@ -156,4 +158,35 @@ class MainDoorSensorBloc
emit(MainDoorSensorFailedState(error: e.toString()));
}
}
_listenToChanges(deviceId) {
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<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus = MainDoorSensorStatusModel.fromJson(
usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(
StatusUpdated event, Emitter<MainDoorSensorState> emit) {
deviceStatus = event.deviceStatus;
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
}
}

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart';
import '../../all_devices/models/factory_reset_model.dart';
@ -71,3 +72,10 @@ class MainDoorSensorFactoryReset extends MainDoorSensorEvent {
MainDoorSensorFactoryReset(
{required this.deviceId, required this.factoryReset});
}
class StatusUpdated extends MainDoorSensorEvent {
final MainDoorSensorStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:meta/meta.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
@ -10,33 +11,71 @@ import 'package:syncrow_web/services/devices_mang_api.dart';
part 'one_gang_glass_switch_event.dart';
part 'one_gang_glass_switch_state.dart';
class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassSwitchState> {
class OneGangGlassSwitchBloc
extends Bloc<OneGangGlassSwitchEvent, OneGangGlassSwitchState> {
OneGangGlassStatusModel deviceStatus;
Timer? _timer;
OneGangGlassSwitchBloc({required String deviceId})
: deviceStatus = OneGangGlassStatusModel(uuid: deviceId, switch1: false, countDown: 0),
: deviceStatus = OneGangGlassStatusModel(
uuid: deviceId, switch1: false, countDown: 0),
super(OneGangGlassSwitchInitial()) {
on<OneGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<OneGangGlassSwitchControl>(_onControl);
on<OneGangGlassSwitchBatchControl>(_onBatchControl);
on<OneGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
on<OneGangGlassFactoryResetEvent>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
Future<void> _onFetchDeviceStatus(
OneGangGlassSwitchFetchDeviceEvent event, Emitter<OneGangGlassSwitchState> emit) async {
Future<void> _onFetchDeviceStatus(OneGangGlassSwitchFetchDeviceEvent event,
Emitter<OneGangGlassSwitchState> emit) async {
emit(OneGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId);
deviceStatus =
OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(OneGangGlassSwitchError(e.toString()));
}
}
Future<void> _onControl(OneGangGlassSwitchControl event, Emitter<OneGangGlassSwitchState> emit) async {
_listenToChanges(deviceId) {
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<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus = OneGangGlassStatusModel.fromJson(
usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(
StatusUpdated event, Emitter<OneGangGlassSwitchState> emit) {
deviceStatus = event.deviceStatus;
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
}
Future<void> _onControl(OneGangGlassSwitchControl event,
Emitter<OneGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
@ -52,10 +91,12 @@ class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassS
);
}
Future<void> _onFactoryReset(OneGangGlassFactoryResetEvent event, Emitter<OneGangGlassSwitchState> emit) async {
Future<void> _onFactoryReset(OneGangGlassFactoryResetEvent event,
Emitter<OneGangGlassSwitchState> emit) async {
emit(OneGangGlassSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId);
final response = await DevicesManagementApi()
.factoryReset(event.factoryReset, event.deviceId);
if (!response) {
emit(OneGangGlassSwitchError('Failed to reset device'));
} else {
@ -66,7 +107,8 @@ class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassS
}
}
Future<void> _onBatchControl(OneGangGlassSwitchBatchControl event, Emitter<OneGangGlassSwitchState> emit) async {
Future<void> _onBatchControl(OneGangGlassSwitchBatchControl event,
Emitter<OneGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
@ -83,11 +125,14 @@ class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassS
}
Future<void> _onFetchBatchStatus(
OneGangGlassSwitchFetchBatchStatusEvent event, Emitter<OneGangGlassSwitchState> emit) async {
OneGangGlassSwitchFetchBatchStatusEvent event,
Emitter<OneGangGlassSwitchState> emit) async {
emit(OneGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status);
final status =
await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = OneGangGlassStatusModel.fromJson(
event.deviceIds.first, status.status);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(OneGangGlassSwitchError(e.toString()));
@ -117,9 +162,11 @@ class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassS
try {
late bool response;
if (isBatch) {
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value));
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) {
@ -131,7 +178,8 @@ class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassS
});
}
void _revertValueAndEmit(String deviceId, String code, bool oldValue, Emitter<OneGangGlassSwitchState> emit) {
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
Emitter<OneGangGlassSwitchState> emit) {
_updateLocalValue(code, oldValue);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
}

View File

@ -39,6 +39,11 @@ class OneGangGlassSwitchFetchBatchStatusEvent extends OneGangGlassSwitchEvent {
OneGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
}
class StatusUpdated extends OneGangGlassSwitchEvent {
final OneGangGlassStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
}
class OneGangGlassFactoryResetEvent extends OneGangGlassSwitchEvent {
final FactoryResetModel factoryReset;
final String deviceId;

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
@ -16,6 +17,7 @@ class WallLightSwitchBloc
on<WallLightSwitchFetchBatchEvent>(_onFetchBatchStatus);
on<WallLightSwitchBatchControl>(_onBatchControl);
on<WallLightFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
late WallLightStatusModel deviceStatus;
@ -31,12 +33,44 @@ class WallLightSwitchBloc
deviceStatus =
WallLightStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(WallLightSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(WallLightSwitchError(e.toString()));
}
}
_listenToChanges(deviceId) {
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<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
WallLightStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(
StatusUpdated event, Emitter<WallLightSwitchState> emit) {
deviceStatus = event.deviceStatus;
emit(WallLightSwitchStatusLoaded(deviceStatus));
}
FutureOr<void> _onControl(
WallLightSwitchControl event, Emitter<WallLightSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);

View File

@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
class WallLightSwitchEvent extends Equatable {
@override
@ -57,3 +58,10 @@ class WallLightFactoryReset extends WallLightSwitchEvent {
@override
List<Object> get props => [deviceId, factoryReset];
}
class StatusUpdated extends WallLightSwitchEvent {
final WallLightStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:meta/meta.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
@ -10,7 +11,8 @@ import 'package:syncrow_web/services/devices_mang_api.dart';
part 'three_gang_glass_switch_event.dart';
part 'three_gang_glass_switch_state.dart';
class ThreeGangGlassSwitchBloc extends Bloc<ThreeGangGlassSwitchEvent, ThreeGangGlassSwitchState> {
class ThreeGangGlassSwitchBloc
extends Bloc<ThreeGangGlassSwitchEvent, ThreeGangGlassSwitchState> {
ThreeGangGlassStatusModel deviceStatus;
Timer? _timer;
@ -29,21 +31,57 @@ class ThreeGangGlassSwitchBloc extends Bloc<ThreeGangGlassSwitchEvent, ThreeGang
on<ThreeGangGlassSwitchBatchControl>(_onBatchControl);
on<ThreeGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
on<ThreeGangGlassFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
Future<void> _onFetchDeviceStatus(
ThreeGangGlassSwitchFetchDeviceEvent event, Emitter<ThreeGangGlassSwitchState> emit) async {
Future<void> _onFetchDeviceStatus(ThreeGangGlassSwitchFetchDeviceEvent event,
Emitter<ThreeGangGlassSwitchState> emit) async {
emit(ThreeGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status);
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(ThreeGangGlassSwitchError(e.toString()));
}
}
Future<void> _onControl(ThreeGangGlassSwitchControl event, Emitter<ThreeGangGlassSwitchState> emit) async {
_listenToChanges(deviceId) {
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<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus = ThreeGangGlassStatusModel.fromJson(
usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(
StatusUpdated event, Emitter<ThreeGangGlassSwitchState> emit) {
deviceStatus = event.deviceStatus;
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
}
Future<void> _onControl(ThreeGangGlassSwitchControl event,
Emitter<ThreeGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
@ -59,7 +97,8 @@ class ThreeGangGlassSwitchBloc extends Bloc<ThreeGangGlassSwitchEvent, ThreeGang
);
}
Future<void> _onBatchControl(ThreeGangGlassSwitchBatchControl event, Emitter<ThreeGangGlassSwitchState> emit) async {
Future<void> _onBatchControl(ThreeGangGlassSwitchBatchControl event,
Emitter<ThreeGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
@ -76,21 +115,26 @@ class ThreeGangGlassSwitchBloc extends Bloc<ThreeGangGlassSwitchEvent, ThreeGang
}
Future<void> _onFetchBatchStatus(
ThreeGangGlassSwitchFetchBatchStatusEvent event, Emitter<ThreeGangGlassSwitchState> emit) async {
ThreeGangGlassSwitchFetchBatchStatusEvent event,
Emitter<ThreeGangGlassSwitchState> emit) async {
emit(ThreeGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = ThreeGangGlassStatusModel.fromJson(event.deviceIds.first, status.status);
final status =
await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = ThreeGangGlassStatusModel.fromJson(
event.deviceIds.first, status.status);
emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus));
} catch (e) {
emit(ThreeGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFactoryReset(ThreeGangGlassFactoryReset event, Emitter<ThreeGangGlassSwitchState> emit) async {
Future<void> _onFactoryReset(ThreeGangGlassFactoryReset event,
Emitter<ThreeGangGlassSwitchState> emit) async {
emit(ThreeGangGlassSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId);
final response = await DevicesManagementApi()
.factoryReset(event.factoryReset, event.deviceId);
if (!response) {
emit(ThreeGangGlassSwitchError('Failed'));
} else {
@ -124,9 +168,11 @@ class ThreeGangGlassSwitchBloc extends Bloc<ThreeGangGlassSwitchEvent, ThreeGang
try {
late bool response;
if (isBatch) {
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value));
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) {
@ -138,7 +184,8 @@ class ThreeGangGlassSwitchBloc extends Bloc<ThreeGangGlassSwitchEvent, ThreeGang
});
}
void _revertValueAndEmit(String deviceId, String code, bool oldValue, Emitter<ThreeGangGlassSwitchState> emit) {
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
Emitter<ThreeGangGlassSwitchState> emit) {
_updateLocalValue(code, oldValue);
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
}

View File

@ -49,3 +49,10 @@ class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent {
required this.factoryReset,
});
}
class StatusUpdated extends ThreeGangGlassSwitchEvent {
final ThreeGangGlassStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}

View File

@ -3,6 +3,7 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
@ -22,6 +23,7 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
on<LivingRoomBatchControl>(_livingRoomBatchControl);
on<LivingRoomFetchBatchEvent>(_livingRoomFetchBatchControl);
on<LivingRoomFactoryResetEvent>(_livingRoomFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
FutureOr<void> _onFetchDeviceStatus(LivingRoomFetchDeviceStatusEvent event,
@ -32,6 +34,7 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
LivingRoomStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(deviceId);
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
} catch (e) {
emit(LivingRoomDeviceManagementError(e.toString()));
@ -144,6 +147,9 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
LivingRoomStatusModel.fromJson(event.devicesIds.first, status.status);
// for (var deviceId in event.devicesIds) {
// _listenToChanges(deviceId);
// }
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
} catch (e) {
emit(LivingRoomDeviceManagementError(e.toString()));
@ -185,4 +191,34 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
emit(LivingRoomDeviceManagementError(e.toString()));
}
}
_listenToChanges(deviceId) {
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<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
LivingRoomStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(StatusUpdated event, Emitter<LivingRoomState> emit) {
deviceStatus = event.deviceStatus;
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
}
}

View File

@ -58,3 +58,10 @@ class LivingRoomFactoryResetEvent extends LivingRoomEvent {
@override
List<Object> get props => [uuid, factoryReset];
}
class StatusUpdated extends LivingRoomEvent {
final LivingRoomStatusModel deviceStatus;
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}

View File

@ -1,12 +1,11 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:meta/meta.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
part 'two_gang_glass_switch_event.dart';
part 'two_gang_glass_switch_state.dart';
@ -14,7 +13,6 @@ class TwoGangGlassSwitchBloc
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
TwoGangGlassStatusModel deviceStatus;
Timer? _timer;
TwoGangGlassSwitchBloc({required String deviceId})
: deviceStatus = TwoGangGlassStatusModel(
uuid: deviceId,
@ -28,6 +26,7 @@ class TwoGangGlassSwitchBloc
on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
on<TwoGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
on<TwoGangGlassFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
Future<void> _onFetchDeviceStatus(TwoGangGlassSwitchFetchDeviceEvent event,
@ -38,12 +37,44 @@ class TwoGangGlassSwitchBloc
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
}
void _listenToChanges(String deviceId) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
ref.onValue.listen((DatabaseEvent event) {
if (event.snapshot.value == null) return;
Map<dynamic, dynamic> data =
event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = [];
data['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
// Parse the new status and add the event
final updatedStatus =
TwoGangGlassStatusModel.fromJson(data['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(updatedStatus));
}
});
} catch (e) {
// Handle errors and emit an error state if necessary
if (!isClosed) {
// add(TwoGangGlassSwitchError('Error listening to updates: $e'));
}
}
}
Future<void> _onControl(TwoGangGlassSwitchControl event,
Emitter<TwoGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
@ -178,4 +209,37 @@ class TwoGangGlassSwitchBloc
_timer?.cancel();
return super.close();
}
// _listenToChanges(deviceId) {
// 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<Status> statusList = [];
// usersMap['status'].forEach((element) {
// statusList
// .add(Status(code: element['code'], value: element['value']));
// });
// deviceStatus = TwoGangGlassStatusModel.fromJson(
// usersMap['productUuid'], statusList);
// if (!isClosed) {
// add(StatusUpdated(deviceStatus));
// }
// });
// } catch (_) {}
// }
void _onStatusUpdated(
StatusUpdated event, Emitter<TwoGangGlassSwitchState> emit) {
// Update the local deviceStatus with the new status from the event
deviceStatus = event.deviceStatus;
// Emit the new state with the updated status
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
}
}

View File

@ -48,3 +48,11 @@ class TwoGangGlassFactoryReset extends TwoGangGlassSwitchEvent {
required this.factoryReset,
});
}
class StatusUpdated extends TwoGangGlassSwitchEvent {
final TwoGangGlassStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}

View File

@ -6,7 +6,8 @@ import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class TwoGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout {
class TwoGangGlassSwitchControlView extends StatelessWidget
with HelperResponsiveLayout {
final String deviceId;
const TwoGangGlassSwitchControlView({required this.deviceId, super.key});
@ -14,25 +15,25 @@ class TwoGangGlassSwitchControlView extends StatelessWidget with HelperResponsiv
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
TwoGangGlassSwitchBloc(deviceId: deviceId)..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
builder: (context, state) {
if (state is TwoGangGlassSwitchLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is TwoGangGlassSwitchStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is TwoGangGlassSwitchError) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceId)
..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
builder: (context, state) {
if (state is TwoGangGlassSwitchLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is TwoGangGlassSwitchStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is TwoGangGlassSwitchError) {
return Center(child: Text(state.message));
} else {
return const Center(child: CircularProgressIndicator());
}
},
));
}
Widget _buildStatusControls(BuildContext context, TwoGangGlassStatusModel status) {
Widget _buildStatusControls(
BuildContext context, TwoGangGlassStatusModel status) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
@ -14,6 +15,7 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
on<TwoGangSwitchFetchBatchEvent>(_onFetchBatchStatus);
on<TwoGangSwitchBatchControl>(_onBatchControl);
on<TwoGangFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
late TwoGangStatusModel deviceStatus;
@ -26,8 +28,8 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(emit);
emit(TwoGangSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangSwitchError(e.toString()));
@ -174,4 +176,34 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
emit(TwoGangSwitchError(e.toString()));
}
}
_listenToChanges(deviceId) {
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<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
TwoGangStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(StatusUpdated event, Emitter<TwoGangSwitchState> emit) {
deviceStatus = event.deviceStatus;
emit(TwoGangSwitchStatusLoaded(deviceStatus));
}
}

View File

@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
class TwoGangSwitchEvent extends Equatable {
@override
@ -57,3 +58,10 @@ class TwoGangFactoryReset extends TwoGangSwitchEvent {
@override
List<Object> get props => [deviceId, factoryReset];
}
class StatusUpdated extends TwoGangSwitchEvent {
final TwoGangStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart';
@ -29,7 +30,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
// _listenToChanges();
_listenToChanges(emit);
} catch (e) {
emit(WallSensorFailedState(error: e.toString()));
return;
@ -38,10 +39,12 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
// Fetch batch status
FutureOr<void> _fetchWallSensorBatchControl(
WallSensorFetchBatchStatusEvent event, Emitter<WallSensorState> emit) async {
WallSensorFetchBatchStatusEvent event,
Emitter<WallSensorState> emit) async {
emit(WallSensorLoadingInitialState());
try {
var response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
var response =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
} catch (e) {
@ -49,26 +52,30 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
}
}
// _listenToChanges() {
// try {
// DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
// Stream<DatabaseEvent> stream = ref.onValue;
_listenToChanges(Emitter<WallSensorState> emit) {
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 = [];
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = [];
// usersMap['status'].forEach((element) {
// statusList.add(StatusModel(code: element['code'], value: element['value']));
// });
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
// deviceStatus = WallSensorModel.fromJson(statusList);
// add(WallSensorUpdatedEvent());
// });
// } catch (_) {}
// }
deviceStatus = WallSensorModel.fromJson(statusList);
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
});
} catch (_) {}
}
void _changeValue(WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
void _changeValue(
WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
if (event.code == 'far_detection') {
deviceStatus.farDetection = event.value;
@ -125,7 +132,8 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
try {
late bool response;
if (isBatch) {
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
@ -150,7 +158,8 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
try {
// await DevicesManagementApi.getDeviceReportsByDate(
// deviceId, event.code, from.toString(), to.toString())
await DevicesManagementApi.getDeviceReports(deviceId, event.code).then((value) {
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
.then((value) {
emit(DeviceReportsState(deviceReport: value, code: event.code));
});
} catch (e) {
@ -159,11 +168,13 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
}
}
void _showDescription(ShowDescriptionEvent event, Emitter<WallSensorState> emit) {
void _showDescription(
ShowDescriptionEvent event, Emitter<WallSensorState> emit) {
emit(WallSensorShowDescriptionState(description: event.description));
}
void _backToGridView(BackToGridViewEvent event, Emitter<WallSensorState> emit) {
void _backToGridView(
BackToGridViewEvent event, Emitter<WallSensorState> emit) {
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
}

View File

@ -4,6 +4,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
@ -34,6 +35,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
on<EditWaterHeaterScheduleEvent>(_onEditSchedule);
on<DeleteScheduleEvent>(_onDeleteSchedule);
on<UpdateScheduleEntryEvent>(_onUpdateSchedule);
on<StatusUpdated>(_onStatusUpdated);
}
late WaterHeaterStatusModel deviceStatus;
@ -78,7 +80,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
final currentState = state as WaterHeaterDeviceStatusLoaded;
final updatedDays = List<bool>.from(currentState.selectedDays);
updatedDays[event.index] = event.value;
emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime));
emit(currentState.copyWith(
selectedDays: updatedDays, selectedTime: currentState.selectedTime));
}
FutureOr<void> _updateFunctionOn(
@ -86,7 +89,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
Emitter<WaterHeaterState> emit,
) {
final currentState = state as WaterHeaterDeviceStatusLoaded;
emit(currentState.copyWith(functionOn: event.isOn, selectedTime: currentState.selectedTime));
emit(currentState.copyWith(
functionOn: event.isOn, selectedTime: currentState.selectedTime));
}
FutureOr<void> _updateScheduleEvent(
@ -101,7 +105,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
));
}
if (event.scheduleMode == ScheduleModes.countdown) {
final countdownRemaining = Duration(hours: event.hours, minutes: event.minutes);
final countdownRemaining =
Duration(hours: event.hours, minutes: event.minutes);
emit(currentState.copyWith(
scheduleMode: ScheduleModes.countdown,
@ -111,11 +116,13 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
countdownRemaining: countdownRemaining,
));
if (!currentState.isCountdownActive! && countdownRemaining > Duration.zero) {
if (!currentState.isCountdownActive! &&
countdownRemaining > Duration.zero) {
_startCountdownTimer(emit, countdownRemaining);
}
} else if (event.scheduleMode == ScheduleModes.inching) {
final inchingDuration = Duration(hours: event.hours, minutes: event.minutes);
final inchingDuration =
Duration(hours: event.hours, minutes: event.minutes);
emit(currentState.copyWith(
scheduleMode: ScheduleModes.inching,
@ -217,7 +224,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
try {
final status = await DevicesManagementApi().deviceControl(
event.deviceId,
Status(code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0),
Status(
code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0),
);
if (!status) {
emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.'));
@ -235,8 +243,10 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
emit(WaterHeaterLoadingState());
try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.scheduleMode == ScheduleModes.countdown) {
final countdownRemaining = Duration(
@ -300,11 +310,42 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
isInchingActive: false,
));
}
_listenToChanges(event.deviceId);
} catch (e) {
emit(WaterHeaterFailedState(error: e.toString()));
}
}
_listenToChanges(deviceId) {
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<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus = WaterHeaterStatusModel.fromJson(
usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(StatusUpdated event, Emitter<WaterHeaterState> emit) {
deviceStatus = event.deviceStatus;
emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
}
void _startCountdownTimer(
Emitter<WaterHeaterState> emit,
Duration countdownRemaining,
@ -334,8 +375,10 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded;
if (currentState.countdownRemaining != null && currentState.countdownRemaining! > Duration.zero) {
final newRemaining = currentState.countdownRemaining! - const Duration(minutes: 1);
if (currentState.countdownRemaining != null &&
currentState.countdownRemaining! > Duration.zero) {
final newRemaining =
currentState.countdownRemaining! - const Duration(minutes: 1);
if (newRemaining <= Duration.zero) {
_countdownTimer?.cancel();
@ -430,7 +473,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
}
}
void _revertValue(String code, dynamic oldValue, void Function(WaterHeaterState state) emit) {
void _revertValue(String code, dynamic oldValue,
void Function(WaterHeaterState state) emit) {
_updateLocalValue(code, oldValue);
if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded;
@ -477,12 +521,13 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
return super.close();
}
FutureOr<void> _getSchedule(GetSchedulesEvent event, Emitter<WaterHeaterState> emit) async {
FutureOr<void> _getSchedule(
GetSchedulesEvent event, Emitter<WaterHeaterState> emit) async {
emit(ScheduleLoadingState());
try {
List<ScheduleModel> schedules =
await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category);
List<ScheduleModel> schedules = await DevicesManagementApi()
.getDeviceSchedules(deviceStatus.uuid, event.category);
emit(WaterHeaterDeviceStatusLoaded(
deviceStatus,
@ -514,7 +559,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
// emit(ScheduleLoadingState());
bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, currentState.status.uuid);
bool success = await DevicesManagementApi()
.addScheduleRecord(newSchedule, currentState.status.uuid);
if (success) {
add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
@ -525,7 +571,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
}
}
FutureOr<void> _onEditSchedule(EditWaterHeaterScheduleEvent event, Emitter<WaterHeaterState> emit) async {
FutureOr<void> _onEditSchedule(EditWaterHeaterScheduleEvent event,
Emitter<WaterHeaterState> emit) async {
if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded;
@ -594,11 +641,13 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
// emit(ScheduleLoadingState());
bool success = await DevicesManagementApi().deleteScheduleRecord(currentState.status.uuid, event.scheduleId);
bool success = await DevicesManagementApi()
.deleteScheduleRecord(currentState.status.uuid, event.scheduleId);
if (success) {
final updatedSchedules =
currentState.schedules.where((schedule) => schedule.scheduleId != event.scheduleId).toList();
final updatedSchedules = currentState.schedules
.where((schedule) => schedule.scheduleId != event.scheduleId)
.toList();
emit(currentState.copyWith(schedules: updatedSchedules));
} else {
@ -608,12 +657,15 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
}
}
FutureOr<void> _batchFetchWaterHeater(FetchWaterHeaterBatchStatusEvent event, Emitter<WaterHeaterState> emit) async {
FutureOr<void> _batchFetchWaterHeater(FetchWaterHeaterBatchStatusEvent event,
Emitter<WaterHeaterState> emit) async {
emit(WaterHeaterLoadingState());
try {
final status = await DevicesManagementApi().getBatchStatus(event.devicesUuid);
deviceStatus = WaterHeaterStatusModel.fromJson(event.devicesUuid.first, status.status);
final status =
await DevicesManagementApi().getBatchStatus(event.devicesUuid);
deviceStatus = WaterHeaterStatusModel.fromJson(
event.devicesUuid.first, status.status);
emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
} catch (e) {
@ -621,7 +673,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
}
}
FutureOr<void> _batchControlWaterHeater(ControlWaterHeaterBatchEvent event, Emitter<WaterHeaterState> emit) async {
FutureOr<void> _batchControlWaterHeater(ControlWaterHeaterBatchEvent event,
Emitter<WaterHeaterState> emit) async {
if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded;

View File

@ -54,6 +54,15 @@ final class WaterHeaterFetchStatusEvent extends WaterHeaterEvent {
final class DecrementCountdownEvent extends WaterHeaterEvent {}
class StatusUpdated extends WaterHeaterEvent {
final WaterHeaterStatusModel deviceStatus;
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
final class AddScheduleEvent extends WaterHeaterEvent {
final List<bool> selectedDays;
final TimeOfDay time;

View File

@ -1,4 +1,5 @@
import 'package:bloc/bloc.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_event.dart';
@ -21,6 +22,7 @@ class WaterLeakBloc extends Bloc<WaterLeakEvent, WaterLeakState> {
on<FetchWaterLeakBatchStatusEvent>(_onFetchBatchStatus);
on<FetchWaterLeakReportsEvent>(_onFetchWaterLeakReports);
on<WaterLeakFactoryResetEvent>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
Future<void> _onFetchWaterLeakStatus(
@ -30,12 +32,43 @@ class WaterLeakBloc extends Bloc<WaterLeakEvent, WaterLeakState> {
final response =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = WaterLeakStatusModel.fromJson(deviceId, response.status);
_listenToChanges();
emit(WaterLeakLoadedState(deviceStatus!));
} catch (e) {
emit(WaterLeakErrorState(e.toString()));
}
}
_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<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
WaterLeakStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus!));
}
});
} catch (_) {}
}
void _onStatusUpdated(StatusUpdated event, Emitter<WaterLeakState> emit) {
deviceStatus = event.deviceStatus;
emit(WaterLeakLoadedState(deviceStatus!));
}
Future<void> _onControl(
WaterLeakControlEvent event, Emitter<WaterLeakState> emit) async {
final oldValue = deviceStatus!.watersensorState;

View File

@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/water_leak/model/water_leak_status_model.dart';
abstract class WaterLeakEvent extends Equatable {
const WaterLeakEvent();
@ -17,6 +18,13 @@ class FetchWaterLeakStatusEvent extends WaterLeakEvent {
List<Object> get props => [deviceId];
}
class StatusUpdated extends WaterLeakEvent {
final WaterLeakStatusModel deviceStatus;
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
class WaterLeakControlEvent extends WaterLeakEvent {
final String deviceId;
final String code;

View File

@ -72,8 +72,6 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
emit(LoadingHome());
terms = await HomeApi().fetchTerms();
emit(HomeInitial());
// emit(PolicyAgreement());
} catch (e) {
return;
}
@ -83,8 +81,6 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
try {
emit(LoadingHome());
policy = await HomeApi().fetchPolicy();
debugPrint("Fetched policy: $policy");
// Emit a state to trigger the UI update
emit(HomeInitial());
} catch (e) {
debugPrint("Error fetching policy: $e");
@ -115,7 +111,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
List<HomeItemModel> homeItems = [
HomeItemModel(
title: 'Access',
title: 'Access Management',
icon: Assets.accessIcon,
active: true,
onPress: (context) {
@ -135,7 +131,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
color: ColorsManager.primaryColor,
),
HomeItemModel(
title: 'Devices',
title: 'Devices Management',
icon: Assets.devicesIcon,
active: true,
onPress: (context) {
@ -146,6 +142,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
},
color: ColorsManager.primaryColor,
),
// HomeItemModel(
// title: 'Move in',
// icon: Assets.moveinIcon,

View File

@ -20,7 +20,6 @@ class TreeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
debugPrint('TreeView constructed with userId = $userId');
return BlocProvider(
create: (_) => UsersBloc(),
// ..add(const LoadCommunityAndSpacesEvent()),

View File

@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bl
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/users_page.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class RolesAndPermissionPage extends StatelessWidget {
@ -27,11 +28,10 @@ class RolesAndPermissionPage extends StatelessWidget {
? const Center(child: CircularProgressIndicator())
: WebScaffold(
enableMenuSidebar: false,
appBarTitle: FittedBox(
child: Text(
'Roles & Permissions',
style: Theme.of(context).textTheme.headlineLarge,
),
appBarTitle: Text(
'Roles & Permissions',
style:
ResponsiveTextTheme.of(context).deviceManagementTitle,
),
rightBody: const NavigateHomeGridView(),
centerBody: Row(

View File

@ -16,6 +16,33 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
on<SearchQueryEvent>(_onSearch);
on<ClearAllData>(_clearAllData);
on<ClearCachedData>(_clearCachedData);
on<OnCommunityAdded>(_onCommunityAdded);
on<OnCommunityUpdated>(_onCommunityUpdate);
}
void _onCommunityUpdate(
OnCommunityUpdated event,
Emitter<SpaceTreeState> emit,
) async {
emit(SpaceTreeLoadingState());
try {
final updatedCommunity = event.updatedCommunity;
final updatedCommunities =
List<CommunityModel>.from(state.communityList);
final index = updatedCommunities
.indexWhere((community) => community.uuid == updatedCommunity.uuid);
if (index != -1) {
updatedCommunities[index] = updatedCommunity;
emit(state.copyWith(communitiesList: updatedCommunities));
} else {
emit(SpaceTreeErrorState('Community not found in the list.'));
}
} catch (e) {
emit(SpaceTreeErrorState('Error updating community: $e'));
}
}
_fetchSpaces(InitialEvent event, Emitter<SpaceTreeState> emit) async {
@ -28,8 +55,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces =
await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid, projectUuid);
List<SpaceModel> spaces = await CommunitySpaceManagementApi()
.getSpaceHierarchy(community.uuid, projectUuid);
return CommunityModel(
uuid: community.uuid,
@ -44,15 +71,27 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
);
emit(state.copyWith(
communitiesList: updatedCommunities, expandedCommunity: [], expandedSpaces: []));
communitiesList: updatedCommunities,
expandedCommunity: [],
expandedSpaces: []));
} catch (e) {
emit(SpaceTreeErrorState('Error loading communities and spaces: $e'));
}
}
_onCommunityExpanded(OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
void _onCommunityAdded(
OnCommunityAdded event, Emitter<SpaceTreeState> emit) async {
final updatedCommunities = List<CommunityModel>.from(state.communityList);
updatedCommunities.add(event.newCommunity);
emit(state.copyWith(communitiesList: updatedCommunities));
}
_onCommunityExpanded(
OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedExpandedCommunityList = List.from(state.expandedCommunities);
List<String> updatedExpandedCommunityList =
List.from(state.expandedCommunities);
if (updatedExpandedCommunityList.contains(event.communityId)) {
updatedExpandedCommunityList.remove(event.communityId);
@ -84,14 +123,19 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
}
}
_onCommunitySelected(OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
_onCommunitySelected(
OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedSelectedCommunities =
List.from(state.selectedCommunities.toSet().toList());
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityId] ?? [];
List<String> updatedSelectedSpaces =
List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks =
List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces =
Map.from(state.selectedCommunityAndSpaces);
List<String> selectedSpacesInCommunity =
communityAndSpaces[event.communityId] ?? [];
List<String> childrenIds = _getAllChildIds(event.children);
@ -124,11 +168,15 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
try {
List<String> updatedSelectedCommunities =
List.from(state.selectedCommunities.toSet().toList());
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List<String> updatedSelectedSpaces =
List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks =
List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces =
Map.from(state.selectedCommunityAndSpaces);
List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityModel.uuid] ?? [];
List<String> selectedSpacesInCommunity =
communityAndSpaces[event.communityModel.uuid] ?? [];
List<String> childrenIds = _getAllChildIds(event.children);
bool isChildSelected = false;
@ -151,9 +199,11 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
selectedSpacesInCommunity.addAll(childrenIds);
}
List<String> spaces = _getThePathToChild(event.communityModel.uuid, event.spaceId);
List<String> spaces =
_getThePathToChild(event.communityModel.uuid, event.spaceId);
for (String space in spaces) {
if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) {
if (!updatedSelectedSpaces.contains(space) &&
!updatedSoldChecks.contains(space)) {
updatedSoldChecks.add(space);
}
}
@ -176,7 +226,9 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
updatedSoldChecks.remove(event.spaceId);
List<String> parents =
_getThePathToChild(event.communityModel.uuid, event.spaceId).toSet().toList();
_getThePathToChild(event.communityModel.uuid, event.spaceId)
.toSet()
.toList();
if (updatedSelectedSpaces.isEmpty) {
updatedSoldChecks.removeWhere(parents.contains);
@ -184,7 +236,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
} else {
// Check if any parent has selected children
for (String space in parents) {
if (!_noChildrenSelected(event.communityModel, space, updatedSelectedSpaces, parents)) {
if (!_noChildrenSelected(
event.communityModel, space, updatedSelectedSpaces, parents)) {
updatedSoldChecks.remove(space);
}
}
@ -209,8 +262,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
}
}
_noChildrenSelected(
CommunityModel community, String spaceId, List<String> selectedSpaces, List<String> parents) {
_noChildrenSelected(CommunityModel community, String spaceId,
List<String> selectedSpaces, List<String> parents) {
if (selectedSpaces.contains(spaceId)) {
return true;
}
@ -237,10 +290,11 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
// Filter communities and expand only those that match the query
filteredCommunity = communities.where((community) {
final containsQueryInCommunity =
community.name.toLowerCase().contains(event.searchQuery.toLowerCase());
final containsQueryInSpaces =
community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase()));
final containsQueryInCommunity = community.name
.toLowerCase()
.contains(event.searchQuery.toLowerCase());
final containsQueryInSpaces = community.spaces.any(
(space) => _containsQuery(space, event.searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces;
}).toList();
@ -293,8 +347,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
// Helper function to determine if any space or its children match the search query
bool _containsQuery(SpaceModel space, String query) {
final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren =
space.children.any((child) => _containsQuery(child, query)); // Recursive check for children
final matchesChildren = space.children.any((child) =>
_containsQuery(child, query)); // Recursive check for children
return matchesSpace || matchesChildren;
}
@ -317,8 +371,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
return children;
}
bool _anySpacesSelectedInCommunity(
CommunityModel community, List<String> selectedSpaces, List<String> partialCheckedList) {
bool _anySpacesSelectedInCommunity(CommunityModel community,
List<String> selectedSpaces, List<String> partialCheckedList) {
bool result = false;
List<String> ids = _getAllChildIds(community.spaces);
for (var id in ids) {
@ -347,7 +401,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
return ids;
}
List<String> _getAllParentsIds(SpaceModel child, String spaceId, List<String> listIds) {
List<String> _getAllParentsIds(
SpaceModel child, String spaceId, List<String> listIds) {
List<String> ids = listIds;
ids.add(child.uuid ?? '');

View File

@ -69,6 +69,23 @@ class SearchQueryEvent extends SpaceTreeEvent {
List<Object> get props => [searchQuery];
}
class OnCommunityAdded extends SpaceTreeEvent {
final CommunityModel newCommunity;
const OnCommunityAdded(this.newCommunity);
@override
List<Object> get props => [newCommunity];
}
class OnCommunityUpdated extends SpaceTreeEvent {
final CommunityModel updatedCommunity;
const OnCommunityUpdated(this.updatedCommunity);
@override
List<Object> get props => [updatedCommunity];
}
class ClearAllData extends SpaceTreeEvent {}
class ClearCachedData extends SpaceTreeEvent {}

View File

@ -83,7 +83,7 @@ class CustomExpansionTileSpaceTree extends StatelessWidget {
),
if (isExpanded && children != null && children!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(left: 48.0),
padding: const EdgeInsets.only(left: 24.0),
child: Column(
children: children ?? [],
),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
@ -8,11 +9,14 @@ import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceTreeView extends StatefulWidget {
final bool? isSide;
final Function onSelect;
const SpaceTreeView({required this.onSelect, super.key});
const SpaceTreeView({required this.onSelect, this.isSide, super.key});
@override
State<SpaceTreeView> createState() => _SpaceTreeViewState();
@ -29,21 +33,77 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
List<CommunityModel> list = state.isSearching ? state.filteredCommunity : state.communityList;
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
builder: (context, state) {
List<CommunityModel> list =
state.isSearching ? state.filteredCommunity : state.communityList;
return Container(
height: MediaQuery.sizeOf(context).height,
decoration: subSectionContainerDecoration,
decoration:
widget.isSide == true ? subSectionContainerDecoration : null,
child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
children: [
CustomSearchBar(
searchQuery: state.searchQuery,
onSearchChanged: (query) {
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
),
widget.isSide == true
? Container(
decoration: const BoxDecoration(
color: ColorsManager.circleRolesBackground,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20)),
border: Border.all(
color: ColorsManager.grayBorder)),
child: TextFormField(
style:
const TextStyle(color: Colors.black),
onChanged: (value) {
context
.read<SpaceTreeBloc>()
.add(SearchQueryEvent(value));
},
decoration: textBoxDecoration(radios: 20)!
.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding:
const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
Assets.textFieldSearch,
width: 24,
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
),
),
),
],
),
),
)
: CustomSearchBar(
onSearchChanged: (query) {
context
.read<SpaceTreeBloc>()
.add(SearchQueryEvent(query));
},
),
const SizedBox(height: 16),
Expanded(
child: ListView(
@ -57,14 +117,18 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
? Center(
child: Text(
'No results found',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w400,
),
),
)
: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left,
scrollbarOrientation:
ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: Padding(
@ -74,30 +138,39 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
shrinkWrap: true,
children: list
.map(
(community) => CustomExpansionTileSpaceTree(
(community) =>
CustomExpansionTileSpaceTree(
title: community.name,
isSelected: state.selectedCommunities
isSelected: state
.selectedCommunities
.contains(community.uuid),
isSoldCheck: state.selectedCommunities
isSoldCheck: state
.selectedCommunities
.contains(community.uuid),
onExpansionChanged: () {
context
.read<SpaceTreeBloc>()
.add(OnCommunityExpanded(community.uuid));
.add(OnCommunityExpanded(
community.uuid));
},
isExpanded: state.expandedCommunities
isExpanded: state
.expandedCommunities
.contains(community.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
community.uuid, community.spaces));
context
.read<SpaceTreeBloc>()
.add(OnCommunitySelected(
community.uuid,
community.spaces));
widget.onSelect();
},
children: community.spaces.map((space) {
children:
community.spaces.map((space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded:
state.expandedSpaces.contains(space.uuid),
isExpanded: state
.expandedSpaces
.contains(space.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(community, space.uuid ?? '',
@ -105,14 +178,20 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
widget.onSelect();
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
community.uuid, space.uuid ?? ''));
context
.read<SpaceTreeBloc>()
.add(OnSpaceExpanded(
community.uuid,
space.uuid ?? ''));
},
isSelected:
state.selectedSpaces.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck: state.soldCheck.contains(space.uuid),
isSelected: state
.selectedSpaces
.contains(
space.uuid) ||
state.soldCheck
.contains(space.uuid),
isSoldCheck: state.soldCheck
.contains(space.uuid),
children: _buildNestedSpaces(
context, state, space, community),
);
@ -200,8 +279,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) {
return space.children.map((child) {
return CustomExpansionTileSpaceTree(
isSelected:
state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid),
isSelected: state.selectedSpaces.contains(child.uuid) ||
state.soldCheck.contains(child.uuid),
isSoldCheck: state.soldCheck.contains(child.uuid),
title: child.name,
isExpanded: state.expandedSpaces.contains(child.uuid),

View File

@ -24,6 +24,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
final String spaceName;
final bool isCreate;
final Function(List<Tag>, List<SubspaceModel>?)? onSave;
final List<Tag> projectTags;
const AddDeviceTypeWidget(
{super.key,
@ -35,7 +37,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
this.allTags,
this.spaceTags,
this.onSave,
required this.spaceName});
required this.spaceName,
required this.projectTags});
@override
Widget build(BuildContext context) {
@ -134,7 +137,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
spaceName: spaceName,
initialTags: initialTags,
title: dialogTitle,
onSave: onSave),
onSave: onSave,
projectTags: projectTags),
);
}
},

View File

@ -1,8 +1,8 @@
import 'dart:developer';
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
@ -17,23 +17,25 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_updat
import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action;
class SpaceManagementBloc
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementState> {
final CommunitySpaceManagementApi _api;
final ProductApi _productApi;
final SpaceModelManagementApi _spaceModelApi;
List<ProductModel>? _cachedProducts;
List<SpaceTemplateModel>? _cachedSpaceModels;
final SpaceTreeBloc _spaceTreeBloc;
List<Tag>? _cachedTags;
SpaceManagementBloc(this._api, this._productApi, this._spaceModelApi)
: super(SpaceManagementInitial()) {
SpaceManagementBloc(
this._api,
this._productApi,
this._spaceModelApi,
this._spaceTreeBloc,
) : super(SpaceManagementInitial()) {
on<LoadCommunityAndSpacesEvent>(_onLoadCommunityAndSpaces);
on<UpdateSpacePositionEvent>(_onUpdateSpacePosition);
on<CreateCommunityEvent>(_onCreateCommunity);
on<SelectCommunityEvent>(_onSelectCommunity);
on<DeleteCommunityEvent>(_onCommunityDelete);
@ -44,44 +46,143 @@ class SpaceManagementBloc
on<NewCommunityEvent>(_onNewCommunity);
on<BlankStateEvent>(_onBlankState);
on<SpaceModelLoadEvent>(_onLoadSpaceModel);
on<UpdateSpaceModelCache>(_updateSpaceModelCache);
on<DeleteSpaceModelFromCache>(_deleteSpaceModelFromCache);
}
void _logEvent(String eventName) {
log('Event Triggered: $eventName');
Future<void> _updateSpaceModelCache(
UpdateSpaceModelCache event, Emitter<SpaceManagementState> emit) async {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<SpaceTemplateModel> allSpaceModels = [];
bool hasNext = true;
int page = 1;
while (hasNext) {
final spaceModels = await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
if (spaceModels.isNotEmpty) {
allSpaceModels.addAll(spaceModels);
page++;
} else {
hasNext = false;
}
}
_cachedSpaceModels = allSpaceModels;
await fetchTags();
emit(SpaceModelLoaded(
communities:
state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
products: _cachedProducts ?? [],
spaceModels: List.from(_cachedSpaceModels ?? []),
allTags: _cachedTags ?? []));
}
void _deleteSpaceModelFromCache(
DeleteSpaceModelFromCache event, Emitter<SpaceManagementState> emit) async {
if (_cachedSpaceModels != null) {
_cachedSpaceModels =
_cachedSpaceModels!.where((model) => model.uuid != event.deletedUuid).toList();
} else {
_cachedSpaceModels = await fetchSpaceModels();
}
await fetchTags();
emit(SpaceModelLoaded(
communities:
state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
products: _cachedProducts ?? [],
spaceModels: List.from(_cachedSpaceModels ?? []),
allTags: _cachedTags ?? []));
}
void updateCachedSpaceModels(List<SpaceTemplateModel> updatedModels) {
_cachedSpaceModels = List.from(updatedModels);
}
void addToCachedSpaceModels(SpaceTemplateModel newModel) {
_cachedSpaceModels?.add(newModel);
}
Future<List<SpaceTemplateModel>> fetchSpaceModels() async {
try {
if (_cachedSpaceModels != null) {
return _cachedSpaceModels!;
}
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<SpaceTemplateModel> allSpaceModels = [];
bool hasNext = true;
int page = 1;
while (hasNext) {
final spaceModels =
await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
if (spaceModels.isNotEmpty) {
allSpaceModels.addAll(spaceModels);
page++;
} else {
hasNext = false;
}
}
_cachedSpaceModels = allSpaceModels;
return allSpaceModels;
} catch (e) {
return [];
}
}
Future<List<Tag>> fetchTags() async {
try {
if (_cachedTags != null) {
return _cachedTags!;
}
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final allTags = await _spaceModelApi.listTags(projectId: projectUuid);
_cachedTags = allTags;
return allTags;
} catch (e) {
return [];
}
}
void _onUpdateCommunity(
UpdateCommunityEvent event,
Emitter<SpaceManagementState> emit,
) async {
_logEvent('UpdateCommunityEvent');
final previousState = state;
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await fetchTags();
emit(SpaceManagementLoading());
final success = await _api.updateCommunity(
event.communityUuid, event.name, projectUuid);
final success = await _api.updateCommunity(event.communityUuid, event.name, projectUuid);
if (success) {
if (previousState is SpaceManagementLoaded) {
final updatedCommunities =
List<CommunityModel>.from(previousState.communities);
final updatedCommunities = List<CommunityModel>.from(previousState.communities);
for (var community in updatedCommunities) {
if (community.uuid == event.communityUuid) {
community.name = event.name;
_spaceTreeBloc.add(OnCommunityAdded(community));
break;
}
}
var prevSpaceModels = await fetchSpaceModels(previousState);
var prevSpaceModels = await fetchSpaceModels();
emit(SpaceManagementLoaded(
communities: updatedCommunities,
products: previousState.products,
selectedCommunity: previousState.selectedCommunity,
spaceModels: prevSpaceModels,
));
communities: updatedCommunities,
products: previousState.products,
selectedCommunity: previousState.selectedCommunity,
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
}
} else {
emit(const SpaceManagementError('Failed to update the community.'));
@ -91,46 +192,6 @@ class SpaceManagementBloc
}
}
Future<List<SpaceTemplateModel>> fetchSpaceModels(
SpaceManagementState previousState) async {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<SpaceTemplateModel> allSpaces = [];
List<SpaceTemplateModel> prevSpaceModels = [];
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
prevSpaceModels = List<SpaceTemplateModel>.from(
(previousState as dynamic).spaceModels ?? [],
);
allSpaces.addAll(prevSpaceModels);
}
if (prevSpaceModels.isEmpty) {
bool hasNext = true;
int page = 1;
while (hasNext) {
final spaces = await _spaceModelApi.listSpaceModels(
page: page, projectId: projectUuid);
if (spaces.isNotEmpty) {
allSpaces.addAll(spaces);
page++;
} else {
hasNext = false;
}
}
prevSpaceModels = await _spaceModelApi.listSpaceModels(
page: 1, projectId: projectUuid);
}
return allSpaces;
} catch (e) {
return [];
}
}
void _onloadProducts() async {
if (_cachedProducts == null) {
final products = await _productApi.fetchProducts();
@ -149,8 +210,7 @@ class SpaceManagementBloc
}
}
Future<List<SpaceModel>> _fetchSpacesForCommunity(
String communityUuid) async {
Future<List<SpaceModel>> _fetchSpacesForCommunity(String communityUuid) async {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
return await _api.getSpaceHierarchy(communityUuid, projectUuid);
@ -161,64 +221,72 @@ class SpaceManagementBloc
Emitter<SpaceManagementState> emit,
) async {
try {
final previousState = state;
await fetchTags();
if (event.communities.isEmpty) {
emit(const SpaceManagementError('No communities provided.'));
return;
}
var prevSpaceModels = await fetchSpaceModels(previousState);
var prevSpaceModels = await fetchSpaceModels();
emit(BlankState(
communities: event.communities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
));
communities: event.communities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
} catch (error) {
emit(SpaceManagementError('Error loading communities: $error'));
}
}
Future<void> _onBlankState(
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
Future<void> _onBlankState(BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
try {
final previousState = state;
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = event.context.read<SpaceTreeBloc>();
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
await fetchSpaceModels();
await fetchTags();
var prevSpaceModels = await fetchSpaceModels(previousState);
var prevSpaceModels = await fetchSpaceModels();
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
if (previousState is SpaceManagementLoaded || previousState is BlankState) {
final prevCommunities = (previousState as dynamic).communities ?? [];
emit(BlankState(
communities: List<CommunityModel>.from(prevCommunities),
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
));
communities: List<CommunityModel>.from(prevCommunities),
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
return;
}
final communities = await _api.fetchCommunities(projectUuid);
final updatedCommunities =
await Future.wait(communities.map((community) async {
final spaces = await _fetchSpacesForCommunity(community.uuid);
if (communities.isEmpty) {
communities = await _api.fetchCommunities(projectUuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces,
region: community.region,
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces = await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces,
region: community.region,
);
}).toList(),
);
}));
communities = updatedCommunities;
}
emit(BlankState(
spaceModels: prevSpaceModels,
communities: updatedCommunities,
communities: communities,
products: _cachedProducts ?? [],
allTags: _cachedTags ?? [],
));
} catch (error) {
emit(SpaceManagementError('Error loading communities: $error'));
@ -229,41 +297,38 @@ class SpaceManagementBloc
LoadCommunityAndSpacesEvent event,
Emitter<SpaceManagementState> emit,
) async {
_logEvent('LoadCommunityAndSpacesEvent');
var spaceBloc = event.context.read<SpaceTreeBloc>();
_onloadProducts();
await fetchTags();
// Wait until `communityList` is loaded
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
var prevState = state;
emit(SpaceManagementLoading());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
// Fetch space models after communities are available
final prevSpaceModels = await fetchSpaceModels();
emit(SpaceManagementLoaded(
communities: communities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
}
_onloadProducts();
List<CommunityModel> communities =
await _api.fetchCommunities(projectUuid);
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces, // New spaces list
region: community.region,
);
}).toList(),
);
final prevSpaceModels = await fetchSpaceModels(prevState);
emit(SpaceManagementLoaded(
communities: updatedCommunities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels));
} catch (e) {
emit(SpaceManagementError('Error loading communities and spaces: $e'));
Future<List<CommunityModel>> _waitForCommunityList(SpaceTreeBloc spaceBloc) async {
// Check if communityList is already populated
if (spaceBloc.state.communityList.isNotEmpty) {
return spaceBloc.state.communityList;
}
final completer = Completer<List<CommunityModel>>();
final subscription = spaceBloc.stream.listen((state) {
if (state.communityList.isNotEmpty) {
completer.complete(state.communityList);
}
});
// Return the list once available, then cancel the listener
final communities = await completer.future;
await subscription.cancel();
return communities;
}
void _onCommunityDelete(
@ -274,10 +339,9 @@ class SpaceManagementBloc
emit(SpaceManagementLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final success =
await _api.deleteCommunity(event.communityUuid, projectUuid);
final success = await _api.deleteCommunity(event.communityUuid, projectUuid);
if (success) {
add(LoadCommunityAndSpacesEvent());
// add(LoadCommunityAndSpacesEvent());
} else {
emit(const SpaceManagementError('Failed to delete the community.'));
}
@ -287,11 +351,6 @@ class SpaceManagementBloc
}
}
void _onUpdateSpacePosition(
UpdateSpacePositionEvent event,
Emitter<SpaceManagementState> emit,
) {}
void _onCreateCommunity(
CreateCommunityEvent event,
Emitter<SpaceManagementState> emit,
@ -301,24 +360,27 @@ class SpaceManagementBloc
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
CommunityModel? newCommunity = await _api.createCommunity(
event.name, event.description, projectUuid);
var prevSpaceModels = await fetchSpaceModels(previousState);
await fetchTags();
CommunityModel? newCommunity =
await _api.createCommunity(event.name, event.description, projectUuid);
var prevSpaceModels = await fetchSpaceModels();
if (newCommunity != null) {
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
if (previousState is SpaceManagementLoaded || previousState is BlankState) {
final prevCommunities = List<CommunityModel>.from(
(previousState as dynamic).communities,
);
final updatedCommunities = prevCommunities..add(newCommunity);
_spaceTreeBloc.add(OnCommunityAdded(newCommunity));
emit(SpaceManagementLoaded(
spaceModels: prevSpaceModels,
communities: updatedCommunities,
products: _cachedProducts ?? [],
selectedCommunity: newCommunity,
selectedSpace: null));
spaceModels: prevSpaceModels,
communities: updatedCommunities,
products: _cachedProducts ?? [],
selectedCommunity: newCommunity,
selectedSpace: null,
allTags: _cachedTags ?? [],
));
}
} else {
emit(const SpaceManagementError('Error creating community'));
@ -358,11 +420,12 @@ class SpaceManagementBloc
required Emitter<SpaceManagementState> emit,
CommunityModel? selectedCommunity,
SpaceModel? selectedSpace,
}) {
}) async {
final previousState = state;
emit(SpaceManagementLoading());
try {
await fetchTags();
if (previousState is SpaceManagementLoaded ||
previousState is BlankState ||
previousState is SpaceModelLoaded) {
@ -378,7 +441,8 @@ class SpaceManagementBloc
products: _cachedProducts ?? [],
selectedCommunity: selectedCommunity,
selectedSpace: selectedSpace,
spaceModels: spaceModels));
spaceModels: spaceModels,
allTags: _cachedTags ?? []));
}
} catch (e) {
emit(SpaceManagementError('Error updating state: $e'));
@ -393,8 +457,7 @@ class SpaceManagementBloc
emit(SpaceManagementLoading());
try {
final updatedSpaces =
await saveSpacesHierarchically(event.spaces, event.communityUuid);
final updatedSpaces = await saveSpacesHierarchically(event.spaces, event.communityUuid);
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
@ -407,8 +470,6 @@ class SpaceManagementBloc
event.communityUuid,
emit,
);
} else {
add(LoadCommunityAndSpacesEvent());
}
} catch (e) {
emit(SpaceManagementError('Error saving spaces: $e'));
@ -425,19 +486,22 @@ class SpaceManagementBloc
String communityUuid,
Emitter<SpaceManagementState> emit,
) async {
var prevSpaceModels = await fetchSpaceModels(previousState);
var prevSpaceModels = await fetchSpaceModels();
await fetchTags();
final communities = List<CommunityModel>.from(previousState.communities);
for (var community in communities) {
if (community.uuid == communityUuid) {
community.spaces = allSpaces;
_spaceTreeBloc.add(InitialEvent());
emit(SpaceManagementLoaded(
communities: communities,
products: _cachedProducts ?? [],
selectedCommunity: community,
selectedSpace: null,
spaceModels: prevSpaceModels));
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
return;
}
}
@ -468,8 +532,7 @@ class SpaceManagementBloc
if (space.uuid != null && space.uuid!.isNotEmpty) {
List<TagModelUpdate> tagUpdates = [];
final prevSpace =
await _api.getSpace(communityUuid, space.uuid!, projectUuid);
final prevSpace = await _api.getSpace(communityUuid, space.uuid!, projectUuid);
final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces;
final List<SubspaceModel>? newSubspaces = space.subspaces;
@ -479,17 +542,17 @@ class SpaceManagementBloc
if (prevSubspaces != null || newSubspaces != null) {
if (prevSubspaces != null && newSubspaces != null) {
for (var prevSubspace in prevSubspaces) {
final existsInNew = newSubspaces
.any((subspace) => subspace.uuid == prevSubspace.uuid);
final existsInNew =
newSubspaces.any((subspace) => subspace.uuid == prevSubspace.uuid);
if (!existsInNew) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.delete, uuid: prevSubspace.uuid));
action: custom_action.Action.delete, uuid: prevSubspace.uuid));
}
}
} else if (prevSubspaces != null && newSubspaces == null) {
for (var prevSubspace in prevSubspaces) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.delete, uuid: prevSubspace.uuid));
action: custom_action.Action.delete, uuid: prevSubspace.uuid));
}
}
@ -502,14 +565,14 @@ class SpaceManagementBloc
if (newSubspace.tags != null) {
for (var tag in newSubspace.tags!) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
uuid: tag.uuid == '' ? null : tag.uuid,
action: custom_action.Action.add,
newTagUuid: tag.uuid == '' ? null : tag.uuid,
tag: tag.tag,
productUuid: tag.product?.uuid));
}
}
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.add,
action: custom_action.Action.add,
subspaceName: newSubspace.subspaceName,
tags: tagUpdates));
}
@ -517,9 +580,7 @@ class SpaceManagementBloc
}
if (prevSubspaces != null && newSubspaces != null) {
final newSubspaceMap = {
for (var subspace in newSubspaces) subspace.uuid: subspace
};
final newSubspaceMap = {for (var subspace in newSubspaces) subspace.uuid: subspace};
for (var prevSubspace in prevSubspaces) {
final newSubspace = newSubspaceMap[prevSubspace.uuid];
@ -528,7 +589,7 @@ class SpaceManagementBloc
final List<TagModelUpdate> tagSubspaceUpdates =
processTagUpdates(prevSubspace.tags, newSubspace.tags);
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.update,
action: custom_action.Action.update,
uuid: newSubspace.uuid,
subspaceName: newSubspace.subspaceName,
tags: tagSubspaceUpdates));
@ -548,6 +609,7 @@ class SpaceManagementBloc
subspaces: subspaceUpdates,
tags: tagUpdates,
direction: space.incomingConnection?.direction,
spaceModelUuid: space.spaceModel?.uuid,
projectId: projectUuid);
} else {
// Call create if the space does not have a UUID
@ -556,10 +618,8 @@ class SpaceManagementBloc
: [];
final createSubspaceBodyModels = space.subspaces?.map((subspace) {
final tagBodyModels = subspace.tags
?.map((tag) => tag.toCreateTagBodyModel())
.toList() ??
[];
final tagBodyModels =
subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? [];
return CreateSubspaceModel()
..subspaceName = subspace.subspaceName
..tags = tagBodyModels;
@ -612,38 +672,43 @@ class SpaceManagementBloc
return result.toList(); // Convert back to a list
}
void _onLoadSpaceModel(
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
void _onLoadSpaceModel(SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
emit(SpaceManagementLoading());
try {
var prevState = state;
await fetchTags();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = event.context.read<SpaceTreeBloc>();
List<CommunityModel> communities = spaceBloc.state.communityList;
List<CommunityModel> communities =
await _api.fetchCommunities(projectUuid);
var prevSpaceModels = await fetchSpaceModels();
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces, // New spaces list
region: community.region,
);
}).toList(),
);
if (communities.isEmpty) {
communities = await _api.fetchCommunities(projectUuid);
var prevSpaceModels = await fetchSpaceModels(prevState);
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces = await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces,
region: community.region,
);
}).toList(),
);
communities = updatedCommunities;
}
emit(SpaceModelLoaded(
communities: updatedCommunities,
communities: communities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels));
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
} catch (e) {
emit(SpaceManagementError('Error loading communities and spaces: $e'));
}
@ -659,9 +724,9 @@ class SpaceManagementBloc
if (prevTags == null && newTags != null) {
for (var newTag in newTags) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
action: custom_action.Action.add,
tag: newTag.tag,
uuid: newTag.uuid,
newTagUuid: newTag.uuid,
productUuid: newTag.product?.uuid,
));
}
@ -672,17 +737,14 @@ class SpaceManagementBloc
// Case 1: Tags deleted
if (prevTags != null && newTags != null) {
for (var prevTag in prevTags) {
final existsInNew =
newTags.any((newTag) => newTag.uuid == prevTag.uuid);
final existsInNew = newTags.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) {
tagUpdates
.add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid));
tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
}
}
} else if (prevTags != null && newTags == null) {
for (var prevTag in prevTags) {
tagUpdates
.add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid));
tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
}
}
@ -695,9 +757,9 @@ class SpaceManagementBloc
if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) &&
!processedTags.contains(newTag.tag)) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
action: custom_action.Action.add,
tag: newTag.tag,
uuid: newTag.uuid == '' ? null : newTag.uuid,
newTagUuid: newTag.uuid == '' ? null : newTag.uuid,
productUuid: newTag.product?.uuid));
processedTags.add(newTag.tag);
}
@ -712,8 +774,9 @@ class SpaceManagementBloc
final newTag = newTagMap[prevTag.uuid];
if (newTag != null) {
tagUpdates.add(TagModelUpdate(
action: Action.update,
action: custom_action.Action.update,
uuid: newTag.uuid,
newTagUuid: newTag.uuid,
tag: newTag.tag,
));
} else {}

View File

@ -1,16 +1,23 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; // Import for Offset
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; // Import for Offset
abstract class SpaceManagementEvent extends Equatable {
const SpaceManagementEvent();
@override
List<Object> get props => [];
List<Object?> get props => [];
}
class LoadCommunityAndSpacesEvent extends SpaceManagementEvent {}
class LoadCommunityAndSpacesEvent extends SpaceManagementEvent {
final BuildContext context;
const LoadCommunityAndSpacesEvent(this.context);
@override
List<Object?> get props => [context];
}
class DeleteCommunityEvent extends SpaceManagementEvent {
final String communityUuid;
@ -74,14 +81,12 @@ class UpdateSpacePositionEvent extends SpaceManagementEvent {
class CreateCommunityEvent extends SpaceManagementEvent {
final String name;
final String description;
final BuildContext context;
const CreateCommunityEvent({
required this.name,
required this.description,
});
const CreateCommunityEvent(this.name, this.description, this.context);
@override
List<Object> get props => [name, description];
List<Object?> get props => [name, description, context];
}
class UpdateCommunityEvent extends SpaceManagementEvent {
@ -141,7 +146,28 @@ class LoadSpaceHierarchyEvent extends SpaceManagementEvent {
List<Object> get props => [communityId];
}
class BlankStateEvent extends SpaceManagementEvent {
final BuildContext context;
class BlankStateEvent extends SpaceManagementEvent {}
const BlankStateEvent(this.context);
@override
List<Object?> get props => [context];
}
class SpaceModelLoadEvent extends SpaceManagementEvent {}
class SpaceModelLoadEvent extends SpaceManagementEvent {
final BuildContext context;
const SpaceModelLoadEvent(this.context);
@override
List<Object?> get props => [context];
}
class UpdateSpaceModelCache extends SpaceManagementEvent {
final SpaceTemplateModel updatedModel;
UpdateSpaceModelCache(this.updatedModel);
}
class DeleteSpaceModelFromCache extends SpaceManagementEvent {
final String deletedUuid;
DeleteSpaceModelFromCache(this.deletedUuid);
}

View File

@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
abstract class SpaceManagementState extends Equatable {
@ -21,26 +22,25 @@ class SpaceManagementLoaded extends SpaceManagementState {
CommunityModel? selectedCommunity;
SpaceModel? selectedSpace;
List<SpaceTemplateModel>? spaceModels;
List<Tag> allTags;
SpaceManagementLoaded(
{required this.communities,
required this.products,
this.selectedCommunity,
this.selectedSpace,
this.spaceModels});
}
class SpaceModelManagenetLoaded extends SpaceManagementState {
SpaceModelManagenetLoaded();
this.spaceModels,
required this.allTags});
}
class BlankState extends SpaceManagementState {
final List<CommunityModel> communities;
final List<ProductModel> products;
List<SpaceTemplateModel>? spaceModels;
final List<Tag> allTags;
BlankState(
{required this.communities, required this.products, this.spaceModels});
{required this.communities, required this.products, this.spaceModels, required this.allTags});
}
class SpaceCreationSuccess extends SpaceManagementState {
@ -65,13 +65,14 @@ class SpaceModelLoaded extends SpaceManagementState {
List<SpaceTemplateModel> spaceModels;
final List<ProductModel> products;
final List<CommunityModel> communities;
final List<Tag> allTags;
SpaceModelLoaded({
required this.communities,
required this.products,
required this.spaceModels,
});
SpaceModelLoaded(
{required this.communities,
required this.products,
required this.spaceModels,
required this.allTags});
@override
List<Object> get props => [communities, products, spaceModels];
List<Object> get props => [communities, products, spaceModels, allTags];
}

View File

@ -25,22 +25,21 @@ class Tag extends BaseTag {
return Tag(
uuid: json['uuid'] ?? '',
internalId: internalId,
tag: json['tag'] ?? '',
product: json['product'] != null
? ProductModel.fromMap(json['product'])
: null,
tag: json['name'] ?? '',
product: json['product'] != null ? ProductModel.fromMap(json['product']) : null,
);
}
@override
Tag copyWith({
String? uuid,
String? tag,
ProductModel? product,
String? location,
String? internalId,
}) {
return Tag(
uuid: uuid,
uuid: uuid ?? this.uuid,
tag: tag ?? this.tag,
product: product ?? this.product,
location: location ?? this.location,
@ -60,7 +59,7 @@ class Tag extends BaseTag {
extension TagModelExtensions on Tag {
TagBodyModel toTagBodyModel() {
return TagBodyModel()
..uuid = uuid ?? ''
..uuid = uuid
..tag = tag ?? ''
..productUuid = product?.uuid;
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
@ -10,6 +11,7 @@ import 'package:syncrow_web/pages/spaces_management/structure_selector/view/cent
import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class SpaceManagementPage extends StatefulWidget {
@ -28,19 +30,22 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => SpaceManagementBloc(
create: (context) => SpaceManagementBloc(
_api,
_productApi,
_spaceModelApi,
)..add(LoadCommunityAndSpacesEvent()),
BlocProvider.of<SpaceTreeBloc>(context),
)..add(LoadCommunityAndSpacesEvent(this.context)),
),
BlocProvider(
create: (_) => CenterBodyBloc(),
create: (context) => CenterBodyBloc(),
),
],
child: WebScaffold(
appBarTitle: Text('Space Management',
style: Theme.of(context).textTheme.headlineLarge),
appBarTitle: Text(
'Space Management',
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
enableMenuSidebar: false,
centerBody: CenterBodyWidget(),
rightBody: const NavigateHomeGridView(),
@ -55,6 +60,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
selectedSpace: null,
products: state.products,
shouldNavigateToSpaceModelPage: false,
projectTags: state.allTags,
);
} else if (state is SpaceManagementLoaded) {
return LoadedSpaceView(
@ -64,6 +70,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
products: state.products,
spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: false,
projectTags: state.allTags,
);
} else if (state is SpaceModelLoaded) {
return LoadedSpaceView(
@ -71,6 +78,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
products: state.products,
spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: true,
projectTags: state.allTags,
);
} else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}'));

View File

@ -73,17 +73,14 @@ class _BlankCommunityWidgetState extends State<BlankCommunityWidget> {
context: parentContext,
builder: (context) => CreateCommunityDialog(
isEditMode: false,
existingCommunityNames: widget.communities.map((community) => community.name).toList(),
existingCommunityNames:
widget.communities.map((community) => community.name).toList(),
onCreateCommunity: (String communityName, String description) {
parentContext.read<SpaceManagementBloc>().add(
CreateCommunityEvent(
name: communityName,
description: description,
),
CreateCommunityEvent(communityName, description, context),
);
},
),
);
}
}

View File

@ -36,16 +36,17 @@ class CommunityStructureArea extends StatefulWidget {
final List<CommunityModel> communities;
final List<SpaceModel> spaces;
final List<SpaceTemplateModel>? spaceModels;
final List<Tag> projectTags;
CommunityStructureArea({
this.selectedCommunity,
this.selectedSpace,
required this.communities,
this.products,
required this.spaces,
this.onSpaceSelected,
this.spaceModels,
});
CommunityStructureArea(
{this.selectedCommunity,
this.selectedSpace,
required this.communities,
this.products,
required this.spaces,
this.onSpaceSelected,
this.spaceModels,
required this.projectTags});
@override
_CommunityStructureAreaState createState() => _CommunityStructureAreaState();
@ -64,8 +65,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void initState() {
super.initState();
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces();
_nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '',
@ -92,14 +92,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (oldWidget.spaces != widget.spaces) {
setState(() {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces();
});
}
if (widget.selectedSpace != oldWidget.selectedSpace &&
widget.selectedSpace != null) {
if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_moveToSpace(widget.selectedSpace!);
});
@ -182,8 +180,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
connection, widget.selectedSpace)
? 1.0
: 0.3, // Adjust opacity
child: CustomPaint(
painter: CurvedLinePainter([connection])),
child: CustomPaint(painter: CurvedLinePainter([connection])),
),
for (var entry in spaces.asMap().entries)
if (entry.value.status != SpaceStatus.deleted &&
@ -193,15 +190,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
top: entry.value.position.dy,
child: SpaceCardWidget(
index: entry.key,
onButtonTap: (int index, Offset newPosition,
String direction) {
_showCreateSpaceDialog(
screenSize,
position:
spaces[index].position + newPosition,
parentIndex: index,
direction: direction,
);
onButtonTap: (int index, Offset newPosition, String direction) {
_showCreateSpaceDialog(screenSize,
position: spaces[index].position + newPosition,
parentIndex: index,
direction: direction,
projectTags: widget.projectTags);
},
position: entry.value.position,
isHovered: entry.value.isHovered,
@ -211,9 +205,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_updateNodePosition(entry.value, newPosition);
},
buildSpaceContainer: (int index) {
final bool isHighlighted =
SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace);
final bool isHighlighted = SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace);
return Opacity(
opacity: isHighlighted ? 1.0 : 0.3,
@ -238,7 +231,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
onTap: () {
_showCreateSpaceDialog(screenSize,
canvasHeight: canvasHeight,
canvasWidth: canvasWidth);
canvasWidth: canvasWidth,
projectTags: widget.projectTags);
},
),
),
@ -292,26 +286,22 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
int? parentIndex,
String? direction,
double? canvasWidth,
double? canvasHeight}) {
double? canvasHeight,
required List<Tag> projectTags}) {
showDialog(
context: context,
builder: (BuildContext context) {
return CreateSpaceDialog(
products: widget.products,
spaceModels: widget.spaceModels,
allTags:
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
projectTags: projectTags,
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
setState(() {
// Set the first space in the center or use passed position
Offset centerPosition =
position ?? ConnectionHelper.getCenterPosition(screenSize);
Offset centerPosition = position ?? ConnectionHelper.getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel(
name: name,
icon: icon,
@ -356,21 +346,17 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
spaceModels: widget.spaceModels,
name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon,
parentSpace: SpaceHelper.findSpaceByInternalId(
widget.selectedSpace?.parent?.internalId, spaces),
projectTags: widget.projectTags,
parentSpace:
SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces),
editSpace: widget.selectedSpace,
currentSpaceModel: widget.selectedSpace?.spaceModel,
tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces,
isEdit: true,
allTags: TagHelper.getAllTagValues(
widget.communities, widget.spaceModels),
onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
setState(() {
// Update the space's properties
widget.selectedSpace!.name = name;
@ -380,8 +366,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
widget.selectedSpace!.tags = tags;
if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
widget.selectedSpace!.status =
SpaceStatus.modified; // Mark as modified
widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified
}
for (var space in spaces) {
@ -411,8 +396,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
List<SpaceModel> result = [];
void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted ||
space.status == SpaceStatus.parentDeleted) {
if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
return;
}
result.add(space);
@ -527,16 +511,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _selectSpace(BuildContext context, SpaceModel space) {
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(
selectedCommunity: widget.selectedCommunity,
selectedSpace: space),
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space),
);
}
void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(
selectedCommunity: widget.selectedCommunity, selectedSpace: null),
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null),
);
}
@ -625,19 +606,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
const double horizontalGap = 200.0;
const double verticalGap = 100.0;
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition,
SpaceModel? duplicatedParent) {
Offset newPosition =
Offset(parentPosition.dx + horizontalGap, original.position.dy);
SpaceModel duplicateRecursive(
SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) {
Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy);
while (spaces.any((s) =>
(s.position - newPosition).distance < horizontalGap &&
s.status != SpaceStatus.deleted)) {
(s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) {
newPosition += Offset(horizontalGap, 0);
}
final duplicatedName =
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final List<SubspaceModel>? duplicatedSubspaces;
final List<Tag>? duplicatedTags;
@ -681,8 +659,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (original.parent != null && duplicatedParent == null) {
final originalParent = original.parent!;
final duplicatedParent =
originalToDuplicate[originalParent] ?? originalParent;
final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent;
final parentConnection = Connection(
startSpace: duplicatedParent,
@ -698,8 +675,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final childrenWithDownDirection = original.children
.where((child) =>
child.incomingConnection?.direction == "down" &&
child.status != SpaceStatus.deleted)
child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted)
.toList();
Offset childStartPosition = childrenWithDownDirection.length == 1
@ -707,8 +683,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
: newPosition + Offset(0, verticalGap);
for (final child in original.children) {
final isDownDirection =
child.incomingConnection?.direction == "down" ?? false;
final isDownDirection = child.incomingConnection?.direction == "down" ?? false;
if (isDownDirection && childrenWithDownDirection.length == 1) {
childStartPosition = duplicated.position + Offset(0, verticalGap);
@ -716,8 +691,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
childStartPosition = duplicated.position + Offset(horizontalGap, 0);
}
final duplicatedChild =
duplicateRecursive(child, childStartPosition, duplicated);
final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated);
duplicated.children.add(duplicatedChild);
childStartPosition += Offset(0, verticalGap);
}
@ -728,8 +702,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (space.parent == null) {
duplicateRecursive(space, space.position, null);
} else {
final duplicatedParent =
originalToDuplicate[space.parent!] ?? space.parent!;
final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!;
duplicateRecursive(space, space.position, duplicatedParent);
}
}

View File

@ -21,15 +21,17 @@ class CommunityTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomExpansionTile(
title: title,
initiallyExpanded: isExpanded,
isSelected: isSelected,
onExpansionChanged: (bool expanded) {
onExpansionChanged(title, expanded);
},
onItemSelected: onItemSelected,
children: children ?? [],
);
return Padding(
padding: const EdgeInsets.all(8.0),
child: CustomExpansionTile(
title: title,
initiallyExpanded: isExpanded,
isSelected: isSelected,
onExpansionChanged: (bool expanded) {
onExpansionChanged(title, expanded);
},
onItemSelected: onItemSelected,
children: children ?? [],
));
}
}

View File

@ -42,6 +42,7 @@ class CreateSpaceDialog extends StatefulWidget {
final List<Tag>? tags;
final List<String>? allTags;
final SpaceTemplateModel? currentSpaceModel;
final List<Tag> projectTags;
const CreateSpaceDialog(
{super.key,
@ -57,7 +58,8 @@ class CreateSpaceDialog extends StatefulWidget {
this.spaceModels,
this.subspaces,
this.tags,
this.currentSpaceModel});
this.currentSpaceModel,
required this.projectTags});
@override
CreateSpaceDialogState createState() => CreateSpaceDialogState();
@ -80,10 +82,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
super.initState();
selectedIcon = widget.icon ?? Assets.location;
nameController = TextEditingController(text: widget.name ?? '');
selectedProducts =
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty;
if (widget.currentSpaceModel != null) {
subspaces = [];
tags = [];
@ -96,15 +96,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
@override
Widget build(BuildContext context) {
bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty ||
subspaces != null && subspaces!.isNotEmpty);
bool isSpaceModelDisabled =
(tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty);
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog(
title: widget.isEdit
? const Text('Edit Space')
: const Text('Create New Space'),
title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'),
backgroundColor: ColorsManager.whiteColors,
content: SizedBox(
width: screenWidth * 0.5,
@ -178,7 +176,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) {
if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) {
if (SpaceHelper.isNameConflict(
value, widget.parentSpace, widget.editSpace)) {
isNameFieldExist = true;
isOkButtonEnabled = false;
} else {
@ -245,9 +244,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: EdgeInsets.zero,
),
onPressed: () {
isSpaceModelDisabled
? null
: _showLinkSpaceModelDialog(context);
isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context);
},
child: ButtonContentWidget(
svgAssets: Assets.link,
@ -257,8 +254,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
)
: Container(
width: screenWidth * 0.25,
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 16.0),
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
@ -273,8 +269,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: ColorsManager.spaceColor),
.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
@ -302,6 +297,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
),
onDeleted: () => setState(() {
this.selectedSpaceModel = null;
subspaces = widget.subspaces ?? [];
tags = widget.tags ?? [];
})),
],
),
@ -343,8 +340,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
onPressed: () async {
isTagsAndSubspaceModelDisabled
? null
: _showSubSpaceDialog(context, enteredName,
[], false, widget.products, subspaces);
: _showSubSpaceDialog(
context, enteredName, [], false, widget.products, subspaces);
},
child: ButtonContentWidget(
icon: Icons.add,
@ -371,22 +368,16 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null)
...subspaces!.map((subspace) {
return Column(
crossAxisAlignment:
CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SubspaceNameDisplayWidget(
text: subspace.subspaceName,
validateName: (updatedName) {
bool nameExists =
subspaces!.any((s) {
bool isSameId = s.internalId ==
subspace.internalId;
bool isSameName = s.subspaceName
.trim()
.toLowerCase() ==
updatedName
.trim()
.toLowerCase();
bool nameExists = subspaces!.any((s) {
bool isSameId = s.internalId == subspace.internalId;
bool isSameName =
s.subspaceName.trim().toLowerCase() ==
updatedName.trim().toLowerCase();
return !isSameId && isSameName;
});
@ -395,8 +386,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
},
onNameChanged: (updatedName) {
setState(() {
subspace.subspaceName =
updatedName;
subspace.subspaceName = updatedName;
});
},
),
@ -405,8 +395,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}),
EditChip(
onTap: () async {
_showSubSpaceDialog(context, enteredName,
[], true, widget.products, subspaces);
_showSubSpaceDialog(context, enteredName, [], true,
widget.products, subspaces);
},
)
],
@ -415,9 +405,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
),
const SizedBox(height: 10),
(tags?.isNotEmpty == true ||
subspaces?.any((subspace) =>
subspace.tags?.isNotEmpty == true) ==
true)
subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true)
? SizedBox(
width: screenWidth * 0.25,
child: Container(
@ -437,16 +425,14 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
// Combine tags from spaceModel and subspaces
...TagHelper.groupTags([
...?tags,
...?subspaces?.expand(
(subspace) => subspace.tags ?? [])
...?subspaces?.expand((subspace) => subspace.tags ?? [])
]).entries.map(
(entry) => Chip(
avatar: SizedBox(
width: 24,
height: 24,
child: SvgPicture.asset(
entry.key.icon ??
'assets/icons/gateway.svg',
entry.key.icon ?? 'assets/icons/gateway.svg',
fit: BoxFit.contain,
),
),
@ -455,15 +441,11 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(
color: ColorsManager
.spaceColor),
?.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor:
ColorsManager.whiteColors,
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16),
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor,
),
@ -472,23 +454,21 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
),
EditChip(onTap: () async {
final result = await showDialog(
await showDialog(
context: context,
builder: (context) => AssignTagDialog(
products: widget.products,
subspaces: subspaces,
allTags: widget.allTags,
addedProducts: TagHelper
.createInitialSelectedProductsForTags(
addedProducts:
TagHelper.createInitialSelectedProductsForTags(
tags ?? [], subspaces),
title: 'Edit Device',
initialTags:
TagHelper.generateInitialForTags(
spaceTags: tags,
subspaces: subspaces),
initialTags: TagHelper.generateInitialForTags(
spaceTags: tags, subspaces: subspaces),
spaceName: widget.name ?? '',
onSave:
(updatedTags, updatedSubspaces) {
projectTags: widget.projectTags,
onSave: (updatedTags, updatedSubspaces) {
setState(() {
tags = updatedTags;
subspaces = updatedSubspaces;
@ -547,25 +527,17 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
});
return;
} else {
String newName = enteredName.isNotEmpty
? enteredName
: (widget.name ?? '');
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
if (newName.isNotEmpty) {
widget.onCreateSpace(
newName,
selectedIcon,
selectedProducts,
selectedSpaceModel,
subspaces,
tags);
widget.onCreateSpace(newName, selectedIcon, selectedProducts,
selectedSpaceModel, subspaces, tags);
Navigator.of(context).pop();
}
}
},
borderRadius: 10,
backgroundColor: isOkButtonEnabled
? ColorsManager.secondaryColor
: ColorsManager.grayColor,
backgroundColor:
isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'),
),
@ -592,7 +564,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
);
}
void _showLinkSpaceModelDialog(BuildContext context) {
showDialog(
context: context,
@ -613,13 +584,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
);
}
void _showSubSpaceDialog(
BuildContext context,
String name,
final List<Tag>? spaceTags,
bool isEdit,
List<ProductModel>? products,
final List<SubspaceModel>? existingSubSpaces) {
void _showSubSpaceDialog(BuildContext context, String name, final List<Tag>? spaceTags,
bool isEdit, List<ProductModel>? products, final List<SubspaceModel>? existingSubSpaces) {
showDialog(
context: context,
builder: (BuildContext context) {
@ -634,12 +600,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
final List<Tag> tagsToAppendToSpace = [];
if (slectedSubspaces != null) {
final updatedIds =
slectedSubspaces.map((s) => s.internalId).toSet();
final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet();
if (existingSubSpaces != null) {
final deletedSubspaces = existingSubSpaces
.where((s) => !updatedIds.contains(s.internalId))
.toList();
final deletedSubspaces =
existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList();
for (var s in deletedSubspaces) {
if (s.tags != null) {
tagsToAppendToSpace.addAll(s.tags!);
@ -659,20 +623,20 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
);
}
void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
List<ProductModel>? products) {
void _showTagCreateDialog(
BuildContext context, String name, bool isEdit, List<ProductModel>? products) {
isEdit
? showDialog(
context: context,
builder: (BuildContext context) {
return AssignTagDialog(
title: 'Edit Device',
addedProducts: TagHelper.createInitialSelectedProductsForTags(
tags, subspaces),
addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
spaceName: name,
products: products,
subspaces: subspaces,
allTags: widget.allTags,
projectTags: widget.projectTags,
onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() {
tags = selectedSpaceTags;
@ -682,8 +646,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) {
for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName ==
selectedSubspace.subspaceName) {
if (subspace.subspaceName == selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags;
}
}
@ -705,9 +668,9 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
spaceTags: tags,
isCreate: true,
allTags: widget.allTags,
projectTags: widget.projectTags,
initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(
tags, subspaces),
TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() {
tags = selectedSpaceTags;
@ -717,8 +680,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) {
for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName ==
selectedSubspace.subspaceName) {
if (subspace.subspaceName == selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags;
}
}

View File

@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart';
@ -19,16 +21,17 @@ class LoadedSpaceView extends StatefulWidget {
final List<ProductModel>? products;
final List<SpaceTemplateModel>? spaceModels;
final bool shouldNavigateToSpaceModelPage;
final List<Tag> projectTags;
const LoadedSpaceView({
super.key,
required this.communities,
this.selectedCommunity,
this.selectedSpace,
this.products,
this.spaceModels,
required this.shouldNavigateToSpaceModelPage,
});
const LoadedSpaceView(
{super.key,
required this.communities,
this.selectedCommunity,
this.selectedSpace,
this.products,
this.spaceModels,
required this.shouldNavigateToSpaceModelPage,
required this.projectTags});
@override
_LoadedSpaceViewState createState() => _LoadedSpaceViewState();
@ -40,6 +43,7 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
@override
void initState() {
super.initState();
_spaceModels = List.from(widget.spaceModels ?? []);
}
@ -47,6 +51,7 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
@override
void didUpdateWidget(covariant LoadedSpaceView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.spaceModels != oldWidget.spaceModels) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
@ -82,12 +87,14 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
Expanded(
child: BlocProvider(
create: (context) => SpaceModelBloc(
BlocProvider.of<SpaceTreeBloc>(context),
api: SpaceModelManagementApi(),
initialSpaceModels: _spaceModels,
initialSpaceModels: widget.spaceModels ?? [],
),
child: SpaceModelPage(
products: widget.products,
onSpaceModelsUpdated: _onSpaceModelsUpdated,
projectTags: widget.projectTags,
),
),
),
@ -97,8 +104,9 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
children: [
SidebarWidget(
communities: widget.communities,
selectedSpaceUuid:
widget.selectedSpace?.uuid ?? widget.selectedCommunity?.uuid ?? '',
selectedSpaceUuid: widget.selectedSpace?.uuid ??
widget.selectedCommunity?.uuid ??
'',
),
CommunityStructureArea(
selectedCommunity: widget.selectedCommunity,
@ -107,6 +115,7 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
products: widget.products,
communities: widget.communities,
spaceModels: _spaceModels,
projectTags: widget.projectTags,
),
],
),

View File

@ -205,30 +205,32 @@ class _SidebarWidgetState extends State<SidebarWidget> {
);
}
Widget _buildSpaceTile(SpaceModel space, CommunityModel community) {
Widget _buildSpaceTile(SpaceModel space, CommunityModel community, {int depth = 1}) {
bool isExpandedSpace = _isSpaceOrChildSelected(space);
return SpaceTile(
title: space.name,
key: ValueKey(space.uuid),
isSelected: _selectedId == space.uuid,
initiallyExpanded: isExpandedSpace,
onExpansionChanged: (bool expanded) {
_handleExpansionChange(space.uuid ?? '', expanded);
},
onItemSelected: () {
setState(() {
_selectedId = space.uuid;
_selectedSpaceUuid = space.uuid;
});
return Padding(
padding: EdgeInsets.only(left: depth * 16.0),
child: SpaceTile(
title: space.name,
key: ValueKey(space.uuid),
isSelected: _selectedId == space.uuid,
initiallyExpanded: isExpandedSpace,
onExpansionChanged: (bool expanded) {
_handleExpansionChange(space.uuid ?? '', expanded);
},
onItemSelected: () {
setState(() {
_selectedId = space.uuid;
_selectedSpaceUuid = space.uuid;
});
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(selectedCommunity: community, selectedSpace: space),
);
},
children: space.children.isNotEmpty
? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList()
: [], // Recursively render child spaces if available
);
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(selectedCommunity: community, selectedSpace: space),
);
},
children: space.children.isNotEmpty
? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList()
: [], // Recursively render child spaces if available
));
}
void _handleExpansionChange(String uuid, bool expanded) {}

View File

@ -35,18 +35,20 @@ class _SpaceTileState extends State<SpaceTile> {
@override
Widget build(BuildContext context) {
return CustomExpansionTile(
isSelected: widget.isSelected,
title: widget.title,
initiallyExpanded: _isExpanded,
onItemSelected: widget.onItemSelected,
onExpansionChanged: (bool expanded) {
setState(() {
_isExpanded = expanded;
});
widget.onExpansionChanged(expanded);
},
children: widget.children ?? [],
);
return Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0),
child: CustomExpansionTile(
isSelected: widget.isSelected,
title: widget.title,
initiallyExpanded: _isExpanded,
onItemSelected: widget.onItemSelected,
onExpansionChanged: (bool expanded) {
setState(() {
_isExpanded = expanded;
});
widget.onExpansionChanged(expanded);
},
children: widget.children ?? [],
));
}
}

View File

@ -4,17 +4,16 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_e
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final List<String> allTags;
final List<Tag> projectTags;
AssignTagBloc(this.allTags) : super(AssignTagInitial()) {
AssignTagBloc(this.projectTags) : super(AssignTagInitial()) {
on<InitializeTags>((event, emit) {
final initialTags = event.initialTags ?? [];
final existingTagCounts = <String, int>{};
for (var tag in initialTags) {
if (tag.product != null) {
existingTagCounts[tag.product!.uuid] =
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1;
}
}
@ -23,17 +22,14 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 ||
selectedProduct.count <= existingCount) {
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) {
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue;
}
final missingCount = selectedProduct.count - existingCount;
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) {
tags.addAll(List.generate(
@ -47,11 +43,11 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
}
}
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded(
tags: tags,
updatedTags: updatedTags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: '',
));
@ -62,9 +58,16 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
tags[event.index] = tags[event.index].copyWith(tag: event.tag);
if (event.index < 0 || event.index >= tags.length) return;
final updatedTags = _calculateAvailableTags(allTags, tags);
tags[event.index] = tags[event.index].copyWith(
tag: event.tag.tag,
uuid: event.tag.uuid,
product: event.tag.product,
internalId: event.tag.internalId,
location: event.tag.location,
);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded(
tags: tags,
@ -82,10 +85,9 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final tags = List<Tag>.from(currentState.tags);
// Update the location
tags[event.index] =
tags[event.index].copyWith(location: event.location);
tags[event.index] = tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded(
tags: tags,
@ -104,7 +106,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded(
tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags),
updatedTags: _calculateAvailableTags(projectTags, tags),
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
@ -115,11 +117,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final currentState = state;
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags)
..remove(event.tagToDelete);
final tags = List<Tag>.from(currentState.tags)..remove(event.tagToDelete);
// Recalculate available tags
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded(
tags: tags,
@ -140,10 +141,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
// Get validation error for duplicate tags
String? _getValidationError(List<Tag> tags) {
final nonEmptyTags = tags
.map((tag) => tag.tag?.trim() ?? '')
.where((tag) => tag.isNotEmpty)
.toList();
final nonEmptyTags =
tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList();
final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) {
@ -162,14 +161,16 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
return null;
}
List<String> _calculateAvailableTags(List<String> allTags, List<Tag> tags) {
final selectedTags = tags
List<Tag> _calculateAvailableTags(List<Tag> allTags, List<Tag> selectedTags) {
final selectedTagSet = selectedTags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim())
.toSet();
final availableTags =
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList();
final availableTags = allTags
.where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
.toList();
return availableTags;
}
}

View File

@ -24,7 +24,7 @@ class InitializeTags extends AssignTagEvent {
class UpdateTagEvent extends AssignTagEvent {
final int index;
final String tag;
final Tag tag;
const UpdateTagEvent({required this.index, required this.tag});

View File

@ -5,7 +5,7 @@ abstract class AssignTagState extends Equatable {
const AssignTagState();
@override
List<Object> get props => [];
List<Object?> get props => [];
}
class AssignTagInitial extends AssignTagState {}
@ -14,7 +14,7 @@ class AssignTagLoading extends AssignTagState {}
class AssignTagLoaded extends AssignTagState {
final List<Tag> tags;
final List<String> updatedTags;
final List<Tag> updatedTags;
final bool isSaveEnabled;
final String? errorMessage;
@ -27,8 +27,7 @@ class AssignTagLoaded extends AssignTagState {
});
@override
List<Object> get props =>
[tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
List<Object?> get props => [tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
}
class AssignTagError extends AssignTagState {
@ -37,5 +36,5 @@ class AssignTagError extends AssignTagState {
const AssignTagError(this.errorMessage);
@override
List<Object> get props => [errorMessage];
List<Object?> get props => [errorMessage];
}

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/dialog_dropdown.dart';
import 'package:syncrow_web/common/dialog_textfield_dropdown.dart';
import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
@ -26,6 +26,7 @@ class AssignTagDialog extends StatelessWidget {
final String spaceName;
final String title;
final Function(List<Tag>, List<SubspaceModel>?)? onSave;
final List<Tag> projectTags;
const AssignTagDialog(
{Key? key,
@ -37,18 +38,17 @@ class AssignTagDialog extends StatelessWidget {
this.allTags,
required this.spaceName,
required this.title,
this.onSave})
this.onSave,
required this.projectTags})
: super(key: key);
@override
Widget build(BuildContext context) {
final List<String> locations = (subspaces ?? [])
.map((subspace) => subspace.subspaceName)
.toList()
..add('Main Space');
final List<String> locations =
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
return BlocProvider(
create: (_) => AssignTagBloc(allTags ?? [])
create: (_) => AssignTagBloc(projectTags)
..add(InitializeTags(
initialTags: initialTags,
addedProducts: addedProducts,
@ -70,8 +70,7 @@ class AssignTagDialog extends StatelessWidget {
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey),
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
@ -80,22 +79,15 @@ class AssignTagDialog extends StatelessWidget {
),
columns: [
DataColumn(
label: Text('#',
style:
Theme.of(context).textTheme.bodyMedium)),
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Device',
style:
Theme.of(context).textTheme.bodyMedium)),
label: Text('Device', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
numeric: false,
label: Text('Tag',
style:
Theme.of(context).textTheme.bodyMedium)),
label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Location',
style:
Theme.of(context).textTheme.bodyMedium)),
label:
Text('Location', style: Theme.of(context).textTheme.bodyMedium)),
],
rows: state.tags.isEmpty
? [
@ -103,12 +95,8 @@ class AssignTagDialog extends StatelessWidget {
DataCell(
Center(
child: Text('No Data Available',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager
.lightGrayColor,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
)),
),
),
@ -126,8 +114,7 @@ class AssignTagDialog extends StatelessWidget {
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
@ -141,31 +128,25 @@ class AssignTagDialog extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager
.lightGrayColor,
color: ColorsManager.lightGrayColor,
width: 1.0,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager
.lightGreyColor,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
context
.read<AssignTagBloc>()
.add(DeleteTag(
tagToDelete: tag,
tags: state.tags));
context.read<AssignTagBloc>().add(
DeleteTag(tagToDelete: tag, tags: state.tags));
controllers.removeAt(index);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints:
const BoxConstraints(),
constraints: const BoxConstraints(),
),
),
],
@ -173,23 +154,20 @@ class AssignTagDialog extends StatelessWidget {
),
DataCell(
Container(
alignment: Alignment
.centerLeft, // Align cell content to the left
alignment:
Alignment.centerLeft, // Align cell content to the left
child: SizedBox(
width: double
.infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown(
key: ValueKey(
'dropdown_${Uuid().v4()}_${index}'),
width: double.infinity,
child: TagDialogTextfieldDropdown(
key: ValueKey('dropdown_${const Uuid().v4()}_$index'),
items: state.updatedTags,
initialValue: tag.tag,
product: tag.product?.uuid ?? 'Unknown',
initialValue: tag,
onSelected: (value) {
controller.text = value;
context
.read<AssignTagBloc>()
.add(UpdateTagEvent(
controller.text = value.tag ?? '';
context.read<AssignTagBloc>().add(UpdateTagEvent(
index: index,
tag: value.trim(),
tag: value,
));
},
),
@ -201,12 +179,9 @@ class AssignTagDialog extends StatelessWidget {
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue:
tag.location ?? 'Main Space',
selectedValue: tag.location ?? 'Main Space',
onSelected: (value) {
context
.read<AssignTagBloc>()
.add(UpdateLocation(
context.read<AssignTagBloc>().add(UpdateLocation(
index: index,
location: value,
));
@ -238,13 +213,11 @@ class AssignTagDialog extends StatelessWidget {
label: 'Add New Device',
onPressed: () async {
final updatedTags = List<Tag>.from(state.tags);
final result =
TagHelper.processTags(updatedTags, subspaces);
final result = TagHelper.processTags(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
Navigator.of(context).pop();
@ -253,8 +226,9 @@ class AssignTagDialog extends StatelessWidget {
builder: (context) => AddDeviceTypeWidget(
products: products,
subspaces: processedSubspaces,
initialSelectedProducts: TagHelper
.createInitialSelectedProductsForTags(
projectTags: projectTags,
initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(
processedTags, processedSubspaces),
spaceName: spaceName,
spaceTags: processedTags,
@ -278,14 +252,11 @@ class AssignTagDialog extends StatelessWidget {
onPressed: state.isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.processTags(
updatedTags, subspaces);
final result = TagHelper.processTags(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<Tag>;
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop();
}

View File

@ -1,45 +1,40 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
class AssignTagModelBloc
extends Bloc<AssignTagModelEvent, AssignTagModelState> {
final List<String> allTags;
class AssignTagModelBloc extends Bloc<AssignTagModelEvent, AssignTagModelState> {
final List<Tag> projectTags;
AssignTagModelBloc(this.allTags) : super(AssignTagModelInitial()) {
AssignTagModelBloc(this.projectTags) : super(AssignTagModelInitial()) {
on<InitializeTagModels>((event, emit) {
final initialTags = event.initialTags ?? [];
final initialTags = event.initialTags;
final existingTagCounts = <String, int>{};
for (var tag in initialTags) {
if (tag.product != null) {
existingTagCounts[tag.product!.uuid] =
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1;
}
}
final tags = <TagModel>[];
final tags = <Tag>[];
for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 ||
selectedProduct.count <= existingCount) {
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) {
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue;
}
final missingCount = selectedProduct.count - existingCount;
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) {
tags.addAll(List.generate(
missingCount,
(index) => TagModel(
(index) => Tag(
tag: '',
product: selectedProduct.product,
location: 'Main Space',
@ -48,7 +43,7 @@ class AssignTagModelBloc
}
}
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded(
tags: tags,
@ -59,11 +54,20 @@ class AssignTagModelBloc
on<UpdateTag>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
tags[event.index] = tags[event.index].copyWith(tag: event.tag);
final updatedTags = _calculateAvailableTags(allTags, tags);
if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
if (event.index < 0 || event.index >= tags.length) return;
tags[event.index] = tags[event.index].copyWith(
tag: event.tag.tag,
uuid: event.tag.uuid,
product: event.tag.product,
internalId: event.tag.internalId,
location: event.tag.location,
);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded(
tags: tags,
@ -77,15 +81,12 @@ class AssignTagModelBloc
on<UpdateLocation>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
// Use copyWith for immutability
tags[event.index] =
tags[event.index].copyWith(location: event.location);
tags[event.index] = tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded(
tags: tags,
@ -99,13 +100,12 @@ class AssignTagModelBloc
on<ValidateTagModels>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
emit(AssignTagModelLoaded(
tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags),
updatedTags: _calculateAvailableTags(projectTags, tags),
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
@ -115,12 +115,10 @@ class AssignTagModelBloc
on<DeleteTagModel>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags)
..remove(event.tagToDelete);
if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags)..remove(event.tagToDelete);
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded(
tags: tags,
@ -128,24 +126,22 @@ class AssignTagModelBloc
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
}
}
});
}
bool _validateTags(List<TagModel> tags) {
bool _validateTags(List<Tag> tags) {
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
final isValid = uniqueTags.length == tags.length && !hasEmptyTag;
return isValid;
}
String? _getValidationError(List<TagModel> tags) {
String? _getValidationError(List<Tag> tags) {
// Check for duplicate tags
final nonEmptyTags = tags
.map((tag) => tag.tag?.trim() ?? '')
.where((tag) => tag.isNotEmpty)
.toList();
final nonEmptyTags =
tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList();
final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) {
@ -164,15 +160,16 @@ class AssignTagModelBloc
return null;
}
List<String> _calculateAvailableTags(
List<String> allTags, List<TagModel> tags) {
final selectedTags = tags
List<Tag> _calculateAvailableTags(List<Tag> allTags, List<Tag> selectedTags) {
final selectedTagSet = selectedTags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim())
.toSet();
final availableTags =
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList();
final availableTags = allTags
.where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
.toList();
return availableTags;
}
}

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
abstract class AssignTagModelEvent extends Equatable {
@ -10,7 +10,7 @@ abstract class AssignTagModelEvent extends Equatable {
}
class InitializeTagModels extends AssignTagModelEvent {
final List<TagModel> initialTags;
final List<Tag> initialTags;
final List<SelectedProduct> addedProducts;
const InitializeTagModels({
@ -24,7 +24,7 @@ class InitializeTagModels extends AssignTagModelEvent {
class UpdateTag extends AssignTagModelEvent {
final int index;
final String tag;
final Tag tag;
const UpdateTag({required this.index, required this.tag});
@ -45,8 +45,8 @@ class UpdateLocation extends AssignTagModelEvent {
class ValidateTagModels extends AssignTagModelEvent {}
class DeleteTagModel extends AssignTagModelEvent {
final TagModel tagToDelete;
final List<TagModel> tags;
final Tag tagToDelete;
final List<Tag> tags;
const DeleteTagModel({required this.tagToDelete, required this.tags});

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
abstract class AssignTagModelState extends Equatable {
const AssignTagModelState();
@ -13,11 +13,11 @@ class AssignTagModelInitial extends AssignTagModelState {}
class AssignTagModelLoading extends AssignTagModelState {}
class AssignTagModelLoaded extends AssignTagModelState {
final List<TagModel> tags;
final List<Tag> tags;
final bool isSaveEnabled;
final String? errorMessage;
final List<String> updatedTags;
final List<Tag> updatedTags;
const AssignTagModelLoaded({
required this.tags,

View File

@ -1,17 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/dialog_dropdown.dart';
import 'package:syncrow_web/common/dialog_textfield_dropdown.dart';
import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
@ -23,8 +23,8 @@ class AssignTagModelsDialog extends StatelessWidget {
final List<SubspaceTemplateModel>? subspaces;
final SpaceTemplateModel? spaceModel;
final List<TagModel> initialTags;
final ValueChanged<List<TagModel>>? onTagsAssigned;
final List<Tag> initialTags;
final ValueChanged<List<Tag>>? onTagsAssigned;
final List<SelectedProduct> addedProducts;
final List<String>? allTags;
final String spaceName;
@ -32,6 +32,7 @@ class AssignTagModelsDialog extends StatelessWidget {
final BuildContext? pageContext;
final List<String>? otherSpaceModels;
final List<SpaceTemplateModel>? allSpaceModels;
final List<Tag> projectTags;
const AssignTagModelsDialog(
{Key? key,
@ -46,18 +47,17 @@ class AssignTagModelsDialog extends StatelessWidget {
this.pageContext,
this.otherSpaceModels,
this.spaceModel,
this.allSpaceModels})
this.allSpaceModels,
required this.projectTags})
: super(key: key);
@override
Widget build(BuildContext context) {
final List<String> locations = (subspaces ?? [])
.map((subspace) => subspace.subspaceName)
.toList()
..add('Main Space');
final List<String> locations =
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
return BlocProvider(
create: (_) => AssignTagModelBloc(allTags ?? [])
create: (_) => AssignTagModelBloc(projectTags)
..add(InitializeTagModels(
initialTags: initialTags,
addedProducts: addedProducts,
@ -81,8 +81,7 @@ class AssignTagModelsDialog extends StatelessWidget {
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey),
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
@ -91,26 +90,17 @@ class AssignTagModelsDialog extends StatelessWidget {
),
columns: [
DataColumn(
label: Text('#',
style: Theme.of(context)
.textTheme
.bodyMedium)),
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Device',
style: Theme.of(context)
.textTheme
.bodyMedium)),
style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
numeric: false,
label: Text('Tag',
style: Theme.of(context)
.textTheme
.bodyMedium)),
label:
Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Location',
style: Theme.of(context)
.textTheme
.bodyMedium)),
style: Theme.of(context).textTheme.bodyMedium)),
],
rows: state.tags.isEmpty
? [
@ -118,13 +108,10 @@ class AssignTagModelsDialog extends StatelessWidget {
DataCell(
Center(
child: Text('No Devices Available',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager
.lightGrayColor,
)),
style:
Theme.of(context).textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
)),
),
),
const DataCell(SizedBox()),
@ -141,8 +128,7 @@ class AssignTagModelsDialog extends StatelessWidget {
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
@ -156,31 +142,25 @@ class AssignTagModelsDialog extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager
.lightGrayColor,
color: ColorsManager.lightGrayColor,
width: 1.0,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager
.lightGreyColor,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
context
.read<
AssignTagModelBloc>()
.add(DeleteTagModel(
tagToDelete: tag,
tags: state.tags));
context.read<AssignTagModelBloc>().add(
DeleteTagModel(
tagToDelete: tag, tags: state.tags));
controllers.removeAt(index);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints:
const BoxConstraints(),
constraints: const BoxConstraints(),
),
),
],
@ -191,19 +171,16 @@ class AssignTagModelsDialog extends StatelessWidget {
alignment: Alignment
.centerLeft, // Align cell content to the left
child: SizedBox(
width: double
.infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown(
key: ValueKey(
'dropdown_${Uuid().v4()}_${index}'),
width: double.infinity,
child: TagDialogTextfieldDropdown(
key: ValueKey(
'dropdown_${const Uuid().v4()}_$index'),
product: tag.product?.uuid ?? 'Unknown',
items: state.updatedTags,
initialValue: tag.tag,
initialValue: tag,
onSelected: (value) {
controller.text = value;
context
.read<
AssignTagModelBloc>()
.add(UpdateTag(
controller.text = value.tag ?? '';
context.read<AssignTagModelBloc>().add(UpdateTag(
index: index,
tag: value,
));
@ -217,12 +194,10 @@ class AssignTagModelsDialog extends StatelessWidget {
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue: tag.location ??
'Main Space',
selectedValue: tag.location ?? 'Main Space',
onSelected: (value) {
context
.read<
AssignTagModelBloc>()
.read<AssignTagModelBloc>()
.add(UpdateLocation(
index: index,
location: value,
@ -254,17 +229,13 @@ class AssignTagModelsDialog extends StatelessWidget {
builder: (buttonContext) => CancelButton(
label: 'Add New Device',
onPressed: () async {
final updatedTags =
List<TagModel>.from(state.tags);
final updatedTags = List<Tag>.from(state.tags);
final result =
TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<TagModel>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
if (context.mounted) {
Navigator.of(context).pop();
@ -272,28 +243,25 @@ class AssignTagModelsDialog extends StatelessWidget {
await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (dialogContext) =>
AddDeviceTypeModelWidget(
products: products,
subspaces: processedSubspaces,
isCreate: false,
initialSelectedProducts: TagHelper
.createInitialSelectedProducts(
processedTags,
processedSubspaces),
allTags: allTags,
spaceName: spaceName,
otherSpaceModels: otherSpaceModels,
spaceTagModels: processedTags,
pageContext: pageContext,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: updatedTags,
uuid: spaceModel?.uuid,
internalId:
spaceModel?.internalId,
subspaceModels:
processedSubspaces)),
builder: (dialogContext) => AddDeviceTypeModelWidget(
products: products,
subspaces: processedSubspaces,
isCreate: false,
initialSelectedProducts:
TagHelper.createInitialSelectedProducts(
processedTags, processedSubspaces),
allTags: allTags,
spaceName: spaceName,
otherSpaceModels: otherSpaceModels,
spaceTagModels: processedTags,
pageContext: pageContext,
projectTags: projectTags,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: updatedTags,
uuid: spaceModel?.uuid,
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces)),
);
}
},
@ -310,22 +278,16 @@ class AssignTagModelsDialog extends StatelessWidget {
: ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled
? () async {
final updatedTags =
List<TagModel>.from(state.tags);
final updatedTags = List<Tag>.from(state.tags);
final result =
TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<TagModel>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces']
as List<dynamic>);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
Navigator.of(context)
.popUntil((route) => route.isFirst);
Navigator.of(context).popUntil((route) => route.isFirst);
await showDialog(
context: context,
@ -334,16 +296,15 @@ class AssignTagModelsDialog extends StatelessWidget {
products: products,
allSpaceModels: allSpaceModels,
allTags: allTags,
projectTags: projectTags,
pageContext: pageContext,
otherSpaceModels: otherSpaceModels,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: processedTags,
uuid: spaceModel?.uuid,
internalId:
spaceModel?.internalId,
subspaceModels:
processedSubspaces),
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces),
);
},
);

View File

@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_mo
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
class TagHelper {
static Map<String, dynamic> updateTags<T>({
@ -131,9 +130,9 @@ class TagHelper {
}
static List<String> getAvailableTagModels(
List<String> allTags, List<TagModel> currentTags, TagModel currentTag) {
List<String> allTags, List<Tag> currentTags, Tag currentTag) {
List<String> availableTagsForTagModel =
TagHelper.getAvailableTags<TagModel>(
TagHelper.getAvailableTags<Tag>(
allTags: allTags,
currentTags: currentTags,
currentTag: currentTag,
@ -142,11 +141,11 @@ class TagHelper {
return availableTagsForTagModel;
}
static List<TagModel> generateInitialTags({
List<TagModel>? spaceTagModels,
static List<Tag> generateInitialTags({
List<Tag>? spaceTagModels,
List<SubspaceTemplateModel>? subspaces,
}) {
final List<TagModel> initialTags = [];
final List<Tag> initialTags = [];
if (spaceTagModels != null) {
initialTags.addAll(spaceTagModels);
@ -212,7 +211,7 @@ class TagHelper {
}
static List<SelectedProduct> createInitialSelectedProducts(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces) {
List<Tag>? tags, List<SubspaceTemplateModel>? subspaces) {
final Map<ProductModel, int> productCounts = {};
if (tags != null) {
@ -282,7 +281,7 @@ class TagHelper {
}
static int? checkTagExistInSubspaceModels(
TagModel tag, List<dynamic>? subspaces) {
Tag tag, List<dynamic>? subspaces) {
if (subspaces == null) return null;
for (int i = 0; i < subspaces.length; i++) {
@ -298,8 +297,8 @@ class TagHelper {
}
static Map<String, dynamic> updateSubspaceTagModels(
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) {
final result = TagHelper.updateTags<TagModel>(
List<Tag> updatedTags, List<SubspaceTemplateModel>? subspaces) {
final result = TagHelper.updateTags<Tag>(
updatedTags: updatedTags,
subspaces: subspaces,
getInternalId: (tag) => tag.internalId,
@ -311,7 +310,7 @@ class TagHelper {
checkTagExistInSubspace: checkTagExistInSubspaceModels,
);
final processedTags = result['updatedTags'] as List<TagModel>;
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(result['subspaces'] as List<dynamic>);

View File

@ -1,12 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart';
class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
SpaceModelBloc() : super(SpaceModelInitial()) {
on<SpaceModelSelectedEvent>((event, emit) {
emit(SpaceModelSelectedState(event.selectedIndex));
});
}
}

View File

@ -1,7 +0,0 @@
abstract class SpaceModelEvent {}
class SpaceModelSelectedEvent extends SpaceModelEvent {
final int selectedIndex;
SpaceModelSelectedEvent(this.selectedIndex);
}

View File

@ -1,9 +0,0 @@
abstract class SpaceModelState {}
class SpaceModelInitial extends SpaceModelState {}
class SpaceModelSelectedState extends SpaceModelState {
final int selectedIndex;
SpaceModelSelectedState(this.selectedIndex);
}

View File

@ -0,0 +1,104 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
class LinkSpaceToModelBloc
extends Bloc<LinkSpaceToModelEvent, LinkSpaceToModelState> {
LinkSpaceToModelBloc() : super(SpaceModelInitial()) {
on<LinkSpaceModelSelectedIdsEvent>(_getSpaceIds);
on<LinkSpaceModelEvent>(_handleLinkSpaceModel);
on<ValidateSpaceModelEvent>(_validateLinkSpaceModel);
on<LinkSpaceModelSelectedEvent>((event, emit) {
emit(SpaceModelSelectedState(event.selectedIndex));
});
}
List<String> spacesListIds = [];
bool hasSelectedSpaces = false;
String validate = '';
Future<void> _getSpaceIds(LinkSpaceModelSelectedIdsEvent event,
Emitter<LinkSpaceToModelState> emit) async {
try {
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
spacesListIds = spacesList;
}
hasSelectedSpaces =
spaceBloc.state.selectedCommunities.any((communityId) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
return spacesList.isNotEmpty;
});
if (hasSelectedSpaces) {
debugPrint("At least one space is selected.");
} else {
debugPrint("No spaces selected.");
}
} catch (e) {
debugPrint("Error in _getSpaceIds: $e");
}
}
Future<void> _handleLinkSpaceModel(
LinkSpaceModelEvent event,
Emitter<LinkSpaceToModelState> emit,
) async {
emit(SpaceModelLoading());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await SpaceModelManagementApi().linkSpaceModel(
spaceModelUuid: event.selectedSpaceMode!,
projectId: projectUuid,
spaceUuids: spacesListIds,
isOverWrite: event.isOverWrite);
emit(SpaceModelLinkSuccess());
} on DioException catch (e) {
final errorMessage = _parseDioError(e);
emit(SpaceModelOperationFailure(errorMessage));
} catch (e) {
emit(SpaceModelOperationFailure('Unexpected error: $e'));
}
}
Future<void> _validateLinkSpaceModel(
ValidateSpaceModelEvent event,
Emitter<LinkSpaceToModelState> emit,
) async {
emit(SpaceModelLoading());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await SpaceModelManagementApi().validateSpaceModel(
projectUuid,
spacesListIds,
);
emit(SpaceValidationSuccess());
} on DioException catch (e) {
final errorMessage = _parseDioError(e);
if (errorMessage ==
'Selected spaces already have linked space model / sub-spaces and devices') {
emit(const AlreadyHaveLinkedState());
} else {
emit(SpaceModelOperationFailure(errorMessage));
}
} catch (e) {
emit(SpaceModelOperationFailure('Unexpected error: $e'));
}
}
String _parseDioError(DioException e) {
if (e.response?.data is Map<String, dynamic>) {
return e.response!.data['error']['message'] ?? 'Unknown error occurred';
}
return e.message ?? 'Network request failed';
}
}

View File

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
abstract class LinkSpaceToModelEvent {}
class LinkSpaceModelSelectedEvent extends LinkSpaceToModelEvent {
final int selectedIndex;
LinkSpaceModelSelectedEvent(this.selectedIndex);
}
class LinkSpaceModelSelectedIdsEvent extends LinkSpaceToModelEvent {}
class LinkSpaceModelEvent extends LinkSpaceToModelEvent {
final String? selectedSpaceMode;
final bool isOverWrite;
LinkSpaceModelEvent({this.selectedSpaceMode, this.isOverWrite = false});
}
class ValidateSpaceModelEvent extends LinkSpaceToModelEvent {
BuildContext? context;
ValidateSpaceModelEvent({this.context});
}

View File

@ -0,0 +1,37 @@
abstract class LinkSpaceToModelState {
const LinkSpaceToModelState();
}
class SpaceModelInitial extends LinkSpaceToModelState {}
class SpaceModelLoading extends LinkSpaceToModelState {}
class LinkSpaceModelLoading extends LinkSpaceToModelState {}
class SpaceModelSelectedState extends LinkSpaceToModelState {
final int selectedIndex;
const SpaceModelSelectedState(this.selectedIndex);
}
class SpaceModelSelectionUpdated extends LinkSpaceToModelState {
final bool hasSelectedSpaces;
const SpaceModelSelectionUpdated(this.hasSelectedSpaces);
}
class SpaceValidationSuccess extends LinkSpaceToModelState {}
class SpaceModelLinkSuccess extends LinkSpaceToModelState {}
class ValidationError extends LinkSpaceToModelState {
final String message;
const ValidationError(this.message);
}
class SpaceModelOperationFailure extends LinkSpaceToModelState {
final String message;
const SpaceModelOperationFailure(this.message);
}
class AlreadyHaveLinkedState extends LinkSpaceToModelState {
const AlreadyHaveLinkedState();
}

View File

@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
@ -24,13 +24,13 @@ class LinkSpaceModelDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SpaceModelBloc()
create: (context) => LinkSpaceToModelBloc()
..add(
SpaceModelSelectedEvent(initialSelectedIndex ?? -1),
LinkSpaceModelSelectedEvent(initialSelectedIndex ?? -1),
),
child: Builder(
builder: (context) {
final bloc = context.read<SpaceModelBloc>();
final bloc = context.read<LinkSpaceToModelBloc>();
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
title: const Text('Link a space model'),
@ -39,7 +39,7 @@ class LinkSpaceModelDialog extends StatelessWidget {
color: ColorsManager.textFieldGreyColor,
width: MediaQuery.of(context).size.width * 0.7,
height: MediaQuery.of(context).size.height * 0.6,
child: BlocBuilder<SpaceModelBloc, SpaceModelState>(
child: BlocBuilder<LinkSpaceToModelBloc, LinkSpaceToModelState>(
builder: (context, state) {
int selectedIndex = -1;
if (state is SpaceModelSelectedState) {
@ -59,7 +59,7 @@ class LinkSpaceModelDialog extends StatelessWidget {
final isSelected = selectedIndex == index;
return GestureDetector(
onTap: () {
bloc.add(SpaceModelSelectedEvent(index));
bloc.add(LinkSpaceModelSelectedEvent(index));
},
child: Container(
margin: const EdgeInsets.all(10.0),
@ -93,7 +93,7 @@ class LinkSpaceModelDialog extends StatelessWidget {
label: 'Cancel',
),
const SizedBox(width: 10),
BlocBuilder<SpaceModelBloc, SpaceModelState>(
BlocBuilder<LinkSpaceToModelBloc, LinkSpaceToModelState>(
builder: (context, state) {
final isEnabled = state is SpaceModelSelectedState &&
state.selectedIndex >= 0;

View File

@ -1,17 +1,14 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
class CreateSpaceModelBloc
extends Bloc<CreateSpaceModelEvent, CreateSpaceModelState> {
@ -97,14 +94,9 @@ class CreateSpaceModelBloc
orElse: () => subspace,
);
// Update the subspace's tags
final eventTagIds = matchingEventSubspace.tags
?.map((e) => e.internalId)
.toSet() ??
{};
final updatedTags = [
...?subspace.tags?.map<TagModel>((tag) {
...?subspace.tags?.map<Tag>((tag) {
final matchingTag =
matchingEventSubspace.tags?.firstWhere(
(e) => e.internalId == tag.internalId,
@ -115,14 +107,14 @@ class CreateSpaceModelBloc
? tag.copyWith(tag: matchingTag?.tag)
: tag;
}) ??
<TagModel>[],
<Tag>[],
...?matchingEventSubspace.tags?.where(
(e) =>
subspace.tags
?.every((t) => t.internalId != e.internalId) ??
true,
) ??
<TagModel>[],
<Tag>[],
];
return subspace.copyWith(
subspaceName: matchingEventSubspace.subspaceName,
@ -247,7 +239,7 @@ class CreateSpaceModelBloc
}
if (newSubspaces != null) {
for (var newSubspace in newSubspaces!) {
for (var newSubspace in newSubspaces) {
// Tag without UUID
if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) {
final List<TagModelUpdate> tagUpdates = [];
@ -256,7 +248,7 @@ class CreateSpaceModelBloc
for (var tag in newSubspace.tags!) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
uuid: tag.uuid == '' ? null : tag.uuid,
newTagUuid: tag.uuid == '' ? null : tag.uuid,
tag: tag.tag,
productUuid: tag.product?.uuid));
}
@ -271,7 +263,7 @@ class CreateSpaceModelBloc
if (prevSubspaces != null && newSubspaces != null) {
final newSubspaceMap = {
for (var subspace in newSubspaces!) subspace.uuid: subspace
for (var subspace in newSubspaces) subspace.uuid: subspace
};
for (var prevSubspace in prevSubspaces) {
@ -312,8 +304,8 @@ class CreateSpaceModelBloc
}
List<TagModelUpdate> processTagUpdates(
List<TagModel>? prevTags,
List<TagModel>? newTags,
List<Tag>? prevTags,
List<Tag>? newTags,
) {
final List<TagModelUpdate> tagUpdates = [];
final processedTags = <String?>{};
@ -323,7 +315,7 @@ class CreateSpaceModelBloc
tagUpdates.add(TagModelUpdate(
action: Action.add,
tag: newTag.tag,
uuid: newTag.uuid,
newTagUuid: newTag.uuid,
productUuid: newTag.product?.uuid,
));
}
@ -335,7 +327,7 @@ class CreateSpaceModelBloc
if (prevTags != null && newTags != null) {
for (var prevTag in prevTags) {
final existsInNew =
newTags!.any((newTag) => newTag.uuid == prevTag.uuid);
newTags.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) {
tagUpdates
.add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid));
@ -352,14 +344,14 @@ class CreateSpaceModelBloc
if (newTags != null) {
final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {};
for (var newTag in newTags!) {
for (var newTag in newTags) {
// Tag without UUID
if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) &&
!processedTags.contains(newTag.tag)) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
tag: newTag.tag,
uuid: newTag.uuid == '' ? null : newTag.uuid,
newTagUuid: newTag.uuid == '' ? null : newTag.uuid,
productUuid: newTag.product?.uuid));
processedTags.add(newTag.tag);
}
@ -368,14 +360,15 @@ class CreateSpaceModelBloc
// Case 3: Tags updated
if (prevTags != null && newTags != null) {
final newTagMap = {for (var tag in newTags!) tag.uuid: tag};
final newTagMap = {for (var tag in newTags) tag.uuid: tag};
for (var prevTag in prevTags!) {
for (var prevTag in prevTags) {
final newTag = newTagMap[prevTag.uuid];
if (newTag != null) {
tagUpdates.add(TagModelUpdate(
action: Action.update,
uuid: newTag.uuid,
uuid: prevTag.uuid,
newTagUuid: newTag.uuid,
tag: newTag.tag,
));
} else {}

View File

@ -1,7 +1,7 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
abstract class CreateSpaceModelEvent extends Equatable {
const CreateSpaceModelEvent();
@ -49,7 +49,7 @@ class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
}
class AddTagsToSpaceTemplate extends CreateSpaceModelEvent {
final List<TagModel> tags;
final List<Tag> tags;
AddTagsToSpaceTemplate(this.tags);
}

View File

@ -1,5 +1,7 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
@ -7,8 +9,10 @@ import 'package:syncrow_web/services/space_model_mang_api.dart';
class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
final SpaceModelManagementApi api;
final SpaceTreeBloc _spaceTreeBloc;
SpaceModelBloc({
SpaceModelBloc(
this._spaceTreeBloc, {
required this.api,
required List<SpaceTemplateModel> initialSpaceModels,
}) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) {
@ -17,21 +21,18 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
on<DeleteSpaceModel>(_onDeleteSpaceModel);
}
Future<void> _onCreateSpaceModel(
CreateSpaceModel event, Emitter<SpaceModelState> emit) async {
Future<void> _onCreateSpaceModel(CreateSpaceModel event, Emitter<SpaceModelState> emit) async {
final currentState = state;
if (currentState is SpaceModelLoaded) {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final newSpaceModel = await api.getSpaceModel(
event.newSpaceModel.uuid ?? '', projectUuid);
final newSpaceModel = await api.getSpaceModel(event.newSpaceModel.uuid ?? '', projectUuid);
if (newSpaceModel != null) {
final updatedSpaceModels =
List<SpaceTemplateModel>.from(currentState.spaceModels)
..add(newSpaceModel);
final updatedSpaceModels = List<SpaceTemplateModel>.from(currentState.spaceModels)
..add(newSpaceModel);
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
}
} catch (e) {
@ -40,19 +41,18 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
}
}
Future<void> _onUpdateSpaceModel(
UpdateSpaceModel event, Emitter<SpaceModelState> emit) async {
Future<void> _onUpdateSpaceModel(UpdateSpaceModel event, Emitter<SpaceModelState> emit) async {
final currentState = state;
if (currentState is SpaceModelLoaded) {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final newSpaceModel =
await api.getSpaceModel(event.spaceModelUuid, projectUuid);
final newSpaceModel = await api.getSpaceModel(event.spaceModelUuid, projectUuid);
if (newSpaceModel != null) {
final updatedSpaceModels = currentState.spaceModels.map((model) {
return model.uuid == event.spaceModelUuid ? newSpaceModel : model;
}).toList();
_spaceTreeBloc.add(InitialEvent());
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
}
} catch (e) {
@ -61,22 +61,20 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
}
}
Future<void> _onDeleteSpaceModel(
DeleteSpaceModel event, Emitter<SpaceModelState> emit) async {
Future<void> _onDeleteSpaceModel(DeleteSpaceModel event, Emitter<SpaceModelState> emit) async {
final currentState = state;
if (currentState is SpaceModelLoaded) {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final deletedSuccessfully =
await api.deleteSpaceModel(event.spaceModelUuid, projectUuid);
final deletedSuccessfully = await api.deleteSpaceModel(event.spaceModelUuid, projectUuid);
if (deletedSuccessfully) {
final updatedSpaceModels = currentState.spaceModels
.where((model) => model.uuid != event.spaceModelUuid)
.toList();
_spaceTreeBloc.add(InitialEvent());
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
}
} catch (e) {

View File

@ -6,7 +6,7 @@ class TagBodyModel {
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'tag': tag,
'name': tag,
'productUuid': productUuid,
};
}

View File

@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
import 'package:uuid/uuid.dart';
@ -9,8 +9,9 @@ class SpaceTemplateModel extends Equatable {
String? uuid;
String modelName;
List<SubspaceTemplateModel>? subspaceModels;
final List<TagModel>? tags;
final List<Tag>? tags;
String internalId;
DateTime? createdAt;
@override
List<Object?> get props => [modelName, subspaceModels, tags];
@ -21,24 +22,27 @@ class SpaceTemplateModel extends Equatable {
required this.modelName,
this.subspaceModels,
this.tags,
this.createdAt,
}) : internalId = internalId ?? const Uuid().v4();
factory SpaceTemplateModel.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4();
return SpaceTemplateModel(
uuid: json['uuid'] ?? '',
createdAt: json['createdAt'] != null
? DateTime.tryParse(json['createdAt'])
: null,
internalId: internalId,
modelName: json['modelName'] ?? '',
subspaceModels: (json['subspaceModels'] as List<dynamic>?)
?.where((e) => e is Map<String, dynamic>) // Validate type
?.where((e) => e is Map<String, dynamic>)
.map((e) =>
SubspaceTemplateModel.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
tags: (json['tags'] as List<dynamic>?)
?.where((item) => item is Map<String, dynamic>) // Validate type
.map((item) => TagModel.fromJson(item as Map<String, dynamic>))
.map((item) => Tag.fromJson(item as Map<String, dynamic>))
.toList() ??
[],
);
@ -47,7 +51,7 @@ class SpaceTemplateModel extends Equatable {
String? uuid,
String? modelName,
List<SubspaceTemplateModel>? subspaceModels,
List<TagModel>? tags,
List<Tag>? tags,
String? internalId,
}) {
return SpaceTemplateModel(

View File

@ -1,11 +1,11 @@
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:uuid/uuid.dart';
class SubspaceTemplateModel {
final String? uuid;
String subspaceName;
final bool disabled;
List<TagModel>? tags;
List<Tag>? tags;
String internalId;
SubspaceTemplateModel({
@ -25,7 +25,7 @@ class SubspaceTemplateModel {
internalId: internalId,
disabled: json['disabled'] ?? false,
tags: (json['tags'] as List<dynamic>?)
?.map((item) => TagModel.fromJson(item))
?.map((item) => Tag.fromJson(item))
.toList() ??
[],
);
@ -44,7 +44,7 @@ class SubspaceTemplateModel {
String? uuid,
String? subspaceName,
bool? disabled,
List<TagModel>? tags,
List<Tag>? tags,
String? internalId,
}) {
return SubspaceTemplateModel(

View File

@ -4,7 +4,7 @@ class CreateTagBodyModel {
Map<String, dynamic> toJson() {
return {
'tag': tag,
'name': tag,
'productUuid': productUuid,
};
}

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