Compare commits

..
Author SHA1 Message Date
mohammad dcfbe5282e add dev and prod firebase option 2025-03-05 14:21:07 +03:00
mohammad 6f2e2e2d4a Check all the devices working with real time 2025-03-02 17:55:26 +03:00
mohammad fe52726f6e WebAppBar ResponsiveLayout 2025-02-26 12:00:30 +03:00
mohammad ba44d1d359 firebase configuration and connect device with real-time bd 2025-02-13 14:07:29 +03:00
mohammadnemer1 a623f1c723 Merge pull request #89 from SyncrowIOT/disable_edit
disable_edit_user
2025-02-06 11:17:14 +03:00
mohammad c5f5992c18 disable_edit_user 2025-02-06 11:12:43 +03:00
Abdullah Alassaf 98ad7090d8 Removed prints and unused code 2025-02-06 01:01:21 +03:00
Abdullah ea08024b82 Merge pull request #87 from SyncrowIOT/bugfix/fix-tag-repeat
Bugfix/update-subspace-tag-value
2025-02-06 00:59:11 +03:00
hannathkadher f6d66185b3 updating tags inside subspace 2025-02-06 00:18:44 +04:00
mohammadnemer1 ead5297ba1 Merge pull request #86 from SyncrowIOT/roles_permissions_bugs
Roles permissions bugs
2025-02-05 21:53:46 +03:00
mohammad 9a6bf5cbaf privacy policy fixes 2025-02-05 18:16:09 +03:00
mohammad 51fbe64209 fixes bugs 2025-02-05 16:53:36 +03:00
hannathkadher 49fa80e7d8 Merge pull request #85 from SyncrowIOT/bugfix/fix-tag-repeat
Fixed tag repeat
2025-02-05 17:47:47 +04:00
hannathkadher 1aa15e5dd6 fixed loading 2025-02-05 17:01:03 +04:00
hannathkadher 962f2d6861 Fixed tgag repeat 2025-02-05 16:00:53 +04:00
Abdullah bd6219f915 Merge pull request #84 from SyncrowIOT/side_tree
Enhanced the side tree design
2025-02-05 11:56:23 +03:00
hannathkadher 8862ad95f3 Merge pull request #83 from SyncrowIOT/bugfix/fix-issue-in-creating-space-with-duplicate
Bugfix/fix-issue-in-creating-space-with-duplicate
2025-02-05 12:09:38 +04:00
hannathkadher af4c0f84cb fixed assign tag issue 2025-02-05 11:15:38 +04:00
hannathkadher c2b77ad1fc fixed issue on duplicate 2025-02-05 11:15:25 +04:00
hannathkadher 95cee89b4c Merge pull request #82 from SyncrowIOT/SP-951-FE-Link-Space-Model-Pop-Up
added edit space sibling conflict
2025-02-04 15:57:37 +04:00
hannathkadher d5fcbe2601 added edit space sibling conflict 2025-02-04 14:15:39 +04:00
hannathkadher 1fa33a271f added disabled space model button on adding tags and subspace, vice versa 2025-02-04 13:46:11 +04:00
hannathkadher 09e2564183 add disabled state to ButtonContentWidget, When disabled, the entire button becomes opaque 2025-02-04 13:29:09 +04:00
hannathkadher 5dee6c2842 Merge pull request #81 from SyncrowIOT/bugifx/tag-validation
Bugifx/tag-validation
2025-02-04 12:36:15 +04:00
hannathkadher c5c5088724 removed logs 2025-02-04 11:38:11 +04:00
hannathkadher d1d570b40f Fixed issue in loading space model 2025-02-04 11:36:26 +04:00
Abdullah a43ff3c07d Merge pull request #80 from SyncrowIOT/side_tree
Side tree
2025-02-04 01:56:52 +03:00
hannathkadher 5e5f127a4b duplicate should be in same vertical offset 2025-02-04 00:12:20 +04:00
hannathkadher 6f51c2d2b6 provide all tags on edit space 2025-02-03 22:23:53 +04:00
mohammadnemer1 a18e8443d0 Merge pull request #77 from SyncrowIOT/web_bugs_fixes
Fix bugs related to the user table, privacy policy, and table filter.
2025-02-03 14:39:38 +03:00
hannathkadher 72241cba6c added error validation 2025-02-03 14:47:05 +04:00
hannathkadher ab3edbaf57 Merge pull request #79 from SyncrowIOT/bugfix/fix-duplicate-space
Bugfix/fix-duplicate-space
2025-02-02 23:39:37 +04:00
hannathkadher 64e3fb7f34 removed print 2025-02-02 23:38:04 +04:00
hannathkadher e6e46be9b4 fixed issues in create space and duplicate 2025-02-02 23:16:34 +04:00
hannathkadher 91dfd53477 fixed issue in creating space 2025-02-02 21:02:58 +04:00
mohammad 5ab9664318 Merged with dev 2025-02-02 11:24:45 +03:00
Abdullah d3bf4de0ca Merge pull request #78 from SyncrowIOT/side_tree
fixed issues in the space tree
2025-02-02 02:48:17 +03:00
hannathkadher e6fa9c2391 Merge pull request #76 from SyncrowIOT/bugfix/empty-subspace
add empty subspace validation
2025-01-31 10:58:11 +04:00
hannathkadher 916b606cb1 deleting subspace won't remove tag 2025-01-30 23:26:26 +04:00
hannathkadher 29c444eede updated tags 2025-01-30 22:01:08 +04:00
hannathkadher 9dd6c9e1e7 updated logic of adding tag to subspace 2025-01-30 21:15:00 +04:00
mohammad b070884bd9 Fix bugs related to the user table, privacy policy, and table filter. 2025-01-30 16:43:45 +03:00
hannathkadher bf5b39e742 add empty subspace validation 2025-01-30 15:00:40 +04:00
Abdullah 7d05a33c52 Merge pull request #75 from SyncrowIOT/side_tree
Side tree
2025-01-30 12:26:48 +03:00
97 changed files with 3495 additions and 1528 deletions
+4
View File
@@ -1,5 +1,9 @@
plugins { plugins {
id "com.android.application" 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 "kotlin-android"
id "dev.flutter.flutter-gradle-plugin" id "dev.flutter.flutter-gradle-plugin"
} }
+68
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"
}
+4
View File
@@ -20,6 +20,10 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false 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 id "org.jetbrains.kotlin.android" version "1.7.10" apply false
} }
+1
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"}}}}}}
+23
View File
@@ -15,6 +15,7 @@
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
E44A9405B1EB1B638DD05A58 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ABF0EC746A2D686A0ED574F /* Pods_RunnerTests.framework */; }; 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 */; }; FF49F60EC38658783D8D66DA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AFAE479A87ECDEBD5D6EB30 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -64,6 +65,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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 */ /* End PBXFileReference section */
@@ -138,6 +140,7 @@
331C8082294A63A400263BE5 /* RunnerTests */, 331C8082294A63A400263BE5 /* RunnerTests */,
1454C118FFCECEEDF59152D2 /* Pods */, 1454C118FFCECEEDF59152D2 /* Pods */,
20A3C64D2B1CFED5A81C3251 /* Frameworks */, 20A3C64D2B1CFED5A81C3251 /* Frameworks */,
B14AB50E8716720E10D074BD /* GoogleService-Info.plist */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -199,6 +202,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
33590C9CD073D3D5EBA02CDE /* [CP] Embed Pods Frameworks */, 33590C9CD073D3D5EBA02CDE /* [CP] Embed Pods Frameworks */,
7A77858F6F15CB76D2D3A872 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */,
); );
buildRules = ( buildRules = (
); );
@@ -264,6 +268,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
F2A3345EC3021060731668D3 /* GoogleService-Info.plist in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -303,6 +308,24 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 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 */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;
+32
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>
+93
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',
);
}
+77
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',
);
}
+10 -2
View File
@@ -1,8 +1,11 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options_dev.dart';
import 'package:syncrow_web/firebase_options_prod.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.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_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart';
@@ -17,9 +20,13 @@ import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async { Future<void> main() async {
try { try {
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development'); const environment =
String.fromEnvironment('FLAVOR', defaultValue: 'production');
await dotenv.load(fileName: '.env.$environment'); await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptionsStaging.currentPlatform,
);
initialSetup(); initialSetup();
} catch (_) {} } catch (_) {}
runApp(MyApp()); runApp(MyApp());
@@ -49,7 +56,8 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>( BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(), create: (context) => VisitorPasswordBloc(),
), ),
+84
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,
));
}
}
@@ -17,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/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.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/style.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
@@ -27,19 +28,19 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
final isLargeScreen = isLargeScreenSize(context); final isLargeScreen = isLargeScreenSize(context);
final isSmallScreen = isSmallScreenSize(context); final isSmallScreen = isSmallScreenSize(context);
final isHalfMediumScreen = isHafMediumScreenSize(context); final isHalfMediumScreen = isHafMediumScreenSize(context);
final padding = isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15); final padding =
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
return WebScaffold( return WebScaffold(
enableMenuSidebar: false, enableMenuSidebar: false,
appBarTitle: FittedBox( appBarTitle: Text(
child: Text(
'Access Management', 'Access Management',
style: Theme.of(context).textTheme.headlineLarge, style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
), ),
rightBody: const NavigateHomeGridView(), rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocProvider( scaffoldBody: BlocProvider(
create: (BuildContext context) => AccessBloc()..add(FetchTableData()), create: (BuildContext context) =>
AccessBloc()..add(FetchTableData()),
child: BlocConsumer<AccessBloc, AccessState>( child: BlocConsumer<AccessBloc, AccessState>(
listener: (context, state) {}, listener: (context, state) {},
builder: (context, state) { builder: (context, state) {
@@ -93,11 +94,14 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
return [ return [
item.passwordName, item.passwordName,
item.passwordType.value, item.passwordType.value,
accessBloc.timestampToDate(item.effectiveTime), accessBloc
accessBloc.timestampToDate(item.invalidTime), .timestampToDate(item.effectiveTime),
accessBloc
.timestampToDate(item.invalidTime),
item.deviceName.toString(), item.deviceName.toString(),
item.authorizerEmail.toString(), item.authorizerEmail.toString(),
accessBloc.timestampToDate(item.invalidTime), accessBloc
.timestampToDate(item.invalidTime),
item.passwordStatus.value, item.passwordStatus.value,
]; ];
}).toList(), }).toList(),
@@ -108,7 +112,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
}))); })));
} }
Wrap _buildVisitorAdminPasswords(BuildContext context, AccessBloc accessBloc) { Wrap _buildVisitorAdminPasswords(
BuildContext context, AccessBloc accessBloc) {
return Wrap( return Wrap(
spacing: 10, spacing: 10,
runSpacing: 10, runSpacing: 10,
@@ -134,7 +139,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
borderRadius: 8, borderRadius: 8,
child: Text( child: Text(
'Create Visitor Password ', 'Create Visitor Password ',
style: context.textTheme.titleSmall!.copyWith(color: Colors.white, fontSize: 12), style: context.textTheme.titleSmall!
.copyWith(color: Colors.white, fontSize: 12),
)), )),
), ),
// Container( // Container(
@@ -172,8 +178,10 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
description: '', description: '',
onSubmitted: (value) { onSubmitted: (value) {
accessBloc.add(FilterDataEvent( accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(), emailAuthorizer:
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex, accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(), passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp, startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp)); endTime: accessBloc.expirationTimeTimeStamp));
@@ -191,8 +199,10 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
description: '', description: '',
onSubmitted: (value) { onSubmitted: (value) {
accessBloc.add(FilterDataEvent( accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(), emailAuthorizer:
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex, accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(), passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp, startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp)); endTime: accessBloc.expirationTimeTimeStamp));
@@ -221,7 +231,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
onSearch: () { onSearch: () {
accessBloc.add(FilterDataEvent( accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(), emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex, selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(), passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp, startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp)); endTime: accessBloc.expirationTimeTimeStamp));
@@ -249,8 +260,10 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
description: '', description: '',
onSubmitted: (value) { onSubmitted: (value) {
accessBloc.add(FilterDataEvent( accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(), emailAuthorizer:
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex, accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(), passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp, startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp)); endTime: accessBloc.expirationTimeTimeStamp));
@@ -274,7 +287,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
onSearch: () { onSearch: () {
accessBloc.add(FilterDataEvent( accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(), emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex, selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(), passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp, startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp)); endTime: accessBloc.expirationTimeTimeStamp));
+31 -12
View File
@@ -31,7 +31,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
////////////////////////////// forget password ////////////////////////////////// ////////////////////////////// forget password //////////////////////////////////
final TextEditingController forgetEmailController = TextEditingController(); final TextEditingController forgetEmailController = TextEditingController();
final TextEditingController forgetPasswordController = TextEditingController(); final TextEditingController forgetPasswordController =
TextEditingController();
final TextEditingController forgetOtp = TextEditingController(); final TextEditingController forgetOtp = TextEditingController();
final forgetFormKey = GlobalKey<FormState>(); final forgetFormKey = GlobalKey<FormState>();
final forgetEmailKey = GlobalKey<FormState>(); final forgetEmailKey = GlobalKey<FormState>();
@@ -48,7 +49,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return; return;
} }
_remainingTime = 1; _remainingTime = 1;
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
try { try {
forgetEmailValidate = ''; forgetEmailValidate = '';
_remainingTime = (await AuthenticationAPI.sendOtp( _remainingTime = (await AuthenticationAPI.sendOtp(
@@ -85,7 +87,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
_timer?.cancel(); _timer?.cancel();
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
} else { } else {
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
} }
}); });
} }
@@ -95,7 +98,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
} }
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async { Future<void> changePassword(
ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState()); emit(LoadingForgetState());
try { try {
var response = await AuthenticationAPI.verifyOtp( var response = await AuthenticationAPI.verifyOtp(
@@ -111,7 +115,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
} on DioException catch (e) { } on DioException catch (e) {
final errorData = e.response!.data; final errorData = e.response!.data;
String errorMessage = errorData['error']['message'] ?? 'something went wrong'; String errorMessage =
errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage; validate = errorMessage;
emit(AuthInitialState()); emit(AuthInitialState());
} }
@@ -125,7 +130,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) { void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime)); emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
} }
///////////////////////////////////// login ///////////////////////////////////// ///////////////////////////////////// login /////////////////////////////////////
@@ -161,15 +168,23 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
password: event.password, password: event.password,
), ),
); );
} catch (failure) { } on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['error']['message'];
if (errorMessage == "Access denied for web platform") {
validate = errorMessage;
} else {
validate = 'Invalid Credentials!'; validate = 'Invalid Credentials!';
}
emit(LoginInitial()); emit(LoginInitial());
return; return;
} }
if (token.accessTokenIsNotEmpty) { if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage(); FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken); await storage.write(
key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write( const FlutterSecureStorage().write(
key: UserModel.userUuidKey, key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString()); value: Token.decodeToken(token.accessToken)['uuid'].toString());
@@ -327,12 +342,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
static Future<String> getTokenAndValidate() async { static Future<String> getTokenAndValidate() async {
try { try {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
final firstLaunch = final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true; StringsManager.firstLaunch) ??
true;
if (firstLaunch) { if (firstLaunch) {
storage.deleteAll(); storage.deleteAll();
} }
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false); await SharedPreferencesHelper.saveBoolToSP(
StringsManager.firstLaunch, false);
final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
if (value.isEmpty) { if (value.isEmpty) {
return 'Token not found'; return 'Token not found';
@@ -385,7 +402,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
final String formattedTime = [ final String formattedTime = [
if (days > 0) '${days}d', // Append 'd' for days if (days > 0) '${days}d', // Append 'd' for days
if (days > 0 || hours > 0) if (days > 0 || hours > 0)
hours.toString().padLeft(2, '0'), // Show hours if there are days or hours hours
.toString()
.padLeft(2, '0'), // Show hours if there are days or hours
minutes.toString().padLeft(2, '0'), minutes.toString().padLeft(2, '0'),
seconds.toString().padLeft(2, '0'), seconds.toString().padLeft(2, '0'),
].join(':'); ].join(':');
@@ -21,7 +21,9 @@ class LoginWithEmailModel {
return { return {
'email': email, 'email': email,
'password': password, 'password': password,
"platform": "web"
// 'regionUuid': regionUuid, // 'regionUuid': regionUuid,
}; };
} }
} }
//[email protected]
@@ -8,6 +8,8 @@ class ForgetPasswordPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const ResponsiveLayout( return const ResponsiveLayout(
desktopBody: ForgetPasswordWebPage(), mobileBody: ForgetPasswordWebPage()); tablet: ForgetPasswordWebPage(),
desktopBody: ForgetPasswordWebPage(),
mobileBody: ForgetPasswordWebPage());
} }
} }
+3 -1
View File
@@ -9,6 +9,8 @@ class LoginPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const ResponsiveLayout( return const ResponsiveLayout(
desktopBody: LoginWebPage(), mobileBody: LoginWebPage()); tablet: LoginWebPage(),
desktopBody: LoginWebPage(),
mobileBody: LoginWebPage());
} }
} }
@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/all_devices/models/device_status.dart';
@@ -19,6 +20,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
on<AcControlEvent>(_onAcControl); on<AcControlEvent>(_onAcControl);
on<AcBatchControlEvent>(_onAcBatchControl); on<AcBatchControlEvent>(_onAcBatchControl);
on<AcFactoryResetEvent>(_onFactoryReset); on<AcFactoryResetEvent>(_onFactoryReset);
on<AcStatusUpdated>(_onAcStatusUpdated);
} }
FutureOr<void> _onFetchAcStatus( FutureOr<void> _onFetchAcStatus(
@@ -28,12 +30,64 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
final status = final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(AcsFailedState(error: e.toString())); 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( FutureOr<void> _onAcControl(
AcControlEvent event, Emitter<AcsState> emit) async { AcControlEvent event, Emitter<AcsState> emit) async {
final oldValue = _getValueByCode(event.code); final oldValue = _getValueByCode(event.code);
@@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart'; 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'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
sealed class AcsEvent extends Equatable { sealed class AcsEvent extends Equatable {
@@ -7,6 +8,7 @@ sealed class AcsEvent extends Equatable {
@override @override
List<Object> get props => []; List<Object> get props => [];
} }
class AcUpdated extends AcsEvent {}
class AcFetchDeviceStatusEvent extends AcsEvent { class AcFetchDeviceStatusEvent extends AcsEvent {
final String deviceId; final String deviceId;
@@ -16,7 +18,10 @@ class AcFetchDeviceStatusEvent extends AcsEvent {
@override @override
List<Object> get props => [deviceId]; List<Object> get props => [deviceId];
} }
class AcStatusUpdated extends AcsEvent {
final AcStatusModel deviceStatus;
AcStatusUpdated(this.deviceStatus);
}
class AcFetchBatchStatusEvent extends AcsEvent { class AcFetchBatchStatusEvent extends AcsEvent {
final List<String> devicesIds; final List<String> devicesIds;
@@ -24,7 +24,8 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( 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>( child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) { builder: (context, state) {
if (state is ACStatusLoaded) { if (state is ACStatusLoaded) {
@@ -98,7 +99,8 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
), ),
Text( Text(
'h', 'h',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor),
), ),
Text( Text(
'30', '30',
@@ -107,7 +109,9 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)), Text('m',
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor)),
IconButton( IconButton(
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
onPressed: () {}, onPressed: () {},
@@ -9,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/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.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/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'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
@@ -19,17 +20,17 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices(context)), create: (context) =>
DeviceManagementBloc()..add(FetchDevices(context)),
), ),
], ],
child: WebScaffold( child: WebScaffold(
appBarTitle: FittedBox( appBarTitle: Text(
child: Text(
'Device Management', 'Device Management',
style: Theme.of(context).textTheme.headlineLarge, style: ResponsiveTextTheme.of(context).deviceManagementTitle,
), ),
), centerBody:
centerBody: BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) { BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -45,8 +46,11 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
child: Text( child: Text(
'Devices', 'Devices',
style: context.textTheme.titleMedium?.copyWith( style: context.textTheme.titleMedium?.copyWith(
color: !state.routineTab ? ColorsManager.whiteColors : ColorsManager.grayColor, color: !state.routineTab
fontWeight: !state.routineTab ? FontWeight.w700 : FontWeight.w400, ? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight:
!state.routineTab ? FontWeight.w700 : FontWeight.w400,
), ),
), ),
), ),
@@ -55,13 +59,18 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
backgroundColor: null, backgroundColor: null,
), ),
onPressed: () { onPressed: () {
context.read<RoutineBloc>().add(const TriggerSwitchTabsEvent(isRoutineTab: true)); context
.read<RoutineBloc>()
.add(const TriggerSwitchTabsEvent(isRoutineTab: true));
}, },
child: Text( child: Text(
'Routines', 'Routines',
style: context.textTheme.titleMedium?.copyWith( style: context.textTheme.titleMedium?.copyWith(
color: state.routineTab ? ColorsManager.whiteColors : ColorsManager.grayColor, color: state.routineTab
fontWeight: state.routineTab ? FontWeight.w700 : FontWeight.w400, ? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight:
state.routineTab ? FontWeight.w700 : FontWeight.w400,
), ),
), ),
), ),
@@ -69,7 +78,8 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
); );
}), }),
rightBody: const NavigateHomeGridView(), rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) { scaffoldBody:
BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) {
if (state.routineTab) { if (state.routineTab) {
return const RoutinesView(); return const RoutinesView();
} }
@@ -84,7 +94,8 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
} else if (deviceState is DeviceManagementLoaded) { } else if (deviceState is DeviceManagementLoaded) {
return DeviceManagementBody(devices: deviceState.devices); return DeviceManagementBody(devices: deviceState.devices);
} else if (deviceState is DeviceManagementFiltered) { } else if (deviceState is DeviceManagementFiltered) {
return DeviceManagementBody(devices: deviceState.filteredDevices); return DeviceManagementBody(
devices: deviceState.filteredDevices);
} else { } else {
return const Center(child: Text('Error fetching Devices')); return const Center(child: Text('Error fetching Devices'));
} }
@@ -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/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_batch_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_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/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/format_date_time.dart'; import 'package:syncrow_web/utils/format_date_time.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.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 DeviceSearchFilters(),
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
height: 45, // height: 45,
width: 125, width: 125,
decoration: containerDecoration, decoration: containerDecoration,
child: Center( child: Center(
@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.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/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.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<ShowCeilingDescriptionEvent>(_showDescription);
on<BackToCeilingGridViewEvent>(_backToGridView); on<BackToCeilingGridViewEvent>(_backToGridView);
on<CeilingFactoryResetEvent>(_onFactoryReset); on<CeilingFactoryResetEvent>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
} }
void _fetchCeilingSensorStatus( void _fetchCeilingSensorStatus(
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async { CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingInitialState()); emit(CeilingLoadingInitialState());
try { try {
var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); var response =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = CeilingSensorModel.fromJson(response.status); deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
// _listenToChanges(); _listenToChanges(event.deviceId);
} catch (e) { } catch (e) {
emit(CeilingFailedState(error: e.toString())); emit(CeilingFailedState(error: e.toString()));
return; return;
} }
} }
// _listenToChanges() { _listenToChanges(deviceId) {
// try { try {
// DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); DatabaseReference ref =
// Stream<DatabaseEvent> stream = ref.onValue; FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
// stream.listen((DatabaseEvent event) { stream.listen((DatabaseEvent event) {
// Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>; Map<dynamic, dynamic> usersMap =
// List<StatusModel> statusList = []; event.snapshot.value as Map<dynamic, dynamic>;
// usersMap['status'].forEach((element) { List<Status> statusList = [];
// 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); deviceStatus = CeilingSensorModel.fromJson(statusList);
// add(WallSensorUpdatedEvent()); if (!isClosed) {
// }); add(StatusUpdated(deviceStatus));
// } catch (_) {} }
// } });
} 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)); emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
if (event.code == 'sensitivity') { if (event.code == 'sensitivity') {
deviceStatus.sensitivity = event.value; deviceStatus.sensitivity = event.value;
@@ -122,7 +136,8 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
try { try {
late bool response; late bool response;
if (isBatch) { if (isBatch) {
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else { } else {
response = await DevicesManagementApi() response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value)); .deviceControl(deviceId, Status(code: code, value: value));
@@ -143,8 +158,8 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
}); });
} }
FutureOr<void> _getDeviceReports( FutureOr<void> _getDeviceReports(GetCeilingDeviceReportsEvent event,
GetCeilingDeviceReportsEvent event, Emitter<CeilingSensorState> emit) async { Emitter<CeilingSensorState> emit) async {
if (event.code.isEmpty) { if (event.code.isEmpty) {
emit(ShowCeilingDescriptionState(description: reportString)); emit(ShowCeilingDescriptionState(description: reportString));
return; return;
@@ -155,7 +170,8 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
try { try {
// await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString()) // 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)); emit(CeilingReportsState(deviceReport: value));
}); });
} catch (e) { } 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)); emit(ShowCeilingDescriptionState(description: event.description));
} }
void _backToGridView(BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) { void _backToGridView(
BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
} }
FutureOr<void> _fetchCeilingSensorBatchControl( FutureOr<void> _fetchCeilingSensorBatchControl(
CeilingFetchDeviceStatusEvent event, Emitter<CeilingSensorState> emit) async { CeilingFetchDeviceStatusEvent event,
Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingInitialState()); emit(CeilingLoadingInitialState());
try { try {
var response = await DevicesManagementApi().getBatchStatus(event.devicesIds); var response =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = CeilingSensorModel.fromJson(response.status); deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
} catch (e) { } catch (e) {
@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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/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 { abstract class CeilingSensorEvent extends Equatable {
const CeilingSensorEvent(); const CeilingSensorEvent();
@@ -83,3 +84,12 @@ class CeilingFactoryResetEvent extends CeilingSensorEvent {
@override @override
List<Object> get props => [devicesId, factoryResetModel]; List<Object> get props => [devicesId, factoryResetModel];
} }
class StatusUpdated extends CeilingSensorEvent {
final CeilingSensorModel deviceStatus;
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.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/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.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<CurtainControl>(_onCurtainControl);
on<CurtainBatchControl>(_onCurtainBatchControl); on<CurtainBatchControl>(_onCurtainBatchControl);
on<CurtainFactoryReset>(_onFactoryReset); on<CurtainFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
} }
FutureOr<void> _onFetchDeviceStatus( FutureOr<void> _onFetchDeviceStatus(
@@ -24,7 +26,7 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
try { try {
final status = final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId);
deviceStatus = _checkStatus(status.status[0].value); deviceStatus = _checkStatus(status.status[0].value);
emit(CurtainStatusLoaded(deviceStatus)); 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( FutureOr<void> _onCurtainControl(
CurtainControl event, Emitter<CurtainState> emit) async { CurtainControl event, Emitter<CurtainState> emit) async {
final oldValue = deviceStatus; final oldValue = deviceStatus;
@@ -60,3 +60,7 @@ class CurtainFactoryReset extends CurtainEvent {
@override @override
List<Object> get props => [deviceId, factoryReset]; List<Object> get props => [deviceId, factoryReset];
} }
class StatusUpdated extends CurtainEvent {
final bool deviceStatus;
const StatusUpdated(this.deviceStatus);
}
@@ -1,6 +1,7 @@
// ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: invalid_use_of_visible_for_testing_member
import 'dart:async'; import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.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/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_event.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<DoorLockControl>(_onDoorLockControl);
on<UpdateLockEvent>(_updateLock); on<UpdateLockEvent>(_updateLock);
on<DoorLockFactoryReset>(_onFactoryReset); 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( FutureOr<void> _onFetchDeviceStatus(
@@ -28,6 +62,8 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = deviceStatus =
DoorLockStatusModel.fromJson(event.deviceId, status.status); DoorLockStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(DoorLockStatusLoaded(deviceStatus)); emit(DoorLockStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(DoorLockControlError(e.toString())); emit(DoorLockControlError(e.toString()));
@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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/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 { sealed class DoorLockEvent extends Equatable {
const DoorLockEvent(); const DoorLockEvent();
@@ -51,3 +52,10 @@ class DoorLockFactoryReset extends DoorLockEvent {
@override @override
List<Object> get props => [deviceId, factoryReset]; List<Object> get props => [deviceId, factoryReset];
} }
class StatusUpdated extends DoorLockEvent {
final DoorLockStatusModel deviceStatus;
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; 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_reports.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.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'; 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<GarageDoorFetchBatchStatusEvent>(_onFetchBatchStatus);
on<GarageDoorFactoryResetEvent>(_onFactoryReset); on<GarageDoorFactoryResetEvent>(_onFactoryReset);
on<EditGarageDoorScheduleEvent>(_onEditSchedule); 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()); emit(GarageDoorLoadingState());
try { try {
var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); var response =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status); deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status);
_listenToChanges(deviceId);
emit(GarageDoorLoadedState(status: deviceStatus)); emit(GarageDoorLoadedState(status: deviceStatus));
} catch (e) { } catch (e) {
emit(GarageDoorErrorState(message: e.toString())); 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()); emit(GarageDoorLoadingState());
try { try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); final status =
deviceStatus = GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status); await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus =
GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status);
emit(GarageDoorBatchStatusLoaded(deviceStatus)); emit(GarageDoorBatchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(GarageDoorBatchControlError(e.toString())); emit(GarageDoorBatchControlError(e.toString()));
} }
} }
Future<void> _addSchedule(AddGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async { Future<void> _addSchedule(
AddGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
try { try {
ScheduleEntry newSchedule = ScheduleEntry( ScheduleEntry newSchedule = ScheduleEntry(
category: event.category, category: event.category,
@@ -71,9 +109,11 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
function: Status(code: 'switch_1', value: event.functionOn), function: Status(code: 'switch_1', value: event.functionOn),
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
); );
bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); bool success =
await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId);
if (success) { if (success) {
add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1')); add(FetchGarageDoorSchedulesEvent(
deviceId: deviceId, category: 'switch_1'));
} else { } else {
emit(GarageDoorLoadedState(status: deviceStatus)); 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; final currentState = state;
if (currentState is GarageDoorLoadedState) { if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith( 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; final currentState = state;
if (currentState is GarageDoorLoadedState) { if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith( 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 { try {
final updatedSchedules = deviceStatus.schedules?.map((schedule) { final updatedSchedules = deviceStatus.schedules?.map((schedule) {
if (schedule.scheduleId == event.scheduleId) { 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 { try {
bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); bool success = await DevicesManagementApi()
.deleteScheduleRecord(deviceStatus.uuid, event.scheduleId);
if (success) { if (success) {
final updatedSchedules = final updatedSchedules = deviceStatus.schedules
deviceStatus.schedules?.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); ?.where((schedule) => schedule.scheduleId != event.scheduleId)
.toList();
deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules);
emit(GarageDoorLoadedState(status: deviceStatus)); emit(GarageDoorLoadedState(status: deviceStatus));
} else { } 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()); emit(ScheduleGarageLoadingState());
try { try {
List<ScheduleModel> schedules = List<ScheduleModel> schedules = await DevicesManagementApi()
await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); .getDeviceSchedules(deviceStatus.uuid, event.category);
deviceStatus = deviceStatus.copyWith(schedules: schedules); deviceStatus = deviceStatus.copyWith(schedules: schedules);
emit( emit(
GarageDoorLoadedState( 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; final currentState = state;
if (currentState is GarageDoorLoadedState) { if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(selectedTime: event.selectedTime)); 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; final currentState = state;
if (currentState is GarageDoorLoadedState) { if (currentState is GarageDoorLoadedState) {
List<bool> updatedDays = List.from(currentState.selectedDays); List<bool> updatedDays = List.from(currentState.selectedDays);
updatedDays[event.dayIndex] = event.isSelected; 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; final currentState = state;
if (currentState is GarageDoorLoadedState) { 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; final currentState = state;
if (currentState is GarageDoorLoadedState) { if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith( 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()); emit(GarageDoorReportsLoadingState());
try { 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 to = DateTime.now().millisecondsSinceEpoch;
final DeviceReport records = 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)); emit(GarageDoorReportsState(deviceReport: records));
} catch (e) { } catch (e) {
emit(GarageDoorReportsFailedState(error: e.toString())); 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; final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false;
_updateLocalValue(event.code, event.value); _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)); emit(GarageDoorLoadedState(status: deviceStatus));
} }
void _handleUpdate(GarageDoorUpdatedEvent event, Emitter<GarageDoorState> emit) { void _handleUpdate(
GarageDoorUpdatedEvent event, Emitter<GarageDoorState> emit) {
emit(GarageDoorLoadedState(status: deviceStatus)); emit(GarageDoorLoadedState(status: deviceStatus));
} }
@@ -253,9 +315,11 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
late bool status; late bool status;
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
if (isBatch) { if (isBatch) {
status = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); status = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else { } else {
status = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); status = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
} }
if (!status) { 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()); emit(GarageDoorLoadingState());
try { try {
final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); final response = await DevicesManagementApi()
.factoryReset(event.factoryReset, event.deviceId);
if (!response) { if (!response) {
emit(const GarageDoorErrorState(message: 'Failed to reset device')); emit(const GarageDoorErrorState(message: 'Failed to reset device'));
} else { } 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) { // if (deviceStatus.countdown1 != 0) {
try { try {
deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); deviceStatus = deviceStatus.copyWith(
delay: deviceStatus.delay + Duration(minutes: 10));
emit(GarageDoorLoadedState(status: deviceStatus)); 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) { } catch (e) {
emit(GarageDoorErrorState(message: e.toString())); 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) { // if (deviceStatus.countdown1 != 0) {
try { try {
if (deviceStatus.delay.inMinutes > 10) { 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)); 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) { } catch (e) {
emit(GarageDoorErrorState(message: e.toString())); emit(GarageDoorErrorState(message: e.toString()));
} }
//} //}
} }
void _garageDoorControlEvent(GarageDoorControlEvent event, Emitter<GarageDoorState> emit) async { void _garageDoorControlEvent(
final oldValue = event.code == 'countdown_1' ? deviceStatus.countdown1 : deviceStatus.switch1; GarageDoorControlEvent event, Emitter<GarageDoorState> emit) async {
final oldValue = event.code == 'countdown_1'
? deviceStatus.countdown1
: deviceStatus.switch1;
_updateLocalValue(event.code, event.value); _updateLocalValue(event.code, event.value);
emit(GarageDoorLoadedState(status: deviceStatus)); emit(GarageDoorLoadedState(status: deviceStatus));
final success = await _runDeBouncer( 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) { switch (code) {
case 'switch_1': case 'switch_1':
if (oldValue is bool) { if (oldValue is bool) {
@@ -336,7 +416,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
break; break;
case 'countdown_1': case 'countdown_1':
if (oldValue is int) { if (oldValue is int) {
deviceStatus = deviceStatus.copyWith(countdown1: oldValue, delay: Duration(seconds: oldValue)); deviceStatus = deviceStatus.copyWith(
countdown1: oldValue, delay: Duration(seconds: oldValue));
} }
break; break;
// Add other cases if needed // Add other cases if needed
@@ -358,7 +439,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
break; break;
case 'countdown_1': case 'countdown_1':
if (value is int) { if (value is int) {
deviceStatus = deviceStatus.copyWith(countdown1: value, delay: Duration(seconds: value)); deviceStatus = deviceStatus.copyWith(
countdown1: value, delay: Duration(seconds: value));
} }
break; break;
case 'countdown_alarm': case 'countdown_alarm':
@@ -401,7 +483,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
return super.close(); return super.close();
} }
FutureOr<void> _onEditSchedule(EditGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async { FutureOr<void> _onEditSchedule(
EditGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
try { try {
ScheduleEntry newSchedule = ScheduleEntry( ScheduleEntry newSchedule = ScheduleEntry(
scheduleId: event.scheduleId, scheduleId: event.scheduleId,
@@ -410,9 +493,11 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
function: Status(code: 'switch_1', value: event.functionOn), function: Status(code: 'switch_1', value: event.functionOn),
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
); );
bool success = await DevicesManagementApi().editScheduleRecord(deviceId, newSchedule); bool success = await DevicesManagementApi()
.editScheduleRecord(deviceId, newSchedule);
if (success) { if (success) {
add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1')); add(FetchGarageDoorSchedulesEvent(
deviceId: deviceId, category: 'switch_1'));
} else { } else {
emit(GarageDoorLoadedState(status: deviceStatus)); emit(GarageDoorLoadedState(status: deviceStatus));
} }
@@ -3,6 +3,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.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/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 { abstract class GarageDoorEvent extends Equatable {
const GarageDoorEvent(); const GarageDoorEvent();
@@ -25,7 +26,8 @@ class GarageDoorControlEvent extends GarageDoorEvent {
final dynamic value; final dynamic value;
final String code; 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 @override
List<Object?> get props => [deviceId, value]; List<Object?> get props => [deviceId, value];
@@ -121,7 +123,8 @@ class FetchGarageDoorRecordsEvent extends GarageDoorEvent {
final String deviceId; final String deviceId;
final String code; final String code;
const FetchGarageDoorRecordsEvent({required this.deviceId, required this.code}); const FetchGarageDoorRecordsEvent(
{required this.deviceId, required this.code});
@override @override
List<Object?> get props => [deviceId, code]; List<Object?> get props => [deviceId, code];
@@ -232,3 +235,10 @@ class GarageDoorFactoryResetEvent extends GarageDoorEvent {
@override @override
List<Object?> get props => [factoryReset, deviceId]; List<Object?> get props => [factoryReset, deviceId];
} }
class StatusUpdated extends GarageDoorEvent {
final GarageDoorStatusModel deviceStatus;
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/all_devices/models/device_status.dart';
@@ -16,6 +17,7 @@ class MainDoorSensorBloc
on<MainDoorSensorFetchBatchEvent>(_onFetchBatchStatus); on<MainDoorSensorFetchBatchEvent>(_onFetchBatchStatus);
on<MainDoorSensorReportsEvent>(_fetchReports); on<MainDoorSensorReportsEvent>(_fetchReports);
on<MainDoorSensorFactoryReset>(_factoryReset); on<MainDoorSensorFactoryReset>(_factoryReset);
on<StatusUpdated>(_onStatusUpdated);
} }
late MainDoorSensorStatusModel deviceStatus; late MainDoorSensorStatusModel deviceStatus;
@@ -28,7 +30,7 @@ class MainDoorSensorBloc
final status = await DevicesManagementApi() final status = await DevicesManagementApi()
.getDeviceStatus(event.deviceId) .getDeviceStatus(event.deviceId)
.then((value) => value.status); .then((value) => value.status);
_listenToChanges(event.deviceId);
deviceStatus = MainDoorSensorStatusModel.fromJson(event.deviceId, status); deviceStatus = MainDoorSensorStatusModel.fromJson(event.deviceId, status);
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
@@ -156,4 +158,35 @@ class MainDoorSensorBloc
emit(MainDoorSensorFailedState(error: e.toString())); 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));
}
} }
@@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart'; 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'; import '../../all_devices/models/factory_reset_model.dart';
@@ -71,3 +72,10 @@ class MainDoorSensorFactoryReset extends MainDoorSensorEvent {
MainDoorSensorFactoryReset( MainDoorSensorFactoryReset(
{required this.deviceId, required this.factoryReset}); {required this.deviceId, required this.factoryReset});
} }
class StatusUpdated extends MainDoorSensorEvent {
final MainDoorSensorStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:meta/meta.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/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.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_event.dart';
part 'one_gang_glass_switch_state.dart'; part 'one_gang_glass_switch_state.dart';
class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassSwitchState> { class OneGangGlassSwitchBloc
extends Bloc<OneGangGlassSwitchEvent, OneGangGlassSwitchState> {
OneGangGlassStatusModel deviceStatus; OneGangGlassStatusModel deviceStatus;
Timer? _timer; Timer? _timer;
OneGangGlassSwitchBloc({required String deviceId}) OneGangGlassSwitchBloc({required String deviceId})
: deviceStatus = OneGangGlassStatusModel(uuid: deviceId, switch1: false, countDown: 0), : deviceStatus = OneGangGlassStatusModel(
uuid: deviceId, switch1: false, countDown: 0),
super(OneGangGlassSwitchInitial()) { super(OneGangGlassSwitchInitial()) {
on<OneGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus); on<OneGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<OneGangGlassSwitchControl>(_onControl); on<OneGangGlassSwitchControl>(_onControl);
on<OneGangGlassSwitchBatchControl>(_onBatchControl); on<OneGangGlassSwitchBatchControl>(_onBatchControl);
on<OneGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus); on<OneGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
on<OneGangGlassFactoryResetEvent>(_onFactoryReset); on<OneGangGlassFactoryResetEvent>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
} }
Future<void> _onFetchDeviceStatus( Future<void> _onFetchDeviceStatus(OneGangGlassSwitchFetchDeviceEvent event,
OneGangGlassSwitchFetchDeviceEvent event, Emitter<OneGangGlassSwitchState> emit) async { Emitter<OneGangGlassSwitchState> emit) async {
emit(OneGangGlassSwitchLoading()); emit(OneGangGlassSwitchLoading());
try { try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); final status =
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status); await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId);
deviceStatus =
OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(OneGangGlassSwitchError(e.toString())); 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); final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value); _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()); emit(OneGangGlassSwitchLoading());
try { try {
final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); final response = await DevicesManagementApi()
.factoryReset(event.factoryReset, event.deviceId);
if (!response) { if (!response) {
emit(OneGangGlassSwitchError('Failed to reset device')); emit(OneGangGlassSwitchError('Failed to reset device'));
} else { } 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); final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value); _updateLocalValue(event.code, event.value);
@@ -83,11 +125,14 @@ class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassS
} }
Future<void> _onFetchBatchStatus( Future<void> _onFetchBatchStatus(
OneGangGlassSwitchFetchBatchStatusEvent event, Emitter<OneGangGlassSwitchState> emit) async { OneGangGlassSwitchFetchBatchStatusEvent event,
Emitter<OneGangGlassSwitchState> emit) async {
emit(OneGangGlassSwitchLoading()); emit(OneGangGlassSwitchLoading());
try { try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); final status =
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = OneGangGlassStatusModel.fromJson(
event.deviceIds.first, status.status);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(OneGangGlassSwitchError(e.toString())); emit(OneGangGlassSwitchError(e.toString()));
@@ -117,9 +162,11 @@ class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassS
try { try {
late bool response; late bool response;
if (isBatch) { if (isBatch) {
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else { } else {
response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
} }
if (!response) { 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); _updateLocalValue(code, oldValue);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} }
@@ -39,6 +39,11 @@ class OneGangGlassSwitchFetchBatchStatusEvent extends OneGangGlassSwitchEvent {
OneGangGlassSwitchFetchBatchStatusEvent(this.deviceIds); OneGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
} }
class StatusUpdated extends OneGangGlassSwitchEvent {
final OneGangGlassStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
}
class OneGangGlassFactoryResetEvent extends OneGangGlassSwitchEvent { class OneGangGlassFactoryResetEvent extends OneGangGlassSwitchEvent {
final FactoryResetModel factoryReset; final FactoryResetModel factoryReset;
final String deviceId; final String deviceId;
@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.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/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.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<WallLightSwitchFetchBatchEvent>(_onFetchBatchStatus);
on<WallLightSwitchBatchControl>(_onBatchControl); on<WallLightSwitchBatchControl>(_onBatchControl);
on<WallLightFactoryReset>(_onFactoryReset); on<WallLightFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
} }
late WallLightStatusModel deviceStatus; late WallLightStatusModel deviceStatus;
@@ -31,12 +33,44 @@ class WallLightSwitchBloc
deviceStatus = deviceStatus =
WallLightStatusModel.fromJson(event.deviceId, status.status); WallLightStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(WallLightSwitchStatusLoaded(deviceStatus)); emit(WallLightSwitchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(WallLightSwitchError(e.toString())); 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( FutureOr<void> _onControl(
WallLightSwitchControl event, Emitter<WallLightSwitchState> emit) async { WallLightSwitchControl event, Emitter<WallLightSwitchState> emit) async {
final oldValue = _getValueByCode(event.code); final oldValue = _getValueByCode(event.code);
@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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/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 { class WallLightSwitchEvent extends Equatable {
@override @override
@@ -57,3 +58,10 @@ class WallLightFactoryReset extends WallLightSwitchEvent {
@override @override
List<Object> get props => [deviceId, factoryReset]; List<Object> get props => [deviceId, factoryReset];
} }
class StatusUpdated extends WallLightSwitchEvent {
final WallLightStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:meta/meta.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/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.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_event.dart';
part 'three_gang_glass_switch_state.dart'; part 'three_gang_glass_switch_state.dart';
class ThreeGangGlassSwitchBloc extends Bloc<ThreeGangGlassSwitchEvent, ThreeGangGlassSwitchState> { class ThreeGangGlassSwitchBloc
extends Bloc<ThreeGangGlassSwitchEvent, ThreeGangGlassSwitchState> {
ThreeGangGlassStatusModel deviceStatus; ThreeGangGlassStatusModel deviceStatus;
Timer? _timer; Timer? _timer;
@@ -29,21 +31,57 @@ class ThreeGangGlassSwitchBloc extends Bloc<ThreeGangGlassSwitchEvent, ThreeGang
on<ThreeGangGlassSwitchBatchControl>(_onBatchControl); on<ThreeGangGlassSwitchBatchControl>(_onBatchControl);
on<ThreeGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus); on<ThreeGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
on<ThreeGangGlassFactoryReset>(_onFactoryReset); on<ThreeGangGlassFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
} }
Future<void> _onFetchDeviceStatus( Future<void> _onFetchDeviceStatus(ThreeGangGlassSwitchFetchDeviceEvent event,
ThreeGangGlassSwitchFetchDeviceEvent event, Emitter<ThreeGangGlassSwitchState> emit) async { Emitter<ThreeGangGlassSwitchState> emit) async {
emit(ThreeGangGlassSwitchLoading()); emit(ThreeGangGlassSwitchLoading());
try { try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); final status =
deviceStatus = ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus)); emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(ThreeGangGlassSwitchError(e.toString())); 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); final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value); _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); final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value); _updateLocalValue(event.code, event.value);
@@ -76,21 +115,26 @@ class ThreeGangGlassSwitchBloc extends Bloc<ThreeGangGlassSwitchEvent, ThreeGang
} }
Future<void> _onFetchBatchStatus( Future<void> _onFetchBatchStatus(
ThreeGangGlassSwitchFetchBatchStatusEvent event, Emitter<ThreeGangGlassSwitchState> emit) async { ThreeGangGlassSwitchFetchBatchStatusEvent event,
Emitter<ThreeGangGlassSwitchState> emit) async {
emit(ThreeGangGlassSwitchLoading()); emit(ThreeGangGlassSwitchLoading());
try { try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); final status =
deviceStatus = ThreeGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = ThreeGangGlassStatusModel.fromJson(
event.deviceIds.first, status.status);
emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus)); emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(ThreeGangGlassSwitchError(e.toString())); emit(ThreeGangGlassSwitchError(e.toString()));
} }
} }
Future<void> _onFactoryReset(ThreeGangGlassFactoryReset event, Emitter<ThreeGangGlassSwitchState> emit) async { Future<void> _onFactoryReset(ThreeGangGlassFactoryReset event,
Emitter<ThreeGangGlassSwitchState> emit) async {
emit(ThreeGangGlassSwitchLoading()); emit(ThreeGangGlassSwitchLoading());
try { try {
final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); final response = await DevicesManagementApi()
.factoryReset(event.factoryReset, event.deviceId);
if (!response) { if (!response) {
emit(ThreeGangGlassSwitchError('Failed')); emit(ThreeGangGlassSwitchError('Failed'));
} else { } else {
@@ -124,9 +168,11 @@ class ThreeGangGlassSwitchBloc extends Bloc<ThreeGangGlassSwitchEvent, ThreeGang
try { try {
late bool response; late bool response;
if (isBatch) { if (isBatch) {
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else { } else {
response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
} }
if (!response) { 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); _updateLocalValue(code, oldValue);
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus)); emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
} }
@@ -49,3 +49,10 @@ class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent {
required this.factoryReset, required this.factoryReset,
}); });
} }
class StatusUpdated extends ThreeGangGlassSwitchEvent {
final ThreeGangGlassStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
@@ -3,6 +3,7 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.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/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.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'; 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<LivingRoomBatchControl>(_livingRoomBatchControl);
on<LivingRoomFetchBatchEvent>(_livingRoomFetchBatchControl); on<LivingRoomFetchBatchEvent>(_livingRoomFetchBatchControl);
on<LivingRoomFactoryResetEvent>(_livingRoomFactoryReset); on<LivingRoomFactoryResetEvent>(_livingRoomFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
} }
FutureOr<void> _onFetchDeviceStatus(LivingRoomFetchDeviceStatusEvent event, FutureOr<void> _onFetchDeviceStatus(LivingRoomFetchDeviceStatusEvent event,
@@ -32,6 +34,7 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = deviceStatus =
LivingRoomStatusModel.fromJson(event.deviceId, status.status); LivingRoomStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(deviceId);
emit(LivingRoomDeviceStatusLoaded(deviceStatus)); emit(LivingRoomDeviceStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(LivingRoomDeviceManagementError(e.toString())); emit(LivingRoomDeviceManagementError(e.toString()));
@@ -144,6 +147,9 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
await DevicesManagementApi().getBatchStatus(event.devicesIds); await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = deviceStatus =
LivingRoomStatusModel.fromJson(event.devicesIds.first, status.status); LivingRoomStatusModel.fromJson(event.devicesIds.first, status.status);
// for (var deviceId in event.devicesIds) {
// _listenToChanges(deviceId);
// }
emit(LivingRoomDeviceStatusLoaded(deviceStatus)); emit(LivingRoomDeviceStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(LivingRoomDeviceManagementError(e.toString())); emit(LivingRoomDeviceManagementError(e.toString()));
@@ -185,4 +191,34 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
emit(LivingRoomDeviceManagementError(e.toString())); 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));
}
} }
@@ -58,3 +58,10 @@ class LivingRoomFactoryResetEvent extends LivingRoomEvent {
@override @override
List<Object> get props => [uuid, factoryReset]; List<Object> get props => [uuid, factoryReset];
} }
class StatusUpdated extends LivingRoomEvent {
final LivingRoomStatusModel deviceStatus;
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
@@ -1,12 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:meta/meta.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/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.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/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
part 'two_gang_glass_switch_event.dart'; part 'two_gang_glass_switch_event.dart';
part 'two_gang_glass_switch_state.dart'; part 'two_gang_glass_switch_state.dart';
@@ -14,7 +13,6 @@ class TwoGangGlassSwitchBloc
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> { extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
TwoGangGlassStatusModel deviceStatus; TwoGangGlassStatusModel deviceStatus;
Timer? _timer; Timer? _timer;
TwoGangGlassSwitchBloc({required String deviceId}) TwoGangGlassSwitchBloc({required String deviceId})
: deviceStatus = TwoGangGlassStatusModel( : deviceStatus = TwoGangGlassStatusModel(
uuid: deviceId, uuid: deviceId,
@@ -28,6 +26,7 @@ class TwoGangGlassSwitchBloc
on<TwoGangGlassSwitchBatchControl>(_onBatchControl); on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
on<TwoGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus); on<TwoGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
on<TwoGangGlassFactoryReset>(_onFactoryReset); on<TwoGangGlassFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
} }
Future<void> _onFetchDeviceStatus(TwoGangGlassSwitchFetchDeviceEvent event, Future<void> _onFetchDeviceStatus(TwoGangGlassSwitchFetchDeviceEvent event,
@@ -38,12 +37,44 @@ class TwoGangGlassSwitchBloc
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = deviceStatus =
TwoGangGlassStatusModel.fromJson(event.deviceId, status.status); TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus)); emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(TwoGangGlassSwitchError(e.toString())); 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, Future<void> _onControl(TwoGangGlassSwitchControl event,
Emitter<TwoGangGlassSwitchState> emit) async { Emitter<TwoGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code); final oldValue = _getValueByCode(event.code);
@@ -178,4 +209,37 @@ class TwoGangGlassSwitchBloc
_timer?.cancel(); _timer?.cancel();
return super.close(); 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));
}
} }
@@ -48,3 +48,11 @@ class TwoGangGlassFactoryReset extends TwoGangGlassSwitchEvent {
required this.factoryReset, required this.factoryReset,
}); });
} }
class StatusUpdated extends TwoGangGlassSwitchEvent {
final TwoGangGlassStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
@@ -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/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.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; final String deviceId;
const TwoGangGlassSwitchControlView({required this.deviceId, super.key}); const TwoGangGlassSwitchControlView({required this.deviceId, super.key});
@@ -14,8 +15,8 @@ class TwoGangGlassSwitchControlView extends StatelessWidget with HelperResponsiv
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceId)
TwoGangGlassSwitchBloc(deviceId: deviceId)..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)), ..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>( child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
builder: (context, state) { builder: (context, state) {
if (state is TwoGangGlassSwitchLoading) { if (state is TwoGangGlassSwitchLoading) {
@@ -23,16 +24,16 @@ class TwoGangGlassSwitchControlView extends StatelessWidget with HelperResponsiv
} else if (state is TwoGangGlassSwitchStatusLoaded) { } else if (state is TwoGangGlassSwitchStatusLoaded) {
return _buildStatusControls(context, state.status); return _buildStatusControls(context, state.status);
} else if (state is TwoGangGlassSwitchError) { } else if (state is TwoGangGlassSwitchError) {
return const Center(child: Text('Error fetching status')); return Center(child: Text(state.message));
} else { } else {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
}, },
), ));
);
} }
Widget _buildStatusControls(BuildContext context, TwoGangGlassStatusModel status) { Widget _buildStatusControls(
BuildContext context, TwoGangGlassStatusModel status) {
final isExtraLarge = isExtraLargeScreenSize(context); final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.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/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.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<TwoGangSwitchFetchBatchEvent>(_onFetchBatchStatus);
on<TwoGangSwitchBatchControl>(_onBatchControl); on<TwoGangSwitchBatchControl>(_onBatchControl);
on<TwoGangFactoryReset>(_onFactoryReset); on<TwoGangFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
} }
late TwoGangStatusModel deviceStatus; late TwoGangStatusModel deviceStatus;
@@ -26,8 +28,8 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
try { try {
final status = final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status); deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(emit);
emit(TwoGangSwitchStatusLoaded(deviceStatus)); emit(TwoGangSwitchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(TwoGangSwitchError(e.toString())); emit(TwoGangSwitchError(e.toString()));
@@ -174,4 +176,34 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
emit(TwoGangSwitchError(e.toString())); 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));
}
} }
@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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/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 { class TwoGangSwitchEvent extends Equatable {
@override @override
@@ -57,3 +58,10 @@ class TwoGangFactoryReset extends TwoGangSwitchEvent {
@override @override
List<Object> get props => [deviceId, factoryReset]; List<Object> get props => [deviceId, factoryReset];
} }
class StatusUpdated extends TwoGangSwitchEvent {
final TwoGangStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}
@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.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/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.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); var response = await DevicesManagementApi().getDeviceStatus(deviceId);
deviceStatus = WallSensorModel.fromJson(response.status); deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
// _listenToChanges(); _listenToChanges(emit);
} catch (e) { } catch (e) {
emit(WallSensorFailedState(error: e.toString())); emit(WallSensorFailedState(error: e.toString()));
return; return;
@@ -38,10 +39,12 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
// Fetch batch status // Fetch batch status
FutureOr<void> _fetchWallSensorBatchControl( FutureOr<void> _fetchWallSensorBatchControl(
WallSensorFetchBatchStatusEvent event, Emitter<WallSensorState> emit) async { WallSensorFetchBatchStatusEvent event,
Emitter<WallSensorState> emit) async {
emit(WallSensorLoadingInitialState()); emit(WallSensorLoadingInitialState());
try { try {
var response = await DevicesManagementApi().getBatchStatus(event.devicesIds); var response =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = WallSensorModel.fromJson(response.status); deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
} catch (e) { } catch (e) {
@@ -49,26 +52,30 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
} }
} }
// _listenToChanges() { _listenToChanges(Emitter<WallSensorState> emit) {
// try { try {
// DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); DatabaseReference ref =
// Stream<DatabaseEvent> stream = ref.onValue; FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
// stream.listen((DatabaseEvent event) { stream.listen((DatabaseEvent event) {
// Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>; Map<dynamic, dynamic> usersMap =
// List<StatusModel> statusList = []; event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = [];
// usersMap['status'].forEach((element) { usersMap['status'].forEach((element) {
// statusList.add(StatusModel(code: element['code'], value: element['value'])); statusList
// }); .add(Status(code: element['code'], value: element['value']));
});
// deviceStatus = WallSensorModel.fromJson(statusList); deviceStatus = WallSensorModel.fromJson(statusList);
// add(WallSensorUpdatedEvent()); emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
// }); });
// } catch (_) {} } catch (_) {}
// } }
void _changeValue(WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async { void _changeValue(
WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
if (event.code == 'far_detection') { if (event.code == 'far_detection') {
deviceStatus.farDetection = event.value; deviceStatus.farDetection = event.value;
@@ -125,7 +132,8 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
try { try {
late bool response; late bool response;
if (isBatch) { if (isBatch) {
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else { } else {
response = await DevicesManagementApi() response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value)); .deviceControl(deviceId, Status(code: code, value: value));
@@ -150,7 +158,8 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
try { try {
// await DevicesManagementApi.getDeviceReportsByDate( // await DevicesManagementApi.getDeviceReportsByDate(
// deviceId, event.code, from.toString(), to.toString()) // 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)); emit(DeviceReportsState(deviceReport: value, code: event.code));
}); });
} catch (e) { } 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)); emit(WallSensorShowDescriptionState(description: event.description));
} }
void _backToGridView(BackToGridViewEvent event, Emitter<WallSensorState> emit) { void _backToGridView(
BackToGridViewEvent event, Emitter<WallSensorState> emit) {
emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
} }
@@ -4,6 +4,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.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/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.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<EditWaterHeaterScheduleEvent>(_onEditSchedule);
on<DeleteScheduleEvent>(_onDeleteSchedule); on<DeleteScheduleEvent>(_onDeleteSchedule);
on<UpdateScheduleEntryEvent>(_onUpdateSchedule); on<UpdateScheduleEntryEvent>(_onUpdateSchedule);
on<StatusUpdated>(_onStatusUpdated);
} }
late WaterHeaterStatusModel deviceStatus; late WaterHeaterStatusModel deviceStatus;
@@ -78,7 +80,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
final currentState = state as WaterHeaterDeviceStatusLoaded; final currentState = state as WaterHeaterDeviceStatusLoaded;
final updatedDays = List<bool>.from(currentState.selectedDays); final updatedDays = List<bool>.from(currentState.selectedDays);
updatedDays[event.index] = event.value; updatedDays[event.index] = event.value;
emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime)); emit(currentState.copyWith(
selectedDays: updatedDays, selectedTime: currentState.selectedTime));
} }
FutureOr<void> _updateFunctionOn( FutureOr<void> _updateFunctionOn(
@@ -86,7 +89,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
Emitter<WaterHeaterState> emit, Emitter<WaterHeaterState> emit,
) { ) {
final currentState = state as WaterHeaterDeviceStatusLoaded; 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( FutureOr<void> _updateScheduleEvent(
@@ -101,7 +105,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
)); ));
} }
if (event.scheduleMode == ScheduleModes.countdown) { 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( emit(currentState.copyWith(
scheduleMode: ScheduleModes.countdown, scheduleMode: ScheduleModes.countdown,
@@ -111,11 +116,13 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
countdownRemaining: countdownRemaining, countdownRemaining: countdownRemaining,
)); ));
if (!currentState.isCountdownActive! && countdownRemaining > Duration.zero) { if (!currentState.isCountdownActive! &&
countdownRemaining > Duration.zero) {
_startCountdownTimer(emit, countdownRemaining); _startCountdownTimer(emit, countdownRemaining);
} }
} else if (event.scheduleMode == ScheduleModes.inching) { } 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( emit(currentState.copyWith(
scheduleMode: ScheduleModes.inching, scheduleMode: ScheduleModes.inching,
@@ -217,7 +224,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
try { try {
final status = await DevicesManagementApi().deviceControl( final status = await DevicesManagementApi().deviceControl(
event.deviceId, event.deviceId,
Status(code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0), Status(
code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0),
); );
if (!status) { if (!status) {
emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.')); emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.'));
@@ -235,8 +243,10 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
emit(WaterHeaterLoadingState()); emit(WaterHeaterLoadingState());
try { try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); final status =
deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.scheduleMode == ScheduleModes.countdown) { if (deviceStatus.scheduleMode == ScheduleModes.countdown) {
final countdownRemaining = Duration( final countdownRemaining = Duration(
@@ -300,11 +310,42 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
isInchingActive: false, isInchingActive: false,
)); ));
} }
_listenToChanges(event.deviceId);
} catch (e) { } catch (e) {
emit(WaterHeaterFailedState(error: e.toString())); 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( void _startCountdownTimer(
Emitter<WaterHeaterState> emit, Emitter<WaterHeaterState> emit,
Duration countdownRemaining, Duration countdownRemaining,
@@ -334,8 +375,10 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
if (state is WaterHeaterDeviceStatusLoaded) { if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded; final currentState = state as WaterHeaterDeviceStatusLoaded;
if (currentState.countdownRemaining != null && currentState.countdownRemaining! > Duration.zero) { if (currentState.countdownRemaining != null &&
final newRemaining = currentState.countdownRemaining! - const Duration(minutes: 1); currentState.countdownRemaining! > Duration.zero) {
final newRemaining =
currentState.countdownRemaining! - const Duration(minutes: 1);
if (newRemaining <= Duration.zero) { if (newRemaining <= Duration.zero) {
_countdownTimer?.cancel(); _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); _updateLocalValue(code, oldValue);
if (state is WaterHeaterDeviceStatusLoaded) { if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded; final currentState = state as WaterHeaterDeviceStatusLoaded;
@@ -477,12 +521,13 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
return super.close(); return super.close();
} }
FutureOr<void> _getSchedule(GetSchedulesEvent event, Emitter<WaterHeaterState> emit) async { FutureOr<void> _getSchedule(
GetSchedulesEvent event, Emitter<WaterHeaterState> emit) async {
emit(ScheduleLoadingState()); emit(ScheduleLoadingState());
try { try {
List<ScheduleModel> schedules = List<ScheduleModel> schedules = await DevicesManagementApi()
await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); .getDeviceSchedules(deviceStatus.uuid, event.category);
emit(WaterHeaterDeviceStatusLoaded( emit(WaterHeaterDeviceStatusLoaded(
deviceStatus, deviceStatus,
@@ -514,7 +559,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
// emit(ScheduleLoadingState()); // emit(ScheduleLoadingState());
bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, currentState.status.uuid); bool success = await DevicesManagementApi()
.addScheduleRecord(newSchedule, currentState.status.uuid);
if (success) { if (success) {
add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid)); 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) { if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded; final currentState = state as WaterHeaterDeviceStatusLoaded;
@@ -594,11 +641,13 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
// emit(ScheduleLoadingState()); // emit(ScheduleLoadingState());
bool success = await DevicesManagementApi().deleteScheduleRecord(currentState.status.uuid, event.scheduleId); bool success = await DevicesManagementApi()
.deleteScheduleRecord(currentState.status.uuid, event.scheduleId);
if (success) { if (success) {
final updatedSchedules = final updatedSchedules = currentState.schedules
currentState.schedules.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); .where((schedule) => schedule.scheduleId != event.scheduleId)
.toList();
emit(currentState.copyWith(schedules: updatedSchedules)); emit(currentState.copyWith(schedules: updatedSchedules));
} else { } 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()); emit(WaterHeaterLoadingState());
try { try {
final status = await DevicesManagementApi().getBatchStatus(event.devicesUuid); final status =
deviceStatus = WaterHeaterStatusModel.fromJson(event.devicesUuid.first, status.status); await DevicesManagementApi().getBatchStatus(event.devicesUuid);
deviceStatus = WaterHeaterStatusModel.fromJson(
event.devicesUuid.first, status.status);
emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
} catch (e) { } 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) { if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded; final currentState = state as WaterHeaterDeviceStatusLoaded;
@@ -54,6 +54,15 @@ final class WaterHeaterFetchStatusEvent extends WaterHeaterEvent {
final class DecrementCountdownEvent 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 class AddScheduleEvent extends WaterHeaterEvent {
final List<bool> selectedDays; final List<bool> selectedDays;
final TimeOfDay time; final TimeOfDay time;
@@ -1,4 +1,5 @@
import 'package:bloc/bloc.dart'; 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_reports.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.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'; 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<FetchWaterLeakBatchStatusEvent>(_onFetchBatchStatus);
on<FetchWaterLeakReportsEvent>(_onFetchWaterLeakReports); on<FetchWaterLeakReportsEvent>(_onFetchWaterLeakReports);
on<WaterLeakFactoryResetEvent>(_onFactoryReset); on<WaterLeakFactoryResetEvent>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
} }
Future<void> _onFetchWaterLeakStatus( Future<void> _onFetchWaterLeakStatus(
@@ -30,12 +32,43 @@ class WaterLeakBloc extends Bloc<WaterLeakEvent, WaterLeakState> {
final response = final response =
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = WaterLeakStatusModel.fromJson(deviceId, response.status); deviceStatus = WaterLeakStatusModel.fromJson(deviceId, response.status);
_listenToChanges();
emit(WaterLeakLoadedState(deviceStatus!)); emit(WaterLeakLoadedState(deviceStatus!));
} catch (e) { } catch (e) {
emit(WaterLeakErrorState(e.toString())); 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( Future<void> _onControl(
WaterLeakControlEvent event, Emitter<WaterLeakState> emit) async { WaterLeakControlEvent event, Emitter<WaterLeakState> emit) async {
final oldValue = deviceStatus!.watersensorState; final oldValue = deviceStatus!.watersensorState;
@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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/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 { abstract class WaterLeakEvent extends Equatable {
const WaterLeakEvent(); const WaterLeakEvent();
@@ -17,6 +18,13 @@ class FetchWaterLeakStatusEvent extends WaterLeakEvent {
List<Object> get props => [deviceId]; 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 { class WaterLeakControlEvent extends WaterLeakEvent {
final String deviceId; final String deviceId;
final String code; final String code;
+9 -3
View File
@@ -1,3 +1,4 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -51,6 +52,8 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
await const FlutterSecureStorage().read(key: UserModel.userUuidKey); await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid); user = await HomeApi().fetchUserInfo(uuid);
add(FetchTermEvent()); add(FetchTermEvent());
add(FetchPolicyEvent());
emit(HomeInitial()); emit(HomeInitial());
} catch (e) { } catch (e) {
return; return;
@@ -61,7 +64,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
try { try {
emit(LoadingHome()); emit(LoadingHome());
terms = await HomeApi().fetchTerms(); terms = await HomeApi().fetchTerms();
add(FetchPolicyEvent()); emit(HomeInitial());
} catch (e) { } catch (e) {
return; return;
} }
@@ -71,7 +74,9 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
try { try {
emit(LoadingHome()); emit(LoadingHome());
policy = await HomeApi().fetchPolicy(); policy = await HomeApi().fetchPolicy();
emit(HomeInitial());
} catch (e) { } catch (e) {
debugPrint("Error fetching policy: $e");
return; return;
} }
} }
@@ -101,7 +106,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
List<HomeItemModel> homeItems = [ List<HomeItemModel> homeItems = [
HomeItemModel( HomeItemModel(
title: 'Access', title: 'Access Management',
icon: Assets.accessIcon, icon: Assets.accessIcon,
active: true, active: true,
onPress: (context) { onPress: (context) {
@@ -119,7 +124,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
color: ColorsManager.primaryColor, color: ColorsManager.primaryColor,
), ),
HomeItemModel( HomeItemModel(
title: 'Devices', title: 'Devices Management',
icon: Assets.devicesIcon, icon: Assets.devicesIcon,
active: true, active: true,
onPress: (context) { onPress: (context) {
@@ -129,6 +134,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
}, },
color: ColorsManager.primaryColor, color: ColorsManager.primaryColor,
), ),
// HomeItemModel( // HomeItemModel(
// title: 'Move in', // title: 'Move in',
// icon: Assets.moveinIcon, // icon: Assets.moveinIcon,
@@ -63,9 +63,11 @@ class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
} }
String get _dialogTitle => String get _dialogTitle =>
_currentPage == 2 ? 'User Agreement' : 'Privacy Policy'; _currentPage == 1 ? 'User Agreement' : 'Privacy Policy';
String get _dialogContent => _currentPage == 2 ? widget.terms : widget.policy; String get _dialogContent => _currentPage == 1 ? widget.terms : widget.policy;
final String staticText =
'<h5 style="color: #FF5722;">If you cancel you will be logged out.</h5>';
Widget _buildScrollableContent() { Widget _buildScrollableContent() {
return Container( return Container(
@@ -85,7 +87,7 @@ class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
controller: _scrollController, controller: _scrollController,
padding: const EdgeInsets.all(25), padding: const EdgeInsets.all(25),
child: Html( child: Html(
data: _dialogContent, data: "$_dialogContent $staticText",
onLinkTap: (url, attributes, element) async { onLinkTap: (url, attributes, element) async {
if (url != null) { if (url != null) {
final uri = Uri.parse(url); final uri = Uri.parse(url);
+23 -5
View File
@@ -9,9 +9,24 @@ import 'package:syncrow_web/pages/home/view/home_card.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class HomeWebPage extends StatelessWidget { class HomeWebPage extends StatefulWidget {
const HomeWebPage({super.key}); const HomeWebPage({super.key});
@override
State<HomeWebPage> createState() => _HomeWebPageState();
}
class _HomeWebPageState extends State<HomeWebPage> {
// Flag to track whether the dialog is already shown.
bool _dialogShown = false;
@override
void initState() {
super.initState();
final homeBloc = BlocProvider.of<HomeBloc>(context);
homeBloc.add(FetchUserInfo());
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size; Size size = MediaQuery.of(context).size;
@@ -23,7 +38,8 @@ class HomeWebPage extends StatelessWidget {
child: BlocConsumer<HomeBloc, HomeState>( child: BlocConsumer<HomeBloc, HomeState>(
listener: (BuildContext context, state) { listener: (BuildContext context, state) {
if (state is HomeInitial) { if (state is HomeInitial) {
if (homeBloc.user!.hasAcceptedWebAgreement == false) { if (homeBloc.user!.hasAcceptedWebAgreement == false && !_dialogShown) {
_dialogShown = true; // Set the flag to true to indicate the dialog is showing.
Future.delayed(const Duration(seconds: 1), () { Future.delayed(const Duration(seconds: 1), () {
showDialog( showDialog(
context: context, context: context,
@@ -35,6 +51,7 @@ class HomeWebPage extends StatelessWidget {
); );
}, },
).then((v) { ).then((v) {
_dialogShown = false;
if (v != null) { if (v != null) {
homeBloc.add(ConfirmUserAgreementEvent()); homeBloc.add(ConfirmUserAgreementEvent());
homeBloc.add(const FetchUserInfo()); homeBloc.add(const FetchUserInfo());
@@ -80,9 +97,9 @@ class HomeWebPage extends StatelessWidget {
height: size.height * 0.6, height: size.height * 0.6,
width: size.width * 0.68, width: size.width * 0.68,
child: GridView.builder( child: GridView.builder(
itemCount: 3, //8 itemCount: 3, // Change this count if needed.
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //4 crossAxisCount: 3, // Adjust as needed.
crossAxisSpacing: 20.0, crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0, mainAxisSpacing: 20.0,
childAspectRatio: 1.5, childAspectRatio: 1.5,
@@ -106,6 +123,7 @@ class HomeWebPage extends StatelessWidget {
), ),
); );
}, },
)); ),
);
} }
} }
@@ -42,7 +42,9 @@ class RolesUserModel {
invitedBy: invitedBy:
json['invitedBy'].toString().toLowerCase().replaceAll("_", " "), json['invitedBy'].toString().toLowerCase().replaceAll("_", " "),
phoneNumber: json['phoneNumber'], phoneNumber: json['phoneNumber'],
jobTitle: json['jobTitle'] ?? "-", jobTitle: json['jobTitle'] == null || json['jobTitle'] == " "
? "_"
: json['jobTitle'],
createdDate: json['createdDate'], createdDate: json['createdDate'],
createdTime: json['createdTime'], createdTime: json['createdTime'],
); );
@@ -79,13 +79,14 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
List<TreeNode> updatedCommunities = []; List<TreeNode> updatedCommunities = [];
List<TreeNode> spacesNodes = []; List<TreeNode> spacesNodes = [];
List<String> communityIds = [];
_onLoadCommunityAndSpaces( _onLoadCommunityAndSpaces(
LoadCommunityAndSpacesEvent event, Emitter<UsersState> emit) async { LoadCommunityAndSpacesEvent event, Emitter<UsersState> emit) async {
try { try {
emit(UsersLoadingState()); emit(UsersLoadingState());
List<CommunityModel> communities = List<CommunityModel> communities =
await CommunitySpaceManagementApi().fetchCommunities(); await CommunitySpaceManagementApi().fetchCommunities();
communityIds = communities.map((community) => community.uuid).toList();
updatedCommunities = await Future.wait( updatedCommunities = await Future.wait(
communities.map((community) async { communities.map((community) async {
List<SpaceModel> spaces = List<SpaceModel> spaces =
@@ -101,13 +102,19 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
); );
}).toList(), }).toList(),
); );
originalCommunities = updatedCommunities;
emit(const SpacesLoadedState()); emit(const SpacesLoadedState());
return updatedCommunities;
} catch (e) { } catch (e) {
emit(ErrorState('Error loading communities and spaces: $e')); emit(ErrorState('Error loading communities and spaces: $e'));
} }
} }
// This variable holds the full original list.
List<TreeNode> originalCommunities = [];
// This variable holds the working list that may be filtered.
// Build tree nodes from your data model.
List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) { List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) {
return spaces.map((space) { return spaces.map((space) {
List<TreeNode> childNodes = List<TreeNode> childNodes =
@@ -123,12 +130,39 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
}).toList(); }).toList();
} }
// Optional helper method to deep clone a TreeNode.
TreeNode _cloneNode(TreeNode node) {
return TreeNode(
uuid: node.uuid,
title: node.title,
isChecked: node.isChecked,
isHighlighted: node.isHighlighted,
isExpanded: node.isExpanded,
children: node.children.map(_cloneNode).toList(),
);
}
// Clone an entire list of tree nodes.
List<TreeNode> _cloneNodes(List<TreeNode> nodes) {
return nodes.map(_cloneNode).toList();
}
// Your search event handler.
void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) { void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) {
emit(UsersLoadingState()); emit(UsersLoadingState());
// If the search term is empty, restore the original list.
if (event.searchTerm!.isEmpty) { if (event.searchTerm!.isEmpty) {
// Clear any highlights on the restored copy.
updatedCommunities = _cloneNodes(originalCommunities);
_clearHighlights(updatedCommunities); _clearHighlights(updatedCommunities);
} else { } else {
_searchAndHighlightNodes(updatedCommunities, event.searchTerm!); // Start with a fresh clone of the original tree.
List<TreeNode> freshClone = _cloneNodes(originalCommunities);
_searchAndHighlightNodes(freshClone, event.searchTerm!);
updatedCommunities = _filterNodes(freshClone, event.searchTerm!);
} }
emit(ChangeStatusSteps()); emit(ChangeStatusSteps());
} }
@@ -155,6 +189,91 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
return anyMatch; return anyMatch;
} }
List<TreeNode> _filterNodes(List<TreeNode> nodes, String searchTerm) {
List<TreeNode> filteredNodes = [];
for (var node in nodes) {
bool isMatch =
node.title.toLowerCase().contains(searchTerm.toLowerCase());
List<TreeNode> filteredChildren = _filterNodes(node.children, searchTerm);
if (isMatch || filteredChildren.isNotEmpty) {
node.isHighlighted = isMatch;
node.children = filteredChildren;
filteredNodes.add(node);
}
}
return filteredNodes;
}
// List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) {
// return spaces.map((space) {
// List<TreeNode> childNodes =
// space.children.isNotEmpty ? _buildTreeNodes(space.children) : [];
// return TreeNode(
// uuid: space.uuid!,
// title: space.name,
// isChecked: false,
// isHighlighted: false,
// isExpanded: childNodes.isNotEmpty,
// children: childNodes,
// );
// }).toList();
// }
// void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) {
// emit(UsersLoadingState());
// if (event.searchTerm!.isEmpty) {
// _clearHighlights(updatedCommunities);
// } else {
// _searchAndHighlightNodes(updatedCommunities, event.searchTerm!);
// updatedCommunities = _filterNodes(updatedCommunities, event.searchTerm!);
// }
// emit(ChangeStatusSteps());
// }
// void _clearHighlights(List<TreeNode> nodes) {
// for (var node in nodes) {
// node.isHighlighted = false;
// if (node.children.isNotEmpty) {
// _clearHighlights(node.children);
// }
// }
// }
// bool _searchAndHighlightNodes(List<TreeNode> nodes, String searchTerm) {
// bool anyMatch = false;
// for (var node in nodes) {
// bool isMatch =
// node.title.toLowerCase().contains(searchTerm.toLowerCase());
// bool childMatch = _searchAndHighlightNodes(node.children, searchTerm);
// node.isHighlighted = isMatch || childMatch;
// anyMatch = anyMatch || node.isHighlighted;
// }
// return anyMatch;
// }
// List<TreeNode> _filterNodes(List<TreeNode> nodes, String searchTerm) {
// List<TreeNode> filteredNodes = [];
// for (var node in nodes) {
// // Check if the current node's title contains the search term.
// bool isMatch =
// node.title.toLowerCase().contains(searchTerm.toLowerCase());
// // Recursively filter the children.
// List<TreeNode> filteredChildren = _filterNodes(node.children, searchTerm);
// // If the current node is a match or any of its children are, include it.
// if (isMatch || filteredChildren.isNotEmpty) {
// // Optionally, update any properties (like isHighlighted) if you still need them.
// node.isHighlighted = isMatch;
// // Replace the children with the filtered ones.
// node.children = filteredChildren;
// filteredNodes.add(node);
// }
// }
// return filteredNodes;
// }
List<String> selectedIds = []; List<String> selectedIds = [];
List<String> getSelectedIds(List<TreeNode> nodes) { List<String> getSelectedIds(List<TreeNode> nodes) {
@@ -177,7 +296,6 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
try { try {
emit(UsersLoadingState()); emit(UsersLoadingState());
roles = await UserPermissionApi().fetchRoles(); roles = await UserPermissionApi().fetchRoles();
// add(PermissionEvent(roleUuid: roles.first.uuid));
emit(RolePermissionInitial()); emit(RolePermissionInitial());
} catch (e) { } catch (e) {
emit(ErrorState('Error loading communities and spaces: $e')); emit(ErrorState('Error loading communities and spaces: $e'));
@@ -208,10 +326,13 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
return anyMatch; return anyMatch;
} }
_sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async { void _sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async {
try { try {
emit(UsersLoadingState()); emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities); List<String> selectedIds = getSelectedIds(updatedCommunities)
.where((id) => !communityIds.contains(id))
.toList();
bool res = await UserPermissionApi().sendInviteUser( bool res = await UserPermissionApi().sendInviteUser(
email: emailController.text, email: emailController.text,
firstName: firstNameController.text, firstName: firstNameController.text,
@@ -221,7 +342,8 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
roleUuid: roleSelected, roleUuid: roleSelected,
spaceUuids: selectedIds, spaceUuids: selectedIds,
); );
if (res == true) {
if (res) {
showCustomDialog( showCustomDialog(
barrierDismissible: false, barrierDismissible: false,
context: event.context, context: event.context,
@@ -251,7 +373,10 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
_editInviteUser(EditInviteUsers event, Emitter<UsersState> emit) async { _editInviteUser(EditInviteUsers event, Emitter<UsersState> emit) async {
try { try {
emit(UsersLoadingState()); emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities); List<String> selectedIds = getSelectedIds(updatedCommunities)
.where((id) => !communityIds.contains(id))
.toList();
bool res = await UserPermissionApi().editInviteUser( bool res = await UserPermissionApi().editInviteUser(
userId: event.userId, userId: event.userId,
firstName: firstNameController.text, firstName: firstNameController.text,
@@ -34,8 +34,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
return Dialog( return Dialog(
child: Container( child: Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.white, color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900, width: 900,
child: Column( child: Column(
children: [ children: [
@@ -64,8 +63,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
children: [ children: [
_buildStep1Indicator(1, "Basics", _blocRole), _buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole), _buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator( _buildStep3Indicator(3, "Role & Permissions", _blocRole),
3, "Role & Permissions", _blocRole),
], ],
), ),
), ),
@@ -113,15 +111,12 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
if (currentStep < 3) { if (currentStep < 3) {
currentStep++; currentStep++;
if (currentStep == 2) { if (currentStep == 2) {
_blocRole.add( _blocRole.add(const CheckStepStatus(isEditUser: false));
const CheckStepStatus(isEditUser: false));
} else if (currentStep == 3) { } else if (currentStep == 3) {
_blocRole _blocRole.add(const CheckSpacesStepStatus());
.add(const CheckSpacesStepStatus());
} }
} else { } else {
_blocRole _blocRole.add(SendInviteUsers(context: context));
.add(SendInviteUsers(context: context));
} }
}); });
}, },
@@ -129,11 +124,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
currentStep < 3 ? "Next" : "Save", currentStep < 3 ? "Next" : "Save",
style: TextStyle( style: TextStyle(
color: (_blocRole.isCompleteSpaces == false || color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics == _blocRole.isCompleteBasics == false ||
false || _blocRole.isCompleteRolePermissions == false) &&
_blocRole
.isCompleteRolePermissions ==
false) &&
currentStep == 3 currentStep == 3
? ColorsManager.grayColor ? ColorsManager.grayColor
: ColorsManager.secondaryColor), : ColorsManager.secondaryColor),
@@ -204,12 +196,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
label, label,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: currentStep == step color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
? ColorsManager.blackColor fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
), ),
), ),
], ],
@@ -236,12 +224,16 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
currentStep = step;
bloc.add(const CheckStepStatus(isEditUser: false)); bloc.add(const CheckStepStatus(isEditUser: false));
currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(const CheckStepStatus(isEditUser: false));
});
if (step3 == 3) { if (step3 == 3) {
Future.delayed(const Duration(seconds: 1), () {
bloc.add(const CheckRoleStepStatus()); bloc.add(const CheckRoleStepStatus());
});
} }
}); });
}, },
child: Column( child: Column(
@@ -268,12 +260,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
label, label,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: currentStep == step color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
? ColorsManager.blackColor fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
), ),
), ),
], ],
@@ -330,12 +318,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
label, label,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: currentStep == step color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
? ColorsManager.blackColor fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
), ),
), ),
], ],
@@ -46,8 +46,9 @@ class BasicsView extends StatelessWidget {
), ),
Row( Row(
children: [ children: [
SizedBox( Flexible(
width: MediaQuery.of(context).size.width * 0.18, child: SizedBox(
// width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08, height: MediaQuery.of(context).size.width * 0.08,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -75,8 +76,8 @@ class BasicsView extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: TextFormField( child: TextFormField(
style: style: const TextStyle(
const TextStyle(color: ColorsManager.blackColor), color: ColorsManager.blackColor),
// onChanged: (value) { // onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200), // Future.delayed(const Duration(milliseconds: 200),
// () { // () {
@@ -103,9 +104,11 @@ class BasicsView extends StatelessWidget {
], ],
), ),
), ),
),
const SizedBox(width: 10), const SizedBox(width: 10),
SizedBox( Flexible(
width: MediaQuery.of(context).size.width * 0.18, child: SizedBox(
// width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08, height: MediaQuery.of(context).size.width * 0.08,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -142,8 +145,7 @@ class BasicsView extends StatelessWidget {
decoration: decoration:
inputTextFormDeco(hintText: "Enter last name") inputTextFormDeco(hintText: "Enter last name")
.copyWith( .copyWith(
hintStyle: context hintStyle: context.textTheme.bodyMedium
.textTheme.bodyMedium
?.copyWith( ?.copyWith(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 12, fontSize: 12,
@@ -159,6 +161,7 @@ class BasicsView extends StatelessWidget {
], ],
), ),
), ),
),
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@@ -218,7 +221,7 @@ class BasicsView extends StatelessWidget {
if (_blocRole.checkEmailValid != "Valid email") { if (_blocRole.checkEmailValid != "Valid email") {
return _blocRole.checkEmailValid; return _blocRole.checkEmailValid;
} }
return null; // return null;
}, },
), ),
), ),
@@ -81,7 +81,7 @@ Future<void> showPopUpFilterMenu({
), ),
const Divider(), const Divider(),
const Text( const Text(
"Filter by Status", "Filter by ",
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
Container( Container(
@@ -27,7 +27,16 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
int currentPage = 1; int currentPage = 1;
List<RolesUserModel> users = []; List<RolesUserModel> users = [];
List<RolesUserModel> initialUsers = []; List<RolesUserModel> initialUsers = [];
List<RolesUserModel> totalUsersCount = [];
String currentSortOrder = ''; String currentSortOrder = '';
String currentSortJopTitle = '';
String currentSortRole = '';
String currentSortCreatedDate = '';
String currentSortStatus = '';
String currentSortCreatedBy = '';
String currentSortOrderDate = ''; String currentSortOrderDate = '';
List<String> roleTypes = []; List<String> roleTypes = [];
List<String> jobTitle = []; List<String> jobTitle = [];
@@ -40,9 +49,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
roleTypes.clear(); roleTypes.clear();
jobTitle.clear(); jobTitle.clear();
createdBy.clear(); createdBy.clear();
// deActivate.clear();
users = await UserPermissionApi().fetchUsers(); users = await UserPermissionApi().fetchUsers();
users.sort((a, b) { users.sort((a, b) {
final dateA = _parseDateTime(a.createdDate); final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.createdDate); final dateB = _parseDateTime(b.createdDate);
@@ -57,15 +64,13 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
for (var user in users) { for (var user in users) {
createdBy.add(user.invitedBy.toString()); createdBy.add(user.invitedBy.toString());
} }
// for (var user in users) {
// deActivate.add(user.status.toString());
// }
initialUsers = List.from(users); initialUsers = List.from(users);
roleTypes = roleTypes.toSet().toList(); roleTypes = roleTypes.toSet().toList();
jobTitle = jobTitle.toSet().toList(); jobTitle = jobTitle.toSet().toList();
createdBy = createdBy.toSet().toList(); createdBy = createdBy.toSet().toList();
// deActivate = deActivate.toSet().toList();
_handlePageChange(ChangePage(1), emit); _handlePageChange(ChangePage(1), emit);
totalUsersCount = initialUsers;
add(ChangePage(currentPage));
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} catch (e) { } catch (e) {
emit(ErrorState(e.toString())); emit(ErrorState(e.toString()));
@@ -96,26 +101,6 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
event.userId, event.newStatus == "disabled" ? false : true); event.userId, event.newStatus == "disabled" ? false : true);
if (res == true) { if (res == true) {
add(const GetUsers()); add(const GetUsers());
// users = users.map((user) {
// if (user.uuid == event.userId) {
// return RolesUserModel(
// uuid: user.uuid,
// createdAt: user.createdAt,
// email: user.email,
// firstName: user.firstName,
// lastName: user.lastName,
// roleType: user.roleType,
// status: event.newStatus,
// isEnabled: event.newStatus == "disabled" ? false : true,
// invitedBy: user.invitedBy,
// phoneNumber: user.phoneNumber,
// jobTitle: user.jobTitle,
// createdDate: user.createdDate,
// createdTime: user.createdTime,
// );
// }
// return user;
// }).toList();
} }
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} catch (e) { } catch (e) {
@@ -125,11 +110,14 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByNameAsc( void _toggleSortUsersByNameAsc(
SortUsersByNameAsc event, Emitter<UserTableState> emit) { SortUsersByNameAsc event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrder == "Asc") { if (currentSortOrder == "Asc") {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
users = List.from(users); users = List.from(users);
emit(UsersLoadedState(users: users));
} else { } else {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = "Asc"; currentSortOrder = "Asc";
@@ -137,28 +125,42 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
.toString() .toString()
.toLowerCase() .toLowerCase()
.compareTo(b.firstName.toString().toLowerCase())); .compareTo(b.firstName.toString().toLowerCase()));
emit(UsersLoadedState(users: users));
} }
currentSortJopTitle = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
emit(UsersLoadedState(users: users));
} }
void _toggleSortUsersByNameDesc( void _toggleSortUsersByNameDesc(
SortUsersByNameDesc event, Emitter<UserTableState> emit) { SortUsersByNameDesc event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrder == "Desc") { if (currentSortOrder == "Desc") {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
users = List.from(initialUsers); // Reset to saved initial state users = List.from(initialUsers);
emit(UsersLoadedState(users: users));
} else { } else {
// Sort descending
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = "Desc"; currentSortOrder = "Desc";
users.sort((a, b) => b.firstName!.compareTo(a.firstName!)); users.sort((a, b) => b.firstName!.compareTo(a.firstName!));
emit(UsersLoadedState(users: users));
} }
currentSortJopTitle = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
emit(UsersLoadedState(users: users));
} }
void _toggleSortUsersByDateNewestToOldest( void _toggleSortUsersByDateNewestToOldest(
DateNewestToOldestEvent event, Emitter<UserTableState> emit) { DateNewestToOldestEvent event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrderDate == "NewestToOldest") { if (currentSortOrderDate == "NewestToOldest") {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
@@ -179,6 +181,10 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByDateOldestToNewest( void _toggleSortUsersByDateOldestToNewest(
DateOldestToNewestEvent event, Emitter<UserTableState> emit) { DateOldestToNewestEvent event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrderDate == "OldestToNewest") { if (currentSortOrderDate == "OldestToNewest") {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
@@ -212,6 +218,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
Future<void> _searchUsers( Future<void> _searchUsers(
SearchUsers event, Emitter<UserTableState> emit) async { SearchUsers event, Emitter<UserTableState> emit) async {
try { try {
emit(TableSearch());
final query = event.query.toLowerCase(); final query = event.query.toLowerCase();
final filteredUsers = initialUsers.where((user) { final filteredUsers = initialUsers.where((user) {
final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); final fullName = "${user.firstName} ${user.lastName}".toLowerCase();
@@ -240,7 +247,8 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} }
void _handlePageChange(ChangePage event, Emitter<UserTableState> emit) { void _handlePageChange(ChangePage event, Emitter<UserTableState> emit) {
const itemsPerPage = 10; currentPage = event.pageNumber;
const itemsPerPage = 20;
final startIndex = (event.pageNumber - 1) * itemsPerPage; final startIndex = (event.pageNumber - 1) * itemsPerPage;
final endIndex = startIndex + itemsPerPage; final endIndex = startIndex + itemsPerPage;
if (startIndex >= users.length) { if (startIndex >= users.length) {
@@ -277,9 +285,15 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") { } else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc"; currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else { } else {}
currentSortOrder = ""; currentSortOrder = "";
} currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
currentSortJopTitle = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers)); emit(UsersLoadedState(users: filteredUsers));
} }
@@ -301,9 +315,16 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") { } else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc"; currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else { } else {}
currentSortOrder = ""; currentSortOrder = "";
} currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
currentSortRole = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers)); emit(UsersLoadedState(users: filteredUsers));
} }
@@ -325,9 +346,15 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") { } else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc"; currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else { } else {}
currentSortOrder = ""; currentSortOrder = '';
} currentSortRole = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers)); emit(UsersLoadedState(users: filteredUsers));
} }
@@ -337,7 +364,20 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
final filteredUsers = initialUsers.where((user) { final filteredUsers = initialUsers.where((user) {
if (selectedStatuses.isEmpty) return true; if (selectedStatuses.isEmpty) return true;
return selectedStatuses.contains(user.status);
return selectedStatuses.any((status) {
final userStatus = user.status?.toLowerCase() ?? '';
switch (status.toLowerCase()) {
case 'active':
return user.isEnabled == true && userStatus != 'invited';
case 'disabled':
return user.isEnabled == false;
case 'invited':
return userStatus == 'invited';
default:
return false;
}
});
}).toList(); }).toList();
if (event.sortOrder == "Asc") { if (event.sortOrder == "Asc") {
currentSortOrder = "Asc"; currentSortOrder = "Asc";
@@ -348,9 +388,14 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") { } else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc"; currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else { totalUsersCount = filteredUsers;
currentSortOrder = ""; } else {}
} currentSortOrder = '';
currentSortRole = '';
currentSortCreatedDate = '';
currentSortCreatedBy = '';
currentSortOrderDate = "";
emit(UsersLoadedState(users: filteredUsers)); emit(UsersLoadedState(users: filteredUsers));
} }
@@ -9,7 +9,10 @@ final class TableInitial extends UserTableState {
@override @override
List<Object> get props => []; List<Object> get props => [];
} }
final class TableSearch extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesLoadingState extends UserTableState { final class RolesLoadingState extends UserTableState {
@override @override
List<Object> get props => []; List<Object> get props => [];
@@ -12,7 +12,7 @@ Future<void> showDateFilterMenu({
Overlay.of(context).context.findRenderObject() as RenderBox; Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect( final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB( Rect.fromLTRB(
overlay.size.width / 2, overlay.size.width / 3,
240, 240,
0, 0,
overlay.size.height, overlay.size.height,
@@ -40,7 +40,6 @@ Future<void> showDateFilterMenu({
), ),
title: Text( title: Text(
"Sort from newest to oldest", "Sort from newest to oldest",
// style: context.textTheme.bodyMedium,
style: TextStyle( style: TextStyle(
color: isSelected == "NewestToOldest" color: isSelected == "NewestToOldest"
? Colors.black ? Colors.black
@@ -65,9 +64,5 @@ Future<void> showDateFilterMenu({
), ),
), ),
], ],
).then((value) { ).then((value) {});
// setState(() {
// _isDropdownOpen = false;
// });
});
} }
@@ -40,7 +40,6 @@ Future<void> showDeActivateFilterMenu({
), ),
title: Text( title: Text(
"Sort A to Z", "Sort A to Z",
// style: context.textTheme.bodyMedium,
style: TextStyle( style: TextStyle(
color: isSelected == "NewestToOldest" color: isSelected == "NewestToOldest"
? Colors.black ? Colors.black
@@ -65,9 +64,5 @@ Future<void> showDeActivateFilterMenu({
), ),
), ),
], ],
).then((value) { ).then((value) {});
// setState(() {
// _isDropdownOpen = false;
// });
});
} }
@@ -12,7 +12,7 @@ Future<void> showNameMenu({
Overlay.of(context).context.findRenderObject() as RenderBox; Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect( final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB( Rect.fromLTRB(
overlay.size.width / 25, overlay.size.width / 35,
240, 240,
0, 0,
overlay.size.height, overlay.size.height,
@@ -40,7 +40,6 @@ Future<void> showNameMenu({
), ),
title: Text( title: Text(
"Sort A to Z", "Sort A to Z",
// style: context.textTheme.bodyMedium,
style: TextStyle( style: TextStyle(
color: isSelected == "Asc" ? Colors.black : Colors.blueGrey), color: isSelected == "Asc" ? Colors.black : Colors.blueGrey),
), ),
@@ -61,9 +60,5 @@ Future<void> showNameMenu({
), ),
), ),
], ],
).then((value) { ).then((value) {});
// setState(() {
// _isDropdownOpen = false;
// });
});
} }
@@ -1,260 +1,264 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class DynamicTableScreen extends StatefulWidget { class _HeaderColumn extends StatelessWidget {
final List<String> titles; final String title;
final List<List<Widget>> rows; final double width;
final void Function(int columnIndex)? onFilter; final bool showFilter;
final VoidCallback? onFilter;
final Function(double) onResize;
DynamicTableScreen( const _HeaderColumn({
{required this.titles, required this.rows, required this.onFilter}); required this.title,
required this.width,
@override required this.showFilter,
_DynamicTableScreenState createState() => _DynamicTableScreenState(); required this.onResize,
} this.onFilter,
Key? key,
class _DynamicTableScreenState extends State<DynamicTableScreen> }) : super(key: key);
with WidgetsBindingObserver {
late List<double> columnWidths;
late double totalWidth;
@override
void initState() {
super.initState();
columnWidths = List<double>.filled(widget.titles.length, 150.0);
totalWidth = columnWidths.reduce((a, b) => a + b);
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
final newScreenWidth = MediaQuery.of(context).size.width;
setState(() {
columnWidths = List<double>.generate(widget.titles.length, (index) {
if (index == 1) {
return newScreenWidth *
0.12; // 20% of screen width for the second column
} else if (index == 9) {
return newScreenWidth *
0.1; // 25% of screen width for the tenth column
}
return newScreenWidth *
0.09; // Default to 10% of screen width for other columns
});
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width; return MouseRegion(
if (columnWidths.every((width) => width == screenWidth * 7)) { cursor: SystemMouseCursors.resizeColumn,
columnWidths = List<double>.generate(widget.titles.length, (index) { child: GestureDetector(
if (index == 1) { onHorizontalDragUpdate: (details) => onResize(details.delta.dx),
return screenWidth * 0.11;
} else if (index == 9) {
return screenWidth * 0.1;
}
return screenWidth * 0.09;
});
setState(() {});
}
return SingleChildScrollView(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
child: Container( child: Container(
decoration: containerDecoration.copyWith( width: width,
color: ColorsManager.whiteColors, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
borderRadius: const BorderRadius.all(Radius.circular(20))), decoration: const BoxDecoration(
child: FittedBox( border: Border(right: BorderSide(color: ColorsManager.boxDivider)),
child: Column( ),
children: [
Container(
width: totalWidth,
decoration: containerDecoration.copyWith(
color: ColorsManager.circleRolesBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15))),
child: Row(
children: List.generate(widget.titles.length, (index) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FittedBox(
child: Container(
padding: const EdgeInsets.only(left: 5, right: 5),
width: columnWidths[index],
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
SizedBox( Expanded(
child: Text( child: Text(
widget.titles[index], title,
maxLines: 2, maxLines: 2,
style: const TextStyle(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 13, fontSize: 13,
color: ColorsManager.grayColor, color: ColorsManager.grayColor,
), ),
), ),
), ),
if (index != 1 && if (showFilter)
index != 9 && IconButton(
index != 8 && icon: SvgPicture.asset(Assets.filterTableIcon),
index != 5) onPressed: onFilter,
FittedBox( padding: EdgeInsets.zero,
child: IconButton( constraints: const BoxConstraints(),
icon: SvgPicture.asset(
Assets.filterTableIcon,
fit: BoxFit.none,
), ),
onPressed: () {
if (widget.onFilter != null) {
widget.onFilter!(index);
}
},
),
)
], ],
), ),
), ),
), ),
GestureDetector( );
onHorizontalDragUpdate: (details) { }
setState(() { }
columnWidths[index] =
(columnWidths[index] + details.delta.dx) class _TableRow extends StatelessWidget {
.clamp(150.0, 300.0); final List<Widget> cells;
totalWidth = columnWidths.reduce((a, b) => a + b); final List<double> columnWidths;
}); final bool isLast;
},
child: MouseRegion( const _TableRow({
cursor: SystemMouseCursors.resizeColumn, required this.cells,
child: Container( required this.columnWidths,
color: Colors.green, required this.isLast,
child: Container( Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
for (int i = 0; i < cells.length; i++)
Container(
width: columnWidths[i],
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
// decoration: BoxDecoration(
// border: Border(
// right: BorderSide(color: ColorsManager.boxDivider),
// ),
// ),
child: cells[i],
),
],
),
if (!isLast)
Divider(
height: 1,
thickness: 1,
color: ColorsManager.boxDivider, color: ColorsManager.boxDivider,
width: 1,
height: 50,
),
),
),
), ),
], ],
); );
}), }
}
//===========================================================================
class DynamicTableScreen extends StatefulWidget {
final List<String> titles;
final List<List<Widget>> rows;
final void Function(int columnIndex)? onFilter;
const DynamicTableScreen({
required this.titles,
required this.rows,
required this.onFilter,
Key? key,
}) : super(key: key);
@override
_DynamicTableScreenState createState() => _DynamicTableScreenState();
}
class _DynamicTableScreenState extends State<DynamicTableScreen> {
late List<double> columnWidths;
final double _minColumnWidth = 100.0;
final double _maxColumnWidth = 300.0;
final double _dividerWidth = 1.0;
double _lastAvailableWidth = 0;
@override
void initState() {
super.initState();
columnWidths = List.filled(widget.titles.length, _minColumnWidth);
}
void _handleColumnResize(int index, double delta) {
setState(() {
double newWidth = columnWidths[index] + delta;
newWidth = newWidth.clamp(_minColumnWidth, _maxColumnWidth);
double actualDelta = newWidth - columnWidths[index];
if (actualDelta == 0) return;
int nextIndex = (index + 1) % columnWidths.length;
columnWidths[index] = newWidth;
columnWidths[nextIndex] = (columnWidths[nextIndex] - actualDelta)
.clamp(_minColumnWidth, _maxColumnWidth);
});
}
Widget _buildHeader() {
return Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.circleRolesBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
), ),
), ),
widget.rows.isEmpty child: Row(
? SizedBox(
height: MediaQuery.of(context).size.height / 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Column( for (int i = 0; i < widget.titles.length; i++)
_HeaderColumn(
title: widget.titles[i],
width: columnWidths[i],
showFilter: i != 1 && i != 9 && i != 8 && i != 5,
onFilter: () => widget.onFilter?.call(i),
onResize: (delta) => _handleColumnResize(i, delta),
),
],
),
);
}
Widget _buildBody() {
if (widget.rows.isEmpty) {
return SizedBox(
height: 300,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
SvgPicture.asset(Assets.emptyTable), SvgPicture.asset(Assets.emptyTable),
const SizedBox( const SizedBox(height: 15),
height: 15,
),
const Text( const Text(
'No Users', 'No Users',
style: TextStyle( style: TextStyle(
color: ColorsManager.lightGrayColor, color: ColorsManager.lightGrayColor,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700), fontWeight: FontWeight.w700,
) ),
],
), ),
], ],
), ),
) ),
: Center( );
child: Container( }
width: totalWidth,
return Container(
decoration: containerDecoration.copyWith( decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15), bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15))), bottomRight: Radius.circular(15),
child: ListView.builder( ),
physics: const NeverScrollableScrollPhysics(), ),
shrinkWrap: true, child: Column(
itemCount: widget.rows.length,
itemBuilder: (context, rowIndex) {
if (columnWidths.every((width) => width == 120.0)) {
columnWidths = List<double>.generate(
widget.titles.length, (index) {
if (index == 1) {
return screenWidth * 0.11;
} else if (index == 9) {
return screenWidth * 0.2;
}
return screenWidth * 0.11;
});
setState(() {});
}
final row = widget.rows[rowIndex];
return Column(
children: [ children: [
Container( for (int rowIndex = 0; rowIndex < widget.rows.length; rowIndex++)
child: Padding( _TableRow(
padding: const EdgeInsets.only( cells: widget.rows[rowIndex],
left: 5, top: 10, right: 5, bottom: 10), columnWidths: columnWidths,
child: Row( isLast: rowIndex == widget.rows.length - 1,
children:
List.generate(row.length, (index) {
return SizedBox(
width: columnWidths[index],
child: SizedBox(
child: Padding(
padding: const EdgeInsets.only(
left: 15, right: 10),
child: row[index],
),
),
);
}),
),
),
),
if (rowIndex < widget.rows.length - 1)
Row(
children: List.generate(
widget.titles.length, (index) {
return SizedBox(
width: columnWidths[index],
child: const Divider(
color: ColorsManager.boxDivider,
thickness: 1,
height: 1,
),
);
}),
), ),
], ],
),
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final availableWidth = constraints.maxWidth;
final totalDividersWidth = (widget.titles.length - 1) * _dividerWidth;
if (_lastAvailableWidth != availableWidth) {
final equalWidth =
(availableWidth - totalDividersWidth) / widget.titles.length;
final clampedWidth =
equalWidth.clamp(_minColumnWidth, _maxColumnWidth);
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
columnWidths = List.filled(widget.titles.length, clampedWidth);
_lastAvailableWidth = availableWidth;
});
});
}
final totalTableWidth =
columnWidths.fold(0.0, (sum, w) => sum + w) + totalDividersWidth;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
width: totalTableWidth,
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
_buildBody(),
],
),
),
); );
}, },
),
),
),
],
),
),
),
); );
} }
} }
@@ -25,7 +25,8 @@ class UsersPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TextEditingController searchController = TextEditingController(); final TextEditingController searchController = TextEditingController();
Widget actionButton({required String title, required Function()? onTap}) { Widget actionButton(
{bool isActive = false, required String title, Function()? onTap}) {
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
child: Padding( child: Padding(
@@ -33,7 +34,9 @@ class UsersPage extends StatelessWidget {
child: Text( child: Text(
title, title,
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: title == "Delete" color: isActive == false && title != "Delete"
? Colors.grey
: title == "Delete"
? ColorsManager.red ? ColorsManager.red
: ColorsManager.spaceColor, : ColorsManager.spaceColor,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@@ -108,7 +111,6 @@ class UsersPage extends StatelessWidget {
final screenSize = MediaQuery.of(context).size; final screenSize = MediaQuery.of(context).size;
final _blocRole = BlocProvider.of<UserTableBloc>(context); final _blocRole = BlocProvider.of<UserTableBloc>(context);
if (state is UsersLoadingState) { if (state is UsersLoadingState) {
_blocRole.add(ChangePage(_blocRole.currentPage));
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is UsersLoadedState) { } else if (state is UsersLoadedState) {
return Padding( return Padding(
@@ -130,9 +132,12 @@ class UsersPage extends StatelessWidget {
child: TextFormField( child: TextFormField(
controller: searchController, controller: searchController,
onChanged: (value) { onChanged: (value) {
context final bloc = context.read<UserTableBloc>();
.read<UserTableBloc>() bloc.add(FilterClearEvent());
.add(SearchUsers(value)); bloc.add(SearchUsers(value));
if (value == '') {
bloc.add(ChangePage(1));
}
}, },
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
decoration: textBoxDecoration(radios: 15)!.copyWith( decoration: textBoxDecoration(radios: 15)!.copyWith(
@@ -215,7 +220,7 @@ class UsersPage extends StatelessWidget {
showPopUpFilterMenu( showPopUpFilterMenu(
position: RelativeRect.fromLTRB( position: RelativeRect.fromLTRB(
overlay.size.width / 4, overlay.size.width / 5.3,
240, 240,
overlay.size.width / 4, overlay.size.width / 4,
0, 0,
@@ -223,8 +228,9 @@ class UsersPage extends StatelessWidget {
list: _blocRole.jobTitle, list: _blocRole.jobTitle,
context: context, context: context,
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortJopTitle,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
@@ -233,14 +239,14 @@ class UsersPage extends StatelessWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
_blocRole.add(FilterUsersByJobEvent( _blocRole.add(FilterUsersByJobEvent(
selectedJob: selectedItems, selectedJob: selectedItems,
sortOrder: _blocRole.currentSortOrder, sortOrder: _blocRole.currentSortJopTitle,
)); ));
}, },
onSortAtoZ: (v) { onSortAtoZ: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortJopTitle = v;
}, },
onSortZtoA: (v) { onSortZtoA: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortJopTitle = v;
}, },
); );
} }
@@ -263,8 +269,9 @@ class UsersPage extends StatelessWidget {
list: _blocRole.roleTypes, list: _blocRole.roleTypes,
context: context, context: context,
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortRole,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
@@ -274,13 +281,13 @@ class UsersPage extends StatelessWidget {
context.read<UserTableBloc>().add( context.read<UserTableBloc>().add(
FilterUsersByRoleEvent( FilterUsersByRoleEvent(
selectedRoles: selectedItems, selectedRoles: selectedItems,
sortOrder: _blocRole.currentSortOrder)); sortOrder: _blocRole.currentSortRole));
}, },
onSortAtoZ: (v) { onSortAtoZ: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortRole = v;
}, },
onSortZtoA: (v) { onSortZtoA: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortRole = v;
}, },
); );
} }
@@ -318,8 +325,9 @@ class UsersPage extends StatelessWidget {
list: _blocRole.createdBy, list: _blocRole.createdBy,
context: context, context: context,
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortCreatedBy,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
@@ -328,13 +336,13 @@ class UsersPage extends StatelessWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
_blocRole.add(FilterUsersByCreatedEvent( _blocRole.add(FilterUsersByCreatedEvent(
selectedCreatedBy: selectedItems, selectedCreatedBy: selectedItems,
sortOrder: _blocRole.currentSortOrder)); sortOrder: _blocRole.currentSortCreatedBy));
}, },
onSortAtoZ: (v) { onSortAtoZ: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortCreatedBy = v;
}, },
onSortZtoA: (v) { onSortZtoA: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortCreatedBy = v;
}, },
); );
} }
@@ -343,6 +351,7 @@ class UsersPage extends StatelessWidget {
for (var item in _blocRole.status) for (var item in _blocRole.status)
item: _blocRole.selectedStatuses.contains(item), item: _blocRole.selectedStatuses.contains(item),
}; };
final RenderBox overlay = Overlay.of(context) final RenderBox overlay = Overlay.of(context)
.context .context
.findRenderObject() as RenderBox; .findRenderObject() as RenderBox;
@@ -350,16 +359,16 @@ class UsersPage extends StatelessWidget {
position: RelativeRect.fromLTRB( position: RelativeRect.fromLTRB(
overlay.size.width / 0, overlay.size.width / 0,
240, 240,
overlay.size.width / 4, overlay.size.width / 5,
0, 0,
), ),
list: _blocRole.status, list: _blocRole.status,
context: context, context: context,
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortStatus,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
.map((entry) => entry.key) .map((entry) => entry.key)
@@ -367,13 +376,13 @@ class UsersPage extends StatelessWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
_blocRole.add(FilterUsersByDeActevateEvent( _blocRole.add(FilterUsersByDeActevateEvent(
selectedActivate: selectedItems, selectedActivate: selectedItems,
sortOrder: _blocRole.currentSortOrder)); sortOrder: _blocRole.currentSortStatus));
}, },
onSortAtoZ: (v) { onSortAtoZ: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortStatus = v;
}, },
onSortZtoA: (v) { onSortZtoA: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortStatus = v;
}, },
); );
} }
@@ -410,7 +419,7 @@ class UsersPage extends StatelessWidget {
return [ return [
Text('${user.firstName} ${user.lastName}'), Text('${user.firstName} ${user.lastName}'),
Text(user.email), Text(user.email),
Text(user.jobTitle ?? '-'), Text(user.jobTitle),
Text(user.roleType ?? ''), Text(user.roleType ?? ''),
Text(user.createdDate ?? ''), Text(user.createdDate ?? ''),
Text(user.createdTime ?? ''), Text(user.createdTime ?? ''),
@@ -427,11 +436,6 @@ class UsersPage extends StatelessWidget {
userId: user.uuid, userId: user.uuid,
onTap: user.status != "invited" onTap: user.status != "invited"
? () { ? () {
// final newStatus = user.status == 'active'
// ? 'disabled'
// : user.status == 'disabled'
// ? 'invited'
// : 'active';
context.read<UserTableBloc>().add( context.read<UserTableBloc>().add(
ChangeUserStatus( ChangeUserStatus(
userId: user.uuid, userId: user.uuid,
@@ -443,18 +447,17 @@ class UsersPage extends StatelessWidget {
), ),
Row( Row(
children: [ children: [
// actionButton( user.isEnabled != false
// title: "Activity Log", ? actionButton(
// onTap: () {}, isActive: true,
// ),
actionButton(
title: "Edit", title: "Edit",
onTap: () { onTap: () {
showDialog( showDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (BuildContext context) { builder: (BuildContext context) {
return EditUserDialog(userId: user.uuid); return EditUserDialog(
userId: user.uuid);
}, },
).then((v) { ).then((v) {
if (v != null) { if (v != null) {
@@ -464,6 +467,9 @@ class UsersPage extends StatelessWidget {
} }
}); });
}, },
)
: actionButton(
title: "Edit",
), ),
actionButton( actionButton(
title: "Delete", title: "Delete",
@@ -486,11 +492,9 @@ class UsersPage extends StatelessWidget {
}); });
}, },
).then((v) { ).then((v) {
if (v != null) {
if (v != null) { if (v != null) {
_blocRole.add(const GetUsers()); _blocRole.add(const GetUsers());
} }
}
}); });
}, },
), ),
@@ -516,12 +520,11 @@ class UsersPage extends StatelessWidget {
const Icon(Icons.keyboard_double_arrow_right), const Icon(Icons.keyboard_double_arrow_right),
firstPageIcon: firstPageIcon:
const Icon(Icons.keyboard_double_arrow_left), const Icon(Icons.keyboard_double_arrow_left),
totalPages: (_blocRole.users.length / totalPages: (_blocRole.totalUsersCount.length /
_blocRole.itemsPerPage) _blocRole.itemsPerPage)
.ceil(), .ceil(),
currentPage: _blocRole.currentPage, currentPage: _blocRole.currentPage,
onPageChanged: (int pageNumber) { onPageChanged: (int pageNumber) {
_blocRole.currentPage = pageNumber;
context context
.read<UserTableBloc>() .read<UserTableBloc>()
.add(ChangePage(pageNumber)); .add(ChangePage(pageNumber));
@@ -8,6 +8,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/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/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.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'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class RolesAndPermissionPage extends StatelessWidget { class RolesAndPermissionPage extends StatelessWidget {
@@ -26,11 +27,10 @@ class RolesAndPermissionPage extends StatelessWidget {
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: WebScaffold( : WebScaffold(
enableMenuSidebar: false, enableMenuSidebar: false,
appBarTitle: FittedBox( appBarTitle: Text(
child: Text(
'Roles & Permissions', 'Roles & Permissions',
style: Theme.of(context).textTheme.headlineLarge, style:
), ResponsiveTextTheme.of(context).deviceManagementTitle,
), ),
rightBody: const NavigateHomeGridView(), rightBody: const NavigateHomeGridView(),
centerBody: Row( centerBody: Row(
@@ -87,6 +87,7 @@ class SpaceManagementBloc
prevSpaceModels = List<SpaceTemplateModel>.from( prevSpaceModels = List<SpaceTemplateModel>.from(
(previousState as dynamic).spaceModels ?? [], (previousState as dynamic).spaceModels ?? [],
); );
allSpaces.addAll(prevSpaceModels);
} }
if (prevSpaceModels.isEmpty) { if (prevSpaceModels.isEmpty) {
@@ -179,6 +180,7 @@ class SpaceManagementBloc
final updatedCommunities = final updatedCommunities =
await Future.wait(communities.map((community) async { await Future.wait(communities.map((community) async {
final spaces = await _fetchSpacesForCommunity(community.uuid); final spaces = await _fetchSpacesForCommunity(community.uuid);
return CommunityModel( return CommunityModel(
uuid: community.uuid, uuid: community.uuid,
createdAt: community.createdAt, createdAt: community.createdAt,
@@ -95,6 +95,9 @@ class SpaceModel {
icon: json['icon'] ?? Assets.location, icon: json['icon'] ?? Assets.location,
position: Offset(json['x'] ?? 0, json['y'] ?? 0), position: Offset(json['x'] ?? 0, json['y'] ?? 0),
isHovered: false, isHovered: false,
spaceModel: json['spaceModel'] != null
? SpaceTemplateModel.fromJson(json['spaceModel'])
: null,
tags: (json['tags'] as List<dynamic>?) tags: (json['tags'] as List<dynamic>?)
?.where((item) => item is Map<String, dynamic>) // Validate type ?.where((item) => item is Map<String, dynamic>) // Validate type
.map((item) => Tag.fromJson(item as Map<String, dynamic>)) .map((item) => Tag.fromJson(item as Map<String, dynamic>))
@@ -10,6 +10,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/product_api.dart';
import 'package:syncrow_web/services/space_mana_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/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'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class SpaceManagementPage extends StatefulWidget { class SpaceManagementPage extends StatefulWidget {
@@ -36,8 +37,10 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
), ),
], ],
child: WebScaffold( child: WebScaffold(
appBarTitle: Text('Space Management', appBarTitle: Text(
style: Theme.of(context).textTheme.headlineLarge), 'Space Management',
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
enableMenuSidebar: false, enableMenuSidebar: false,
centerBody: CenterBodyWidget(), centerBody: CenterBodyWidget(),
rightBody: const NavigateHomeGridView(), rightBody: const NavigateHomeGridView(),
@@ -22,6 +22,9 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_li
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart';
import 'package:syncrow_web/pages/spaces_management/helper/connection_helper.dart';
import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.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/space_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@@ -130,7 +133,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
communities: widget.communities, communities: widget.communities,
communityName: widget.selectedCommunity?.name, communityName: widget.selectedCommunity?.name,
community: widget.selectedCommunity, community: widget.selectedCommunity,
isSave: isSave(spaces), isSave: SpaceHelper.isSave(spaces),
isEditingName: isEditingName, isEditingName: isEditingName,
nameController: _nameController, nameController: _nameController,
onSave: _saveSpaces, onSave: _saveSpaces,
@@ -175,7 +178,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
children: [ children: [
for (var connection in connections) for (var connection in connections)
Opacity( Opacity(
opacity: _isHighlightedConnection(connection) opacity: ConnectionHelper.isHighlightedConnection(
connection, widget.selectedSpace)
? 1.0 ? 1.0
: 0.3, // Adjust opacity : 0.3, // Adjust opacity
child: CustomPaint( child: CustomPaint(
@@ -195,7 +199,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
screenSize, screenSize,
position: position:
spaces[index].position + newPosition, spaces[index].position + newPosition,
parentIndex: index, parentIndex: index,
direction: direction, direction: direction,
); );
@@ -209,7 +212,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}, },
buildSpaceContainer: (int index) { buildSpaceContainer: (int index) {
final bool isHighlighted = final bool isHighlighted =
_isHighlightedSpace(spaces[index]); SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace);
return Opacity( return Opacity(
opacity: isHighlighted ? 1.0 : 0.3, opacity: isHighlighted ? 1.0 : 0.3,
@@ -295,7 +299,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
allTags: _getAllTagValues(spaces), allTags:
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null, parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name, onCreateSpace: (String name,
String icon, String icon,
@@ -306,7 +311,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
setState(() { setState(() {
// Set the first space in the center or use passed position // Set the first space in the center or use passed position
Offset centerPosition = Offset centerPosition =
position ?? _getCenterPosition(screenSize); position ?? ConnectionHelper.getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
name: name, name: name,
icon: icon, icon: icon,
@@ -351,11 +356,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
name: widget.selectedSpace!.name, name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon, icon: widget.selectedSpace!.icon,
parentSpace: SpaceHelper.findSpaceByInternalId(
widget.selectedSpace?.parent?.internalId, spaces),
editSpace: widget.selectedSpace, editSpace: widget.selectedSpace,
currentSpaceModel: widget.selectedSpace?.spaceModel,
tags: widget.selectedSpace?.tags, tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces, subspaces: widget.selectedSpace?.subspaces,
isEdit: true, isEdit: true,
allTags: _getAllTagValues(spaces), allTags: TagHelper.getAllTagValues(
widget.communities, widget.spaceModels),
onCreateSpace: (String name, onCreateSpace: (String name,
String icon, String icon,
List<SelectedProduct> selectedProducts, List<SelectedProduct> selectedProducts,
@@ -374,6 +383,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
widget.selectedSpace!.status = widget.selectedSpace!.status =
SpaceStatus.modified; // Mark as modified SpaceStatus.modified; // Mark as modified
} }
for (var space in spaces) {
if (space.internalId == widget.selectedSpace?.internalId) {
space.status = SpaceStatus.modified;
space.subspaces = subspaces;
space.tags = tags;
space.name = name;
}
}
}); });
}, },
key: Key(widget.selectedSpace!.name), key: Key(widget.selectedSpace!.name),
@@ -452,7 +470,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}).toList(); }).toList();
if (spacesToSave.isEmpty) { if (spacesToSave.isEmpty) {
debugPrint("No new or modified spaces to save.");
return; return;
} }
@@ -516,17 +533,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
); );
} }
bool _isHighlightedSpace(SpaceModel space) {
final selectedSpace = widget.selectedSpace;
if (selectedSpace == null) return true;
return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children
?.any((child) => child.internalId == space.internalId) ==
true;
}
void _deselectSpace(BuildContext context) { void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent( SelectSpaceEvent(
@@ -534,28 +540,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
); );
} }
bool _isHighlightedConnection(Connection connection) {
if (widget.selectedSpace == null) return true;
return connection.startSpace == widget.selectedSpace ||
connection.endSpace == widget.selectedSpace;
}
Offset _getCenterPosition(Size screenSize) {
return Offset(
screenSize.width / 2 - 260,
screenSize.height / 2 - 200,
);
}
bool isSave(List<SpaceModel> spaces) {
return spaces.isNotEmpty &&
spaces.any((space) =>
space.status == SpaceStatus.newSpace ||
space.status == SpaceStatus.modified ||
space.status == SpaceStatus.deleted);
}
void _onDuplicate(BuildContext parentContext) { void _onDuplicate(BuildContext parentContext) {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
@@ -641,17 +625,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
const double horizontalGap = 200.0; const double horizontalGap = 200.0;
const double verticalGap = 100.0; const double verticalGap = 100.0;
final Map<String, int> nameCounters = {};
String _generateCopyName(String originalName) {
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
nameCounters[baseName] = (nameCounters[baseName] ?? 0) + 1;
return "$baseName(${nameCounters[baseName]})";
}
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition,
SpaceModel? duplicatedParent) { SpaceModel? duplicatedParent) {
Offset newPosition = parentPosition + Offset(horizontalGap, 0); Offset newPosition =
Offset(parentPosition.dx + horizontalGap, original.position.dy);
while (spaces.any((s) => while (spaces.any((s) =>
(s.position - newPosition).distance < horizontalGap && (s.position - newPosition).distance < horizontalGap &&
@@ -659,7 +636,18 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
newPosition += Offset(horizontalGap, 0); newPosition += Offset(horizontalGap, 0);
} }
final duplicatedName = _generateCopyName(original.name); final duplicatedName =
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final List<SubspaceModel>? duplicatedSubspaces;
final List<Tag>? duplicatedTags;
if (original.spaceModel != null) {
duplicatedTags = [];
duplicatedSubspaces = [];
} else {
duplicatedTags = original.tags;
duplicatedSubspaces = original.subspaces;
}
final duplicated = SpaceModel( final duplicated = SpaceModel(
name: duplicatedName, name: duplicatedName,
@@ -670,8 +658,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
status: SpaceStatus.newSpace, status: SpaceStatus.newSpace,
parent: duplicatedParent, parent: duplicatedParent,
spaceModel: original.spaceModel, spaceModel: original.spaceModel,
subspaces: original.subspaces, subspaces: duplicatedSubspaces,
tags: original.tags, tags: duplicatedTags,
); );
originalToDuplicate[original] = duplicated; originalToDuplicate[original] = duplicated;
@@ -684,7 +672,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection( final newConnection = Connection(
startSpace: duplicatedParent, startSpace: duplicatedParent,
endSpace: duplicated, endSpace: duplicated,
direction: "down", direction: original.incomingConnection?.direction ?? 'down',
); );
connections.add(newConnection); connections.add(newConnection);
duplicated.incomingConnection = newConnection; duplicated.incomingConnection = newConnection;
@@ -723,10 +711,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
child.incomingConnection?.direction == "down" ?? false; child.incomingConnection?.direction == "down" ?? false;
if (isDownDirection && childrenWithDownDirection.length == 1) { if (isDownDirection && childrenWithDownDirection.length == 1) {
// Place the only "down" child vertically aligned with the parent
childStartPosition = duplicated.position + Offset(0, verticalGap); childStartPosition = duplicated.position + Offset(0, verticalGap);
} else if (!isDownDirection) { } else if (!isDownDirection) {
// Position children with other directions horizontally
childStartPosition = duplicated.position + Offset(horizontalGap, 0); childStartPosition = duplicated.position + Offset(horizontalGap, 0);
} }
@@ -747,14 +733,4 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
duplicateRecursive(space, space.position, duplicatedParent); duplicateRecursive(space, space.position, duplicatedParent);
} }
} }
List<String> _getAllTagValues(List<SpaceModel> spaces) {
final List<String> allTags = [];
for (final space in spaces) {
if (space.tags != null) {
allTags.addAll(space.listAllTagValues());
}
}
return allTags;
}
} }
@@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.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/space_template_model.dart';
@@ -40,6 +41,7 @@ class CreateSpaceDialog extends StatefulWidget {
final List<SubspaceModel>? subspaces; final List<SubspaceModel>? subspaces;
final List<Tag>? tags; final List<Tag>? tags;
final List<String>? allTags; final List<String>? allTags;
final SpaceTemplateModel? currentSpaceModel;
const CreateSpaceDialog( const CreateSpaceDialog(
{super.key, {super.key,
@@ -54,7 +56,8 @@ class CreateSpaceDialog extends StatefulWidget {
this.selectedProducts = const [], this.selectedProducts = const [],
this.spaceModels, this.spaceModels,
this.subspaces, this.subspaces,
this.tags}); this.tags,
this.currentSpaceModel});
@override @override
CreateSpaceDialogState createState() => CreateSpaceDialogState(); CreateSpaceDialogState createState() => CreateSpaceDialogState();
@@ -81,12 +84,22 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled = isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty; enteredName.isNotEmpty || nameController.text.isNotEmpty;
if (widget.currentSpaceModel != null) {
subspaces = [];
tags = [];
} else {
tags = widget.tags ?? []; tags = widget.tags ?? [];
subspaces = widget.subspaces ?? []; subspaces = widget.subspaces ?? [];
} }
selectedSpaceModel = widget.currentSpaceModel;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty ||
subspaces != null && subspaces!.isNotEmpty);
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog( return AlertDialog(
title: widget.isEdit title: widget.isEdit
@@ -165,7 +178,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty; isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) { if (!isNameFieldInvalid) {
if (_isNameConflict(value)) { if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) {
isNameFieldExist = true; isNameFieldExist = true;
isOkButtonEnabled = false; isOkButtonEnabled = false;
} else { } else {
@@ -232,11 +245,14 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
onPressed: () { onPressed: () {
_showLinkSpaceModelDialog(context); isSpaceModelDisabled
? null
: _showLinkSpaceModelDialog(context);
}, },
child: const ButtonContentWidget( child: ButtonContentWidget(
svgAssets: Assets.link, svgAssets: Assets.link,
label: 'Link a space model', label: 'Link a space model',
disabled: isSpaceModelDisabled,
), ),
) )
: Container( : Container(
@@ -325,12 +341,15 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
overlayColor: ColorsManager.transparentColor, overlayColor: ColorsManager.transparentColor,
), ),
onPressed: () async { onPressed: () async {
_showSubSpaceDialog(context, enteredName, [], isTagsAndSubspaceModelDisabled
false, widget.products, subspaces); ? null
: _showSubSpaceDialog(context, enteredName,
[], false, widget.products, subspaces);
}, },
child: const ButtonContentWidget( child: ButtonContentWidget(
icon: Icons.add, icon: Icons.add,
label: 'Create Sub Space', label: 'Create Sub Space',
disabled: isTagsAndSubspaceModelDisabled,
), ),
) )
: SizedBox( : SizedBox(
@@ -457,7 +476,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
context: context, context: context,
builder: (context) => AssignTagDialog( builder: (context) => AssignTagDialog(
products: widget.products, products: widget.products,
subspaces: widget.subspaces, subspaces: subspaces,
allTags: widget.allTags,
addedProducts: TagHelper addedProducts: TagHelper
.createInitialSelectedProductsForTags( .createInitialSelectedProductsForTags(
tags ?? [], subspaces), tags ?? [], subspaces),
@@ -483,20 +503,22 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
) )
: TextButton( : TextButton(
onPressed: () { onPressed: () {
_showTagCreateDialog( isTagsAndSubspaceModelDisabled
? null
: _showTagCreateDialog(
context, context,
enteredName, enteredName,
widget.isEdit, widget.isEdit,
widget.products, widget.products,
subspaces,
); );
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
child: const ButtonContentWidget( child: ButtonContentWidget(
icon: Icons.add, icon: Icons.add,
label: 'Add Devices', label: 'Add Devices',
disabled: isTagsAndSubspaceModelDisabled,
)) ))
], ],
), ),
@@ -570,14 +592,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
bool _isNameConflict(String value) {
return (widget.parentSpace?.children.any((child) => child.name == value) ??
false) ||
(widget.parentSpace?.name == value) ||
(widget.editSpace?.parent?.name == value) ||
(widget.editSpace?.children.any((child) => child.name == value) ??
false);
}
void _showLinkSpaceModelDialog(BuildContext context) { void _showLinkSpaceModelDialog(BuildContext context) {
showDialog( showDialog(
@@ -617,9 +631,26 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
products: products, products: products,
existingSubSpaces: existingSubSpaces, existingSubSpaces: existingSubSpaces,
onSave: (slectedSubspaces) { onSave: (slectedSubspaces) {
final List<Tag> tagsToAppendToSpace = [];
if (slectedSubspaces != null) {
final updatedIds =
slectedSubspaces.map((s) => s.internalId).toSet();
if (existingSubSpaces != null) {
final deletedSubspaces = existingSubSpaces
.where((s) => !updatedIds.contains(s.internalId))
.toList();
for (var s in deletedSubspaces) {
if (s.tags != null) {
tagsToAppendToSpace.addAll(s.tags!);
}
}
}
}
if (slectedSubspaces != null) { if (slectedSubspaces != null) {
setState(() { setState(() {
subspaces = slectedSubspaces; subspaces = slectedSubspaces;
tags?.addAll(tagsToAppendToSpace);
selectedSpaceModel = null; selectedSpaceModel = null;
}); });
} }
@@ -629,7 +660,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
} }
void _showTagCreateDialog(BuildContext context, String name, bool isEdit, void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
List<ProductModel>? products, List<SubspaceModel>? subspaces) { List<ProductModel>? products) {
isEdit isEdit
? showDialog( ? showDialog(
context: context, context: context,
@@ -11,7 +11,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem
import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart';
class LoadedSpaceView extends StatelessWidget { class LoadedSpaceView extends StatefulWidget {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final CommunityModel? selectedCommunity; final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace; final SpaceModel? selectedSpace;
@@ -26,41 +26,82 @@ class LoadedSpaceView extends StatelessWidget {
this.selectedSpace, this.selectedSpace,
this.products, this.products,
this.spaceModels, this.spaceModels,
required this.shouldNavigateToSpaceModelPage required this.shouldNavigateToSpaceModelPage,
}); });
@override @override
Widget build(BuildContext context) { _LoadedSpaceViewState createState() => _LoadedSpaceViewState();
}
class _LoadedSpaceViewState extends State<LoadedSpaceView> {
late List<SpaceTemplateModel> _spaceModels;
@override
void initState() {
super.initState();
_spaceModels = List.from(widget.spaceModels ?? []);
}
@override
@override
void didUpdateWidget(covariant LoadedSpaceView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.spaceModels != oldWidget.spaceModels) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_spaceModels = List.from(widget.spaceModels ?? []);
});
}
});
}
}
void _onSpaceModelsUpdated(List<SpaceTemplateModel> updatedModels) {
if (mounted && updatedModels != _spaceModels) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_spaceModels = updatedModels;
});
}
});
}
}
@override
Widget build(BuildContext context) {
return Stack( return Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
Row( Row(
children: [ children: [
SidebarWidget( SidebarWidget(
communities: communities, communities: widget.communities,
selectedSpaceUuid: selectedSpaceUuid: widget.selectedSpace?.uuid ??
selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '', widget.selectedCommunity?.uuid ??
'',
), ),
shouldNavigateToSpaceModelPage widget.shouldNavigateToSpaceModelPage
? Expanded( ? Expanded(
child: BlocProvider( child: BlocProvider(
create: (context) => SpaceModelBloc( create: (context) => SpaceModelBloc(
api: SpaceModelManagementApi(), api: SpaceModelManagementApi(),
initialSpaceModels: spaceModels ?? [], initialSpaceModels: _spaceModels,
), ),
child: SpaceModelPage( child: SpaceModelPage(
products: products, products: widget.products,
onSpaceModelsUpdated: _onSpaceModelsUpdated,
), ),
), ),
) )
: CommunityStructureArea( : CommunityStructureArea(
selectedCommunity: selectedCommunity, selectedCommunity: widget.selectedCommunity,
selectedSpace: selectedSpace, selectedSpace: widget.selectedSpace,
spaces: selectedCommunity?.spaces ?? [], spaces: widget.selectedCommunity?.spaces ?? [],
products: products, products: widget.products,
communities: communities, communities: widget.communities,
spaceModels: spaceModels, spaceModels: _spaceModels,
), ),
], ],
), ),
@@ -68,13 +109,4 @@ class LoadedSpaceView extends StatelessWidget {
], ],
); );
} }
SpaceModel? findSpaceByUuid(String? uuid, List<CommunityModel> communities) {
for (var community in communities) {
for (var space in community.spaces) {
if (space.uuid == uuid) return space;
}
}
return null;
}
} }
@@ -4,7 +4,9 @@ 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'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> { class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
AssignTagBloc() : super(AssignTagInitial()) { final List<String> allTags;
AssignTagBloc(this.allTags) : super(AssignTagInitial()) {
on<InitializeTags>((event, emit) { on<InitializeTags>((event, emit) {
final initialTags = event.initialTags ?? []; final initialTags = event.initialTags ?? [];
@@ -16,25 +18,25 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
} }
} }
final allTags = <Tag>[]; final tags = <Tag>[];
for (var selectedProduct in event.addedProducts) { for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 || if (selectedProduct.count == 0 ||
selectedProduct.count <= existingCount) { selectedProduct.count <= existingCount) {
allTags.addAll(initialTags tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId)); .where((tag) => tag.product?.uuid == selectedProduct.productId));
continue; continue;
} }
final missingCount = selectedProduct.count - existingCount; final missingCount = selectedProduct.count - existingCount;
allTags.addAll(initialTags tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId)); .where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) { if (missingCount > 0) {
allTags.addAll(List.generate( tags.addAll(List.generate(
missingCount, missingCount,
(index) => Tag( (index) => Tag(
tag: '', tag: '',
@@ -45,10 +47,14 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
} }
} }
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: allTags, tags: tags,
isSaveEnabled: _validateTags(allTags), updatedTags: updatedTags,
errorMessage: '')); isSaveEnabled: _validateTags(tags),
errorMessage: '',
));
}); });
on<UpdateTagEvent>((event, emit) { on<UpdateTagEvent>((event, emit) {
@@ -56,9 +62,13 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags); final tags = List<Tag>.from(currentState.tags);
tags[event.index].tag = event.tag; tags[event.index] = tags[event.index].copyWith(tag: event.tag);
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags), errorMessage: _getValidationError(tags),
)); ));
@@ -71,13 +81,17 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags); final tags = List<Tag>.from(currentState.tags);
// Use copyWith for immutability // Update the location
tags[event.index] = tags[event.index] =
tags[event.index].copyWith(location: event.location); tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
)); ));
} }
}); });
@@ -90,6 +104,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags),
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags), errorMessage: _getValidationError(tags),
)); ));
@@ -100,34 +115,37 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final currentState = state; final currentState = state;
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final updatedTags = List<Tag>.from(currentState.tags) final tags = List<Tag>.from(currentState.tags)
..remove(event.tagToDelete); ..remove(event.tagToDelete);
// Recalculate available tags
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: updatedTags, tags: tags,
isSaveEnabled: _validateTags(updatedTags), updatedTags: updatedTags,
)); isSaveEnabled: _validateTags(tags),
} else { errorMessage: _getValidationError(tags),
emit(const AssignTagLoaded(
tags: [],
isSaveEnabled: false,
)); ));
} }
}); });
} }
// Validate the tags for duplicates or empty values
bool _validateTags(List<Tag> tags) { bool _validateTags(List<Tag> tags) {
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
final isValid = uniqueTags.length == tags.length && !hasEmptyTag; return uniqueTags.length == tags.length && !hasEmptyTag;
return isValid;
} }
// Get validation error for duplicate tags
String? _getValidationError(List<Tag> tags) { String? _getValidationError(List<Tag> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final nonEmptyTags = tags
if (hasEmptyTag) return 'Tags cannot be empty.';
final duplicateTags = tags
.map((tag) => tag.tag?.trim() ?? '') .map((tag) => tag.tag?.trim() ?? '')
.where((tag) => tag.isNotEmpty)
.toList();
final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) { .fold<Map<String, int>>({}, (map, tag) {
map[tag] = (map[tag] ?? 0) + 1; map[tag] = (map[tag] ?? 0) + 1;
return map; return map;
@@ -143,4 +161,15 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
return null; return null;
} }
List<String> _calculateAvailableTags(List<String> allTags, List<Tag> tags) {
final selectedTags = tags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim())
.toSet();
final availableTags =
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList();
return availableTags;
}
} }
@@ -1,6 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
abstract class AssignTagState extends Equatable { abstract class AssignTagState extends Equatable {
const AssignTagState(); const AssignTagState();
@@ -15,17 +14,21 @@ class AssignTagLoading extends AssignTagState {}
class AssignTagLoaded extends AssignTagState { class AssignTagLoaded extends AssignTagState {
final List<Tag> tags; final List<Tag> tags;
final List<String> updatedTags;
final bool isSaveEnabled; final bool isSaveEnabled;
final String? errorMessage; final String? errorMessage;
const AssignTagLoaded({ const AssignTagLoaded({
required this.tags, required this.tags,
required this.isSaveEnabled, required this.isSaveEnabled,
this.errorMessage, required this.updatedTags,
required this.errorMessage,
}); });
@override @override
List<Object> get props => [tags, isSaveEnabled]; List<Object> get props =>
[tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
} }
class AssignTagError extends AssignTagState { class AssignTagError extends AssignTagState {
@@ -14,6 +14,7 @@ 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'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:uuid/uuid.dart';
class AssignTagDialog extends StatelessWidget { class AssignTagDialog extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
@@ -47,7 +48,7 @@ class AssignTagDialog extends StatelessWidget {
..add('Main Space'); ..add('Main Space');
return BlocProvider( return BlocProvider(
create: (_) => AssignTagBloc() create: (_) => AssignTagBloc(allTags ?? [])
..add(InitializeTags( ..add(InitializeTags(
initialTags: initialTags, initialTags: initialTags,
addedProducts: addedProducts, addedProducts: addedProducts,
@@ -71,6 +72,7 @@ class AssignTagDialog extends StatelessWidget {
child: DataTable( child: DataTable(
headingRowColor: WidgetStateProperty.all( headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey), ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length),
border: TableBorder.all( border: TableBorder.all(
color: ColorsManager.dataHeaderGrey, color: ColorsManager.dataHeaderGrey,
width: 1, width: 1,
@@ -118,8 +120,7 @@ class AssignTagDialog extends StatelessWidget {
: List.generate(state.tags.length, (index) { : List.generate(state.tags.length, (index) {
final tag = state.tags[index]; final tag = state.tags[index];
final controller = controllers[index]; final controller = controllers[index];
final availableTags = getAvailableTags(
allTags ?? [], state.tags, tag);
return DataRow( return DataRow(
cells: [ cells: [
DataCell(Text((index + 1).toString())), DataCell(Text((index + 1).toString())),
@@ -158,6 +159,8 @@ class AssignTagDialog extends StatelessWidget {
.add(DeleteTag( .add(DeleteTag(
tagToDelete: tag, tagToDelete: tag,
tags: state.tags)); tags: state.tags));
controllers.removeAt(index);
}, },
tooltip: 'Delete Tag', tooltip: 'Delete Tag',
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@@ -176,7 +179,9 @@ class AssignTagDialog extends StatelessWidget {
width: double width: double
.infinity, // Ensure full width for dropdown .infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown( child: DialogTextfieldDropdown(
items: availableTags, key: ValueKey(
'dropdown_${Uuid().v4()}_${index}'),
items: state.updatedTags,
initialValue: tag.tag, initialValue: tag.tag,
onSelected: (value) { onSelected: (value) {
controller.text = value; controller.text = value;
@@ -233,11 +238,13 @@ class AssignTagDialog extends StatelessWidget {
label: 'Add New Device', label: 'Add New Device',
onPressed: () async { onPressed: () async {
final updatedTags = List<Tag>.from(state.tags); final updatedTags = List<Tag>.from(state.tags);
final result = processTags(updatedTags, subspaces); final result =
TagHelper.processTags(updatedTags, subspaces);
final processedTags = final processedTags =
result['updatedTags'] as List<Tag>; result['updatedTags'] as List<Tag>;
final processedSubspaces = result['subspaces']; final processedSubspaces = List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
Navigator.of(context).pop(); Navigator.of(context).pop();
@@ -253,6 +260,7 @@ class AssignTagDialog extends StatelessWidget {
spaceTags: processedTags, spaceTags: processedTags,
isCreate: false, isCreate: false,
onSave: onSave, onSave: onSave,
allTags: allTags,
), ),
); );
}, },
@@ -263,20 +271,21 @@ class AssignTagDialog extends StatelessWidget {
Expanded( Expanded(
child: DefaultButton( child: DefaultButton(
borderRadius: 10, borderRadius: 10,
backgroundColor: state.isSaveEnabled backgroundColor: ColorsManager.secondaryColor,
? ColorsManager.secondaryColor foregroundColor: state.isSaveEnabled
: ColorsManager.grayColor, ? ColorsManager.whiteColors
foregroundColor: ColorsManager.whiteColors, : ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled onPressed: state.isSaveEnabled
? () async { ? () async {
final updatedTags = List<Tag>.from(state.tags); final updatedTags = List<Tag>.from(state.tags);
final result = final result = TagHelper.processTags(
processTags(updatedTags, subspaces); updatedTags, subspaces);
final processedTags = final processedTags =
result['updatedTags'] as List<Tag>; result['updatedTags'] as List<Tag>;
final processedSubspaces = final processedSubspaces =
result['subspaces'] as List<SubspaceModel>; List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces); onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
@@ -298,118 +307,4 @@ class AssignTagDialog extends StatelessWidget {
), ),
); );
} }
List<String> getAvailableTags(
List<String> allTags, List<Tag> currentTags, Tag currentTag) {
return allTags
.where((tagValue) => !currentTags
.where((e) => e != currentTag) // Exclude the current row
.map((e) => e.tag)
.contains(tagValue))
.toList();
}
Map<String, dynamic> processTags(
List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
final modifiedTags = List<Tag>.from(updatedTags);
final modifiedSubspaces = List<SubspaceModel>.from(subspaces ?? []);
if (subspaces != null) {
for (var subspace in subspaces) {
subspace.tags?.removeWhere(
(tag) => !modifiedTags
.any((updatedTag) => updatedTag.internalId == tag.internalId),
);
}
}
for (var tag in modifiedTags.toList()) {
if (modifiedSubspaces.isEmpty) continue;
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces);
if ((tag.location == 'Main Space' || tag.location == null) &&
(prevIndice == null ||
modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) {
continue;
}
if ((tag.location == 'Main Space' || tag.location == null) &&
prevIndice != null) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
prevIndice == null) {
final newIndex = modifiedSubspaces
.indexWhere((subspace) => subspace.subspaceName == tag.location);
if (newIndex != -1) {
if (modifiedSubspaces[newIndex]
.tags
?.any((t) => t.internalId == tag.internalId) !=
true) {
tag.location = modifiedSubspaces[newIndex].subspaceName;
modifiedSubspaces[newIndex].tags?.add(tag);
}
}
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
tag.location != modifiedSubspaces[prevIndice!].subspaceName) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
final newIndex = modifiedSubspaces
.indexWhere((subspace) => subspace.subspaceName == tag.location);
if (newIndex != -1) {
if (modifiedSubspaces[newIndex]
.tags
?.any((t) => t.internalId == tag.internalId) !=
true) {
tag.location = modifiedSubspaces[newIndex].subspaceName;
modifiedSubspaces[newIndex].tags?.add(tag);
}
}
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
tag.location == modifiedSubspaces[prevIndice!].subspaceName) {
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location == 'Main Space' || tag.location == null) &&
prevIndice != null) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
}
}
return {
'updatedTags': modifiedTags,
'subspaces': modifiedSubspaces,
};
}
int? checkTagExistInSubspace(Tag tag, List<SubspaceModel>? subspaces) {
if (subspaces == null) return null;
for (int i = 0; i < subspaces.length; i++) {
final subspace = subspaces[i];
if (subspace.tags == null) continue;
for (var t in subspace.tags!) {
if (tag.internalId == t.internalId) {
return i;
}
}
}
return null;
}
} }
@@ -5,7 +5,9 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model
class AssignTagModelBloc class AssignTagModelBloc
extends Bloc<AssignTagModelEvent, AssignTagModelState> { extends Bloc<AssignTagModelEvent, AssignTagModelState> {
AssignTagModelBloc() : super(AssignTagModelInitial()) { final List<String> allTags;
AssignTagModelBloc(this.allTags) : super(AssignTagModelInitial()) {
on<InitializeTagModels>((event, emit) { on<InitializeTagModels>((event, emit) {
final initialTags = event.initialTags ?? []; final initialTags = event.initialTags ?? [];
@@ -17,25 +19,25 @@ class AssignTagModelBloc
} }
} }
final allTags = <TagModel>[]; final tags = <TagModel>[];
for (var selectedProduct in event.addedProducts) { for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 || if (selectedProduct.count == 0 ||
selectedProduct.count <= existingCount) { selectedProduct.count <= existingCount) {
allTags.addAll(initialTags tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId)); .where((tag) => tag.product?.uuid == selectedProduct.productId));
continue; continue;
} }
final missingCount = selectedProduct.count - existingCount; final missingCount = selectedProduct.count - existingCount;
allTags.addAll(initialTags tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId)); .where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) { if (missingCount > 0) {
allTags.addAll(List.generate( tags.addAll(List.generate(
missingCount, missingCount,
(index) => TagModel( (index) => TagModel(
tag: '', tag: '',
@@ -46,9 +48,12 @@ class AssignTagModelBloc
} }
} }
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: allTags, tags: tags,
isSaveEnabled: _validateTags(allTags), updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: '')); errorMessage: ''));
}); });
@@ -57,9 +62,12 @@ class AssignTagModelBloc
if (currentState is AssignTagModelLoaded && if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) { currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags); final tags = List<TagModel>.from(currentState.tags);
tags[event.index].tag = event.tag; tags[event.index] = tags[event.index].copyWith(tag: event.tag);
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: tags, tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags), errorMessage: _getValidationError(tags),
)); ));
@@ -77,9 +85,13 @@ class AssignTagModelBloc
tags[event.index] = tags[event.index] =
tags[event.index].copyWith(location: event.location); tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: tags, tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
)); ));
} }
}); });
@@ -93,6 +105,7 @@ class AssignTagModelBloc
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: tags, tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags),
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags), errorMessage: _getValidationError(tags),
)); ));
@@ -104,24 +117,22 @@ class AssignTagModelBloc
if (currentState is AssignTagModelLoaded && if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) { currentState.tags.isNotEmpty) {
final updatedTags = List<TagModel>.from(currentState.tags) final tags = List<TagModel>.from(currentState.tags)
..remove(event.tagToDelete); ..remove(event.tagToDelete);
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: updatedTags, tags: tags,
isSaveEnabled: _validateTags(updatedTags), updatedTags: updatedTags,
)); isSaveEnabled: _validateTags(tags),
} else { errorMessage: _getValidationError(tags),
emit(const AssignTagModelLoaded(
tags: [],
isSaveEnabled: false,
)); ));
} }
}); });
} }
bool _validateTags(List<TagModel> tags) { bool _validateTags(List<TagModel> tags) {
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
final isValid = uniqueTags.length == tags.length && !hasEmptyTag; final isValid = uniqueTags.length == tags.length && !hasEmptyTag;
@@ -129,14 +140,14 @@ class AssignTagModelBloc
} }
String? _getValidationError(List<TagModel> tags) { String? _getValidationError(List<TagModel> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
if (hasEmptyTag) {
return 'Tags cannot be empty.';
}
// Check for duplicate tags // Check for duplicate tags
final duplicateTags = tags
final nonEmptyTags = tags
.map((tag) => tag.tag?.trim() ?? '') .map((tag) => tag.tag?.trim() ?? '')
.where((tag) => tag.isNotEmpty)
.toList();
final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) { .fold<Map<String, int>>({}, (map, tag) {
map[tag] = (map[tag] ?? 0) + 1; map[tag] = (map[tag] ?? 0) + 1;
return map; return map;
@@ -152,4 +163,16 @@ class AssignTagModelBloc
return null; return null;
} }
List<String> _calculateAvailableTags(
List<String> allTags, List<TagModel> tags) {
final selectedTags = tags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim())
.toSet();
final availableTags =
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList();
return availableTags;
}
} }
@@ -17,14 +17,17 @@ class AssignTagModelLoaded extends AssignTagModelState {
final bool isSaveEnabled; final bool isSaveEnabled;
final String? errorMessage; final String? errorMessage;
final List<String> updatedTags;
const AssignTagModelLoaded({ const AssignTagModelLoaded({
required this.tags, required this.tags,
required this.isSaveEnabled, required this.isSaveEnabled,
required this.updatedTags,
this.errorMessage, this.errorMessage,
}); });
@override @override
List<Object?> get props => [tags, isSaveEnabled, errorMessage]; List<Object?> get props => [tags, updatedTags, isSaveEnabled, errorMessage];
} }
class AssignTagModelError extends AssignTagModelState { class AssignTagModelError extends AssignTagModelState {
@@ -16,6 +16,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/c
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.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'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:uuid/uuid.dart';
class AssignTagModelsDialog extends StatelessWidget { class AssignTagModelsDialog extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
@@ -56,7 +57,7 @@ class AssignTagModelsDialog extends StatelessWidget {
..add('Main Space'); ..add('Main Space');
return BlocProvider( return BlocProvider(
create: (_) => AssignTagModelBloc() create: (_) => AssignTagModelBloc(allTags ?? [])
..add(InitializeTagModels( ..add(InitializeTagModels(
initialTags: initialTags, initialTags: initialTags,
addedProducts: addedProducts, addedProducts: addedProducts,
@@ -82,6 +83,7 @@ class AssignTagModelsDialog extends StatelessWidget {
child: DataTable( child: DataTable(
headingRowColor: WidgetStateProperty.all( headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey), ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length),
border: TableBorder.all( border: TableBorder.all(
color: ColorsManager.dataHeaderGrey, color: ColorsManager.dataHeaderGrey,
width: 1, width: 1,
@@ -133,8 +135,6 @@ class AssignTagModelsDialog extends StatelessWidget {
: List.generate(state.tags.length, (index) { : List.generate(state.tags.length, (index) {
final tag = state.tags[index]; final tag = state.tags[index];
final controller = controllers[index]; final controller = controllers[index];
final availableTags = getAvailableTags(
allTags ?? [], state.tags, tag);
return DataRow( return DataRow(
cells: [ cells: [
@@ -175,6 +175,7 @@ class AssignTagModelsDialog extends StatelessWidget {
.add(DeleteTagModel( .add(DeleteTagModel(
tagToDelete: tag, tagToDelete: tag,
tags: state.tags)); tags: state.tags));
controllers.removeAt(index);
}, },
tooltip: 'Delete Tag', tooltip: 'Delete Tag',
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@@ -193,7 +194,9 @@ class AssignTagModelsDialog extends StatelessWidget {
width: double width: double
.infinity, // Ensure full width for dropdown .infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown( child: DialogTextfieldDropdown(
items: availableTags, key: ValueKey(
'dropdown_${Uuid().v4()}_${index}'),
items: state.updatedTags,
initialValue: tag.tag, initialValue: tag.tag,
onSelected: (value) { onSelected: (value) {
controller.text = value; controller.text = value;
@@ -254,11 +257,15 @@ class AssignTagModelsDialog extends StatelessWidget {
final updatedTags = final updatedTags =
List<TagModel>.from(state.tags); List<TagModel>.from(state.tags);
final result = final result =
processTags(updatedTags, subspaces); TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
final processedTags = final processedTags =
result['updatedTags'] as List<TagModel>; result['updatedTags'] as List<TagModel>;
final processedSubspaces = result['subspaces']; final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
if (context.mounted) { if (context.mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
@@ -297,22 +304,25 @@ class AssignTagModelsDialog extends StatelessWidget {
Expanded( Expanded(
child: DefaultButton( child: DefaultButton(
borderRadius: 10, borderRadius: 10,
backgroundColor: state.isSaveEnabled backgroundColor: ColorsManager.secondaryColor,
? ColorsManager.secondaryColor foregroundColor: state.isSaveEnabled
: ColorsManager.grayColor, ? ColorsManager.whiteColors
foregroundColor: ColorsManager.whiteColors, : ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled onPressed: state.isSaveEnabled
? () async { ? () async {
final updatedTags = final updatedTags =
List<TagModel>.from(state.tags); List<TagModel>.from(state.tags);
final result = final result =
processTags(updatedTags, subspaces); TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
final processedTags = final processedTags =
result['updatedTags'] as List<TagModel>; result['updatedTags'] as List<TagModel>;
final processedSubspaces = final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] result['subspaces']
as List<SubspaceTemplateModel>; as List<dynamic>);
Navigator.of(context) Navigator.of(context)
.popUntil((route) => route.isFirst); .popUntil((route) => route.isFirst);
@@ -356,120 +366,4 @@ class AssignTagModelsDialog extends StatelessWidget {
), ),
)); ));
} }
List<String> getAvailableTags(
List<String> allTags, List<TagModel> currentTags, TagModel currentTag) {
return allTags
.where((tagValue) => !currentTags
.where((e) => e != currentTag) // Exclude the current row
.map((e) => e.tag)
.contains(tagValue))
.toList();
}
int? checkTagExistInSubspace(
TagModel tag, List<SubspaceTemplateModel>? subspaces) {
if (subspaces == null) return null;
for (int i = 0; i < subspaces.length; i++) {
final subspace = subspaces[i];
if (subspace.tags == null) continue;
for (var t in subspace.tags!) {
if (tag.internalId == t.internalId) {
return i;
}
}
}
return null;
}
Map<String, dynamic> processTags(
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) {
final modifiedTags = List<TagModel>.from(updatedTags);
final modifiedSubspaces = List<SubspaceTemplateModel>.from(subspaces ?? []);
if (subspaces != null) {
for (var subspace in subspaces) {
subspace.tags?.removeWhere(
(tag) => !modifiedTags
.any((updatedTag) => updatedTag.internalId == tag.internalId),
);
}
}
for (var tag in modifiedTags.toList()) {
if (modifiedSubspaces.isEmpty) continue;
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces);
if ((tag.location == 'Main Space' || tag.location == null) &&
(prevIndice == null ||
modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) {
continue;
}
if ((tag.location == 'Main Space' || tag.location == null) &&
prevIndice != null) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
prevIndice == null) {
final newIndex = modifiedSubspaces
.indexWhere((subspace) => subspace.subspaceName == tag.location);
if (newIndex != -1) {
if (modifiedSubspaces[newIndex]
.tags
?.any((t) => t.internalId == tag.internalId) !=
true) {
tag.location = modifiedSubspaces[newIndex].subspaceName;
modifiedSubspaces[newIndex].tags?.add(tag);
}
}
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
tag.location != modifiedSubspaces[prevIndice!].subspaceName) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
final newIndex = modifiedSubspaces
.indexWhere((subspace) => subspace.subspaceName == tag.location);
if (newIndex != -1) {
if (modifiedSubspaces[newIndex]
.tags
?.any((t) => t.internalId == tag.internalId) !=
true) {
tag.location = modifiedSubspaces[newIndex].subspaceName;
modifiedSubspaces[newIndex].tags?.add(tag);
}
}
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
tag.location == modifiedSubspaces[prevIndice!].subspaceName) {
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location == 'Main Space' || tag.location == null) &&
prevIndice != null) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
}
}
return {
'updatedTags': modifiedTags,
'subspaces': modifiedSubspaces,
};
}
} }
@@ -0,0 +1,21 @@
import 'dart:ui';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class ConnectionHelper {
static Offset getCenterPosition(Size screenSize) {
return Offset(
screenSize.width / 2 - 260,
screenSize.height / 2 - 200,
);
}
static bool isHighlightedConnection(
Connection connection, SpaceModel? selectedSpace) {
if (selectedSpace == null) return true;
return connection.startSpace == selectedSpace ||
connection.endSpace == selectedSpace;
}
}
@@ -0,0 +1,94 @@
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';
class SpaceHelper {
static SpaceModel? findSpaceByUuid(
String? uuid, List<CommunityModel> communities) {
for (var community in communities) {
for (var space in community.spaces) {
if (space.uuid == uuid) return space;
}
}
return null;
}
static SpaceModel? findSpaceByInternalId(
String? internalId, List<SpaceModel> spaces) {
if (internalId != null) {
for (var space in spaces) {
if (space.internalId == internalId) return space;
}
}
return null;
}
static String generateUniqueSpaceName(
String originalName, List<SpaceModel> spaces) {
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
int maxNumber = 0;
for (var space in spaces) {
final match = RegExp(r'^(.*?)\((\d+)\)$').firstMatch(space.name);
if (match != null && match.group(1)?.trim() == baseName) {
int existingNumber = int.parse(match.group(2)!);
if (existingNumber > maxNumber) {
maxNumber = existingNumber;
}
}
}
return "$baseName(${maxNumber + 1})";
}
static bool isSave(List<SpaceModel> spaces) {
return spaces.isNotEmpty &&
spaces.any((space) =>
space.status == SpaceStatus.newSpace ||
space.status == SpaceStatus.modified ||
space.status == SpaceStatus.deleted);
}
static bool isHighlightedSpace(SpaceModel space, SpaceModel? selectedSpace) {
if (selectedSpace == null) return true;
return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children
?.any((child) => child.internalId == space.internalId) ==
true;
}
static bool isNameConflict(
String value, SpaceModel? parentSpace, SpaceModel? editSpace) {
final siblings = parentSpace?.children
.where((child) => child.internalId != editSpace?.internalId)
.toList() ??
[];
final editSiblings = editSpace?.parent?.children
.where((child) => child.internalId != editSpace.internalId)
.toList() ??
[];
final editSiblingConflict =
editSiblings.any((child) => child.name == value);
final siblingConflict = siblings.any((child) => child.name == value);
final parentConflict = parentSpace?.name == value &&
parentSpace?.internalId != editSpace?.internalId;
final parentOfEditSpaceConflict = editSpace?.parent?.name == value &&
editSpace?.parent?.internalId != editSpace?.internalId;
final childConflict =
editSpace?.children.any((child) => child.name == value) ?? false;
return siblingConflict ||
parentConflict ||
editSiblingConflict ||
parentOfEditSpaceConflict ||
childConflict;
}
}
@@ -1,12 +1,147 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.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/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/selected_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/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.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/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_model.dart';
class TagHelper { class TagHelper {
static Map<String, dynamic> updateTags<T>({
required List<T> updatedTags,
required List<dynamic>? subspaces,
required String Function(T) getInternalId,
required String? Function(T) getLocation,
required void Function(T, String) setLocation,
required String Function(dynamic) getSubspaceName,
required List<T>? Function(dynamic) getSubspaceTags,
required void Function(dynamic, List<T>?) setSubspaceTags,
required int? Function(T, List<dynamic>) checkTagExistInSubspace,
}) {
final modifiedTags = List<T>.from(updatedTags);
final modifiedSubspaces = List<dynamic>.from(subspaces ?? []);
if (subspaces != null) {
for (var subspace in subspaces) {
getSubspaceTags(subspace)?.removeWhere(
(tag) => !modifiedTags.any(
(updatedTag) => getInternalId(updatedTag) == getInternalId(tag)),
);
}
}
for (var tag in modifiedTags.toList()) {
if (modifiedSubspaces.isEmpty) continue;
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces);
final tagLocation = getLocation(tag);
if ((tagLocation == 'Main Space' || tagLocation == null) &&
(prevIndice == null ||
getSubspaceName(modifiedSubspaces[prevIndice]) == 'Main Space')) {
continue;
}
if ((tagLocation == 'Main Space' || tagLocation == null) &&
prevIndice != null) {
getSubspaceTags(modifiedSubspaces[prevIndice])
?.removeWhere((t) => getInternalId(t) == getInternalId(tag));
continue;
}
if ((tagLocation != 'Main Space' && tagLocation != null) &&
prevIndice == null) {
final newIndex = modifiedSubspaces
.indexWhere((subspace) => getSubspaceName(subspace) == tagLocation);
if (newIndex != -1) {
if (getSubspaceTags(modifiedSubspaces[newIndex])
?.any((t) => getInternalId(t) == getInternalId(tag)) !=
true) {
setLocation(tag, getSubspaceName(modifiedSubspaces[newIndex]));
final subspaceTags =
getSubspaceTags(modifiedSubspaces[newIndex]) ?? [];
subspaceTags.add(tag);
setSubspaceTags(modifiedSubspaces[newIndex], subspaceTags);
}
}
modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag));
continue;
}
if ((tagLocation != 'Main Space' && tagLocation != null) &&
tagLocation != getSubspaceName(modifiedSubspaces[prevIndice!])) {
getSubspaceTags(modifiedSubspaces[prevIndice])
?.removeWhere((t) => getInternalId(t) == getInternalId(tag));
final newIndex = modifiedSubspaces
.indexWhere((subspace) => getSubspaceName(subspace) == tagLocation);
if (newIndex != -1) {
if (getSubspaceTags(modifiedSubspaces[newIndex])
?.any((t) => getInternalId(t) == getInternalId(tag)) !=
true) {
setLocation(tag, getSubspaceName(modifiedSubspaces[newIndex]));
final subspaceTags =
getSubspaceTags(modifiedSubspaces[newIndex]) ?? [];
subspaceTags.add(tag);
setSubspaceTags(modifiedSubspaces[newIndex], subspaceTags);
}
}
modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag));
continue;
}
if ((tagLocation != 'Main Space' && tagLocation != null) &&
tagLocation == getSubspaceName(modifiedSubspaces[prevIndice!])) {
modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag));
continue;
}
if ((tagLocation == 'Main Space' || tagLocation == null) &&
prevIndice != null) {
getSubspaceTags(modifiedSubspaces[prevIndice])
?.removeWhere((t) => getInternalId(t) == getInternalId(tag));
}
}
return {
'updatedTags': modifiedTags,
'subspaces': modifiedSubspaces,
};
}
static List<String> getAvailableTags<T>({
required List<String> allTags,
required List<T> currentTags,
required T currentTag,
required String? Function(T) getTag, // Allow nullable return type
}) {
return allTags
.where((tagValue) => !currentTags
.where((e) => e != currentTag) // Exclude the current row
.map((e) => getTag(e) ?? '') // Handle null values gracefully
.contains(tagValue))
.toList();
}
static List<String> getAvailableTagModels(
List<String> allTags, List<TagModel> currentTags, TagModel currentTag) {
List<String> availableTagsForTagModel =
TagHelper.getAvailableTags<TagModel>(
allTags: allTags,
currentTags: currentTags,
currentTag: currentTag,
getTag: (tag) => tag.tag ?? '',
);
return availableTagsForTagModel;
}
static List<TagModel> generateInitialTags({ static List<TagModel> generateInitialTags({
List<TagModel>? spaceTagModels, List<TagModel>? spaceTagModels,
List<SubspaceTemplateModel>? subspaces, List<SubspaceTemplateModel>? subspaces,
@@ -145,4 +280,140 @@ class TagHelper {
)) ))
.toList(); .toList();
} }
static int? checkTagExistInSubspaceModels(
TagModel tag, List<dynamic>? subspaces) {
if (subspaces == null) return null;
for (int i = 0; i < subspaces.length; i++) {
final subspace = subspaces[i] as SubspaceTemplateModel; // Explicit cast
if (subspace.tags == null) continue;
for (var t in subspace.tags!) {
if (tag.internalId == t.internalId) {
return i;
}
}
}
return null;
}
static Map<String, dynamic> updateSubspaceTagModels(
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) {
final result = TagHelper.updateTags<TagModel>(
updatedTags: updatedTags,
subspaces: subspaces,
getInternalId: (tag) => tag.internalId,
getLocation: (tag) => tag.location,
setLocation: (tag, location) => tag.location = location,
getSubspaceName: (subspace) => subspace.subspaceName,
getSubspaceTags: (subspace) => subspace.tags,
setSubspaceTags: (subspace, tags) => subspace.tags = tags,
checkTagExistInSubspace: checkTagExistInSubspaceModels,
);
final processedTags = result['updatedTags'] as List<TagModel>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(result['subspaces'] as List<dynamic>);
for (var subspace in processedSubspaces) {
final subspaceTags = subspace.tags;
if (subspaceTags != null) {
for (int i = 0; i < subspaceTags.length; i++) {
final tag = subspaceTags[i];
// Find the updated tag inside processedTags
final changedTag = updatedTags.firstWhere(
(t) => t.internalId == tag.internalId,
orElse: () => tag,
);
if (changedTag.tag != tag.tag) {
subspaceTags[i] = changedTag.copyWith(tag: changedTag.tag);
}
}
}
subspace.tags = subspaceTags;
}
return {'updatedTags': processedTags, 'subspaces': processedSubspaces};
}
static int? checkTagExistInSubspace(Tag tag, List<dynamic>? subspaces) {
if (subspaces == null) return null;
for (int i = 0; i < subspaces.length; i++) {
final subspace = subspaces[i];
if (subspace.tags == null) continue;
for (var t in subspace.tags!) {
if (tag.internalId == t.internalId) {
return i;
}
}
}
return null;
}
static Map<String, dynamic> processTags(
List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
final result = TagHelper.updateTags<Tag>(
updatedTags: updatedTags,
subspaces: subspaces,
getInternalId: (tag) => tag.internalId,
getLocation: (tag) => tag.location,
setLocation: (tag, location) => tag.location = location,
getSubspaceName: (subspace) => subspace.subspaceName,
getSubspaceTags: (subspace) => subspace.tags,
setSubspaceTags: (subspace, tags) => subspace.tags = tags,
checkTagExistInSubspace: checkTagExistInSubspace,
);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
for (var subspace in processedSubspaces) {
final subspaceTags = subspace.tags;
if (subspaceTags != null) {
for (int i = 0; i < subspaceTags.length; i++) {
final tag = subspaceTags[i];
final changedTag = updatedTags.firstWhere(
(t) => t.internalId == tag.internalId,
orElse: () => tag,
);
if (changedTag.tag != tag.tag) {
subspaceTags[i] = changedTag.copyWith(tag: changedTag.tag);
}
}
}
subspace.tags = subspaceTags;
}
return {'updatedTags': processedTags, 'subspaces': processedSubspaces};
}
static List<String> getAllTagValues(
List<CommunityModel> communities, List<SpaceTemplateModel>? spaceModels) {
final Set<String> allTags = {};
if (spaceModels != null) {
for (var model in spaceModels) {
allTags.addAll(model.listAllTagValues());
}
}
for (final community in communities) {
for (final space in community.spaces) {
if (space.tags != null) {
allTags.addAll(space.listAllTagValues());
}
}
}
return allTags.toList();
}
} }
@@ -1,5 +1,4 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_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/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_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
@@ -11,8 +11,10 @@ import 'package:syncrow_web/utils/color_manager.dart';
class SpaceModelPage extends StatelessWidget { class SpaceModelPage extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
final Function(List<SpaceTemplateModel>)? onSpaceModelsUpdated;
const SpaceModelPage({Key? key, this.products}) : super(key: key); const SpaceModelPage({Key? key, this.products, this.onSpaceModelsUpdated})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -25,6 +27,10 @@ class SpaceModelPage extends StatelessWidget {
final allTagValues = _getAllTagValues(spaceModels); final allTagValues = _getAllTagValues(spaceModels);
final allSpaceModelNames = _getAllSpaceModelName(spaceModels); final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
if (onSpaceModelsUpdated != null) {
onSpaceModelsUpdated!(spaceModels);
}
return Scaffold( return Scaffold(
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
body: Padding( body: Padding(
@@ -6,16 +6,23 @@ class ButtonContentWidget extends StatelessWidget {
final IconData? icon; final IconData? icon;
final String label; final String label;
final String? svgAssets; final String? svgAssets;
final bool disabled;
const ButtonContentWidget( const ButtonContentWidget({
{Key? key, this.icon, required this.label, this.svgAssets}) Key? key,
: super(key: key); this.icon,
required this.label,
this.svgAssets,
this.disabled = false,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return SizedBox( return Opacity(
opacity: disabled ? 0.5 : 1.0,
child: SizedBox(
width: screenWidth * 0.25, width: screenWidth * 0.25,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -27,7 +34,8 @@ class ButtonContentWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), padding:
const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
child: Row( child: Row(
children: [ children: [
if (icon != null) if (icon != null)
@@ -58,6 +66,7 @@ class ButtonContentWidget extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
} }
@@ -129,10 +129,16 @@ class CreateSpaceModelDialog extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
SubspaceModelCreate( SubspaceModelCreate(
subspaces: state.space.subspaceModels ?? [], subspaces: state.space.subspaceModels ?? [],
onSpaceModelUpdate: (updatedSubspaces) { tags: state.space.tags ?? [],
onSpaceModelUpdate: (updatedSubspaces, updatedTags) {
context context
.read<CreateSpaceModelBloc>() .read<CreateSpaceModelBloc>()
.add(AddSubspacesToSpaceTemplate(updatedSubspaces)); .add(AddSubspacesToSpaceTemplate(updatedSubspaces));
if (updatedTags != null) {
context
.read<CreateSpaceModelBloc>()
.add(AddTagsToSpaceTemplate(updatedTags));
}
}, },
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/common/edit_chip.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/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/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
@@ -8,13 +9,16 @@ import 'package:syncrow_web/utils/color_manager.dart';
class SubspaceModelCreate extends StatefulWidget { class SubspaceModelCreate extends StatefulWidget {
final List<SubspaceTemplateModel> subspaces; final List<SubspaceTemplateModel> subspaces;
final void Function(List<SubspaceTemplateModel> newSubspaces)? final void Function(
List<SubspaceTemplateModel> newSubspaces, List<TagModel>? tags)?
onSpaceModelUpdate; onSpaceModelUpdate;
final List<TagModel> tags;
const SubspaceModelCreate({ const SubspaceModelCreate({
Key? key, Key? key,
required this.subspaces, required this.subspaces,
this.onSpaceModelUpdate, this.onSpaceModelUpdate,
required this.tags,
}) : super(key: key); }) : super(key: key);
@override @override
@@ -24,11 +28,13 @@ class SubspaceModelCreate extends StatefulWidget {
class _SubspaceModelCreateState extends State<SubspaceModelCreate> { class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
late List<SubspaceTemplateModel> _subspaces; late List<SubspaceTemplateModel> _subspaces;
String? errorSubspaceId; String? errorSubspaceId;
late List<TagModel> _tags;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_subspaces = List.from(widget.subspaces); _subspaces = List.from(widget.subspaces);
_tags = List.from(widget.tags);
} }
@override @override
@@ -105,14 +111,26 @@ class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
isEdit: true, isEdit: true,
dialogTitle: dialogTitle, dialogTitle: dialogTitle,
existingSubSpaces: _subspaces, existingSubSpaces: _subspaces,
onUpdate: (subspaceModels) { onUpdate: (subspaceModels) {
final updatedIds = subspaceModels.map((s) => s.internalId).toSet();
final deletedSubspaces = _subspaces
.where((s) => !updatedIds.contains(s.internalId))
.toList();
final List<TagModel> tagsToAppendToSpace = [];
for (var s in deletedSubspaces) {
if (s.tags != null) {
tagsToAppendToSpace.addAll(s.tags!);
}
}
setState(() { setState(() {
_subspaces = subspaceModels; _subspaces = subspaceModels;
errorSubspaceId = null; _tags.addAll(tagsToAppendToSpace);
}); });
if (widget.onSpaceModelUpdate != null) { if (widget.onSpaceModelUpdate != null) {
widget.onSpaceModelUpdate!(subspaceModels); widget.onSpaceModelUpdate!(subspaceModels, _tags);
} }
}, },
); );
@@ -52,6 +52,13 @@ class _SubspaceNameDisplayWidgetState extends State<SubspaceNameDisplayWidget> {
void _handleValidationAndSave() { void _handleValidationAndSave() {
final updatedName = _controller.text; final updatedName = _controller.text;
if (updatedName.isEmpty) {
setState(() {
errorText = 'Subspace name cannot be empty.';
});
return;
}
if (widget.validateName(updatedName)) { if (widget.validateName(updatedName)) {
setState(() { setState(() {
errorText = null; errorText = null;
+6 -16
View File
@@ -18,8 +18,7 @@ class UserPermissionApi {
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
debugPrint('fetchUsers Response: $json'); debugPrint('fetchUsers Response: $json');
final List<dynamic> data = final List<dynamic> data = json['data'] ?? [];
json['data'] ?? []; // Default to an empty list if no data
return data.map((item) => RolesUserModel.fromJson(item)).toList(); return data.map((item) => RolesUserModel.fromJson(item)).toList();
}, },
); );
@@ -35,9 +34,8 @@ class UserPermissionApi {
path: ApiEndpoints.roleTypes, path: ApiEndpoints.roleTypes,
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
final List<RoleTypeModel> fetchedRoles = (json['data'] as List) final List<RoleTypeModel> fetchedRoles =
.map((item) => RoleTypeModel.fromJson(item)) (json['data'] as List).map((item) => RoleTypeModel.fromJson(item)).toList();
.toList();
return fetchedRoles; return fetchedRoles;
}, },
); );
@@ -49,9 +47,7 @@ class UserPermissionApi {
path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid), path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return (json as List) return (json as List).map((data) => PermissionOption.fromJson(data)).toList();
.map((data) => PermissionOption.fromJson(data))
.toList();
}, },
); );
return response ?? []; return response ?? [];
@@ -89,7 +85,6 @@ class UserPermissionApi {
} }
}, },
); );
print('sendInviteUser=$body');
return response ?? []; return response ?? [];
} on DioException catch (e) { } on DioException catch (e) {
@@ -119,7 +114,7 @@ class UserPermissionApi {
); );
return response ?? 'Unknown error occurred'; return response ?? 'Unknown error occurred';
} on DioException catch (e) { } on DioException catch (e) {
final errorMessage = e.response?.data['error']; final errorMessage = e.response?.data['error']['message'];
return errorMessage; return errorMessage;
} catch (e) { } catch (e) {
return e.toString(); return e.toString();
@@ -197,15 +192,11 @@ class UserPermissionApi {
"disable": status, "disable": status,
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c" "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c"
}; };
print('changeUserStatusById==$bodya');
print('changeUserStatusById==$userUuid');
final response = await _httpService.put( final response = await _httpService.put(
path: ApiEndpoints.changeUserStatus path: ApiEndpoints.changeUserStatus.replaceAll("{invitedUserUuid}", userUuid),
.replaceAll("{invitedUserUuid}", userUuid),
body: bodya, body: bodya,
expectedResponseModel: (json) { expectedResponseModel: (json) {
print('changeUserStatusById==${json['success']}');
return json['success']; return json['success'];
}, },
); );
@@ -213,7 +204,6 @@ class UserPermissionApi {
return response; return response;
} catch (e) { } catch (e) {
return false; return false;
print(e);
} }
} }
} }
+25 -6
View File
@@ -1,18 +1,37 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ResponsiveLayout extends StatelessWidget { class ResponsiveLayout extends StatelessWidget {
final Widget desktopBody;
final Widget mobileBody; final Widget mobileBody;
const ResponsiveLayout( final Widget? tablet;
{super.key, required this.desktopBody, required this.mobileBody}); final Widget desktopBody;
const ResponsiveLayout({
super.key,
required this.mobileBody,
this.tablet,
required this.desktopBody,
});
static bool isMobile(BuildContext context) =>
MediaQuery.of(context).size.width < 650;
static bool isTablet(BuildContext context) =>
MediaQuery.of(context).size.width < 1100 &&
MediaQuery.of(context).size.width >= 650;
static bool isDesktop(BuildContext context) =>
MediaQuery.of(context).size.width >= 1100;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
if (constraints.maxWidth < 600) { if (constraints.maxWidth >= 1100) {
return mobileBody;
} else {
return desktopBody; return desktopBody;
} else if (constraints.maxWidth >= 650) {
return tablet!;
} else {
return mobileBody;
} }
}, },
); );
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/responsive_layout.dart';
class ResponsiveTextTheme extends ThemeExtension<ResponsiveTextTheme> {
final TextStyle deviceManagementTitle;
ResponsiveTextTheme({
required this.deviceManagementTitle,
});
@override
ThemeExtension<ResponsiveTextTheme> copyWith() => this;
@override
ThemeExtension<ResponsiveTextTheme> lerp(
ThemeExtension<ResponsiveTextTheme>? other, double t) =>
this;
static ResponsiveTextTheme of(BuildContext context) {
final isMobile = ResponsiveLayout.isMobile(context);
return Theme.of(context).extension<ResponsiveTextTheme>() ??
ResponsiveTextTheme(
deviceManagementTitle: TextStyle(
fontSize: isMobile ? 20 : 30,
fontWeight: FontWeight.w700,
color: ColorsManager.whiteColors),
);
}
}
+201 -145
View File
@@ -5,10 +5,10 @@ import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/responsive_layout.dart';
import 'package:syncrow_web/utils/user_drop_down_menu.dart'; import 'package:syncrow_web/utils/user_drop_down_menu.dart';
class WebAppBar extends StatefulWidget { class WebAppBar extends StatelessWidget {
final Widget? title; final Widget? title;
final Widget? centerBody; final Widget? centerBody;
final Widget? rightBody; final Widget? rightBody;
@@ -16,178 +16,234 @@ class WebAppBar extends StatefulWidget {
const WebAppBar({super.key, this.title, this.centerBody, this.rightBody}); const WebAppBar({super.key, this.title, this.centerBody, this.rightBody});
@override @override
State<WebAppBar> createState() => _WebAppBarState(); Widget build(BuildContext context) {
return BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) {
final user = context.read<HomeBloc>().user;
return ResponsiveLayout(
mobileBody: MobileAppBar(
title: title,
centerBody: centerBody,
rightBody: rightBody,
user: user,
),
tablet: TabletAppBar(
title: title,
centerBody: centerBody,
rightBody: rightBody,
user: user,
),
desktopBody: DesktopAppBar(
title: title,
centerBody: centerBody,
rightBody: rightBody,
user: user,
),
);
},
);
}
} }
class _WebAppBarState extends State<WebAppBar> with HelperResponsiveLayout { class DesktopAppBar extends StatelessWidget {
@override final Widget? title;
void initState() { final Widget? centerBody;
super.initState(); final Widget? rightBody;
} final dynamic user;
const DesktopAppBar({
super.key,
this.title,
this.centerBody,
this.rightBody,
required this.user,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isSmallScreen = isSmallScreenSize(context);
bool isHalfMediumScreen = isHafMediumScreenSize(context);
return BlocBuilder<HomeBloc, HomeState>(builder: (context, state) {
final user = context.read<HomeBloc>().user;
return Container( return Container(
height: (isSmallScreen || isHalfMediumScreen) ? 130 : 100, height: 100,
decoration: const BoxDecoration(color: ColorsManager.secondaryColor), decoration: const BoxDecoration(color: ColorsManager.secondaryColor),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: isSmallScreen || isHalfMediumScreen child: Row(
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.title != null)
Align(
alignment: Alignment.centerLeft,
child: widget.title!,
),
if (widget.centerBody != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: widget.centerBody,
),
if (widget.rightBody != null || user != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (widget.rightBody != null) widget.rightBody!,
Row(
children: [
SizedBox.square(
dimension: 40,
child: CircleAvatar(
backgroundColor: ColorsManager.whiteColors,
child: SizedBox.square(
dimension: 35,
child: SvgPicture.asset(
Assets.logoGrey,
fit: BoxFit.cover,
),
),
),
),
const SizedBox(
width: 10,
),
if (user != null)
Text(
'${user.firstName} ${user.lastName}',
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
],
),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Expanded( Expanded(
child: Row( child: Row(
children: [ children: [
widget.title!, if (title != null) title!,
if (widget.centerBody != null) if (centerBody != null)
Padding( Padding(
padding: const EdgeInsets.only(left: 80), padding: const EdgeInsets.only(left: 80),
child: widget.centerBody!, child: centerBody!,
), ),
], ],
), ),
), ),
Row( _buildUserSection(context),
mainAxisSize: MainAxisSize.min, ],
),
);
}
Widget _buildUserSection(BuildContext context) {
return Row(
children: [ children: [
if (widget.rightBody != null) if (rightBody != null) rightBody!,
Align( const SizedBox(width: 24),
alignment: Alignment.centerRight, _UserAvatar(),
child: widget.rightBody, const SizedBox(width: 12),
),
const SizedBox(
width: 10,
),
SizedBox.square(
dimension: 40,
child: CircleAvatar(
backgroundColor: ColorsManager.whiteColors,
child: SizedBox.square(
dimension: 35,
child: SvgPicture.asset(
Assets.logoGrey,
fit: BoxFit.cover,
),
),
),
),
const SizedBox(
width: 10,
),
if (user != null) if (user != null)
Text( Text(
'${user.firstName} ${user.lastName}', '${user.firstName} ${user.lastName}',
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
const SizedBox( const SizedBox(width: 12),
width: 10,
),
UserDropdownMenu(user: user), UserDropdownMenu(user: user),
// GestureDetector( ],
// onTap: () { );
// showCustomDialog( }
// context: context, }
// barrierDismissible: true,
// title: 'Logout', class TabletAppBar extends StatelessWidget {
// message: 'Are you sure you want to logout?', final Widget? title;
// actions: [ final Widget? centerBody;
// GestureDetector( final Widget? rightBody;
// onTap: () { final dynamic user;
// AuthBloc.logout();
// context.go(RoutesConst.auth); const TabletAppBar({
// }, super.key,
// child: DefaultButton( this.title,
// child: Text( this.centerBody,
// 'Ok', this.rightBody,
// style: Theme.of(context) required this.user,
// .textTheme });
// .bodyMedium!
// .copyWith(fontSize: 12, color: Colors.white), @override
// ), Widget build(BuildContext context) {
// ), return Container(
// ), height: 100,
// const SizedBox( decoration: const BoxDecoration(color: ColorsManager.secondaryColor),
// height: 10, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
// ), child: Column(
// GestureDetector( children: [
// onTap: () { Row(
// context.pop(); mainAxisAlignment: MainAxisAlignment.spaceBetween,
// }, children: [
// child: DefaultButton( if (title != null) Expanded(child: title!),
// child: Text( _buildUserSection(context),
// 'Cancel', ],
// style: Theme.of(context) ),
// .textTheme if (centerBody != null) Expanded(child: centerBody!),
// .bodyMedium! ],
// .copyWith(fontSize: 12, color: Colors.white), ),
// ), );
// ), }
// ),
// ], Widget _buildUserSection(BuildContext context) {
// ); return Row(
// }, children: [
// child: const Icon( if (rightBody != null) rightBody!,
// Icons.logout, const SizedBox(width: 16),
// color: ColorsManager.whiteColors, _UserAvatar(),
// ), if (user != null) ...[
// ) const SizedBox(width: 8),
Text(
'${user.firstName} ${user.lastName}',
style:
Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 14),
),
],
UserDropdownMenu(user: user),
],
);
}
}
class MobileAppBar extends StatelessWidget {
final Widget? title;
final Widget? centerBody;
final Widget? rightBody;
final dynamic user;
const MobileAppBar({
super.key,
this.title,
this.centerBody,
this.rightBody,
required this.user,
});
@override
Widget build(BuildContext context) {
return Container(
height: 135,
decoration: const BoxDecoration(color: ColorsManager.secondaryColor),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (title != null) title!,
_buildUserSection(context),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (centerBody != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: centerBody!,
),
if (rightBody != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: rightBody!,
),
], ],
), ),
], ],
), ),
); );
}); }
Widget _buildUserSection(BuildContext context) {
return Row(
children: [
_UserAvatar(),
if (user != null) ...[
const SizedBox(width: 8),
Text(
'${user.firstName} ${user.lastName}',
style:
Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 14),
),
],
UserDropdownMenu(user: user),
],
);
}
}
class _UserAvatar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox.square(
dimension: 40,
child: CircleAvatar(
backgroundColor: ColorsManager.whiteColors,
child: SizedBox.square(
dimension: 35,
child: SvgPicture.asset(
Assets.logoGrey,
fit: BoxFit.cover,
),
),
),
);
} }
} }
@@ -5,12 +5,20 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import firebase_analytics
import firebase_core
import firebase_crashlytics
import firebase_database
import flutter_secure_storage_macos import flutter_secure_storage_macos
import path_provider_foundation import path_provider_foundation
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
+23
View File
@@ -22,6 +22,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DAF1C60594A51D692304366 /* Pods_Runner.framework */; }; 108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DAF1C60594A51D692304366 /* Pods_Runner.framework */; };
2901225E5FAB0C696EE79F77 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 996A2A515D007C9FED5396A5 /* GoogleService-Info.plist */; };
2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */; }; 2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */; };
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
@@ -84,6 +85,7 @@
81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; }; 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; }; 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
996A2A515D007C9FED5396A5 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; }; A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
AB949539E0D0A8E2BDAB9ADF /* 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>"; }; AB949539E0D0A8E2BDAB9ADF /* 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>"; };
E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -138,6 +140,7 @@
33CC10EE2044A3C60003C045 /* Products */, 33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */, D73912EC22F37F3D000D13A0 /* Frameworks */,
75DCDFECC7757C5159E8F0C5 /* Pods */, 75DCDFECC7757C5159E8F0C5 /* Pods */,
996A2A515D007C9FED5396A5 /* GoogleService-Info.plist */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -241,6 +244,7 @@
33CC110E2044A8840003C045 /* Bundle Framework */, 33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */, 3399D490228B24CF009A79C7 /* ShellScript */,
92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */, 92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */,
7E188D2155D07A3E9E027C0F /* FlutterFire: "flutterfire upload-crashlytics-symbols" */,
); );
buildRules = ( buildRules = (
); );
@@ -317,6 +321,7 @@
files = ( files = (
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
2901225E5FAB0C696EE79F77 /* GoogleService-Info.plist in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -361,6 +366,24 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
}; };
7E188D2155D07A3E9E027C0F /* 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=macos --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";
};
8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */ = { 8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
+32
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>
+100 -4
View File
@@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: e051259913915ea5bc8fe18664596bea08592fd123930605d562969cd7315fcd
url: "https://pub.dev"
source: hosted
version: "1.3.51"
args: args:
dependency: transitive dependency: transitive
description: description:
@@ -153,6 +161,94 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
firebase_analytics:
dependency: "direct main"
description:
name: firebase_analytics
sha256: "47428047a0778f72af53a3c7cb5d556e1cb25e2327cc8aa40d544971dc6245b2"
url: "https://pub.dev"
source: hosted
version: "11.4.2"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: "1076f4b041f76143e14878c70f0758f17fe5910c0cd992db9e93bd3c3584512b"
url: "https://pub.dev"
source: hosted
version: "4.3.2"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: "8f6dd64ea6d28b7f5b9e739d183a9e1c7f17027794a3e9aba1879621d42426ef"
url: "https://pub.dev"
source: hosted
version: "0.5.10+8"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "93dc4dd12f9b02c5767f235307f609e61ed9211047132d07f9e02c668f0bfc33"
url: "https://pub.dev"
source: hosted
version: "3.11.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf
url: "https://pub.dev"
source: hosted
version: "5.4.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "0e13c80f0de8acaa5d0519cbe23c8b4cc138a2d5d508b5755c861bdfc9762678"
url: "https://pub.dev"
source: hosted
version: "2.20.0"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
sha256: "6273ed71bcd8a6fb4d0ca13d3abddbb3301796807efaad8782b5f90156f26f03"
url: "https://pub.dev"
source: hosted
version: "4.3.2"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: "94f3986e1a10e5a883f2ad5e3d719aef98a8a0f9a49357f6e45b7d3696ea6a97"
url: "https://pub.dev"
source: hosted
version: "3.8.2"
firebase_database:
dependency: "direct main"
description:
name: firebase_database
sha256: cd2354dfef68e52c0713b5efbb7f4e10dfc2aff2f945c7bc8db34d1934170627
url: "https://pub.dev"
source: hosted
version: "11.3.2"
firebase_database_platform_interface:
dependency: transitive
description:
name: firebase_database_platform_interface
sha256: d430983f4d877c9f72f88b3d715cca9a50021dd7ccd8e3ae6fb79603853317de
url: "https://pub.dev"
source: hosted
version: "0.2.6+2"
firebase_database_web:
dependency: transitive
description:
name: firebase_database_web
sha256: f64edae62c5beaa08e9e611a0736d64ab11a812983a0aa132695d2d191311ea7
url: "https://pub.dev"
source: hosted
version: "0.2.6+8"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@@ -572,10 +668,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936" sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.2"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
@@ -777,10 +873,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "1.1.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
+5
View File
@@ -56,6 +56,11 @@ dependencies:
number_pagination: ^1.1.6 number_pagination: ^1.1.6
url_launcher: ^6.2.5 url_launcher: ^6.2.5
flutter_html: ^3.0.0-beta.2 flutter_html: ^3.0.0-beta.2
firebase_analytics: ^11.4.0
firebase_core: ^3.11.0
firebase_crashlytics: ^4.3.2
firebase_database: ^11.3.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
+16
View File
@@ -40,6 +40,8 @@
<script src="flutter.js" defer></script> <script src="flutter.js" defer></script>
</head> </head>
<body> <body>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
<script> <script>
window.addEventListener('load', function(ev) { window.addEventListener('load', function(ev) {
// Download main.dart.js // Download main.dart.js
@@ -54,6 +56,20 @@
} }
}); });
}); });
var firebaseConfig = {
apiKey: "AIzaSyCVEvKsJYzhWDFM-9Od68FE0nPpP933st0",
authDomain: "test2-8a3d2.firebaseapp.com",
databaseURL: "https://test2-8a3d2-default-rtdb.firebaseio.com",
projectId: "test2-8a3d2",
storageBucket: "test2-8a3d2.firebasestorage.app",
messagingSenderId: "427332280600",
appId: "1:427332280600:web:ad50516a87a35a1a0c7e6d",
measurementId: "G-Z1RTTTV5H9"
};
firebase.initializeApp(firebaseConfig);
</script> </script>
</body> </body>
</html> </html>
@@ -6,10 +6,13 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <firebase_core/firebase_core_plugin_c_api.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FirebaseCorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar( FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
+1
View File
@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
firebase_core
flutter_secure_storage_windows flutter_secure_storage_windows
url_launcher_windows url_launcher_windows
) )