diff --git a/android/app/build.gradle b/android/app/build.gradle index 75ed15fd..8981dae5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,9 @@ plugins { id "com.android.application" + // START: FlutterFire Configuration + id 'com.google.gms.google-services' + id 'com.google.firebase.crashlytics' + // END: FlutterFire Configuration id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" } diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 00000000..3cc77ccd --- /dev/null +++ b/android/app/google-services.json @@ -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" +} \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index 1d6d19b7..85edcfc9 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -20,6 +20,10 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "7.3.0" apply false + // START: FlutterFire Configuration + id "com.google.gms.google-services" version "4.3.15" apply false + id "com.google.firebase.crashlytics" version "2.8.1" apply false + // END: FlutterFire Configuration id "org.jetbrains.kotlin.android" version "1.7.10" apply false } diff --git a/assets/icons/delete_space_link_icon.svg b/assets/icons/delete_space_link_icon.svg new file mode 100644 index 00000000..a55d2e04 --- /dev/null +++ b/assets/icons/delete_space_link_icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/delete_space_model.svg b/assets/icons/delete_space_model.svg new file mode 100644 index 00000000..7fb9a9b0 --- /dev/null +++ b/assets/icons/delete_space_model.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/space_link_icon.svg b/assets/icons/space_link_icon.svg new file mode 100644 index 00000000..f10c57ad --- /dev/null +++ b/assets/icons/space_link_icon.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/success_icon.svg b/assets/icons/success_icon.svg new file mode 100644 index 00000000..6f5dbf9e --- /dev/null +++ b/assets/icons/success_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/delete_space_link_icon.svg b/assets/images/delete_space_link_icon.svg new file mode 100644 index 00000000..a55d2e04 --- /dev/null +++ b/assets/images/delete_space_link_icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/space_link_icon.svg b/assets/images/space_link_icon.svg new file mode 100644 index 00000000..f10c57ad --- /dev/null +++ b/assets/images/space_link_icon.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/success_icon.svg b/assets/images/success_icon.svg new file mode 100644 index 00000000..6f5dbf9e --- /dev/null +++ b/assets/images/success_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/firebase.json b/firebase.json new file mode 100644 index 00000000..fa1105a3 --- /dev/null +++ b/firebase.json @@ -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"}}}}}} \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index dffe452f..4f245401 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; E44A9405B1EB1B638DD05A58 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ABF0EC746A2D686A0ED574F /* Pods_RunnerTests.framework */; }; + F2A3345EC3021060731668D3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B14AB50E8716720E10D074BD /* GoogleService-Info.plist */; }; FF49F60EC38658783D8D66DA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AFAE479A87ECDEBD5D6EB30 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ @@ -64,6 +65,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B14AB50E8716720E10D074BD /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; 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 = ""; }; /* End PBXFileReference section */ @@ -138,6 +140,7 @@ 331C8082294A63A400263BE5 /* RunnerTests */, 1454C118FFCECEEDF59152D2 /* Pods */, 20A3C64D2B1CFED5A81C3251 /* Frameworks */, + B14AB50E8716720E10D074BD /* GoogleService-Info.plist */, ); sourceTree = ""; }; @@ -199,6 +202,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 33590C9CD073D3D5EBA02CDE /* [CP] Embed Pods Frameworks */, + 7A77858F6F15CB76D2D3A872 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */, ); buildRules = ( ); @@ -264,6 +268,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + F2A3345EC3021060731668D3 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -303,6 +308,24 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 7A77858F6F15CB76D2D3A872 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\""; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:$HOME/.pub-cache/bin\"\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PODS_ROOT/FirebaseCrashlytics/upload-symbols\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n"; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 00000000..9cdebed0 --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,32 @@ + + + + + API_KEY + AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw + GCM_SENDER_ID + 427332280600 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.syncrowWeb + PROJECT_ID + test2-8a3d2 + STORAGE_BUCKET + test2-8a3d2.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:427332280600:ios:14346b200780dc760c7e6d + DATABASE_URL + https://test2-8a3d2-default-rtdb.firebaseio.com + + \ No newline at end of file diff --git a/lib/common/dialog_textfield_dropdown.dart b/lib/common/tag_dialog_textfield_dropdown.dart similarity index 75% rename from lib/common/dialog_textfield_dropdown.dart rename to lib/common/tag_dialog_textfield_dropdown.dart index ac88e5dc..219e03ce 100644 --- a/lib/common/dialog_textfield_dropdown.dart +++ b/lib/common/tag_dialog_textfield_dropdown.dart @@ -1,35 +1,38 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class DialogTextfieldDropdown extends StatefulWidget { - final List items; - final ValueChanged onSelected; - final String? initialValue; +class TagDialogTextfieldDropdown extends StatefulWidget { + final List items; + final ValueChanged onSelected; + final Tag? initialValue; + final String product; - const DialogTextfieldDropdown({ + const TagDialogTextfieldDropdown({ Key? key, required this.items, required this.onSelected, this.initialValue, + required this.product, }) : super(key: key); @override - _DialogTextfieldDropdownState createState() => - _DialogTextfieldDropdownState(); + _DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState(); } -class _DialogTextfieldDropdownState extends State { +class _DialogTextfieldDropdownState extends State { bool _isOpen = false; OverlayEntry? _overlayEntry; final TextEditingController _controller = TextEditingController(); final FocusNode _focusNode = FocusNode(); - List _filteredItems = []; + List _filteredItems = []; @override void initState() { super.initState(); - _controller.text = widget.initialValue ?? ''; - _filteredItems = List.from(widget.items); + _controller.text = widget.initialValue?.tag ?? ''; + + _filterItems(); _focusNode.addListener(() { if (!_focusNode.hasFocus) { @@ -38,6 +41,12 @@ class _DialogTextfieldDropdownState extends State { }); } + void _filterItems() { + setState(() { + _filteredItems = widget.items.where((tag) => tag.product?.uuid == widget.product).toList(); + }); + } + void _toggleDropdown() { if (_isOpen) { _closeDropdown(); @@ -87,7 +96,7 @@ class _DialogTextfieldDropdownState extends State { shrinkWrap: true, itemCount: _filteredItems.length, itemBuilder: (context, index) { - final item = _filteredItems[index]; + final tag = _filteredItems[index]; return Container( decoration: const BoxDecoration( @@ -99,19 +108,16 @@ class _DialogTextfieldDropdownState extends State { ), ), child: ListTile( - title: Text(item, + title: Text(tag.tag ?? '', style: Theme.of(context) .textTheme .bodyMedium - ?.copyWith( - color: ColorsManager - .textPrimaryColor)), + ?.copyWith(color: ColorsManager.textPrimaryColor)), onTap: () { - _controller.text = item; - widget.onSelected(item); + _controller.text = tag.tag ?? ''; + widget.onSelected(tag); setState(() { - _filteredItems - .remove(item); // Remove selected item + _filteredItems.remove(tag); }); _closeDropdown(); }, @@ -150,11 +156,14 @@ class _DialogTextfieldDropdownState extends State { controller: _controller, focusNode: _focusNode, onFieldSubmitted: (value) { - widget.onSelected(value); + final selectedTag = _filteredItems.firstWhere((tag) => tag.tag == value, + orElse: () => Tag(tag: value)); + widget.onSelected(selectedTag); _closeDropdown(); }, onTapOutside: (event) { - widget.onSelected(_controller.text); + widget.onSelected(_filteredItems.firstWhere((tag) => tag.tag == _controller.text, + orElse: () => Tag(tag: _controller.text))); _closeDropdown(); }, style: Theme.of(context).textTheme.bodyMedium, diff --git a/lib/common/widgets/custom_expansion_tile.dart b/lib/common/widgets/custom_expansion_tile.dart index 8df9b663..74151ca2 100644 --- a/lib/common/widgets/custom_expansion_tile.dart +++ b/lib/common/widgets/custom_expansion_tile.dart @@ -56,24 +56,6 @@ class CustomExpansionTileState extends State { children: [ Row( children: [ - // Checkbox with independent state management - Checkbox( - value: false, - onChanged: (bool? value) { - setState(() {}); - }, - side: WidgetStateBorderSide.resolveWith((states) { - return const BorderSide(color: ColorsManager.grayBorder); - }), - fillColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.selected)) { - return ColorsManager.grayBorder; - } else { - return ColorsManager.checkBoxFillColor; - } - }), - checkColor: ColorsManager.whiteColors, - ), // Expand/collapse icon, now wrapped in a GestureDetector for specific onTap if (widget.children != null && widget.children!.isNotEmpty) GestureDetector( @@ -84,7 +66,9 @@ class CustomExpansionTileState extends State { }); }, child: Icon( - _isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right, + _isExpanded + ? Icons.keyboard_arrow_down + : Icons.keyboard_arrow_right, color: ColorsManager.lightGrayColor, size: 16.0, // Adjusted size for better alignment ), @@ -101,8 +85,10 @@ class CustomExpansionTileState extends State { _capitalizeFirstLetter(widget.title), style: TextStyle( color: widget.isSelected - ? ColorsManager.blackColor // Change color to black when selected - : ColorsManager.lightGrayColor, // Gray when not selected + ? ColorsManager + .blackColor // Change color to black when selected + : ColorsManager + .lightGrayColor, // Gray when not selected fontWeight: FontWeight.w400, ), ), @@ -111,9 +97,11 @@ class CustomExpansionTileState extends State { ], ), // The expanded section (children) that shows when the tile is expanded - if (_isExpanded && widget.children != null && widget.children!.isNotEmpty) + if (_isExpanded && + widget.children != null && + widget.children!.isNotEmpty) Padding( - padding: const EdgeInsets.only(left: 48.0), // Indented children + padding: const EdgeInsets.only(left: 24.0), // Indented children child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: widget.children!, diff --git a/lib/firebase_options_dev.dart b/lib/firebase_options_dev.dart new file mode 100644 index 00000000..93e8600c --- /dev/null +++ b/lib/firebase_options_dev.dart @@ -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', + ); +} diff --git a/lib/firebase_options_prod.dart b/lib/firebase_options_prod.dart new file mode 100644 index 00000000..485696b8 --- /dev/null +++ b/lib/firebase_options_prod.dart @@ -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', + ); +} diff --git a/lib/main.dart b/lib/main.dart index ee1a55f5..f2f640e4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,10 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:go_router/go_router.dart'; +import 'package:syncrow_web/firebase_options_prod.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; @@ -18,9 +20,13 @@ import 'package:syncrow_web/utils/theme/theme.dart'; Future main() async { try { - const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development'); + const environment = + String.fromEnvironment('FLAVOR', defaultValue: 'production'); await dotenv.load(fileName: '.env.$environment'); WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptionsStaging.currentPlatform, + ); initialSetup(); } catch (_) {} runApp(MyApp()); @@ -49,7 +55,8 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())), + BlocProvider( + create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider( create: (context) => VisitorPasswordBloc(), ), diff --git a/lib/main_dev.dart b/lib/main_dev.dart new file mode 100644 index 00000000..9d00ebf7 --- /dev/null +++ b/lib/main_dev.dart @@ -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 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( + create: (context) => VisitorPasswordBloc(), + ), + BlocProvider( + create: (context) => RoutineBloc(), + ), + BlocProvider( + 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, + )); + } +} diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index 4b1c37d6..562bd5b5 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -8,9 +8,6 @@ import 'package:syncrow_web/pages/common/hour_picker_dialog.dart'; import 'package:syncrow_web/services/access_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; -import 'package:syncrow_web/utils/constants/strings_manager.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; -import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; class AccessBloc extends Bloc { diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index c60b4bb2..d7c7a9dd 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; -import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart'; import 'package:syncrow_web/pages/common/custom_table.dart'; @@ -18,6 +17,7 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/style.dart'; +import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { @@ -33,11 +33,9 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { return WebScaffold( enableMenuSidebar: false, - appBarTitle: FittedBox( - child: Text( - 'Access Management', - style: Theme.of(context).textTheme.headlineLarge, - ), + appBarTitle: Text( + 'Access Management', + style: ResponsiveTextTheme.of(context).deviceManagementTitle, ), rightBody: const NavigateHomeGridView(), scaffoldBody: BlocProvider( diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index b22dae7b..9e0ac2f9 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -15,7 +15,6 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/services/auth_api.dart'; import 'package:syncrow_web/utils/constants/strings_manager.dart'; import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; -import 'package:syncrow_web/utils/navigation_service.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; class AuthBloc extends Bloc { diff --git a/lib/pages/auth/view/forget_password_page.dart b/lib/pages/auth/view/forget_password_page.dart index 0ab2c2df..5ec7e4a7 100644 --- a/lib/pages/auth/view/forget_password_page.dart +++ b/lib/pages/auth/view/forget_password_page.dart @@ -8,6 +8,8 @@ class ForgetPasswordPage extends StatelessWidget { @override Widget build(BuildContext context) { return const ResponsiveLayout( - desktopBody: ForgetPasswordWebPage(), mobileBody: ForgetPasswordWebPage()); + tablet: ForgetPasswordWebPage(), + desktopBody: ForgetPasswordWebPage(), + mobileBody: ForgetPasswordWebPage()); } } diff --git a/lib/pages/auth/view/login_page.dart b/lib/pages/auth/view/login_page.dart index 31907c68..6f302eeb 100644 --- a/lib/pages/auth/view/login_page.dart +++ b/lib/pages/auth/view/login_page.dart @@ -9,6 +9,8 @@ class LoginPage extends StatelessWidget { @override Widget build(BuildContext context) { return const ResponsiveLayout( - desktopBody: LoginWebPage(), mobileBody: LoginWebPage()); + tablet: LoginWebPage(), + desktopBody: LoginWebPage(), + mobileBody: LoginWebPage()); } } diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index 7c6ee628..7c35034e 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:dio/dio.dart'; +import 'package:firebase_database/firebase_database.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; @@ -19,6 +20,7 @@ class AcBloc extends Bloc { on(_onAcControl); on(_onAcBatchControl); on(_onFactoryReset); + on(_onAcStatusUpdated); } FutureOr _onFetchAcStatus( @@ -28,12 +30,64 @@ class AcBloc extends Bloc { final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); + _listenToChanges(event.deviceId); emit(ACStatusLoaded(deviceStatus)); } catch (e) { emit(AcsFailedState(error: e.toString())); } } + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream 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 usersMap = + event.snapshot.value as Map; + + List 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 emit) { + deviceStatus = event.deviceStatus; + emit(ACStatusLoaded(deviceStatus)); + } + + // Future 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 _onAcControl( AcControlEvent event, Emitter emit) async { final oldValue = _getValueByCode(event.code); diff --git a/lib/pages/device_managment/ac/bloc/ac_event.dart b/lib/pages/device_managment/ac/bloc/ac_event.dart index 8d49df96..5492e198 100644 --- a/lib/pages/device_managment/ac/bloc/ac_event.dart +++ b/lib/pages/device_managment/ac/bloc/ac_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; sealed class AcsEvent extends Equatable { @@ -7,6 +8,7 @@ sealed class AcsEvent extends Equatable { @override List get props => []; } +class AcUpdated extends AcsEvent {} class AcFetchDeviceStatusEvent extends AcsEvent { final String deviceId; @@ -16,7 +18,10 @@ class AcFetchDeviceStatusEvent extends AcsEvent { @override List get props => [deviceId]; } - +class AcStatusUpdated extends AcsEvent { + final AcStatusModel deviceStatus; + AcStatusUpdated(this.deviceStatus); +} class AcFetchBatchStatusEvent extends AcsEvent { final List devicesIds; diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 5197d722..071344d7 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -24,7 +24,8 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); return BlocProvider( - create: (context) => AcBloc(deviceId: device.uuid!)..add(AcFetchDeviceStatusEvent(device.uuid!)), + create: (context) => AcBloc(deviceId: device.uuid!) + ..add(AcFetchDeviceStatusEvent(device.uuid!)), child: BlocBuilder( builder: (context, state) { if (state is ACStatusLoaded) { @@ -98,7 +99,8 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { ), Text( 'h', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor), ), Text( '30', @@ -107,7 +109,9 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { fontWeight: FontWeight.bold, ), ), - Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)), + Text('m', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor)), IconButton( padding: const EdgeInsets.all(0), onPressed: () {}, diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index e52debb0..7ed3a377 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -5,9 +5,6 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; -import 'package:syncrow_web/utils/constants/strings_manager.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; -import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; part 'device_managment_event.dart'; part 'device_managment_state.dart'; diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 7bcf1b3d..e71d5af8 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -244,6 +244,7 @@ SOS SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), ModeFunction(deviceId: uuid ?? '', deviceName: name ?? ''), TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + CurrentTempFunction(deviceId: uuid ?? '', deviceName: name ?? ''), LevelFunction(deviceId: uuid ?? '', deviceName: name ?? ''), ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? ''), ]; diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index 1bc4d071..45af9751 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; @@ -10,6 +9,7 @@ import 'package:syncrow_web/pages/routines/view/routines_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; +import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { @@ -25,13 +25,12 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { ), ], child: WebScaffold( - appBarTitle: FittedBox( - child: Text( - 'Device Management', - style: Theme.of(context).textTheme.headlineLarge, - ), + appBarTitle: Text( + 'Device Management', + style: ResponsiveTextTheme.of(context).deviceManagementTitle, ), - centerBody: BlocBuilder(builder: (context, state) { + centerBody: + BlocBuilder(builder: (context, state) { return Row( mainAxisSize: MainAxisSize.min, children: [ @@ -48,8 +47,11 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { child: Text( 'Devices', style: context.textTheme.titleMedium?.copyWith( - color: !state.routineTab ? ColorsManager.whiteColors : ColorsManager.grayColor, - fontWeight: !state.routineTab ? FontWeight.w700 : FontWeight.w400, + color: !state.routineTab + ? ColorsManager.whiteColors + : ColorsManager.grayColor, + fontWeight: + !state.routineTab ? FontWeight.w700 : FontWeight.w400, ), ), ), @@ -58,13 +60,18 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { backgroundColor: null, ), onPressed: () { - context.read().add(const TriggerSwitchTabsEvent(isRoutineTab: true)); + context + .read() + .add(const TriggerSwitchTabsEvent(isRoutineTab: true)); }, child: Text( 'Routines', style: context.textTheme.titleMedium?.copyWith( - color: state.routineTab ? ColorsManager.whiteColors : ColorsManager.grayColor, - fontWeight: state.routineTab ? FontWeight.w700 : FontWeight.w400, + color: state.routineTab + ? ColorsManager.whiteColors + : ColorsManager.grayColor, + fontWeight: + state.routineTab ? FontWeight.w700 : FontWeight.w400, ), ), ), @@ -72,7 +79,8 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { ); }), rightBody: const NavigateHomeGridView(), - scaffoldBody: BlocBuilder(builder: (context, state) { + scaffoldBody: + BlocBuilder(builder: (context, state) { if (state.routineTab) { return const RoutinesView(); } @@ -87,7 +95,8 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { } else if (deviceState is DeviceManagementLoaded) { return DeviceManagementBody(devices: deviceState.devices); } else if (deviceState is DeviceManagementFiltered) { - return DeviceManagementBody(devices: deviceState.filteredDevices); + return DeviceManagementBody( + devices: deviceState.filteredDevices); } else { return const Center(child: Text('Error fetching Devices')); } diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 52b2321c..907e5390 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -8,7 +8,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart'; -import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/utils/format_date_time.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; @@ -95,7 +94,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { const DeviceSearchFilters(), const SizedBox(height: 12), Container( - height: 45, + // height: 45, width: 125, decoration: containerDecoration, child: Center( diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart b/lib/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart index 4e13bfd6..4e8d5a8b 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:firebase_database/firebase_database.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart'; @@ -22,42 +23,55 @@ class CeilingSensorBloc extends Bloc { on(_showDescription); on(_backToGridView); on(_onFactoryReset); + on(_onStatusUpdated); } void _fetchCeilingSensorStatus( CeilingInitialEvent event, Emitter emit) async { emit(CeilingLoadingInitialState()); try { - var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); + var response = + await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = CeilingSensorModel.fromJson(response.status); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); - // _listenToChanges(); + _listenToChanges(event.deviceId); } catch (e) { emit(CeilingFailedState(error: e.toString())); return; } } - // _listenToChanges() { - // try { - // DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); - // Stream stream = ref.onValue; + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; - // stream.listen((DatabaseEvent event) { - // Map usersMap = event.snapshot.value as Map; - // List statusList = []; + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; - // usersMap['status'].forEach((element) { - // statusList.add(StatusModel(code: element['code'], value: element['value'])); - // }); + List statusList = []; + usersMap['status'].forEach((element) { + statusList + .add(Status(code: element['code'], value: element['value'])); + }); - // deviceStatus = WallSensorModel.fromJson(statusList); - // add(WallSensorUpdatedEvent()); - // }); - // } catch (_) {} - // } + deviceStatus = CeilingSensorModel.fromJson(statusList); + if (!isClosed) { + add(StatusUpdated(deviceStatus)); + } + }); + } catch (_) {} + } - void _changeValue(CeilingChangeValueEvent event, Emitter emit) async { + void _onStatusUpdated(StatusUpdated event, Emitter emit) { + deviceStatus = event.deviceStatus; + emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); + } + + void _changeValue( + CeilingChangeValueEvent event, Emitter emit) async { emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); if (event.code == 'sensitivity') { deviceStatus.sensitivity = event.value; @@ -122,7 +136,8 @@ class CeilingSensorBloc extends Bloc { try { late bool response; if (isBatch) { - response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + response = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); } else { response = await DevicesManagementApi() .deviceControl(deviceId, Status(code: code, value: value)); @@ -143,8 +158,8 @@ class CeilingSensorBloc extends Bloc { }); } - FutureOr _getDeviceReports( - GetCeilingDeviceReportsEvent event, Emitter emit) async { + FutureOr _getDeviceReports(GetCeilingDeviceReportsEvent event, + Emitter emit) async { if (event.code.isEmpty) { emit(ShowCeilingDescriptionState(description: reportString)); return; @@ -155,7 +170,8 @@ class CeilingSensorBloc extends Bloc { try { // await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString()) - await DevicesManagementApi.getDeviceReports(deviceId, event.code).then((value) { + await DevicesManagementApi.getDeviceReports(deviceId, event.code) + .then((value) { emit(CeilingReportsState(deviceReport: value)); }); } catch (e) { @@ -165,19 +181,23 @@ class CeilingSensorBloc extends Bloc { } } - void _showDescription(ShowCeilingDescriptionEvent event, Emitter emit) { + void _showDescription( + ShowCeilingDescriptionEvent event, Emitter emit) { emit(ShowCeilingDescriptionState(description: event.description)); } - void _backToGridView(BackToCeilingGridViewEvent event, Emitter emit) { + void _backToGridView( + BackToCeilingGridViewEvent event, Emitter emit) { emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); } FutureOr _fetchCeilingSensorBatchControl( - CeilingFetchDeviceStatusEvent event, Emitter emit) async { + CeilingFetchDeviceStatusEvent event, + Emitter emit) async { emit(CeilingLoadingInitialState()); try { - var response = await DevicesManagementApi().getBatchStatus(event.devicesIds); + var response = + await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = CeilingSensorModel.fromJson(response.status); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); } catch (e) { diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart b/lib/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart index 582c9836..1dc7d8d7 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; abstract class CeilingSensorEvent extends Equatable { const CeilingSensorEvent(); @@ -83,3 +84,12 @@ class CeilingFactoryResetEvent extends CeilingSensorEvent { @override List get props => [devicesId, factoryResetModel]; } + + + +class StatusUpdated extends CeilingSensorEvent { + final CeilingSensorModel deviceStatus; + const StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} diff --git a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart index 4599f360..251d999f 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:firebase_database/firebase_database.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; @@ -16,6 +17,7 @@ class CurtainBloc extends Bloc { on(_onCurtainControl); on(_onCurtainBatchControl); on(_onFactoryReset); + on(_onStatusUpdated); } FutureOr _onFetchDeviceStatus( @@ -24,7 +26,7 @@ class CurtainBloc extends Bloc { try { final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - + _listenToChanges(event.deviceId); deviceStatus = _checkStatus(status.status[0].value); emit(CurtainStatusLoaded(deviceStatus)); @@ -33,6 +35,48 @@ class CurtainBloc extends Bloc { } } + void _listenToChanges(String deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + final data = event.snapshot.value as Map?; + if (data == null) return; + + List 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 emit) { + emit(CurtainStatusLoading()); + deviceStatus = event.deviceStatus; + emit(CurtainStatusLoaded(deviceStatus)); + } + FutureOr _onCurtainControl( CurtainControl event, Emitter emit) async { final oldValue = deviceStatus; diff --git a/lib/pages/device_managment/curtain/bloc/curtain_event.dart b/lib/pages/device_managment/curtain/bloc/curtain_event.dart index 7236016c..dd6700f9 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_event.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_event.dart @@ -60,3 +60,7 @@ class CurtainFactoryReset extends CurtainEvent { @override List get props => [deviceId, factoryReset]; } +class StatusUpdated extends CurtainEvent { + final bool deviceStatus; + const StatusUpdated(this.deviceStatus); +} \ No newline at end of file diff --git a/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart b/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart index c50203f3..f83ced1a 100644 --- a/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart +++ b/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart @@ -1,6 +1,7 @@ // ignore_for_file: invalid_use_of_visible_for_testing_member import 'dart:async'; +import 'package:firebase_database/firebase_database.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_event.dart'; @@ -18,6 +19,39 @@ class DoorLockBloc extends Bloc { //on(_onDoorLockControl); on(_updateLock); on(_onFactoryReset); + on(_onStatusUpdated); + } + + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + + List 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 emit) { + emit(DoorLockStatusLoading()); + + deviceStatus = event.deviceStatus; + emit(DoorLockStatusLoaded(deviceStatus)); } FutureOr _onFetchDeviceStatus( @@ -28,6 +62,8 @@ class DoorLockBloc extends Bloc { await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = DoorLockStatusModel.fromJson(event.deviceId, status.status); + _listenToChanges(event.deviceId); + emit(DoorLockStatusLoaded(deviceStatus)); } catch (e) { emit(DoorLockControlError(e.toString())); diff --git a/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart b/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart index 54fa1ddf..3f28724e 100644 --- a/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart +++ b/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart'; sealed class DoorLockEvent extends Equatable { const DoorLockEvent(); @@ -51,3 +52,10 @@ class DoorLockFactoryReset extends DoorLockEvent { @override List get props => [deviceId, factoryReset]; } + +class StatusUpdated extends DoorLockEvent { + final DoorLockStatusModel deviceStatus; + const StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index 7060c668..025de303 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:firebase_database/firebase_database.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; @@ -39,31 +40,68 @@ class GarageDoorBloc extends Bloc { on(_onFetchBatchStatus); on(_onFactoryReset); on(_onEditSchedule); + on(_onStatusUpdated); + } + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + + List 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 emit) async { + void _onStatusUpdated(StatusUpdated event, Emitter emit) { + deviceStatus = event.deviceStatus; + emit(GarageDoorLoadedState(status: deviceStatus)); + } + + void _fetchGarageDoorStatus( + GarageDoorInitialEvent event, Emitter emit) async { emit(GarageDoorLoadingState()); try { - var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); + var response = + await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status); + _listenToChanges(deviceId); emit(GarageDoorLoadedState(status: deviceStatus)); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } } - Future _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event, Emitter emit) async { + Future _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event, + Emitter emit) async { emit(GarageDoorLoadingState()); try { - final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); - deviceStatus = GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status); + final status = + await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = + GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status); emit(GarageDoorBatchStatusLoaded(deviceStatus)); } catch (e) { emit(GarageDoorBatchControlError(e.toString())); } } - Future _addSchedule(AddGarageDoorScheduleEvent event, Emitter emit) async { + Future _addSchedule( + AddGarageDoorScheduleEvent event, Emitter emit) async { try { ScheduleEntry newSchedule = ScheduleEntry( category: event.category, @@ -71,9 +109,11 @@ class GarageDoorBloc extends Bloc { function: Status(code: 'switch_1', value: event.functionOn), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), ); - bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); + bool success = + await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); if (success) { - add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1')); + add(FetchGarageDoorSchedulesEvent( + deviceId: deviceId, category: 'switch_1')); } else { emit(GarageDoorLoadedState(status: deviceStatus)); } @@ -82,16 +122,19 @@ class GarageDoorBloc extends Bloc { } } - void _onUpdateCountdownAlarm(UpdateCountdownAlarmEvent event, Emitter emit) { + void _onUpdateCountdownAlarm( + UpdateCountdownAlarmEvent event, Emitter emit) { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith( - status: currentState.status.copyWith(countdownAlarm: event.countdownAlarm), + status: + currentState.status.copyWith(countdownAlarm: event.countdownAlarm), )); } } - void _onUpdateTrTimeCon(UpdateTrTimeConEvent event, Emitter emit) { + void _onUpdateTrTimeCon( + UpdateTrTimeConEvent event, Emitter emit) { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith( @@ -100,7 +143,8 @@ class GarageDoorBloc extends Bloc { } } - Future _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter emit) async { + Future _updateSchedule(UpdateGarageDoorScheduleEvent event, + Emitter emit) async { try { final updatedSchedules = deviceStatus.schedules?.map((schedule) { if (schedule.scheduleId == event.scheduleId) { @@ -127,12 +171,15 @@ class GarageDoorBloc extends Bloc { } } - Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter emit) async { + Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, + Emitter emit) async { try { - bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); + bool success = await DevicesManagementApi() + .deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); if (success) { - final updatedSchedules = - deviceStatus.schedules?.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); + final updatedSchedules = deviceStatus.schedules + ?.where((schedule) => schedule.scheduleId != event.scheduleId) + .toList(); deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); emit(GarageDoorLoadedState(status: deviceStatus)); } else { @@ -143,11 +190,12 @@ class GarageDoorBloc extends Bloc { } } - Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter emit) async { + Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, + Emitter emit) async { emit(ScheduleGarageLoadingState()); try { - List schedules = - await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); + List schedules = await DevicesManagementApi() + .getDeviceSchedules(deviceStatus.uuid, event.category); deviceStatus = deviceStatus.copyWith(schedules: schedules); emit( GarageDoorLoadedState( @@ -165,30 +213,37 @@ class GarageDoorBloc extends Bloc { } } - Future _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter emit) async { + Future _updateSelectedTime( + UpdateSelectedTimeEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith(selectedTime: event.selectedTime)); } } - Future _updateSelectedDay(UpdateSelectedDayEvent event, Emitter emit) async { + Future _updateSelectedDay( + UpdateSelectedDayEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { List updatedDays = List.from(currentState.selectedDays); updatedDays[event.dayIndex] = event.isSelected; - emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith( + selectedDays: updatedDays, selectedTime: currentState.selectedTime)); } } - Future _updateFunctionOn(UpdateFunctionOnEvent event, Emitter emit) async { + Future _updateFunctionOn( + UpdateFunctionOnEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { - emit(currentState.copyWith(functionOn: event.functionOn, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith( + functionOn: event.functionOn, + selectedTime: currentState.selectedTime)); } } - Future _initializeAddSchedule(InitializeAddScheduleEvent event, Emitter emit) async { + Future _initializeAddSchedule( + InitializeAddScheduleEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith( @@ -200,20 +255,25 @@ class GarageDoorBloc extends Bloc { } } - Future _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter emit) async { + Future _fetchRecords( + FetchGarageDoorRecordsEvent event, Emitter emit) async { emit(GarageDoorReportsLoadingState()); try { - final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; + final from = DateTime.now() + .subtract(const Duration(days: 30)) + .millisecondsSinceEpoch; final to = DateTime.now().millisecondsSinceEpoch; final DeviceReport records = - await DevicesManagementApi.getDeviceReportsByDate(event.deviceId, 'switch_1', from.toString(), to.toString()); + await DevicesManagementApi.getDeviceReportsByDate( + event.deviceId, 'switch_1', from.toString(), to.toString()); emit(GarageDoorReportsState(deviceReport: records)); } catch (e) { emit(GarageDoorReportsFailedState(error: e.toString())); } } - Future _onBatchControl(GarageDoorBatchControlEvent event, Emitter emit) async { + Future _onBatchControl( + GarageDoorBatchControlEvent event, Emitter emit) async { final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false; _updateLocalValue(event.code, event.value); @@ -233,11 +293,13 @@ class GarageDoorBloc extends Bloc { } } - void _backToGridView(BackToGarageDoorGridViewEvent event, Emitter emit) { + void _backToGridView( + BackToGarageDoorGridViewEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); } - void _handleUpdate(GarageDoorUpdatedEvent event, Emitter emit) { + void _handleUpdate( + GarageDoorUpdatedEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); } @@ -253,9 +315,11 @@ class GarageDoorBloc extends Bloc { late bool status; await Future.delayed(const Duration(milliseconds: 500)); if (isBatch) { - status = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + status = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); } else { - status = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); + status = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); } if (!status) { @@ -270,10 +334,12 @@ class GarageDoorBloc extends Bloc { } } - Future _onFactoryReset(GarageDoorFactoryResetEvent event, Emitter emit) async { + Future _onFactoryReset( + GarageDoorFactoryResetEvent event, Emitter emit) async { emit(GarageDoorLoadingState()); try { - final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); + final response = await DevicesManagementApi() + .factoryReset(event.factoryReset, event.deviceId); if (!response) { emit(const GarageDoorErrorState(message: 'Failed to reset device')); } else { @@ -284,34 +350,47 @@ class GarageDoorBloc extends Bloc { } } - void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { + void _increaseDelay( + IncreaseGarageDoorDelayEvent event, Emitter emit) async { // if (deviceStatus.countdown1 != 0) { try { - deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); + deviceStatus = deviceStatus.copyWith( + delay: deviceStatus.delay + Duration(minutes: 10)); emit(GarageDoorLoadedState(status: deviceStatus)); - add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); + add(GarageDoorControlEvent( + deviceId: event.deviceId, + value: deviceStatus.delay.inSeconds, + code: 'countdown_1')); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } // } } - void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { + void _decreaseDelay( + DecreaseGarageDoorDelayEvent event, Emitter emit) async { // if (deviceStatus.countdown1 != 0) { try { if (deviceStatus.delay.inMinutes > 10) { - deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); + deviceStatus = deviceStatus.copyWith( + delay: deviceStatus.delay - Duration(minutes: 10)); } emit(GarageDoorLoadedState(status: deviceStatus)); - add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); + add(GarageDoorControlEvent( + deviceId: event.deviceId, + value: deviceStatus.delay.inSeconds, + code: 'countdown_1')); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } //} } - void _garageDoorControlEvent(GarageDoorControlEvent event, Emitter emit) async { - final oldValue = event.code == 'countdown_1' ? deviceStatus.countdown1 : deviceStatus.switch1; + void _garageDoorControlEvent( + GarageDoorControlEvent event, Emitter emit) async { + final oldValue = event.code == 'countdown_1' + ? deviceStatus.countdown1 + : deviceStatus.switch1; _updateLocalValue(event.code, event.value); emit(GarageDoorLoadedState(status: deviceStatus)); final success = await _runDeBouncer( @@ -327,7 +406,8 @@ class GarageDoorBloc extends Bloc { } } - void _revertValue(String code, dynamic oldValue, Emitter emit) { + void _revertValue( + String code, dynamic oldValue, Emitter emit) { switch (code) { case 'switch_1': if (oldValue is bool) { @@ -336,7 +416,8 @@ class GarageDoorBloc extends Bloc { break; case 'countdown_1': if (oldValue is int) { - deviceStatus = deviceStatus.copyWith(countdown1: oldValue, delay: Duration(seconds: oldValue)); + deviceStatus = deviceStatus.copyWith( + countdown1: oldValue, delay: Duration(seconds: oldValue)); } break; // Add other cases if needed @@ -358,7 +439,8 @@ class GarageDoorBloc extends Bloc { break; case 'countdown_1': if (value is int) { - deviceStatus = deviceStatus.copyWith(countdown1: value, delay: Duration(seconds: value)); + deviceStatus = deviceStatus.copyWith( + countdown1: value, delay: Duration(seconds: value)); } break; case 'countdown_alarm': @@ -401,7 +483,8 @@ class GarageDoorBloc extends Bloc { return super.close(); } - FutureOr _onEditSchedule(EditGarageDoorScheduleEvent event, Emitter emit) async { + FutureOr _onEditSchedule( + EditGarageDoorScheduleEvent event, Emitter emit) async { try { ScheduleEntry newSchedule = ScheduleEntry( scheduleId: event.scheduleId, @@ -410,9 +493,11 @@ class GarageDoorBloc extends Bloc { function: Status(code: 'switch_1', value: event.functionOn), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), ); - bool success = await DevicesManagementApi().editScheduleRecord(deviceId, newSchedule); + bool success = await DevicesManagementApi() + .editScheduleRecord(deviceId, newSchedule); if (success) { - add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1')); + add(FetchGarageDoorSchedulesEvent( + deviceId: deviceId, category: 'switch_1')); } else { emit(GarageDoorLoadedState(status: deviceStatus)); } diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart index d1fb15bb..fc625e32 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; abstract class GarageDoorEvent extends Equatable { const GarageDoorEvent(); @@ -25,7 +26,8 @@ class GarageDoorControlEvent extends GarageDoorEvent { final dynamic value; final String code; - const GarageDoorControlEvent({required this.deviceId, required this.value, required this.code}); + const GarageDoorControlEvent( + {required this.deviceId, required this.value, required this.code}); @override List get props => [deviceId, value]; @@ -121,7 +123,8 @@ class FetchGarageDoorRecordsEvent extends GarageDoorEvent { final String deviceId; final String code; - const FetchGarageDoorRecordsEvent({required this.deviceId, required this.code}); + const FetchGarageDoorRecordsEvent( + {required this.deviceId, required this.code}); @override List get props => [deviceId, code]; @@ -232,3 +235,10 @@ class GarageDoorFactoryResetEvent extends GarageDoorEvent { @override List get props => [factoryReset, deviceId]; } + +class StatusUpdated extends GarageDoorEvent { + final GarageDoorStatusModel deviceStatus; + const StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart index 933ce28b..493e3037 100644 --- a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:dio/dio.dart'; +import 'package:firebase_database/firebase_database.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; @@ -16,6 +17,7 @@ class MainDoorSensorBloc on(_onFetchBatchStatus); on(_fetchReports); on(_factoryReset); + on(_onStatusUpdated); } late MainDoorSensorStatusModel deviceStatus; @@ -28,7 +30,7 @@ class MainDoorSensorBloc final status = await DevicesManagementApi() .getDeviceStatus(event.deviceId) .then((value) => value.status); - + _listenToChanges(event.deviceId); deviceStatus = MainDoorSensorStatusModel.fromJson(event.deviceId, status); emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); } catch (e) { @@ -156,4 +158,35 @@ class MainDoorSensorBloc emit(MainDoorSensorFailedState(error: e.toString())); } } + + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + + List 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 emit) { + deviceStatus = event.deviceStatus; + emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); + } } diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart index c2864333..569cfa11 100644 --- a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart'; import '../../all_devices/models/factory_reset_model.dart'; @@ -71,3 +72,10 @@ class MainDoorSensorFactoryReset extends MainDoorSensorEvent { MainDoorSensorFactoryReset( {required this.deviceId, required this.factoryReset}); } + +class StatusUpdated extends MainDoorSensorEvent { + final MainDoorSensorStatusModel deviceStatus; + StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} diff --git a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart index eb72eecd..12aeaa88 100644 --- a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart +++ b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:firebase_database/firebase_database.dart'; import 'package:meta/meta.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; @@ -10,33 +11,71 @@ import 'package:syncrow_web/services/devices_mang_api.dart'; part 'one_gang_glass_switch_event.dart'; part 'one_gang_glass_switch_state.dart'; -class OneGangGlassSwitchBloc extends Bloc { +class OneGangGlassSwitchBloc + extends Bloc { OneGangGlassStatusModel deviceStatus; Timer? _timer; OneGangGlassSwitchBloc({required String deviceId}) - : deviceStatus = OneGangGlassStatusModel(uuid: deviceId, switch1: false, countDown: 0), + : deviceStatus = OneGangGlassStatusModel( + uuid: deviceId, switch1: false, countDown: 0), super(OneGangGlassSwitchInitial()) { on(_onFetchDeviceStatus); on(_onControl); on(_onBatchControl); on(_onFetchBatchStatus); on(_onFactoryReset); + on(_onStatusUpdated); } - Future _onFetchDeviceStatus( - OneGangGlassSwitchFetchDeviceEvent event, Emitter emit) async { + Future _onFetchDeviceStatus(OneGangGlassSwitchFetchDeviceEvent event, + Emitter emit) async { emit(OneGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + _listenToChanges(event.deviceId); + deviceStatus = + OneGangGlassStatusModel.fromJson(event.deviceId, status.status); emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(OneGangGlassSwitchError(e.toString())); } } - Future _onControl(OneGangGlassSwitchControl event, Emitter emit) async { + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + + List 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 emit) { + deviceStatus = event.deviceStatus; + emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); + } + + Future _onControl(OneGangGlassSwitchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -52,10 +91,12 @@ class OneGangGlassSwitchBloc extends Bloc _onFactoryReset(OneGangGlassFactoryResetEvent event, Emitter emit) async { + Future _onFactoryReset(OneGangGlassFactoryResetEvent event, + Emitter emit) async { emit(OneGangGlassSwitchLoading()); try { - final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); + final response = await DevicesManagementApi() + .factoryReset(event.factoryReset, event.deviceId); if (!response) { emit(OneGangGlassSwitchError('Failed to reset device')); } else { @@ -66,7 +107,8 @@ class OneGangGlassSwitchBloc extends Bloc _onBatchControl(OneGangGlassSwitchBatchControl event, Emitter emit) async { + Future _onBatchControl(OneGangGlassSwitchBatchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -83,11 +125,14 @@ class OneGangGlassSwitchBloc extends Bloc _onFetchBatchStatus( - OneGangGlassSwitchFetchBatchStatusEvent event, Emitter emit) async { + OneGangGlassSwitchFetchBatchStatusEvent event, + Emitter emit) async { emit(OneGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); - deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); + final status = + await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = OneGangGlassStatusModel.fromJson( + event.deviceIds.first, status.status); emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(OneGangGlassSwitchError(e.toString())); @@ -117,9 +162,11 @@ class OneGangGlassSwitchBloc extends Bloc emit) { + void _revertValueAndEmit(String deviceId, String code, bool oldValue, + Emitter emit) { _updateLocalValue(code, oldValue); emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); } diff --git a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart index 83d9b7b9..c652e31a 100644 --- a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart +++ b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart @@ -39,6 +39,11 @@ class OneGangGlassSwitchFetchBatchStatusEvent extends OneGangGlassSwitchEvent { OneGangGlassSwitchFetchBatchStatusEvent(this.deviceIds); } +class StatusUpdated extends OneGangGlassSwitchEvent { + final OneGangGlassStatusModel deviceStatus; + StatusUpdated(this.deviceStatus); +} + class OneGangGlassFactoryResetEvent extends OneGangGlassSwitchEvent { final FactoryResetModel factoryReset; final String deviceId; diff --git a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart index 595e7e06..c2038330 100644 --- a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart +++ b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:firebase_database/firebase_database.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart'; @@ -16,6 +17,7 @@ class WallLightSwitchBloc on(_onFetchBatchStatus); on(_onBatchControl); on(_onFactoryReset); + on(_onStatusUpdated); } late WallLightStatusModel deviceStatus; @@ -31,12 +33,44 @@ class WallLightSwitchBloc deviceStatus = WallLightStatusModel.fromJson(event.deviceId, status.status); + _listenToChanges(event.deviceId); emit(WallLightSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(WallLightSwitchError(e.toString())); } } + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + + List 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 emit) { + deviceStatus = event.deviceStatus; + emit(WallLightSwitchStatusLoaded(deviceStatus)); + } + FutureOr _onControl( WallLightSwitchControl event, Emitter emit) async { final oldValue = _getValueByCode(event.code); diff --git a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart index 5c601484..45b6e4d1 100644 --- a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart +++ b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart'; class WallLightSwitchEvent extends Equatable { @override @@ -57,3 +58,10 @@ class WallLightFactoryReset extends WallLightSwitchEvent { @override List get props => [deviceId, factoryReset]; } + +class StatusUpdated extends WallLightSwitchEvent { + final WallLightStatusModel deviceStatus; + StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} diff --git a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart index 8e3c109e..174cd167 100644 --- a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart +++ b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:firebase_database/firebase_database.dart'; import 'package:meta/meta.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; @@ -10,7 +11,8 @@ import 'package:syncrow_web/services/devices_mang_api.dart'; part 'three_gang_glass_switch_event.dart'; part 'three_gang_glass_switch_state.dart'; -class ThreeGangGlassSwitchBloc extends Bloc { +class ThreeGangGlassSwitchBloc + extends Bloc { ThreeGangGlassStatusModel deviceStatus; Timer? _timer; @@ -29,21 +31,57 @@ class ThreeGangGlassSwitchBloc extends Bloc(_onBatchControl); on(_onFetchBatchStatus); on(_onFactoryReset); + on(_onStatusUpdated); } - Future _onFetchDeviceStatus( - ThreeGangGlassSwitchFetchDeviceEvent event, Emitter emit) async { + Future _onFetchDeviceStatus(ThreeGangGlassSwitchFetchDeviceEvent event, + Emitter emit) async { emit(ThreeGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status); + _listenToChanges(event.deviceId); emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(ThreeGangGlassSwitchError(e.toString())); } } - Future _onControl(ThreeGangGlassSwitchControl event, Emitter emit) async { + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + + List 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 emit) { + deviceStatus = event.deviceStatus; + emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus)); + } + + Future _onControl(ThreeGangGlassSwitchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -59,7 +97,8 @@ class ThreeGangGlassSwitchBloc extends Bloc _onBatchControl(ThreeGangGlassSwitchBatchControl event, Emitter emit) async { + Future _onBatchControl(ThreeGangGlassSwitchBatchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -76,21 +115,26 @@ class ThreeGangGlassSwitchBloc extends Bloc _onFetchBatchStatus( - ThreeGangGlassSwitchFetchBatchStatusEvent event, Emitter emit) async { + ThreeGangGlassSwitchFetchBatchStatusEvent event, + Emitter emit) async { emit(ThreeGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); - deviceStatus = ThreeGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); + final status = + await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = ThreeGangGlassStatusModel.fromJson( + event.deviceIds.first, status.status); emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus)); } catch (e) { emit(ThreeGangGlassSwitchError(e.toString())); } } - Future _onFactoryReset(ThreeGangGlassFactoryReset event, Emitter emit) async { + Future _onFactoryReset(ThreeGangGlassFactoryReset event, + Emitter emit) async { emit(ThreeGangGlassSwitchLoading()); try { - final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); + final response = await DevicesManagementApi() + .factoryReset(event.factoryReset, event.deviceId); if (!response) { emit(ThreeGangGlassSwitchError('Failed')); } else { @@ -124,9 +168,11 @@ class ThreeGangGlassSwitchBloc extends Bloc emit) { + void _revertValueAndEmit(String deviceId, String code, bool oldValue, + Emitter emit) { _updateLocalValue(code, oldValue); emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus)); } diff --git a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart index 558b9824..82b93fba 100644 --- a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart +++ b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart @@ -49,3 +49,10 @@ class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent { required this.factoryReset, }); } + +class StatusUpdated extends ThreeGangGlassSwitchEvent { + final ThreeGangGlassStatusModel deviceStatus; + StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} diff --git a/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart b/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart index ca264c13..a7a03a7f 100644 --- a/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart +++ b/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:firebase_database/firebase_database.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; @@ -22,6 +23,7 @@ class LivingRoomBloc extends Bloc { on(_livingRoomBatchControl); on(_livingRoomFetchBatchControl); on(_livingRoomFactoryReset); + on(_onStatusUpdated); } FutureOr _onFetchDeviceStatus(LivingRoomFetchDeviceStatusEvent event, @@ -32,6 +34,7 @@ class LivingRoomBloc extends Bloc { await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = LivingRoomStatusModel.fromJson(event.deviceId, status.status); + _listenToChanges(deviceId); emit(LivingRoomDeviceStatusLoaded(deviceStatus)); } catch (e) { emit(LivingRoomDeviceManagementError(e.toString())); @@ -144,6 +147,9 @@ class LivingRoomBloc extends Bloc { await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = LivingRoomStatusModel.fromJson(event.devicesIds.first, status.status); + // for (var deviceId in event.devicesIds) { + // _listenToChanges(deviceId); + // } emit(LivingRoomDeviceStatusLoaded(deviceStatus)); } catch (e) { emit(LivingRoomDeviceManagementError(e.toString())); @@ -185,4 +191,34 @@ class LivingRoomBloc extends Bloc { emit(LivingRoomDeviceManagementError(e.toString())); } } + + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + + List 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 emit) { + deviceStatus = event.deviceStatus; + emit(LivingRoomDeviceStatusLoaded(deviceStatus)); + } } diff --git a/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart b/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart index c0ada0f6..d1776d52 100644 --- a/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart +++ b/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart @@ -58,3 +58,10 @@ class LivingRoomFactoryResetEvent extends LivingRoomEvent { @override List get props => [uuid, factoryReset]; } + +class StatusUpdated extends LivingRoomEvent { + final LivingRoomStatusModel deviceStatus; + const StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} diff --git a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart index 5169b0e4..406821da 100644 --- a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart +++ b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart @@ -1,12 +1,11 @@ import 'dart:async'; - import 'package:bloc/bloc.dart'; +import 'package:firebase_database/firebase_database.dart'; import 'package:meta/meta.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; - part 'two_gang_glass_switch_event.dart'; part 'two_gang_glass_switch_state.dart'; @@ -14,7 +13,6 @@ class TwoGangGlassSwitchBloc extends Bloc { TwoGangGlassStatusModel deviceStatus; Timer? _timer; - TwoGangGlassSwitchBloc({required String deviceId}) : deviceStatus = TwoGangGlassStatusModel( uuid: deviceId, @@ -28,6 +26,7 @@ class TwoGangGlassSwitchBloc on(_onBatchControl); on(_onFetchBatchStatus); on(_onFactoryReset); + on(_onStatusUpdated); } Future _onFetchDeviceStatus(TwoGangGlassSwitchFetchDeviceEvent event, @@ -38,12 +37,44 @@ class TwoGangGlassSwitchBloc await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status); + _listenToChanges(event.deviceId); emit(TwoGangGlassSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(TwoGangGlassSwitchError(e.toString())); } } + void _listenToChanges(String deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + ref.onValue.listen((DatabaseEvent event) { + if (event.snapshot.value == null) return; + + Map data = + event.snapshot.value as Map; + List 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 _onControl(TwoGangGlassSwitchControl event, Emitter emit) async { final oldValue = _getValueByCode(event.code); @@ -178,4 +209,37 @@ class TwoGangGlassSwitchBloc _timer?.cancel(); return super.close(); } + + // _listenToChanges(deviceId) { + // try { + // DatabaseReference ref = + // FirebaseDatabase.instance.ref('device-status/$deviceId'); + // Stream stream = ref.onValue; + + // stream.listen((DatabaseEvent event) { + // Map usersMap = + // event.snapshot.value as Map; + + // List 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 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)); + } } diff --git a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_event.dart b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_event.dart index f88d61fe..02b61bd0 100644 --- a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_event.dart +++ b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_event.dart @@ -48,3 +48,11 @@ class TwoGangGlassFactoryReset extends TwoGangGlassSwitchEvent { required this.factoryReset, }); } + +class StatusUpdated extends TwoGangGlassSwitchEvent { + final TwoGangGlassStatusModel deviceStatus; + StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} + diff --git a/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart index 72f69763..cca794e9 100644 --- a/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart +++ b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart @@ -6,7 +6,8 @@ import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class TwoGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout { +class TwoGangGlassSwitchControlView extends StatelessWidget + with HelperResponsiveLayout { final String deviceId; const TwoGangGlassSwitchControlView({required this.deviceId, super.key}); @@ -14,25 +15,25 @@ class TwoGangGlassSwitchControlView extends StatelessWidget with HelperResponsiv @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => - TwoGangGlassSwitchBloc(deviceId: deviceId)..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)), - child: BlocBuilder( - builder: (context, state) { - if (state is TwoGangGlassSwitchLoading) { - return const Center(child: CircularProgressIndicator()); - } else if (state is TwoGangGlassSwitchStatusLoaded) { - return _buildStatusControls(context, state.status); - } else if (state is TwoGangGlassSwitchError) { - return const Center(child: Text('Error fetching status')); - } else { - return const Center(child: CircularProgressIndicator()); - } - }, - ), - ); + create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceId) + ..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is TwoGangGlassSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is TwoGangGlassSwitchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is TwoGangGlassSwitchError) { + return Center(child: Text(state.message)); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + )); } - Widget _buildStatusControls(BuildContext context, TwoGangGlassStatusModel status) { + Widget _buildStatusControls( + BuildContext context, TwoGangGlassStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); diff --git a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart index 0d35d8e8..788d8676 100644 --- a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart +++ b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:firebase_database/firebase_database.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; @@ -14,6 +15,7 @@ class TwoGangSwitchBloc extends Bloc { on(_onFetchBatchStatus); on(_onBatchControl); on(_onFactoryReset); + on(_onStatusUpdated); } late TwoGangStatusModel deviceStatus; @@ -26,8 +28,8 @@ class TwoGangSwitchBloc extends Bloc { try { final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status); + _listenToChanges(emit); emit(TwoGangSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(TwoGangSwitchError(e.toString())); @@ -174,4 +176,34 @@ class TwoGangSwitchBloc extends Bloc { emit(TwoGangSwitchError(e.toString())); } } + + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + + List 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 emit) { + deviceStatus = event.deviceStatus; + emit(TwoGangSwitchStatusLoaded(deviceStatus)); + } } diff --git a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart index 16973b3a..b45fd11f 100644 --- a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart +++ b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; class TwoGangSwitchEvent extends Equatable { @override @@ -57,3 +58,10 @@ class TwoGangFactoryReset extends TwoGangSwitchEvent { @override List get props => [deviceId, factoryReset]; } + +class StatusUpdated extends TwoGangSwitchEvent { + final TwoGangStatusModel deviceStatus; + StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} diff --git a/lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart b/lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart index 41598439..a8a52fff 100644 --- a/lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart +++ b/lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:firebase_database/firebase_database.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart'; @@ -29,7 +30,7 @@ class WallSensorBloc extends Bloc { var response = await DevicesManagementApi().getDeviceStatus(deviceId); deviceStatus = WallSensorModel.fromJson(response.status); emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); - // _listenToChanges(); + _listenToChanges(emit); } catch (e) { emit(WallSensorFailedState(error: e.toString())); return; @@ -38,10 +39,12 @@ class WallSensorBloc extends Bloc { // Fetch batch status FutureOr _fetchWallSensorBatchControl( - WallSensorFetchBatchStatusEvent event, Emitter emit) async { + WallSensorFetchBatchStatusEvent event, + Emitter emit) async { emit(WallSensorLoadingInitialState()); try { - var response = await DevicesManagementApi().getBatchStatus(event.devicesIds); + var response = + await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = WallSensorModel.fromJson(response.status); emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); } catch (e) { @@ -49,26 +52,30 @@ class WallSensorBloc extends Bloc { } } - // _listenToChanges() { - // try { - // DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); - // Stream stream = ref.onValue; + _listenToChanges(Emitter emit) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; - // stream.listen((DatabaseEvent event) { - // Map usersMap = event.snapshot.value as Map; - // List statusList = []; + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + List statusList = []; - // usersMap['status'].forEach((element) { - // statusList.add(StatusModel(code: element['code'], value: element['value'])); - // }); + usersMap['status'].forEach((element) { + statusList + .add(Status(code: element['code'], value: element['value'])); + }); - // deviceStatus = WallSensorModel.fromJson(statusList); - // add(WallSensorUpdatedEvent()); - // }); - // } catch (_) {} - // } + deviceStatus = WallSensorModel.fromJson(statusList); + emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); + }); + } catch (_) {} + } - void _changeValue(WallSensorChangeValueEvent event, Emitter emit) async { + void _changeValue( + WallSensorChangeValueEvent event, Emitter emit) async { emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); if (event.code == 'far_detection') { deviceStatus.farDetection = event.value; @@ -125,7 +132,8 @@ class WallSensorBloc extends Bloc { try { late bool response; if (isBatch) { - response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + response = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); } else { response = await DevicesManagementApi() .deviceControl(deviceId, Status(code: code, value: value)); @@ -150,7 +158,8 @@ class WallSensorBloc extends Bloc { try { // await DevicesManagementApi.getDeviceReportsByDate( // deviceId, event.code, from.toString(), to.toString()) - await DevicesManagementApi.getDeviceReports(deviceId, event.code).then((value) { + await DevicesManagementApi.getDeviceReports(deviceId, event.code) + .then((value) { emit(DeviceReportsState(deviceReport: value, code: event.code)); }); } catch (e) { @@ -159,11 +168,13 @@ class WallSensorBloc extends Bloc { } } - void _showDescription(ShowDescriptionEvent event, Emitter emit) { + void _showDescription( + ShowDescriptionEvent event, Emitter emit) { emit(WallSensorShowDescriptionState(description: event.description)); } - void _backToGridView(BackToGridViewEvent event, Emitter emit) { + void _backToGridView( + BackToGridViewEvent event, Emitter emit) { emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 498c55fb..18a0787f 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:firebase_database/firebase_database.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; @@ -34,6 +35,7 @@ class WaterHeaterBloc extends Bloc { on(_onEditSchedule); on(_onDeleteSchedule); on(_onUpdateSchedule); + on(_onStatusUpdated); } late WaterHeaterStatusModel deviceStatus; @@ -78,7 +80,8 @@ class WaterHeaterBloc extends Bloc { final currentState = state as WaterHeaterDeviceStatusLoaded; final updatedDays = List.from(currentState.selectedDays); updatedDays[event.index] = event.value; - emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith( + selectedDays: updatedDays, selectedTime: currentState.selectedTime)); } FutureOr _updateFunctionOn( @@ -86,7 +89,8 @@ class WaterHeaterBloc extends Bloc { Emitter emit, ) { final currentState = state as WaterHeaterDeviceStatusLoaded; - emit(currentState.copyWith(functionOn: event.isOn, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith( + functionOn: event.isOn, selectedTime: currentState.selectedTime)); } FutureOr _updateScheduleEvent( @@ -101,7 +105,8 @@ class WaterHeaterBloc extends Bloc { )); } if (event.scheduleMode == ScheduleModes.countdown) { - final countdownRemaining = Duration(hours: event.hours, minutes: event.minutes); + final countdownRemaining = + Duration(hours: event.hours, minutes: event.minutes); emit(currentState.copyWith( scheduleMode: ScheduleModes.countdown, @@ -111,11 +116,13 @@ class WaterHeaterBloc extends Bloc { countdownRemaining: countdownRemaining, )); - if (!currentState.isCountdownActive! && countdownRemaining > Duration.zero) { + if (!currentState.isCountdownActive! && + countdownRemaining > Duration.zero) { _startCountdownTimer(emit, countdownRemaining); } } else if (event.scheduleMode == ScheduleModes.inching) { - final inchingDuration = Duration(hours: event.hours, minutes: event.minutes); + final inchingDuration = + Duration(hours: event.hours, minutes: event.minutes); emit(currentState.copyWith( scheduleMode: ScheduleModes.inching, @@ -217,7 +224,8 @@ class WaterHeaterBloc extends Bloc { try { final status = await DevicesManagementApi().deviceControl( event.deviceId, - Status(code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0), + Status( + code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0), ); if (!status) { emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.')); @@ -235,8 +243,10 @@ class WaterHeaterBloc extends Bloc { emit(WaterHeaterLoadingState()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + WaterHeaterStatusModel.fromJson(event.deviceId, status.status); if (deviceStatus.scheduleMode == ScheduleModes.countdown) { final countdownRemaining = Duration( @@ -300,11 +310,42 @@ class WaterHeaterBloc extends Bloc { isInchingActive: false, )); } + _listenToChanges(event.deviceId); } catch (e) { emit(WaterHeaterFailedState(error: e.toString())); } } + _listenToChanges(deviceId) { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + + List 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 emit) { + deviceStatus = event.deviceStatus; + emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); + } + void _startCountdownTimer( Emitter emit, Duration countdownRemaining, @@ -334,8 +375,10 @@ class WaterHeaterBloc extends Bloc { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; - if (currentState.countdownRemaining != null && currentState.countdownRemaining! > Duration.zero) { - final newRemaining = currentState.countdownRemaining! - const Duration(minutes: 1); + if (currentState.countdownRemaining != null && + currentState.countdownRemaining! > Duration.zero) { + final newRemaining = + currentState.countdownRemaining! - const Duration(minutes: 1); if (newRemaining <= Duration.zero) { _countdownTimer?.cancel(); @@ -430,7 +473,8 @@ class WaterHeaterBloc extends Bloc { } } - void _revertValue(String code, dynamic oldValue, void Function(WaterHeaterState state) emit) { + void _revertValue(String code, dynamic oldValue, + void Function(WaterHeaterState state) emit) { _updateLocalValue(code, oldValue); if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; @@ -477,12 +521,13 @@ class WaterHeaterBloc extends Bloc { return super.close(); } - FutureOr _getSchedule(GetSchedulesEvent event, Emitter emit) async { + FutureOr _getSchedule( + GetSchedulesEvent event, Emitter emit) async { emit(ScheduleLoadingState()); try { - List schedules = - await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); + List schedules = await DevicesManagementApi() + .getDeviceSchedules(deviceStatus.uuid, event.category); emit(WaterHeaterDeviceStatusLoaded( deviceStatus, @@ -514,7 +559,8 @@ class WaterHeaterBloc extends Bloc { // emit(ScheduleLoadingState()); - bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, currentState.status.uuid); + bool success = await DevicesManagementApi() + .addScheduleRecord(newSchedule, currentState.status.uuid); if (success) { add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid)); @@ -525,7 +571,8 @@ class WaterHeaterBloc extends Bloc { } } - FutureOr _onEditSchedule(EditWaterHeaterScheduleEvent event, Emitter emit) async { + FutureOr _onEditSchedule(EditWaterHeaterScheduleEvent event, + Emitter emit) async { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; @@ -594,11 +641,13 @@ class WaterHeaterBloc extends Bloc { // emit(ScheduleLoadingState()); - bool success = await DevicesManagementApi().deleteScheduleRecord(currentState.status.uuid, event.scheduleId); + bool success = await DevicesManagementApi() + .deleteScheduleRecord(currentState.status.uuid, event.scheduleId); if (success) { - final updatedSchedules = - currentState.schedules.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); + final updatedSchedules = currentState.schedules + .where((schedule) => schedule.scheduleId != event.scheduleId) + .toList(); emit(currentState.copyWith(schedules: updatedSchedules)); } else { @@ -608,12 +657,15 @@ class WaterHeaterBloc extends Bloc { } } - FutureOr _batchFetchWaterHeater(FetchWaterHeaterBatchStatusEvent event, Emitter emit) async { + FutureOr _batchFetchWaterHeater(FetchWaterHeaterBatchStatusEvent event, + Emitter emit) async { emit(WaterHeaterLoadingState()); try { - final status = await DevicesManagementApi().getBatchStatus(event.devicesUuid); - deviceStatus = WaterHeaterStatusModel.fromJson(event.devicesUuid.first, status.status); + final status = + await DevicesManagementApi().getBatchStatus(event.devicesUuid); + deviceStatus = WaterHeaterStatusModel.fromJson( + event.devicesUuid.first, status.status); emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); } catch (e) { @@ -621,7 +673,8 @@ class WaterHeaterBloc extends Bloc { } } - FutureOr _batchControlWaterHeater(ControlWaterHeaterBatchEvent event, Emitter emit) async { + FutureOr _batchControlWaterHeater(ControlWaterHeaterBatchEvent event, + Emitter emit) async { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index ff5de32c..e4cc8474 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -54,6 +54,15 @@ final class WaterHeaterFetchStatusEvent extends WaterHeaterEvent { final class DecrementCountdownEvent extends WaterHeaterEvent {} + +class StatusUpdated extends WaterHeaterEvent { + final WaterHeaterStatusModel deviceStatus; + const StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} + + final class AddScheduleEvent extends WaterHeaterEvent { final List selectedDays; final TimeOfDay time; diff --git a/lib/pages/device_managment/water_leak/bloc/water_leak_bloc.dart b/lib/pages/device_managment/water_leak/bloc/water_leak_bloc.dart index f1063dc9..6d3ca9a6 100644 --- a/lib/pages/device_managment/water_leak/bloc/water_leak_bloc.dart +++ b/lib/pages/device_managment/water_leak/bloc/water_leak_bloc.dart @@ -1,4 +1,5 @@ import 'package:bloc/bloc.dart'; +import 'package:firebase_database/firebase_database.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_event.dart'; @@ -21,6 +22,7 @@ class WaterLeakBloc extends Bloc { on(_onFetchBatchStatus); on(_onFetchWaterLeakReports); on(_onFactoryReset); + on(_onStatusUpdated); } Future _onFetchWaterLeakStatus( @@ -30,12 +32,43 @@ class WaterLeakBloc extends Bloc { final response = await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = WaterLeakStatusModel.fromJson(deviceId, response.status); + _listenToChanges(); emit(WaterLeakLoadedState(deviceStatus!)); } catch (e) { emit(WaterLeakErrorState(e.toString())); } } + _listenToChanges() { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + + List 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 emit) { + deviceStatus = event.deviceStatus; + emit(WaterLeakLoadedState(deviceStatus!)); + } + Future _onControl( WaterLeakControlEvent event, Emitter emit) async { final oldValue = deviceStatus!.watersensorState; diff --git a/lib/pages/device_managment/water_leak/bloc/water_leak_event.dart b/lib/pages/device_managment/water_leak/bloc/water_leak_event.dart index 9c048280..39864462 100644 --- a/lib/pages/device_managment/water_leak/bloc/water_leak_event.dart +++ b/lib/pages/device_managment/water_leak/bloc/water_leak_event.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/model/water_leak_status_model.dart'; abstract class WaterLeakEvent extends Equatable { const WaterLeakEvent(); @@ -17,6 +18,13 @@ class FetchWaterLeakStatusEvent extends WaterLeakEvent { List get props => [deviceId]; } +class StatusUpdated extends WaterLeakEvent { + final WaterLeakStatusModel deviceStatus; + const StatusUpdated(this.deviceStatus); + @override + List get props => [deviceStatus]; +} + class WaterLeakControlEvent extends WaterLeakEvent { final String deviceId; final String code; diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 25b8b98d..584a6e17 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -72,8 +72,6 @@ class HomeBloc extends Bloc { emit(LoadingHome()); terms = await HomeApi().fetchTerms(); emit(HomeInitial()); - - // emit(PolicyAgreement()); } catch (e) { return; } @@ -83,8 +81,6 @@ class HomeBloc extends Bloc { try { emit(LoadingHome()); policy = await HomeApi().fetchPolicy(); - debugPrint("Fetched policy: $policy"); - // Emit a state to trigger the UI update emit(HomeInitial()); } catch (e) { debugPrint("Error fetching policy: $e"); @@ -115,7 +111,7 @@ class HomeBloc extends Bloc { List homeItems = [ HomeItemModel( - title: 'Access', + title: 'Access Management', icon: Assets.accessIcon, active: true, onPress: (context) { @@ -135,7 +131,7 @@ class HomeBloc extends Bloc { color: ColorsManager.primaryColor, ), HomeItemModel( - title: 'Devices', + title: 'Devices Management', icon: Assets.devicesIcon, active: true, onPress: (context) { @@ -146,6 +142,7 @@ class HomeBloc extends Bloc { }, color: ColorsManager.primaryColor, ), + // HomeItemModel( // title: 'Move in', // icon: Assets.moveinIcon, diff --git a/lib/pages/roles_and_permission/model/edit_user_model.dart b/lib/pages/roles_and_permission/model/edit_user_model.dart index 4075ac12..81430fa3 100644 --- a/lib/pages/roles_and_permission/model/edit_user_model.dart +++ b/lib/pages/roles_and_permission/model/edit_user_model.dart @@ -219,6 +219,9 @@ class UserSpaceModel { final double x; final double y; final String icon; + final String communityUuid; + + //communityUuid UserSpaceModel({ required this.uuid, @@ -231,22 +234,23 @@ class UserSpaceModel { required this.x, required this.y, required this.icon, + required this.communityUuid, }); /// Create a [UserSpaceModel] from JSON data factory UserSpaceModel.fromJson(Map json) { return UserSpaceModel( - uuid: json['uuid'] as String, - createdAt: json['createdAt'] as String, - updatedAt: json['updatedAt'] as String, - spaceTuyaUuid: json['spaceTuyaUuid'] as String?, - spaceName: json['spaceName'] as String, - invitationCode: json['invitationCode'] as String?, - disabled: json['disabled'] as bool, - x: (json['x'] as num).toDouble(), - y: (json['y'] as num).toDouble(), - icon: json['icon'] as String, - ); + uuid: json['uuid'] as String, + createdAt: json['createdAt'] as String, + updatedAt: json['updatedAt'] as String, + spaceTuyaUuid: json['spaceTuyaUuid'] as String?, + spaceName: json['spaceName'] as String, + invitationCode: json['invitationCode'] as String?, + disabled: json['disabled'] as bool, + x: (json['x'] as num).toDouble(), + y: (json['y'] as num).toDouble(), + icon: json['icon'] as String, + communityUuid: json['communityUuid'] as String); } /// Convert the [UserSpaceModel] to JSON @@ -262,6 +266,7 @@ class UserSpaceModel { 'x': x, 'y': y, 'icon': icon, + 'communityUuid': communityUuid }; } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index 356c4e31..54187152 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -1,5 +1,6 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/custom_dialog.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart'; @@ -8,14 +9,14 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialo import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/user_permission.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:syncrow_web/utils/constants/strings_manager.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; -import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; +import 'package:syncrow_web/utils/navigation_service.dart'; class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { @@ -65,8 +66,10 @@ class UsersBloc extends Bloc { void isCompleteSpacesFun( CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities); - isCompleteSpaces = selectedIds.isNotEmpty; + var spaceBloc = + NavigationService.navigatorKey.currentContext!.read(); + + isCompleteSpaces = spaceBloc.state.selectedCommunities.isNotEmpty; emit(ChangeStatusSteps()); } @@ -340,9 +343,11 @@ class UsersBloc extends Bloc { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities) - .where((id) => !communityIds.contains(id)) - .toList(); + // List selectedIds = + // getSelectedIds(updatedCommunities).where((id) => !communityIds.contains(id)).toList(); + + List selectedSpacesId = getSelectedSpacesIds(); + // List selectedIds = getSelectedIds(updatedCommunities); bool res = await UserPermissionApi().sendInviteUser( email: emailController.text, @@ -351,7 +356,7 @@ class UsersBloc extends Bloc { lastName: lastNameController.text, phoneNumber: phoneController.text, roleUuid: roleSelected, - spaceUuids: selectedIds, + spaceUuids: selectedSpacesId, projectUuid: projectUuid); if (res) { @@ -381,12 +386,20 @@ class UsersBloc extends Bloc { } } + List getSelectedSpacesIds() { + List selectedSpacesId = []; + var spaceBloc = + NavigationService.navigatorKey.currentContext!.read(); + for (var community in spaceBloc.state.selectedCommunities) { + selectedSpacesId + .addAll(spaceBloc.state.selectedCommunityAndSpaces[community] ?? []); + } + return selectedSpacesId; + } + _editInviteUser(EditInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities) - .where((id) => !communityIds.contains(id)) - .toList(); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; bool res = await UserPermissionApi().editInviteUser( @@ -396,7 +409,7 @@ class UsersBloc extends Bloc { lastName: lastNameController.text, phoneNumber: phoneController.text, roleUuid: roleSelected, - spaceUuids: selectedIds, + spaceUuids: getSelectedSpacesIds(), projectUuid: projectUuid); if (res == true) { showCustomDialog( @@ -502,6 +515,8 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); try { + var spaceBloc = + NavigationService.navigatorKey.currentContext!.read(); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; if (event.uuid?.isNotEmpty ?? false) { @@ -516,13 +531,20 @@ class UsersBloc extends Bloc { phoneController.text = res.phoneNumber ?? ''; jobTitleController.text = res.jobTitle ?? ''; res.roleType; - if (updatedCommunities.isNotEmpty) { - // Create a list of UUIDs to mark - final uuidsToMark = res.spaces.map((space) => space.uuid).toList(); - // Print all IDs and mark nodes in updatedCommunities - debugPrint('Printing and marking nodes in updatedCommunities:'); - _printAndMarkNodes(updatedCommunities, uuidsToMark); - } + res.spaces.map((space) { + selectedIds.add(space.uuid); + CommunityModel community = spaceBloc.state.communityList + .firstWhere((item) => item.uuid == space.communityUuid); + spaceBloc.add(OnSpaceSelected(community, space.uuid, [])); + }).toList(); + + // if (updatedCommunities.isNotEmpty) { + // // Create a list of UUIDs to mark + // final uuidsToMark = res.spaces.map((space) => space.uuid).toList(); + // // Print all IDs and mark nodes in updatedCommunities + // debugPrint('Printing and marking nodes in updatedCommunities:'); + // _printAndMarkNodes(updatedCommunities, uuidsToMark); + // } final roleId = roles .firstWhere((element) => element.type == @@ -615,4 +637,16 @@ class UsersBloc extends Bloc { } return null; } + + @override + Future close() { + emailController.dispose(); + firstNameController.dispose(); + lastNameController.dispose(); + emailController.dispose(); + phoneController.dispose(); + jobTitleController.dispose(); + roleSearchController.dispose(); + return super.close(); + } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index 15981ea6..44ba81ff 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; @@ -25,7 +24,7 @@ class _AddNewUserDialogState extends State { Widget build(BuildContext context) { return BlocProvider( create: (BuildContext context) => UsersBloc() - ..add(const LoadCommunityAndSpacesEvent()) + // ..add(const LoadCommunityAndSpacesEvent()) ..add(const RoleEvent()), child: BlocConsumer( listener: (context, state) {}, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart index 2863a862..cbe15ecd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart @@ -20,7 +20,6 @@ class TreeView extends StatelessWidget { @override Widget build(BuildContext context) { final _blocRole = BlocProvider.of(context); - debugPrint('TreeView constructed with userId = $userId'); return BlocProvider( create: (_) => UsersBloc(), // ..add(const LoadCommunityAndSpacesEvent()), diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart index 22c603ab..071de067 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart @@ -26,13 +26,12 @@ class _EditUserDialogState extends State { Widget build(BuildContext context) { return BlocProvider( create: (BuildContext context) => UsersBloc() - ..add(const LoadCommunityAndSpacesEvent()) + // ..add(const LoadCommunityAndSpacesEvent()) ..add(const RoleEvent()) ..add(GetUserByIdEvent(uuid: widget.userId)), child: BlocConsumer(listener: (context, state) { if (state is SpacesLoadedState) { - BlocProvider.of(context) - .add(GetUserByIdEvent(uuid: widget.userId)); + BlocProvider.of(context).add(GetUserByIdEvent(uuid: widget.userId)); } }, builder: (context, state) { final _blocRole = BlocProvider.of(context); @@ -40,8 +39,7 @@ class _EditUserDialogState extends State { return Dialog( child: Container( decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(20))), + color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))), width: 900, child: Column( children: [ @@ -70,8 +68,7 @@ class _EditUserDialogState extends State { children: [ _buildStep1Indicator(1, "Basics", _blocRole), _buildStep2Indicator(2, "Spaces", _blocRole), - _buildStep3Indicator( - 3, "Role & Permissions", _blocRole), + _buildStep3Indicator(3, "Role & Permissions", _blocRole), ], ), ), @@ -119,15 +116,13 @@ class _EditUserDialogState extends State { if (currentStep < 3) { currentStep++; if (currentStep == 2) { - _blocRole - .add(CheckStepStatus(isEditUser: true)); + _blocRole.add(CheckStepStatus(isEditUser: true)); } else if (currentStep == 3) { _blocRole.add(const CheckSpacesStepStatus()); } } else { - _blocRole.add(EditInviteUsers( - context: context, - userId: widget.userId!)); + _blocRole + .add(EditInviteUsers(context: context, userId: widget.userId!)); } }); }, @@ -136,8 +131,7 @@ class _EditUserDialogState extends State { style: TextStyle( color: (_blocRole.isCompleteSpaces == false || _blocRole.isCompleteBasics == false || - _blocRole.isCompleteRolePermissions == - false) && + _blocRole.isCompleteRolePermissions == false) && currentStep == 3 ? ColorsManager.grayColor : ColorsManager.secondaryColor), @@ -210,12 +204,8 @@ class _EditUserDialogState extends State { label, style: TextStyle( fontSize: 16, - color: currentStep == step - ? ColorsManager.blackColor - : ColorsManager.greyColor, - fontWeight: currentStep == step - ? FontWeight.bold - : FontWeight.normal, + color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor, + fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal, ), ), ], @@ -273,12 +263,8 @@ class _EditUserDialogState extends State { label, style: TextStyle( fontSize: 16, - color: currentStep == step - ? ColorsManager.blackColor - : ColorsManager.greyColor, - fontWeight: currentStep == step - ? FontWeight.bold - : FontWeight.normal, + color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor, + fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal, ), ), ], @@ -335,12 +321,8 @@ class _EditUserDialogState extends State { label, style: TextStyle( fontSize: 16, - color: currentStep == step - ? ColorsManager.blackColor - : ColorsManager.greyColor, - fontWeight: currentStep == step - ? FontWeight.bold - : FontWeight.normal, + color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor, + fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal, ), ), ], diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index f4ccfafc..63f870e6 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -1,14 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -import 'package:syncrow_web/utils/style.dart'; class SpacesAccessView extends StatelessWidget { final String? userId; @@ -27,10 +22,8 @@ class SpacesAccessView extends StatelessWidget { children: [ Text( 'Spaces access', - style: context.textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 20, - color: Colors.black), + style: context.textTheme.bodyLarge + ?.copyWith(fontWeight: FontWeight.w700, fontSize: 20, color: Colors.black), ), const SizedBox( height: 35, @@ -42,77 +35,78 @@ class SpacesAccessView extends StatelessWidget { const SizedBox( height: 25, ), - Expanded( - child: SizedBox( - child: Column( - children: [ - Expanded( - flex: 2, - child: Container( - decoration: const BoxDecoration( - color: ColorsManager.circleRolesBackground, - borderRadius: BorderRadius.only( - topRight: Radius.circular(20), - topLeft: Radius.circular(20)), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(20)), - border: Border.all( - color: ColorsManager.grayBorder)), - child: TextFormField( - style: - const TextStyle(color: Colors.black), - // controller: _blocRole.firstNameController, - onChanged: (value) { - _blocRole.add(SearchAnode( - nodes: _blocRole.updatedCommunities, - searchTerm: value)); - }, - decoration: textBoxDecoration(radios: 20)! - .copyWith( - fillColor: Colors.white, - suffixIcon: Padding( - padding: - const EdgeInsets.only(right: 16), - child: SvgPicture.asset( - Assets.textFieldSearch, - width: 24, - height: 24, - ), - ), - hintStyle: context.textTheme.bodyMedium - ?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.textGray), - ), - ), - ), - ), - ], - ), - ), - ), - ), - Expanded( - flex: 7, - child: Container( - color: ColorsManager.circleRolesBackground, - padding: const EdgeInsets.all(8.0), - child: Container( - color: ColorsManager.whiteColors, - child: TreeView(userId: userId)))) - ], - ), - ), - ), + Expanded(child: SpaceTreeView(onSelect: () {})) + // Expanded( + // child: SizedBox( + // child: Column( + // children: [ + // Expanded( + // flex: 2, + // child: Container( + // decoration: const BoxDecoration( + // color: ColorsManager.circleRolesBackground, + // borderRadius: BorderRadius.only( + // topRight: Radius.circular(20), + // topLeft: Radius.circular(20)), + // ), + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: Row( + // children: [ + // Expanded( + // child: Container( + // decoration: BoxDecoration( + // borderRadius: const BorderRadius.all( + // Radius.circular(20)), + // border: Border.all( + // color: ColorsManager.grayBorder)), + // child: TextFormField( + // style: + // const TextStyle(color: Colors.black), + // // controller: _blocRole.firstNameController, + // onChanged: (value) { + // _blocRole.add(SearchAnode( + // nodes: _blocRole.updatedCommunities, + // searchTerm: value)); + // }, + // decoration: textBoxDecoration(radios: 20)! + // .copyWith( + // fillColor: Colors.white, + // suffixIcon: Padding( + // padding: + // const EdgeInsets.only(right: 16), + // child: SvgPicture.asset( + // Assets.textFieldSearch, + // width: 24, + // height: 24, + // ), + // ), + // hintStyle: context.textTheme.bodyMedium + // ?.copyWith( + // fontWeight: FontWeight.w400, + // fontSize: 12, + // color: ColorsManager.textGray), + // ), + // ), + // ), + // ), + // ], + // ), + // ), + // ), + // ), + // Expanded( + // flex: 7, + // child: Container( + // color: ColorsManager.circleRolesBackground, + // padding: const EdgeInsets.all(8.0), + // child: Container( + // color: ColorsManager.whiteColors, + // child: TreeView(userId: userId)))) + // ], + // ), + // ), + // ), ], ), ), diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 9794a068..3c4df20c 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -13,6 +13,8 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/vi import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/name_filter.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/user_table.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/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -24,8 +26,7 @@ class UsersPage extends StatelessWidget { Widget build(BuildContext context) { final TextEditingController searchController = TextEditingController(); - Widget actionButton( - {bool isActive = false, required String title, Function()? onTap}) { + Widget actionButton({bool isActive = false, required String title, Function()? onTap}) { return InkWell( onTap: onTap, child: Padding( @@ -58,8 +59,7 @@ class UsersPage extends StatelessWidget { : ColorsManager.disabledPink.withOpacity(0.5), ), child: Padding( - padding: - const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5), + padding: const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, @@ -83,15 +83,12 @@ class UsersPage extends StatelessWidget { } Widget changeIconStatus( - {required String userId, - required String status, - required Function()? onTap}) { + {required String userId, required String status, required Function()? onTap}) { return Center( child: InkWell( onTap: onTap, child: Padding( - padding: - const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), + padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), child: SvgPicture.asset( status == "invited" ? Assets.invitedIcon @@ -159,6 +156,7 @@ class UsersPage extends StatelessWidget { const SizedBox(width: 20), InkWell( onTap: () { + context.read().add(ClearCachedData()); showDialog( context: context, barrierDismissible: false, @@ -197,14 +195,10 @@ class UsersPage extends StatelessWidget { context: context, isSelected: _blocRole.currentSortOrder, aToZTap: () { - context - .read() - .add(const SortUsersByNameAsc()); + context.read().add(const SortUsersByNameAsc()); }, zToaTap: () { - context - .read() - .add(const SortUsersByNameDesc()); + context.read().add(const SortUsersByNameDesc()); }, ); } @@ -213,9 +207,8 @@ class UsersPage extends StatelessWidget { for (var item in _blocRole.jobTitle) item: _blocRole.selectedJobTitles.contains(item), }; - final RenderBox overlay = Overlay.of(context) - .context - .findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; showPopUpFilterMenu( position: RelativeRect.fromLTRB( @@ -255,9 +248,8 @@ class UsersPage extends StatelessWidget { for (var item in _blocRole.roleTypes) item: _blocRole.selectedRoles.contains(item), }; - final RenderBox overlay = Overlay.of(context) - .context - .findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; showPopUpFilterMenu( position: RelativeRect.fromLTRB( overlay.size.width / 4, @@ -277,10 +269,9 @@ class UsersPage extends StatelessWidget { .map((entry) => entry.key) .toList(); Navigator.of(context).pop(); - context.read().add( - FilterUsersByRoleEvent( - selectedRoles: selectedItems, - sortOrder: _blocRole.currentSortRole)); + context.read().add(FilterUsersByRoleEvent( + selectedRoles: selectedItems, + sortOrder: _blocRole.currentSortRole)); }, onSortAtoZ: (v) { _blocRole.currentSortRole = v; @@ -295,14 +286,10 @@ class UsersPage extends StatelessWidget { context: context, isSelected: _blocRole.currentSortOrder, aToZTap: () { - context - .read() - .add(const DateNewestToOldestEvent()); + context.read().add(const DateNewestToOldestEvent()); }, zToaTap: () { - context - .read() - .add(const DateOldestToNewestEvent()); + context.read().add(const DateOldestToNewestEvent()); }, ); } @@ -311,9 +298,8 @@ class UsersPage extends StatelessWidget { for (var item in _blocRole.createdBy) item: _blocRole.selectedCreatedBy.contains(item), }; - final RenderBox overlay = Overlay.of(context) - .context - .findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; showPopUpFilterMenu( position: RelativeRect.fromLTRB( overlay.size.width / 1, @@ -351,9 +337,8 @@ class UsersPage extends StatelessWidget { item: _blocRole.selectedStatuses.contains(item), }; - final RenderBox overlay = Overlay.of(context) - .context - .findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; showPopUpFilterMenu( position: RelativeRect.fromLTRB( overlay.size.width / 0, @@ -390,14 +375,10 @@ class UsersPage extends StatelessWidget { context: context, isSelected: _blocRole.currentSortOrderDate, aToZTap: () { - context - .read() - .add(const DateNewestToOldestEvent()); + context.read().add(const DateNewestToOldestEvent()); }, zToaTap: () { - context - .read() - .add(const DateOldestToNewestEvent()); + context.read().add(const DateOldestToNewestEvent()); }, ); } @@ -424,23 +405,17 @@ class UsersPage extends StatelessWidget { Text(user.createdTime ?? ''), Text(user.invitedBy), status( - status: user.isEnabled == false - ? 'disabled' - : user.status, + status: user.isEnabled == false ? 'disabled' : user.status, ), changeIconStatus( - status: user.isEnabled == false - ? 'disabled' - : user.status, + status: user.isEnabled == false ? 'disabled' : user.status, userId: user.uuid, onTap: user.status != "invited" ? () { - context.read().add( - ChangeUserStatus( - userId: user.uuid, - newStatus: user.isEnabled == false - ? 'disabled' - : user.status)); + context.read().add(ChangeUserStatus( + userId: user.uuid, + newStatus: + user.isEnabled == false ? 'disabled' : user.status)); } : null, ), @@ -451,12 +426,12 @@ class UsersPage extends StatelessWidget { isActive: true, title: "Edit", onTap: () { + context.read().add(ClearCachedData()); showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { - return EditUserDialog( - userId: user.uuid); + return EditUserDialog(userId: user.uuid); }, ).then((v) { if (v != null) { @@ -477,13 +452,10 @@ class UsersPage extends StatelessWidget { context: context, barrierDismissible: false, builder: (BuildContext context) { - return DeleteUserDialog( - onTapDelete: () async { + return DeleteUserDialog(onTapDelete: () async { try { - _blocRole.add(DeleteUserEvent( - user.uuid, context)); - await Future.delayed( - const Duration(seconds: 2)); + _blocRole.add(DeleteUserEvent(user.uuid, context)); + await Future.delayed(const Duration(seconds: 2)); return true; } catch (e) { return false; @@ -513,20 +485,14 @@ class UsersPage extends StatelessWidget { visiblePagesCount: 4, buttonRadius: 10, selectedButtonColor: ColorsManager.secondaryColor, - buttonUnSelectedBorderColor: - ColorsManager.grayBorder, - lastPageIcon: - const Icon(Icons.keyboard_double_arrow_right), - firstPageIcon: - const Icon(Icons.keyboard_double_arrow_left), - totalPages: (_blocRole.totalUsersCount.length / - _blocRole.itemsPerPage) - .ceil(), + buttonUnSelectedBorderColor: ColorsManager.grayBorder, + lastPageIcon: const Icon(Icons.keyboard_double_arrow_right), + firstPageIcon: const Icon(Icons.keyboard_double_arrow_left), + totalPages: + (_blocRole.totalUsersCount.length / _blocRole.itemsPerPage).ceil(), currentPage: _blocRole.currentPage, onPageChanged: (int pageNumber) { - context - .read() - .add(ChangePage(pageNumber)); + context.read().add(ChangePage(pageNumber)); }, ), ), diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart index cd1cadeb..4ba83cc1 100644 --- a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bl import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/users_page.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; class RolesAndPermissionPage extends StatelessWidget { @@ -27,11 +28,10 @@ class RolesAndPermissionPage extends StatelessWidget { ? const Center(child: CircularProgressIndicator()) : WebScaffold( enableMenuSidebar: false, - appBarTitle: FittedBox( - child: Text( - 'Roles & Permissions', - style: Theme.of(context).textTheme.headlineLarge, - ), + appBarTitle: Text( + 'Roles & Permissions', + style: + ResponsiveTextTheme.of(context).deviceManagementTitle, ), rightBody: const NavigateHomeGridView(), centerBody: Row( diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index ed2af95a..933b35d7 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -41,6 +41,11 @@ class DeviceDialogHelper { final deviceSelectedFunctions = routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; + if (removeComparetors) { + //remove the current temp function in the 'if container' + functions.removeAt(3); + } + switch (productType) { case 'AC': return ACHelper.showACFunctionsDialog(context, functions, data['device'], diff --git a/lib/pages/routines/models/ac/ac_function.dart b/lib/pages/routines/models/ac/ac_function.dart index 43b58394..0af1ff69 100644 --- a/lib/pages/routines/models/ac/ac_function.dart +++ b/lib/pages/routines/models/ac/ac_function.dart @@ -151,3 +151,32 @@ class ChildLockFunction extends ACFunction { ), ]; } + +class CurrentTempFunction extends ACFunction { + final int min; + final int max; + final int step; + + CurrentTempFunction({required super.deviceId, required super.deviceName}) + : min = -100, + max = 990, + step = 1, + super( + code: 'temp_current', + operationName: 'Current Temperature', + icon: Assets.currentTemp, + ); + + @override + List getOperationalValues() { + List values = []; + for (int temp = min; temp <= max; temp += step) { + values.add(ACOperationalValue( + icon: Assets.currentTemp, + description: "${temp / 10}°C", + value: temp, + )); + } + return values; + } +} diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index 0982601e..b0efb4a9 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_vie import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/snack_bar.dart'; class RoutinesView extends StatefulWidget { const RoutinesView({super.key}); diff --git a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart index 5f28e539..90c56ede 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart @@ -329,8 +329,8 @@ class ACHelper { ) { return Slider( value: initialValue is int ? initialValue.toDouble() : 200.0, - min: 200, - max: 300, + min: selectCode == 'temp_current' ? -100 : 200, + max: selectCode == 'temp_current' ? 900 : 300, divisions: 10, label: '${((initialValue ?? 160) / 10).toInt()}°C', onChanged: (value) { diff --git a/lib/pages/space_tree/bloc/space_tree_bloc.dart b/lib/pages/space_tree/bloc/space_tree_bloc.dart index c2a26164..31adaeb1 100644 --- a/lib/pages/space_tree/bloc/space_tree_bloc.dart +++ b/lib/pages/space_tree/bloc/space_tree_bloc.dart @@ -16,6 +16,33 @@ class SpaceTreeBloc extends Bloc { on(_onSearch); on(_clearAllData); on(_clearCachedData); + on(_onCommunityAdded); + on(_onCommunityUpdate); + } + + void _onCommunityUpdate( + OnCommunityUpdated event, + Emitter emit, + ) async { + emit(SpaceTreeLoadingState()); + + try { + final updatedCommunity = event.updatedCommunity; + final updatedCommunities = + List.from(state.communityList); + + final index = updatedCommunities + .indexWhere((community) => community.uuid == updatedCommunity.uuid); + + if (index != -1) { + updatedCommunities[index] = updatedCommunity; + emit(state.copyWith(communitiesList: updatedCommunities)); + } else { + emit(SpaceTreeErrorState('Community not found in the list.')); + } + } catch (e) { + emit(SpaceTreeErrorState('Error updating community: $e')); + } } _fetchSpaces(InitialEvent event, Emitter emit) async { @@ -28,8 +55,8 @@ class SpaceTreeBloc extends Bloc { List updatedCommunities = await Future.wait( communities.map((community) async { - List spaces = - await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid, projectUuid); + List spaces = await CommunitySpaceManagementApi() + .getSpaceHierarchy(community.uuid, projectUuid); return CommunityModel( uuid: community.uuid, @@ -44,15 +71,27 @@ class SpaceTreeBloc extends Bloc { ); emit(state.copyWith( - communitiesList: updatedCommunities, expandedCommunity: [], expandedSpaces: [])); + communitiesList: updatedCommunities, + expandedCommunity: [], + expandedSpaces: [])); } catch (e) { emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); } } - _onCommunityExpanded(OnCommunityExpanded event, Emitter emit) async { + void _onCommunityAdded( + OnCommunityAdded event, Emitter emit) async { + final updatedCommunities = List.from(state.communityList); + updatedCommunities.add(event.newCommunity); + + emit(state.copyWith(communitiesList: updatedCommunities)); + } + + _onCommunityExpanded( + OnCommunityExpanded event, Emitter emit) async { try { - List updatedExpandedCommunityList = List.from(state.expandedCommunities); + List updatedExpandedCommunityList = + List.from(state.expandedCommunities); if (updatedExpandedCommunityList.contains(event.communityId)) { updatedExpandedCommunityList.remove(event.communityId); @@ -84,13 +123,19 @@ class SpaceTreeBloc extends Bloc { } } - _onCommunitySelected(OnCommunitySelected event, Emitter emit) async { + _onCommunitySelected( + OnCommunitySelected event, Emitter emit) async { try { List updatedSelectedCommunities = List.from(state.selectedCommunities.toSet().toList()); - List updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); - List updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); - Map> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces); + List updatedSelectedSpaces = + List.from(state.selectedSpaces.toSet().toList()); + List updatedSoldChecks = + List.from(state.soldCheck.toSet().toList()); + Map> communityAndSpaces = + Map.from(state.selectedCommunityAndSpaces); + List selectedSpacesInCommunity = + communityAndSpaces[event.communityId] ?? []; List childrenIds = _getAllChildIds(event.children); @@ -98,14 +143,16 @@ class SpaceTreeBloc extends Bloc { // Select the community and all its children updatedSelectedCommunities.add(event.communityId); updatedSelectedSpaces.addAll(childrenIds); + selectedSpacesInCommunity.addAll(childrenIds); } else { // Unselect the community and all its children updatedSelectedCommunities.remove(event.communityId); updatedSelectedSpaces.removeWhere(childrenIds.contains); updatedSoldChecks.removeWhere(childrenIds.contains); + selectedSpacesInCommunity.removeWhere(childrenIds.contains); } - communityAndSpaces[event.communityId] = updatedSelectedSpaces; + communityAndSpaces[event.communityId] = selectedSpacesInCommunity; emit(state.copyWith( selectedCommunities: updatedSelectedCommunities, @@ -121,9 +168,15 @@ class SpaceTreeBloc extends Bloc { try { List updatedSelectedCommunities = List.from(state.selectedCommunities.toSet().toList()); - List updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); - List updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); - Map> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces); + List updatedSelectedSpaces = + List.from(state.selectedSpaces.toSet().toList()); + List updatedSoldChecks = + List.from(state.soldCheck.toSet().toList()); + Map> communityAndSpaces = + Map.from(state.selectedCommunityAndSpaces); + + List selectedSpacesInCommunity = + communityAndSpaces[event.communityModel.uuid] ?? []; List childrenIds = _getAllChildIds(event.children); bool isChildSelected = false; @@ -139,13 +192,18 @@ class SpaceTreeBloc extends Bloc { // First click: Select the space and all its children updatedSelectedSpaces.add(event.spaceId); updatedSelectedCommunities.add(event.communityModel.uuid); + selectedSpacesInCommunity.add(event.spaceId); + if (childrenIds.isNotEmpty) { updatedSelectedSpaces.addAll(childrenIds); + selectedSpacesInCommunity.addAll(childrenIds); } - List spaces = _getThePathToChild(event.communityModel.uuid, event.spaceId); + List spaces = + _getThePathToChild(event.communityModel.uuid, event.spaceId); for (String space in spaces) { - if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) { + if (!updatedSelectedSpaces.contains(space) && + !updatedSoldChecks.contains(space)) { updatedSoldChecks.add(space); } } @@ -153,19 +211,24 @@ class SpaceTreeBloc extends Bloc { childrenIds.isNotEmpty && isChildSelected) { // Second click: Unselect space but keep children + selectedSpacesInCommunity.remove(event.spaceId); updatedSelectedSpaces.remove(event.spaceId); updatedSoldChecks.add(event.spaceId); } else { // Third click: Unselect space and all its children + selectedSpacesInCommunity.remove(event.spaceId); updatedSelectedSpaces.remove(event.spaceId); if (childrenIds.isNotEmpty) { updatedSelectedSpaces.removeWhere(childrenIds.contains); updatedSoldChecks.removeWhere(childrenIds.contains); + selectedSpacesInCommunity.removeWhere(childrenIds.contains); } updatedSoldChecks.remove(event.spaceId); List parents = - _getThePathToChild(event.communityModel.uuid, event.spaceId).toSet().toList(); + _getThePathToChild(event.communityModel.uuid, event.spaceId) + .toSet() + .toList(); if (updatedSelectedSpaces.isEmpty) { updatedSoldChecks.removeWhere(parents.contains); @@ -173,7 +236,8 @@ class SpaceTreeBloc extends Bloc { } else { // Check if any parent has selected children for (String space in parents) { - if (!_noChildrenSelected(event.communityModel, space, updatedSelectedSpaces, parents)) { + if (!_noChildrenSelected( + event.communityModel, space, updatedSelectedSpaces, parents)) { updatedSoldChecks.remove(space); } } @@ -185,7 +249,7 @@ class SpaceTreeBloc extends Bloc { } } - communityAndSpaces[event.communityModel.uuid] = updatedSelectedSpaces; + communityAndSpaces[event.communityModel.uuid] = selectedSpacesInCommunity; emit(state.copyWith( selectedCommunities: updatedSelectedCommunities.toSet().toList(), @@ -198,8 +262,8 @@ class SpaceTreeBloc extends Bloc { } } - _noChildrenSelected( - CommunityModel community, String spaceId, List selectedSpaces, List parents) { + _noChildrenSelected(CommunityModel community, String spaceId, + List selectedSpaces, List parents) { if (selectedSpaces.contains(spaceId)) { return true; } @@ -226,10 +290,11 @@ class SpaceTreeBloc extends Bloc { // Filter communities and expand only those that match the query filteredCommunity = communities.where((community) { - final containsQueryInCommunity = - community.name.toLowerCase().contains(event.searchQuery.toLowerCase()); - final containsQueryInSpaces = - community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase())); + final containsQueryInCommunity = community.name + .toLowerCase() + .contains(event.searchQuery.toLowerCase()); + final containsQueryInSpaces = community.spaces.any( + (space) => _containsQuery(space, event.searchQuery.toLowerCase())); return containsQueryInCommunity || containsQueryInSpaces; }).toList(); @@ -282,8 +347,8 @@ class SpaceTreeBloc extends Bloc { // Helper function to determine if any space or its children match the search query bool _containsQuery(SpaceModel space, String query) { final matchesSpace = space.name.toLowerCase().contains(query); - final matchesChildren = - space.children.any((child) => _containsQuery(child, query)); // Recursive check for children + final matchesChildren = space.children.any((child) => + _containsQuery(child, query)); // Recursive check for children return matchesSpace || matchesChildren; } @@ -306,8 +371,8 @@ class SpaceTreeBloc extends Bloc { return children; } - bool _anySpacesSelectedInCommunity( - CommunityModel community, List selectedSpaces, List partialCheckedList) { + bool _anySpacesSelectedInCommunity(CommunityModel community, + List selectedSpaces, List partialCheckedList) { bool result = false; List ids = _getAllChildIds(community.spaces); for (var id in ids) { @@ -336,7 +401,8 @@ class SpaceTreeBloc extends Bloc { return ids; } - List _getAllParentsIds(SpaceModel child, String spaceId, List listIds) { + List _getAllParentsIds( + SpaceModel child, String spaceId, List listIds) { List ids = listIds; ids.add(child.uuid ?? ''); diff --git a/lib/pages/space_tree/bloc/space_tree_event.dart b/lib/pages/space_tree/bloc/space_tree_event.dart index e8fa996f..fdf1240b 100644 --- a/lib/pages/space_tree/bloc/space_tree_event.dart +++ b/lib/pages/space_tree/bloc/space_tree_event.dart @@ -69,6 +69,23 @@ class SearchQueryEvent extends SpaceTreeEvent { List get props => [searchQuery]; } +class OnCommunityAdded extends SpaceTreeEvent { + final CommunityModel newCommunity; + const OnCommunityAdded(this.newCommunity); + + @override + List get props => [newCommunity]; +} + +class OnCommunityUpdated extends SpaceTreeEvent { + final CommunityModel updatedCommunity; + const OnCommunityUpdated(this.updatedCommunity); + + @override + List get props => [updatedCommunity]; +} + + class ClearAllData extends SpaceTreeEvent {} class ClearCachedData extends SpaceTreeEvent {} diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index 9e40f320..515a8448 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -83,7 +83,7 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { ), if (isExpanded && children != null && children!.isNotEmpty) Padding( - padding: const EdgeInsets.only(left: 48.0), + padding: const EdgeInsets.only(left: 24.0), child: Column( children: children ?? [], ), diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 5d52a4d3..92bc858d 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; @@ -8,11 +9,14 @@ import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SpaceTreeView extends StatefulWidget { + final bool? isSide; final Function onSelect; - const SpaceTreeView({required this.onSelect, super.key}); + const SpaceTreeView({required this.onSelect, this.isSide, super.key}); @override State createState() => _SpaceTreeViewState(); @@ -33,17 +37,60 @@ class _SpaceTreeViewState extends State { List list = state.isSearching ? state.filteredCommunity : state.communityList; return Container( height: MediaQuery.sizeOf(context).height, - decoration: subSectionContainerDecoration, + color: ColorsManager.whiteColors, + decoration: widget.isSide == true ? subSectionContainerDecoration : null, child: state is SpaceTreeLoadingState ? const Center(child: CircularProgressIndicator()) : Column( children: [ - CustomSearchBar( - searchQuery: state.searchQuery, - onSearchChanged: (query) { - context.read().add(SearchQueryEvent(query)); - }, - ), + widget.isSide == true + ? Container( + decoration: const BoxDecoration( + color: ColorsManager.circleRolesBackground, + borderRadius: BorderRadius.only( + topRight: Radius.circular(20), topLeft: Radius.circular(20)), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + border: Border.all(color: ColorsManager.grayBorder)), + child: TextFormField( + style: const TextStyle(color: Colors.black), + onChanged: (value) { + context.read().add(SearchQueryEvent(value)); + }, + decoration: textBoxDecoration(radios: 20)!.copyWith( + fillColor: Colors.white, + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 16), + child: SvgPicture.asset( + Assets.textFieldSearch, + width: 24, + height: 24, + ), + ), + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + ), + ), + ), + ], + ), + ), + ) + : CustomSearchBar( + onSearchChanged: (query) { + context.read().add(SearchQueryEvent(query)); + }, + ), const SizedBox(height: 16), Expanded( child: ListView( diff --git a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart index 1930963b..ede6afb9 100644 --- a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart @@ -24,6 +24,8 @@ class AddDeviceTypeWidget extends StatelessWidget { final String spaceName; final bool isCreate; final Function(List, List?)? onSave; + final List projectTags; + const AddDeviceTypeWidget( {super.key, @@ -35,7 +37,8 @@ class AddDeviceTypeWidget extends StatelessWidget { this.allTags, this.spaceTags, this.onSave, - required this.spaceName}); + required this.spaceName, + required this.projectTags}); @override Widget build(BuildContext context) { @@ -134,7 +137,8 @@ class AddDeviceTypeWidget extends StatelessWidget { spaceName: spaceName, initialTags: initialTags, title: dialogTitle, - onSave: onSave), + onSave: onSave, + projectTags: projectTags), ); } }, diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 358bdd48..48208c87 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -1,8 +1,8 @@ -import 'dart:developer'; - +import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; @@ -17,23 +17,25 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_updat import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; -import 'package:syncrow_web/utils/constants/action_enum.dart'; -import 'package:syncrow_web/utils/constants/strings_manager.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; -import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action; -class SpaceManagementBloc - extends Bloc { +class SpaceManagementBloc extends Bloc { final CommunitySpaceManagementApi _api; final ProductApi _productApi; final SpaceModelManagementApi _spaceModelApi; List? _cachedProducts; + List? _cachedSpaceModels; + final SpaceTreeBloc _spaceTreeBloc; + List? _cachedTags; - SpaceManagementBloc(this._api, this._productApi, this._spaceModelApi) - : super(SpaceManagementInitial()) { + SpaceManagementBloc( + this._api, + this._productApi, + this._spaceModelApi, + this._spaceTreeBloc, + ) : super(SpaceManagementInitial()) { on(_onLoadCommunityAndSpaces); - on(_onUpdateSpacePosition); on(_onCreateCommunity); on(_onSelectCommunity); on(_onCommunityDelete); @@ -44,44 +46,143 @@ class SpaceManagementBloc on(_onNewCommunity); on(_onBlankState); on(_onLoadSpaceModel); + on(_updateSpaceModelCache); + on(_deleteSpaceModelFromCache); } - void _logEvent(String eventName) { - log('Event Triggered: $eventName'); + Future _updateSpaceModelCache( + UpdateSpaceModelCache event, Emitter emit) async { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + + List allSpaceModels = []; + + bool hasNext = true; + int page = 1; + + while (hasNext) { + final spaceModels = await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid); + if (spaceModels.isNotEmpty) { + allSpaceModels.addAll(spaceModels); + page++; + } else { + hasNext = false; + } + } + + _cachedSpaceModels = allSpaceModels; + await fetchTags(); + + emit(SpaceModelLoaded( + communities: + state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [], + products: _cachedProducts ?? [], + spaceModels: List.from(_cachedSpaceModels ?? []), + allTags: _cachedTags ?? [])); + } + + void _deleteSpaceModelFromCache( + DeleteSpaceModelFromCache event, Emitter emit) async { + if (_cachedSpaceModels != null) { + _cachedSpaceModels = + _cachedSpaceModels!.where((model) => model.uuid != event.deletedUuid).toList(); + } else { + _cachedSpaceModels = await fetchSpaceModels(); + } + await fetchTags(); + + emit(SpaceModelLoaded( + communities: + state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [], + products: _cachedProducts ?? [], + spaceModels: List.from(_cachedSpaceModels ?? []), + allTags: _cachedTags ?? [])); + } + + void updateCachedSpaceModels(List updatedModels) { + _cachedSpaceModels = List.from(updatedModels); + } + + void addToCachedSpaceModels(SpaceTemplateModel newModel) { + _cachedSpaceModels?.add(newModel); + } + + Future> fetchSpaceModels() async { + try { + if (_cachedSpaceModels != null) { + return _cachedSpaceModels!; + } + + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + + List allSpaceModels = []; + + bool hasNext = true; + int page = 1; + + while (hasNext) { + final spaceModels = + await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid); + if (spaceModels.isNotEmpty) { + allSpaceModels.addAll(spaceModels); + page++; + } else { + hasNext = false; + } + } + + _cachedSpaceModels = allSpaceModels; + + return allSpaceModels; + } catch (e) { + return []; + } + } + + Future> fetchTags() async { + try { + if (_cachedTags != null) { + return _cachedTags!; + } + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + final allTags = await _spaceModelApi.listTags(projectId: projectUuid); + _cachedTags = allTags; + return allTags; + } catch (e) { + return []; + } } void _onUpdateCommunity( UpdateCommunityEvent event, Emitter emit, ) async { - _logEvent('UpdateCommunityEvent'); - final previousState = state; try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + await fetchTags(); emit(SpaceManagementLoading()); - final success = await _api.updateCommunity( - event.communityUuid, event.name, projectUuid); + final success = await _api.updateCommunity(event.communityUuid, event.name, projectUuid); if (success) { if (previousState is SpaceManagementLoaded) { - final updatedCommunities = - List.from(previousState.communities); + final updatedCommunities = List.from(previousState.communities); for (var community in updatedCommunities) { if (community.uuid == event.communityUuid) { community.name = event.name; + _spaceTreeBloc.add(OnCommunityAdded(community)); + break; } } - var prevSpaceModels = await fetchSpaceModels(previousState); + var prevSpaceModels = await fetchSpaceModels(); emit(SpaceManagementLoaded( - communities: updatedCommunities, - products: previousState.products, - selectedCommunity: previousState.selectedCommunity, - spaceModels: prevSpaceModels, - )); + communities: updatedCommunities, + products: previousState.products, + selectedCommunity: previousState.selectedCommunity, + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); } } else { emit(const SpaceManagementError('Failed to update the community.')); @@ -91,46 +192,6 @@ class SpaceManagementBloc } } - Future> fetchSpaceModels( - SpaceManagementState previousState) async { - try { - final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - - List allSpaces = []; - List prevSpaceModels = []; - - if (previousState is SpaceManagementLoaded || - previousState is BlankState) { - prevSpaceModels = List.from( - (previousState as dynamic).spaceModels ?? [], - ); - allSpaces.addAll(prevSpaceModels); - } - - if (prevSpaceModels.isEmpty) { - bool hasNext = true; - int page = 1; - - while (hasNext) { - final spaces = await _spaceModelApi.listSpaceModels( - page: page, projectId: projectUuid); - if (spaces.isNotEmpty) { - allSpaces.addAll(spaces); - page++; - } else { - hasNext = false; - } - } - prevSpaceModels = await _spaceModelApi.listSpaceModels( - page: 1, projectId: projectUuid); - } - - return allSpaces; - } catch (e) { - return []; - } - } - void _onloadProducts() async { if (_cachedProducts == null) { final products = await _productApi.fetchProducts(); @@ -149,8 +210,7 @@ class SpaceManagementBloc } } - Future> _fetchSpacesForCommunity( - String communityUuid) async { + Future> _fetchSpacesForCommunity(String communityUuid) async { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; return await _api.getSpaceHierarchy(communityUuid, projectUuid); @@ -161,64 +221,72 @@ class SpaceManagementBloc Emitter emit, ) async { try { - final previousState = state; + await fetchTags(); if (event.communities.isEmpty) { emit(const SpaceManagementError('No communities provided.')); return; } - var prevSpaceModels = await fetchSpaceModels(previousState); + var prevSpaceModels = await fetchSpaceModels(); emit(BlankState( - communities: event.communities, - products: _cachedProducts ?? [], - spaceModels: prevSpaceModels, - )); + communities: event.communities, + products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); } catch (error) { emit(SpaceManagementError('Error loading communities: $error')); } } - Future _onBlankState( - BlankStateEvent event, Emitter emit) async { + Future _onBlankState(BlankStateEvent event, Emitter emit) async { try { final previousState = state; final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + var spaceBloc = event.context.read(); + List communities = await _waitForCommunityList(spaceBloc); + await fetchSpaceModels(); + await fetchTags(); - var prevSpaceModels = await fetchSpaceModels(previousState); + var prevSpaceModels = await fetchSpaceModels(); - if (previousState is SpaceManagementLoaded || - previousState is BlankState) { + if (previousState is SpaceManagementLoaded || previousState is BlankState) { final prevCommunities = (previousState as dynamic).communities ?? []; emit(BlankState( - communities: List.from(prevCommunities), - products: _cachedProducts ?? [], - spaceModels: prevSpaceModels, - )); + communities: List.from(prevCommunities), + products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); return; } - final communities = await _api.fetchCommunities(projectUuid); - final updatedCommunities = - await Future.wait(communities.map((community) async { - final spaces = await _fetchSpacesForCommunity(community.uuid); + if (communities.isEmpty) { + communities = await _api.fetchCommunities(projectUuid); - return CommunityModel( - uuid: community.uuid, - createdAt: community.createdAt, - updatedAt: community.updatedAt, - name: community.name, - description: community.description, - spaces: spaces, - region: community.region, + List updatedCommunities = await Future.wait( + communities.map((community) async { + List spaces = await _fetchSpacesForCommunity(community.uuid); + return CommunityModel( + uuid: community.uuid, + createdAt: community.createdAt, + updatedAt: community.updatedAt, + name: community.name, + description: community.description, + spaces: spaces, + region: community.region, + ); + }).toList(), ); - })); + + communities = updatedCommunities; + } emit(BlankState( spaceModels: prevSpaceModels, - communities: updatedCommunities, + communities: communities, products: _cachedProducts ?? [], + allTags: _cachedTags ?? [], )); } catch (error) { emit(SpaceManagementError('Error loading communities: $error')); @@ -229,41 +297,38 @@ class SpaceManagementBloc LoadCommunityAndSpacesEvent event, Emitter emit, ) async { - _logEvent('LoadCommunityAndSpacesEvent'); + var spaceBloc = event.context.read(); + _onloadProducts(); + await fetchTags(); + // Wait until `communityList` is loaded + List communities = await _waitForCommunityList(spaceBloc); - var prevState = state; - emit(SpaceManagementLoading()); - try { - final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + // Fetch space models after communities are available + final prevSpaceModels = await fetchSpaceModels(); + emit(SpaceManagementLoaded( + communities: communities, + products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); + } - _onloadProducts(); - List communities = - await _api.fetchCommunities(projectUuid); - - List updatedCommunities = await Future.wait( - communities.map((community) async { - List spaces = - await _fetchSpacesForCommunity(community.uuid); - return CommunityModel( - uuid: community.uuid, - createdAt: community.createdAt, - updatedAt: community.updatedAt, - name: community.name, - description: community.description, - spaces: spaces, // New spaces list - region: community.region, - ); - }).toList(), - ); - - final prevSpaceModels = await fetchSpaceModels(prevState); - emit(SpaceManagementLoaded( - communities: updatedCommunities, - products: _cachedProducts ?? [], - spaceModels: prevSpaceModels)); - } catch (e) { - emit(SpaceManagementError('Error loading communities and spaces: $e')); + Future> _waitForCommunityList(SpaceTreeBloc spaceBloc) async { + // Check if communityList is already populated + if (spaceBloc.state.communityList.isNotEmpty) { + return spaceBloc.state.communityList; } + + final completer = Completer>(); + final subscription = spaceBloc.stream.listen((state) { + if (state.communityList.isNotEmpty) { + completer.complete(state.communityList); + } + }); + + // Return the list once available, then cancel the listener + final communities = await completer.future; + await subscription.cancel(); + return communities; } void _onCommunityDelete( @@ -274,10 +339,9 @@ class SpaceManagementBloc emit(SpaceManagementLoading()); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - final success = - await _api.deleteCommunity(event.communityUuid, projectUuid); + final success = await _api.deleteCommunity(event.communityUuid, projectUuid); if (success) { - add(LoadCommunityAndSpacesEvent()); + // add(LoadCommunityAndSpacesEvent()); } else { emit(const SpaceManagementError('Failed to delete the community.')); } @@ -287,11 +351,6 @@ class SpaceManagementBloc } } - void _onUpdateSpacePosition( - UpdateSpacePositionEvent event, - Emitter emit, - ) {} - void _onCreateCommunity( CreateCommunityEvent event, Emitter emit, @@ -301,24 +360,27 @@ class SpaceManagementBloc try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - - CommunityModel? newCommunity = await _api.createCommunity( - event.name, event.description, projectUuid); - var prevSpaceModels = await fetchSpaceModels(previousState); + await fetchTags(); + CommunityModel? newCommunity = + await _api.createCommunity(event.name, event.description, projectUuid); + var prevSpaceModels = await fetchSpaceModels(); if (newCommunity != null) { - if (previousState is SpaceManagementLoaded || - previousState is BlankState) { + if (previousState is SpaceManagementLoaded || previousState is BlankState) { final prevCommunities = List.from( (previousState as dynamic).communities, ); final updatedCommunities = prevCommunities..add(newCommunity); + _spaceTreeBloc.add(OnCommunityAdded(newCommunity)); + emit(SpaceManagementLoaded( - spaceModels: prevSpaceModels, - communities: updatedCommunities, - products: _cachedProducts ?? [], - selectedCommunity: newCommunity, - selectedSpace: null)); + spaceModels: prevSpaceModels, + communities: updatedCommunities, + products: _cachedProducts ?? [], + selectedCommunity: newCommunity, + selectedSpace: null, + allTags: _cachedTags ?? [], + )); } } else { emit(const SpaceManagementError('Error creating community')); @@ -358,11 +420,12 @@ class SpaceManagementBloc required Emitter emit, CommunityModel? selectedCommunity, SpaceModel? selectedSpace, - }) { + }) async { final previousState = state; emit(SpaceManagementLoading()); try { + await fetchTags(); if (previousState is SpaceManagementLoaded || previousState is BlankState || previousState is SpaceModelLoaded) { @@ -378,7 +441,8 @@ class SpaceManagementBloc products: _cachedProducts ?? [], selectedCommunity: selectedCommunity, selectedSpace: selectedSpace, - spaceModels: spaceModels)); + spaceModels: spaceModels, + allTags: _cachedTags ?? [])); } } catch (e) { emit(SpaceManagementError('Error updating state: $e')); @@ -393,8 +457,7 @@ class SpaceManagementBloc emit(SpaceManagementLoading()); try { - final updatedSpaces = - await saveSpacesHierarchically(event.spaces, event.communityUuid); + final updatedSpaces = await saveSpacesHierarchically(event.spaces, event.communityUuid); final allSpaces = await _fetchSpacesForCommunity(event.communityUuid); @@ -407,8 +470,6 @@ class SpaceManagementBloc event.communityUuid, emit, ); - } else { - add(LoadCommunityAndSpacesEvent()); } } catch (e) { emit(SpaceManagementError('Error saving spaces: $e')); @@ -425,19 +486,22 @@ class SpaceManagementBloc String communityUuid, Emitter emit, ) async { - var prevSpaceModels = await fetchSpaceModels(previousState); - + var prevSpaceModels = await fetchSpaceModels(); + await fetchTags(); final communities = List.from(previousState.communities); for (var community in communities) { if (community.uuid == communityUuid) { community.spaces = allSpaces; + _spaceTreeBloc.add(InitialEvent()); + emit(SpaceManagementLoaded( communities: communities, products: _cachedProducts ?? [], selectedCommunity: community, selectedSpace: null, - spaceModels: prevSpaceModels)); + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); return; } } @@ -468,8 +532,7 @@ class SpaceManagementBloc if (space.uuid != null && space.uuid!.isNotEmpty) { List tagUpdates = []; - final prevSpace = - await _api.getSpace(communityUuid, space.uuid!, projectUuid); + final prevSpace = await _api.getSpace(communityUuid, space.uuid!, projectUuid); final List subspaceUpdates = []; final List? prevSubspaces = prevSpace?.subspaces; final List? newSubspaces = space.subspaces; @@ -479,17 +542,17 @@ class SpaceManagementBloc if (prevSubspaces != null || newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) { for (var prevSubspace in prevSubspaces) { - final existsInNew = newSubspaces - .any((subspace) => subspace.uuid == prevSubspace.uuid); + final existsInNew = + newSubspaces.any((subspace) => subspace.uuid == prevSubspace.uuid); if (!existsInNew) { subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.delete, uuid: prevSubspace.uuid)); + action: custom_action.Action.delete, uuid: prevSubspace.uuid)); } } } else if (prevSubspaces != null && newSubspaces == null) { for (var prevSubspace in prevSubspaces) { subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.delete, uuid: prevSubspace.uuid)); + action: custom_action.Action.delete, uuid: prevSubspace.uuid)); } } @@ -502,14 +565,14 @@ class SpaceManagementBloc if (newSubspace.tags != null) { for (var tag in newSubspace.tags!) { tagUpdates.add(TagModelUpdate( - action: Action.add, - uuid: tag.uuid == '' ? null : tag.uuid, + action: custom_action.Action.add, + newTagUuid: tag.uuid == '' ? null : tag.uuid, tag: tag.tag, productUuid: tag.product?.uuid)); } } subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.add, + action: custom_action.Action.add, subspaceName: newSubspace.subspaceName, tags: tagUpdates)); } @@ -517,9 +580,7 @@ class SpaceManagementBloc } if (prevSubspaces != null && newSubspaces != null) { - final newSubspaceMap = { - for (var subspace in newSubspaces) subspace.uuid: subspace - }; + final newSubspaceMap = {for (var subspace in newSubspaces) subspace.uuid: subspace}; for (var prevSubspace in prevSubspaces) { final newSubspace = newSubspaceMap[prevSubspace.uuid]; @@ -528,7 +589,7 @@ class SpaceManagementBloc final List tagSubspaceUpdates = processTagUpdates(prevSubspace.tags, newSubspace.tags); subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.update, + action: custom_action.Action.update, uuid: newSubspace.uuid, subspaceName: newSubspace.subspaceName, tags: tagSubspaceUpdates)); @@ -548,6 +609,7 @@ class SpaceManagementBloc subspaces: subspaceUpdates, tags: tagUpdates, direction: space.incomingConnection?.direction, + spaceModelUuid: space.spaceModel?.uuid, projectId: projectUuid); } else { // Call create if the space does not have a UUID @@ -556,10 +618,8 @@ class SpaceManagementBloc : []; final createSubspaceBodyModels = space.subspaces?.map((subspace) { - final tagBodyModels = subspace.tags - ?.map((tag) => tag.toCreateTagBodyModel()) - .toList() ?? - []; + final tagBodyModels = + subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? []; return CreateSubspaceModel() ..subspaceName = subspace.subspaceName ..tags = tagBodyModels; @@ -612,38 +672,43 @@ class SpaceManagementBloc return result.toList(); // Convert back to a list } - void _onLoadSpaceModel( - SpaceModelLoadEvent event, Emitter emit) async { + void _onLoadSpaceModel(SpaceModelLoadEvent event, Emitter emit) async { emit(SpaceManagementLoading()); + try { - var prevState = state; + await fetchTags(); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + var spaceBloc = event.context.read(); + List communities = spaceBloc.state.communityList; - List communities = - await _api.fetchCommunities(projectUuid); + var prevSpaceModels = await fetchSpaceModels(); - List updatedCommunities = await Future.wait( - communities.map((community) async { - List spaces = - await _fetchSpacesForCommunity(community.uuid); - return CommunityModel( - uuid: community.uuid, - createdAt: community.createdAt, - updatedAt: community.updatedAt, - name: community.name, - description: community.description, - spaces: spaces, // New spaces list - region: community.region, - ); - }).toList(), - ); + if (communities.isEmpty) { + communities = await _api.fetchCommunities(projectUuid); - var prevSpaceModels = await fetchSpaceModels(prevState); + List updatedCommunities = await Future.wait( + communities.map((community) async { + List spaces = await _fetchSpacesForCommunity(community.uuid); + return CommunityModel( + uuid: community.uuid, + createdAt: community.createdAt, + updatedAt: community.updatedAt, + name: community.name, + description: community.description, + spaces: spaces, + region: community.region, + ); + }).toList(), + ); + + communities = updatedCommunities; + } emit(SpaceModelLoaded( - communities: updatedCommunities, + communities: communities, products: _cachedProducts ?? [], - spaceModels: prevSpaceModels)); + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); } catch (e) { emit(SpaceManagementError('Error loading communities and spaces: $e')); } @@ -659,9 +724,9 @@ class SpaceManagementBloc if (prevTags == null && newTags != null) { for (var newTag in newTags) { tagUpdates.add(TagModelUpdate( - action: Action.add, + action: custom_action.Action.add, tag: newTag.tag, - uuid: newTag.uuid, + newTagUuid: newTag.uuid, productUuid: newTag.product?.uuid, )); } @@ -672,17 +737,14 @@ class SpaceManagementBloc // Case 1: Tags deleted if (prevTags != null && newTags != null) { for (var prevTag in prevTags) { - final existsInNew = - newTags.any((newTag) => newTag.uuid == prevTag.uuid); + final existsInNew = newTags.any((newTag) => newTag.uuid == prevTag.uuid); if (!existsInNew) { - tagUpdates - .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); + tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid)); } } } else if (prevTags != null && newTags == null) { for (var prevTag in prevTags) { - tagUpdates - .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); + tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid)); } } @@ -695,9 +757,9 @@ class SpaceManagementBloc if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) && !processedTags.contains(newTag.tag)) { tagUpdates.add(TagModelUpdate( - action: Action.add, + action: custom_action.Action.add, tag: newTag.tag, - uuid: newTag.uuid == '' ? null : newTag.uuid, + newTagUuid: newTag.uuid == '' ? null : newTag.uuid, productUuid: newTag.product?.uuid)); processedTags.add(newTag.tag); } @@ -712,8 +774,9 @@ class SpaceManagementBloc final newTag = newTagMap[prevTag.uuid]; if (newTag != null) { tagUpdates.add(TagModelUpdate( - action: Action.update, + action: custom_action.Action.update, uuid: newTag.uuid, + newTagUuid: newTag.uuid, tag: newTag.tag, )); } else {} diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart index d25534b4..6bd685ba 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart @@ -1,16 +1,23 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; // Import for Offset +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; // Import for Offset abstract class SpaceManagementEvent extends Equatable { const SpaceManagementEvent(); @override - List get props => []; + List get props => []; } -class LoadCommunityAndSpacesEvent extends SpaceManagementEvent {} +class LoadCommunityAndSpacesEvent extends SpaceManagementEvent { + final BuildContext context; + + const LoadCommunityAndSpacesEvent(this.context); + @override + List get props => [context]; +} class DeleteCommunityEvent extends SpaceManagementEvent { final String communityUuid; @@ -74,14 +81,12 @@ class UpdateSpacePositionEvent extends SpaceManagementEvent { class CreateCommunityEvent extends SpaceManagementEvent { final String name; final String description; + final BuildContext context; - const CreateCommunityEvent({ - required this.name, - required this.description, - }); + const CreateCommunityEvent(this.name, this.description, this.context); @override - List get props => [name, description]; + List get props => [name, description, context]; } class UpdateCommunityEvent extends SpaceManagementEvent { @@ -141,7 +146,28 @@ class LoadSpaceHierarchyEvent extends SpaceManagementEvent { List get props => [communityId]; } +class BlankStateEvent extends SpaceManagementEvent { + final BuildContext context; -class BlankStateEvent extends SpaceManagementEvent {} + const BlankStateEvent(this.context); + @override + List get props => [context]; +} -class SpaceModelLoadEvent extends SpaceManagementEvent {} +class SpaceModelLoadEvent extends SpaceManagementEvent { + final BuildContext context; + + const SpaceModelLoadEvent(this.context); + @override + List get props => [context]; +} + +class UpdateSpaceModelCache extends SpaceManagementEvent { + final SpaceTemplateModel updatedModel; + UpdateSpaceModelCache(this.updatedModel); +} + +class DeleteSpaceModelFromCache extends SpaceManagementEvent { + final String deletedUuid; + DeleteSpaceModelFromCache(this.deletedUuid); +} \ No newline at end of file diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart index 571651e5..3efa7c00 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; abstract class SpaceManagementState extends Equatable { @@ -21,26 +22,25 @@ class SpaceManagementLoaded extends SpaceManagementState { CommunityModel? selectedCommunity; SpaceModel? selectedSpace; List? spaceModels; + List allTags; SpaceManagementLoaded( {required this.communities, required this.products, this.selectedCommunity, this.selectedSpace, - this.spaceModels}); -} - -class SpaceModelManagenetLoaded extends SpaceManagementState { - SpaceModelManagenetLoaded(); + this.spaceModels, + required this.allTags}); } class BlankState extends SpaceManagementState { final List communities; final List products; List? spaceModels; + final List allTags; BlankState( - {required this.communities, required this.products, this.spaceModels}); + {required this.communities, required this.products, this.spaceModels, required this.allTags}); } class SpaceCreationSuccess extends SpaceManagementState { @@ -65,13 +65,14 @@ class SpaceModelLoaded extends SpaceManagementState { List spaceModels; final List products; final List communities; + final List allTags; - SpaceModelLoaded({ - required this.communities, - required this.products, - required this.spaceModels, - }); + SpaceModelLoaded( + {required this.communities, + required this.products, + required this.spaceModels, + required this.allTags}); @override - List get props => [communities, products, spaceModels]; + List get props => [communities, products, spaceModels, allTags]; } diff --git a/lib/pages/spaces_management/all_spaces/model/tag.dart b/lib/pages/spaces_management/all_spaces/model/tag.dart index 34bd08bb..8959986c 100644 --- a/lib/pages/spaces_management/all_spaces/model/tag.dart +++ b/lib/pages/spaces_management/all_spaces/model/tag.dart @@ -25,22 +25,21 @@ class Tag extends BaseTag { return Tag( uuid: json['uuid'] ?? '', internalId: internalId, - tag: json['tag'] ?? '', - product: json['product'] != null - ? ProductModel.fromMap(json['product']) - : null, + tag: json['name'] ?? '', + product: json['product'] != null ? ProductModel.fromMap(json['product']) : null, ); } @override Tag copyWith({ + String? uuid, String? tag, ProductModel? product, String? location, String? internalId, }) { return Tag( - uuid: uuid, + uuid: uuid ?? this.uuid, tag: tag ?? this.tag, product: product ?? this.product, location: location ?? this.location, @@ -60,7 +59,7 @@ class Tag extends BaseTag { extension TagModelExtensions on Tag { TagBodyModel toTagBodyModel() { return TagBodyModel() - ..uuid = uuid ?? '' + ..uuid = uuid ..tag = tag ?? '' ..productUuid = product?.uuid; } diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 02000e1a..00b2cf44 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; @@ -10,6 +11,7 @@ import 'package:syncrow_web/pages/spaces_management/structure_selector/view/cent import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; +import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; class SpaceManagementPage extends StatefulWidget { @@ -28,19 +30,22 @@ class SpaceManagementPageState extends State { return MultiBlocProvider( providers: [ BlocProvider( - create: (_) => SpaceManagementBloc( + create: (context) => SpaceManagementBloc( _api, _productApi, _spaceModelApi, - )..add(LoadCommunityAndSpacesEvent()), + BlocProvider.of(context), + )..add(LoadCommunityAndSpacesEvent(this.context)), ), BlocProvider( - create: (_) => CenterBodyBloc(), + create: (context) => CenterBodyBloc(), ), ], child: WebScaffold( - appBarTitle: Text('Space Management', - style: Theme.of(context).textTheme.headlineLarge), + appBarTitle: Text( + 'Space Management', + style: ResponsiveTextTheme.of(context).deviceManagementTitle, + ), enableMenuSidebar: false, centerBody: CenterBodyWidget(), rightBody: const NavigateHomeGridView(), @@ -55,6 +60,7 @@ class SpaceManagementPageState extends State { selectedSpace: null, products: state.products, shouldNavigateToSpaceModelPage: false, + projectTags: state.allTags, ); } else if (state is SpaceManagementLoaded) { return LoadedSpaceView( @@ -64,6 +70,7 @@ class SpaceManagementPageState extends State { products: state.products, spaceModels: state.spaceModels, shouldNavigateToSpaceModelPage: false, + projectTags: state.allTags, ); } else if (state is SpaceModelLoaded) { return LoadedSpaceView( @@ -71,6 +78,7 @@ class SpaceManagementPageState extends State { products: state.products, spaceModels: state.spaceModels, shouldNavigateToSpaceModelPage: true, + projectTags: state.allTags, ); } else if (state is SpaceManagementError) { return Center(child: Text('Error: ${state.errorMessage}')); diff --git a/lib/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart index 999c27bd..66f1a026 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart @@ -73,17 +73,14 @@ class _BlankCommunityWidgetState extends State { context: parentContext, builder: (context) => CreateCommunityDialog( isEditMode: false, - existingCommunityNames: widget.communities.map((community) => community.name).toList(), + existingCommunityNames: + widget.communities.map((community) => community.name).toList(), onCreateCommunity: (String communityName, String description) { parentContext.read().add( - CreateCommunityEvent( - name: communityName, - description: description, - ), + CreateCommunityEvent(communityName, description, context), ); }, ), ); } - } diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index f82bce56..12d76452 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -36,16 +36,17 @@ class CommunityStructureArea extends StatefulWidget { final List communities; final List spaces; final List? spaceModels; + final List projectTags; - CommunityStructureArea({ - this.selectedCommunity, - this.selectedSpace, - required this.communities, - this.products, - required this.spaces, - this.onSpaceSelected, - this.spaceModels, - }); + CommunityStructureArea( + {this.selectedCommunity, + this.selectedSpace, + required this.communities, + this.products, + required this.spaces, + this.onSpaceSelected, + this.spaceModels, + required this.projectTags}); @override _CommunityStructureAreaState createState() => _CommunityStructureAreaState(); @@ -64,8 +65,7 @@ class _CommunityStructureAreaState extends State { void initState() { super.initState(); spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; - connections = - widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; + connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; _adjustCanvasSizeForSpaces(); _nameController = TextEditingController( text: widget.selectedCommunity?.name ?? '', @@ -92,14 +92,12 @@ class _CommunityStructureAreaState extends State { if (oldWidget.spaces != widget.spaces) { setState(() { spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; - connections = - widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; + connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; _adjustCanvasSizeForSpaces(); }); } - if (widget.selectedSpace != oldWidget.selectedSpace && - widget.selectedSpace != null) { + if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) { WidgetsBinding.instance.addPostFrameCallback((_) { _moveToSpace(widget.selectedSpace!); }); @@ -182,8 +180,7 @@ class _CommunityStructureAreaState extends State { connection, widget.selectedSpace) ? 1.0 : 0.3, // Adjust opacity - child: CustomPaint( - painter: CurvedLinePainter([connection])), + child: CustomPaint(painter: CurvedLinePainter([connection])), ), for (var entry in spaces.asMap().entries) if (entry.value.status != SpaceStatus.deleted && @@ -193,15 +190,12 @@ class _CommunityStructureAreaState extends State { top: entry.value.position.dy, child: SpaceCardWidget( index: entry.key, - onButtonTap: (int index, Offset newPosition, - String direction) { - _showCreateSpaceDialog( - screenSize, - position: - spaces[index].position + newPosition, - parentIndex: index, - direction: direction, - ); + onButtonTap: (int index, Offset newPosition, String direction) { + _showCreateSpaceDialog(screenSize, + position: spaces[index].position + newPosition, + parentIndex: index, + direction: direction, + projectTags: widget.projectTags); }, position: entry.value.position, isHovered: entry.value.isHovered, @@ -211,9 +205,8 @@ class _CommunityStructureAreaState extends State { _updateNodePosition(entry.value, newPosition); }, buildSpaceContainer: (int index) { - final bool isHighlighted = - SpaceHelper.isHighlightedSpace( - spaces[index], widget.selectedSpace); + final bool isHighlighted = SpaceHelper.isHighlightedSpace( + spaces[index], widget.selectedSpace); return Opacity( opacity: isHighlighted ? 1.0 : 0.3, @@ -238,7 +231,8 @@ class _CommunityStructureAreaState extends State { onTap: () { _showCreateSpaceDialog(screenSize, canvasHeight: canvasHeight, - canvasWidth: canvasWidth); + canvasWidth: canvasWidth, + projectTags: widget.projectTags); }, ), ), @@ -292,26 +286,22 @@ class _CommunityStructureAreaState extends State { int? parentIndex, String? direction, double? canvasWidth, - double? canvasHeight}) { + double? canvasHeight, + required List projectTags}) { showDialog( context: context, builder: (BuildContext context) { return CreateSpaceDialog( products: widget.products, spaceModels: widget.spaceModels, - allTags: - TagHelper.getAllTagValues(widget.communities, widget.spaceModels), + allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels), parentSpace: parentIndex != null ? spaces[parentIndex] : null, - onCreateSpace: (String name, - String icon, - List selectedProducts, - SpaceTemplateModel? spaceModel, - List? subspaces, - List? tags) { + projectTags: projectTags, + onCreateSpace: (String name, String icon, List selectedProducts, + SpaceTemplateModel? spaceModel, List? subspaces, List? tags) { setState(() { // Set the first space in the center or use passed position - Offset centerPosition = - position ?? ConnectionHelper.getCenterPosition(screenSize); + Offset centerPosition = position ?? ConnectionHelper.getCenterPosition(screenSize); SpaceModel newSpace = SpaceModel( name: name, icon: icon, @@ -356,21 +346,17 @@ class _CommunityStructureAreaState extends State { spaceModels: widget.spaceModels, name: widget.selectedSpace!.name, icon: widget.selectedSpace!.icon, - parentSpace: SpaceHelper.findSpaceByInternalId( - widget.selectedSpace?.parent?.internalId, spaces), + projectTags: widget.projectTags, + parentSpace: + SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces), editSpace: widget.selectedSpace, currentSpaceModel: widget.selectedSpace?.spaceModel, tags: widget.selectedSpace?.tags, subspaces: widget.selectedSpace?.subspaces, isEdit: true, - allTags: TagHelper.getAllTagValues( - widget.communities, widget.spaceModels), - onCreateSpace: (String name, - String icon, - List selectedProducts, - SpaceTemplateModel? spaceModel, - List? subspaces, - List? tags) { + allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels), + onCreateSpace: (String name, String icon, List selectedProducts, + SpaceTemplateModel? spaceModel, List? subspaces, List? tags) { setState(() { // Update the space's properties widget.selectedSpace!.name = name; @@ -380,8 +366,7 @@ class _CommunityStructureAreaState extends State { widget.selectedSpace!.tags = tags; if (widget.selectedSpace!.status != SpaceStatus.newSpace) { - widget.selectedSpace!.status = - SpaceStatus.modified; // Mark as modified + widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified } for (var space in spaces) { @@ -411,8 +396,7 @@ class _CommunityStructureAreaState extends State { List result = []; void flatten(SpaceModel space) { - if (space.status == SpaceStatus.deleted || - space.status == SpaceStatus.parentDeleted) { + if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) { return; } result.add(space); @@ -527,16 +511,13 @@ class _CommunityStructureAreaState extends State { void _selectSpace(BuildContext context, SpaceModel space) { context.read().add( - SelectSpaceEvent( - selectedCommunity: widget.selectedCommunity, - selectedSpace: space), + SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space), ); } void _deselectSpace(BuildContext context) { context.read().add( - SelectSpaceEvent( - selectedCommunity: widget.selectedCommunity, selectedSpace: null), + SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null), ); } @@ -625,19 +606,16 @@ class _CommunityStructureAreaState extends State { const double horizontalGap = 200.0; const double verticalGap = 100.0; - SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, - SpaceModel? duplicatedParent) { - Offset newPosition = - Offset(parentPosition.dx + horizontalGap, original.position.dy); + SpaceModel duplicateRecursive( + SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) { + Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy); while (spaces.any((s) => - (s.position - newPosition).distance < horizontalGap && - s.status != SpaceStatus.deleted)) { + (s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) { newPosition += Offset(horizontalGap, 0); } - final duplicatedName = - SpaceHelper.generateUniqueSpaceName(original.name, spaces); + final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); final List? duplicatedSubspaces; final List? duplicatedTags; @@ -681,8 +659,7 @@ class _CommunityStructureAreaState extends State { if (original.parent != null && duplicatedParent == null) { final originalParent = original.parent!; - final duplicatedParent = - originalToDuplicate[originalParent] ?? originalParent; + final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent; final parentConnection = Connection( startSpace: duplicatedParent, @@ -698,8 +675,7 @@ class _CommunityStructureAreaState extends State { final childrenWithDownDirection = original.children .where((child) => - child.incomingConnection?.direction == "down" && - child.status != SpaceStatus.deleted) + child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted) .toList(); Offset childStartPosition = childrenWithDownDirection.length == 1 @@ -707,8 +683,7 @@ class _CommunityStructureAreaState extends State { : newPosition + Offset(0, verticalGap); for (final child in original.children) { - final isDownDirection = - child.incomingConnection?.direction == "down" ?? false; + final isDownDirection = child.incomingConnection?.direction == "down" ?? false; if (isDownDirection && childrenWithDownDirection.length == 1) { childStartPosition = duplicated.position + Offset(0, verticalGap); @@ -716,8 +691,7 @@ class _CommunityStructureAreaState extends State { childStartPosition = duplicated.position + Offset(horizontalGap, 0); } - final duplicatedChild = - duplicateRecursive(child, childStartPosition, duplicated); + final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated); duplicated.children.add(duplicatedChild); childStartPosition += Offset(0, verticalGap); } @@ -728,8 +702,7 @@ class _CommunityStructureAreaState extends State { if (space.parent == null) { duplicateRecursive(space, space.position, null); } else { - final duplicatedParent = - originalToDuplicate[space.parent!] ?? space.parent!; + final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!; duplicateRecursive(space, space.position, duplicatedParent); } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_tile.dart b/lib/pages/spaces_management/all_spaces/widgets/community_tile.dart index 69a723c0..b097b0c3 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_tile.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_tile.dart @@ -21,15 +21,17 @@ class CommunityTile extends StatelessWidget { @override Widget build(BuildContext context) { - return CustomExpansionTile( - title: title, - initiallyExpanded: isExpanded, - isSelected: isSelected, - onExpansionChanged: (bool expanded) { - onExpansionChanged(title, expanded); - }, - onItemSelected: onItemSelected, - children: children ?? [], - ); + return Padding( + padding: const EdgeInsets.all(8.0), + child: CustomExpansionTile( + title: title, + initiallyExpanded: isExpanded, + isSelected: isSelected, + onExpansionChanged: (bool expanded) { + onExpansionChanged(title, expanded); + }, + onItemSelected: onItemSelected, + children: children ?? [], + )); } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index f3df1637..56e1212e 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -42,6 +42,7 @@ class CreateSpaceDialog extends StatefulWidget { final List? tags; final List? allTags; final SpaceTemplateModel? currentSpaceModel; + final List projectTags; const CreateSpaceDialog( {super.key, @@ -57,7 +58,8 @@ class CreateSpaceDialog extends StatefulWidget { this.spaceModels, this.subspaces, this.tags, - this.currentSpaceModel}); + this.currentSpaceModel, + required this.projectTags}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -80,10 +82,8 @@ class CreateSpaceDialogState extends State { super.initState(); selectedIcon = widget.icon ?? Assets.location; nameController = TextEditingController(text: widget.name ?? ''); - selectedProducts = - widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; - isOkButtonEnabled = - enteredName.isNotEmpty || nameController.text.isNotEmpty; + selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; + isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty; if (widget.currentSpaceModel != null) { subspaces = []; tags = []; @@ -96,15 +96,13 @@ class CreateSpaceDialogState extends State { @override Widget build(BuildContext context) { - bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty || - subspaces != null && subspaces!.isNotEmpty); + bool isSpaceModelDisabled = + (tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty); bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null); final screenWidth = MediaQuery.of(context).size.width; return AlertDialog( - title: widget.isEdit - ? const Text('Edit Space') - : const Text('Create New Space'), + title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'), backgroundColor: ColorsManager.whiteColors, content: SizedBox( width: screenWidth * 0.5, @@ -178,7 +176,8 @@ class CreateSpaceDialogState extends State { isNameFieldInvalid = value.isEmpty; if (!isNameFieldInvalid) { - if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) { + if (SpaceHelper.isNameConflict( + value, widget.parentSpace, widget.editSpace)) { isNameFieldExist = true; isOkButtonEnabled = false; } else { @@ -245,9 +244,7 @@ class CreateSpaceDialogState extends State { padding: EdgeInsets.zero, ), onPressed: () { - isSpaceModelDisabled - ? null - : _showLinkSpaceModelDialog(context); + isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context); }, child: ButtonContentWidget( svgAssets: Assets.link, @@ -257,8 +254,7 @@ class CreateSpaceDialogState extends State { ) : Container( width: screenWidth * 0.25, - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), decoration: BoxDecoration( color: ColorsManager.boxColor, borderRadius: BorderRadius.circular(10), @@ -273,8 +269,7 @@ class CreateSpaceDialogState extends State { style: Theme.of(context) .textTheme .bodyMedium! - .copyWith( - color: ColorsManager.spaceColor), + .copyWith(color: ColorsManager.spaceColor), ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( @@ -302,6 +297,8 @@ class CreateSpaceDialogState extends State { ), onDeleted: () => setState(() { this.selectedSpaceModel = null; + subspaces = widget.subspaces ?? []; + tags = widget.tags ?? []; })), ], ), @@ -343,8 +340,8 @@ class CreateSpaceDialogState extends State { onPressed: () async { isTagsAndSubspaceModelDisabled ? null - : _showSubSpaceDialog(context, enteredName, - [], false, widget.products, subspaces); + : _showSubSpaceDialog( + context, enteredName, [], false, widget.products, subspaces); }, child: ButtonContentWidget( icon: Icons.add, @@ -371,22 +368,16 @@ class CreateSpaceDialogState extends State { if (subspaces != null) ...subspaces!.map((subspace) { return Column( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ SubspaceNameDisplayWidget( text: subspace.subspaceName, validateName: (updatedName) { - bool nameExists = - subspaces!.any((s) { - bool isSameId = s.internalId == - subspace.internalId; - bool isSameName = s.subspaceName - .trim() - .toLowerCase() == - updatedName - .trim() - .toLowerCase(); + bool nameExists = subspaces!.any((s) { + bool isSameId = s.internalId == subspace.internalId; + bool isSameName = + s.subspaceName.trim().toLowerCase() == + updatedName.trim().toLowerCase(); return !isSameId && isSameName; }); @@ -395,8 +386,7 @@ class CreateSpaceDialogState extends State { }, onNameChanged: (updatedName) { setState(() { - subspace.subspaceName = - updatedName; + subspace.subspaceName = updatedName; }); }, ), @@ -405,8 +395,8 @@ class CreateSpaceDialogState extends State { }), EditChip( onTap: () async { - _showSubSpaceDialog(context, enteredName, - [], true, widget.products, subspaces); + _showSubSpaceDialog(context, enteredName, [], true, + widget.products, subspaces); }, ) ], @@ -415,9 +405,7 @@ class CreateSpaceDialogState extends State { ), const SizedBox(height: 10), (tags?.isNotEmpty == true || - subspaces?.any((subspace) => - subspace.tags?.isNotEmpty == true) == - true) + subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) ? SizedBox( width: screenWidth * 0.25, child: Container( @@ -437,16 +425,14 @@ class CreateSpaceDialogState extends State { // Combine tags from spaceModel and subspaces ...TagHelper.groupTags([ ...?tags, - ...?subspaces?.expand( - (subspace) => subspace.tags ?? []) + ...?subspaces?.expand((subspace) => subspace.tags ?? []) ]).entries.map( (entry) => Chip( avatar: SizedBox( width: 24, height: 24, child: SvgPicture.asset( - entry.key.icon ?? - 'assets/icons/gateway.svg', + entry.key.icon ?? 'assets/icons/gateway.svg', fit: BoxFit.contain, ), ), @@ -455,15 +441,11 @@ class CreateSpaceDialogState extends State { style: Theme.of(context) .textTheme .bodySmall - ?.copyWith( - color: ColorsManager - .spaceColor), + ?.copyWith(color: ColorsManager.spaceColor), ), - backgroundColor: - ColorsManager.whiteColors, + backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), + borderRadius: BorderRadius.circular(16), side: const BorderSide( color: ColorsManager.spaceColor, ), @@ -472,23 +454,21 @@ class CreateSpaceDialogState extends State { ), EditChip(onTap: () async { - final result = await showDialog( + await showDialog( context: context, builder: (context) => AssignTagDialog( products: widget.products, subspaces: subspaces, allTags: widget.allTags, - addedProducts: TagHelper - .createInitialSelectedProductsForTags( + addedProducts: + TagHelper.createInitialSelectedProductsForTags( tags ?? [], subspaces), title: 'Edit Device', - initialTags: - TagHelper.generateInitialForTags( - spaceTags: tags, - subspaces: subspaces), + initialTags: TagHelper.generateInitialForTags( + spaceTags: tags, subspaces: subspaces), spaceName: widget.name ?? '', - onSave: - (updatedTags, updatedSubspaces) { + projectTags: widget.projectTags, + onSave: (updatedTags, updatedSubspaces) { setState(() { tags = updatedTags; subspaces = updatedSubspaces; @@ -547,25 +527,17 @@ class CreateSpaceDialogState extends State { }); return; } else { - String newName = enteredName.isNotEmpty - ? enteredName - : (widget.name ?? ''); + String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? ''); if (newName.isNotEmpty) { - widget.onCreateSpace( - newName, - selectedIcon, - selectedProducts, - selectedSpaceModel, - subspaces, - tags); + widget.onCreateSpace(newName, selectedIcon, selectedProducts, + selectedSpaceModel, subspaces, tags); Navigator.of(context).pop(); } } }, borderRadius: 10, - backgroundColor: isOkButtonEnabled - ? ColorsManager.secondaryColor - : ColorsManager.grayColor, + backgroundColor: + isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor, foregroundColor: ColorsManager.whiteColors, child: const Text('OK'), ), @@ -592,7 +564,6 @@ class CreateSpaceDialogState extends State { ); } - void _showLinkSpaceModelDialog(BuildContext context) { showDialog( context: context, @@ -613,13 +584,8 @@ class CreateSpaceDialogState extends State { ); } - void _showSubSpaceDialog( - BuildContext context, - String name, - final List? spaceTags, - bool isEdit, - List? products, - final List? existingSubSpaces) { + void _showSubSpaceDialog(BuildContext context, String name, final List? spaceTags, + bool isEdit, List? products, final List? existingSubSpaces) { showDialog( context: context, builder: (BuildContext context) { @@ -634,12 +600,10 @@ class CreateSpaceDialogState extends State { final List tagsToAppendToSpace = []; if (slectedSubspaces != null) { - final updatedIds = - slectedSubspaces.map((s) => s.internalId).toSet(); + final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet(); if (existingSubSpaces != null) { - final deletedSubspaces = existingSubSpaces - .where((s) => !updatedIds.contains(s.internalId)) - .toList(); + final deletedSubspaces = + existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList(); for (var s in deletedSubspaces) { if (s.tags != null) { tagsToAppendToSpace.addAll(s.tags!); @@ -659,20 +623,20 @@ class CreateSpaceDialogState extends State { ); } - void _showTagCreateDialog(BuildContext context, String name, bool isEdit, - List? products) { + void _showTagCreateDialog( + BuildContext context, String name, bool isEdit, List? products) { isEdit ? showDialog( context: context, builder: (BuildContext context) { return AssignTagDialog( title: 'Edit Device', - addedProducts: TagHelper.createInitialSelectedProductsForTags( - tags, subspaces), + addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces), spaceName: name, products: products, subspaces: subspaces, allTags: widget.allTags, + projectTags: widget.projectTags, onSave: (selectedSpaceTags, selectedSubspaces) { setState(() { tags = selectedSpaceTags; @@ -682,8 +646,7 @@ class CreateSpaceDialogState extends State { if (subspaces != null) { for (final subspace in subspaces!) { for (final selectedSubspace in selectedSubspaces) { - if (subspace.subspaceName == - selectedSubspace.subspaceName) { + if (subspace.subspaceName == selectedSubspace.subspaceName) { subspace.tags = selectedSubspace.tags; } } @@ -705,9 +668,9 @@ class CreateSpaceDialogState extends State { spaceTags: tags, isCreate: true, allTags: widget.allTags, + projectTags: widget.projectTags, initialSelectedProducts: - TagHelper.createInitialSelectedProductsForTags( - tags, subspaces), + TagHelper.createInitialSelectedProductsForTags(tags, subspaces), onSave: (selectedSpaceTags, selectedSubspaces) { setState(() { tags = selectedSpaceTags; @@ -717,8 +680,7 @@ class CreateSpaceDialogState extends State { if (subspaces != null) { for (final subspace in subspaces!) { for (final selectedSubspace in selectedSubspaces) { - if (subspace.subspaceName == - selectedSubspace.subspaceName) { + if (subspace.subspaceName == selectedSubspace.subspaceName) { subspace.tags = selectedSubspace.tags; } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index dccd7fd9..66b2d6da 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart'; @@ -19,16 +21,17 @@ class LoadedSpaceView extends StatefulWidget { final List? products; final List? spaceModels; final bool shouldNavigateToSpaceModelPage; + final List projectTags; - const LoadedSpaceView({ - super.key, - required this.communities, - this.selectedCommunity, - this.selectedSpace, - this.products, - this.spaceModels, - required this.shouldNavigateToSpaceModelPage, - }); + const LoadedSpaceView( + {super.key, + required this.communities, + this.selectedCommunity, + this.selectedSpace, + this.products, + this.spaceModels, + required this.shouldNavigateToSpaceModelPage, + required this.projectTags}); @override _LoadedSpaceViewState createState() => _LoadedSpaceViewState(); @@ -40,6 +43,7 @@ class _LoadedSpaceViewState extends State { @override void initState() { super.initState(); + _spaceModels = List.from(widget.spaceModels ?? []); } @@ -47,6 +51,7 @@ class _LoadedSpaceViewState extends State { @override void didUpdateWidget(covariant LoadedSpaceView oldWidget) { super.didUpdateWidget(oldWidget); + if (widget.spaceModels != oldWidget.spaceModels) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { @@ -82,12 +87,14 @@ class _LoadedSpaceViewState extends State { Expanded( child: BlocProvider( create: (context) => SpaceModelBloc( + BlocProvider.of(context), api: SpaceModelManagementApi(), - initialSpaceModels: _spaceModels, + initialSpaceModels: widget.spaceModels ?? [], ), child: SpaceModelPage( products: widget.products, onSpaceModelsUpdated: _onSpaceModelsUpdated, + projectTags: widget.projectTags, ), ), ), @@ -97,8 +104,9 @@ class _LoadedSpaceViewState extends State { children: [ SidebarWidget( communities: widget.communities, - selectedSpaceUuid: - widget.selectedSpace?.uuid ?? widget.selectedCommunity?.uuid ?? '', + selectedSpaceUuid: widget.selectedSpace?.uuid ?? + widget.selectedCommunity?.uuid ?? + '', ), CommunityStructureArea( selectedCommunity: widget.selectedCommunity, @@ -107,6 +115,7 @@ class _LoadedSpaceViewState extends State { products: widget.products, communities: widget.communities, spaceModels: _spaceModels, + projectTags: widget.projectTags, ), ], ), diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index a38743dc..17566da7 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -205,30 +205,32 @@ class _SidebarWidgetState extends State { ); } - Widget _buildSpaceTile(SpaceModel space, CommunityModel community) { + Widget _buildSpaceTile(SpaceModel space, CommunityModel community, {int depth = 1}) { bool isExpandedSpace = _isSpaceOrChildSelected(space); - return SpaceTile( - title: space.name, - key: ValueKey(space.uuid), - isSelected: _selectedId == space.uuid, - initiallyExpanded: isExpandedSpace, - onExpansionChanged: (bool expanded) { - _handleExpansionChange(space.uuid ?? '', expanded); - }, - onItemSelected: () { - setState(() { - _selectedId = space.uuid; - _selectedSpaceUuid = space.uuid; - }); + return Padding( + padding: EdgeInsets.only(left: depth * 16.0), + child: SpaceTile( + title: space.name, + key: ValueKey(space.uuid), + isSelected: _selectedId == space.uuid, + initiallyExpanded: isExpandedSpace, + onExpansionChanged: (bool expanded) { + _handleExpansionChange(space.uuid ?? '', expanded); + }, + onItemSelected: () { + setState(() { + _selectedId = space.uuid; + _selectedSpaceUuid = space.uuid; + }); - context.read().add( - SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), - ); - }, - children: space.children.isNotEmpty - ? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList() - : [], // Recursively render child spaces if available - ); + context.read().add( + SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), + ); + }, + children: space.children.isNotEmpty + ? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList() + : [], // Recursively render child spaces if available + )); } void _handleExpansionChange(String uuid, bool expanded) {} diff --git a/lib/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart index 63cf9b7d..d72f22ac 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart @@ -35,18 +35,20 @@ class _SpaceTileState extends State { @override Widget build(BuildContext context) { - return CustomExpansionTile( - isSelected: widget.isSelected, - title: widget.title, - initiallyExpanded: _isExpanded, - onItemSelected: widget.onItemSelected, - onExpansionChanged: (bool expanded) { - setState(() { - _isExpanded = expanded; - }); - widget.onExpansionChanged(expanded); - }, - children: widget.children ?? [], - ); + return Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0), + child: CustomExpansionTile( + isSelected: widget.isSelected, + title: widget.title, + initiallyExpanded: _isExpanded, + onItemSelected: widget.onItemSelected, + onExpansionChanged: (bool expanded) { + setState(() { + _isExpanded = expanded; + }); + widget.onExpansionChanged(expanded); + }, + children: widget.children ?? [], + )); } } diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart index a06e6977..74161b6f 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart @@ -4,17 +4,16 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_e import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; class AssignTagBloc extends Bloc { - final List allTags; + final List projectTags; - AssignTagBloc(this.allTags) : super(AssignTagInitial()) { + AssignTagBloc(this.projectTags) : super(AssignTagInitial()) { on((event, emit) { final initialTags = event.initialTags ?? []; final existingTagCounts = {}; for (var tag in initialTags) { if (tag.product != null) { - existingTagCounts[tag.product!.uuid] = - (existingTagCounts[tag.product!.uuid] ?? 0) + 1; + existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1; } } @@ -23,17 +22,14 @@ class AssignTagBloc extends Bloc { for (var selectedProduct in event.addedProducts) { final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; - if (selectedProduct.count == 0 || - selectedProduct.count <= existingCount) { - tags.addAll(initialTags - .where((tag) => tag.product?.uuid == selectedProduct.productId)); + if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) { + tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId)); continue; } final missingCount = selectedProduct.count - existingCount; - tags.addAll(initialTags - .where((tag) => tag.product?.uuid == selectedProduct.productId)); + tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId)); if (missingCount > 0) { tags.addAll(List.generate( @@ -47,11 +43,11 @@ class AssignTagBloc extends Bloc { } } - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagLoaded( tags: tags, - updatedTags: updatedTags, + updatedTags: updatedTags, isSaveEnabled: _validateTags(tags), errorMessage: '', )); @@ -62,9 +58,16 @@ class AssignTagBloc extends Bloc { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); - tags[event.index] = tags[event.index].copyWith(tag: event.tag); + if (event.index < 0 || event.index >= tags.length) return; - final updatedTags = _calculateAvailableTags(allTags, tags); + tags[event.index] = tags[event.index].copyWith( + tag: event.tag.tag, + uuid: event.tag.uuid, + product: event.tag.product, + internalId: event.tag.internalId, + location: event.tag.location, + ); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagLoaded( tags: tags, @@ -82,10 +85,9 @@ class AssignTagBloc extends Bloc { final tags = List.from(currentState.tags); // Update the location - tags[event.index] = - tags[event.index].copyWith(location: event.location); + tags[event.index] = tags[event.index].copyWith(location: event.location); - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagLoaded( tags: tags, @@ -104,7 +106,7 @@ class AssignTagBloc extends Bloc { emit(AssignTagLoaded( tags: tags, - updatedTags: _calculateAvailableTags(allTags, tags), + updatedTags: _calculateAvailableTags(projectTags, tags), isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -115,11 +117,10 @@ class AssignTagBloc extends Bloc { final currentState = state; if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { - final tags = List.from(currentState.tags) - ..remove(event.tagToDelete); + final tags = List.from(currentState.tags)..remove(event.tagToDelete); // Recalculate available tags - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagLoaded( tags: tags, @@ -140,10 +141,8 @@ class AssignTagBloc extends Bloc { // Get validation error for duplicate tags String? _getValidationError(List tags) { - final nonEmptyTags = tags - .map((tag) => tag.tag?.trim() ?? '') - .where((tag) => tag.isNotEmpty) - .toList(); + final nonEmptyTags = + tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList(); final duplicateTags = nonEmptyTags .fold>({}, (map, tag) { @@ -162,14 +161,16 @@ class AssignTagBloc extends Bloc { return null; } - List _calculateAvailableTags(List allTags, List tags) { - final selectedTags = tags + List _calculateAvailableTags(List allTags, List selectedTags) { + final selectedTagSet = selectedTags .where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) .map((tag) => tag.tag!.trim()) .toSet(); - final availableTags = - allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); + final availableTags = allTags + .where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim())) + .toList(); + return availableTags; } } diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart index 9116b094..7d81ffdb 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart @@ -24,7 +24,7 @@ class InitializeTags extends AssignTagEvent { class UpdateTagEvent extends AssignTagEvent { final int index; - final String tag; + final Tag tag; const UpdateTagEvent({required this.index, required this.tag}); diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart index 6a2dae4b..53700a33 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart @@ -5,7 +5,7 @@ abstract class AssignTagState extends Equatable { const AssignTagState(); @override - List get props => []; + List get props => []; } class AssignTagInitial extends AssignTagState {} @@ -14,7 +14,7 @@ class AssignTagLoading extends AssignTagState {} class AssignTagLoaded extends AssignTagState { final List tags; - final List updatedTags; + final List updatedTags; final bool isSaveEnabled; final String? errorMessage; @@ -27,8 +27,7 @@ class AssignTagLoaded extends AssignTagState { }); @override - List get props => - [tags, updatedTags, isSaveEnabled, errorMessage ?? '']; + List get props => [tags, updatedTags, isSaveEnabled, errorMessage ?? '']; } class AssignTagError extends AssignTagState { @@ -37,5 +36,5 @@ class AssignTagError extends AssignTagState { const AssignTagError(this.errorMessage); @override - List get props => [errorMessage]; + List get props => [errorMessage]; } diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index 3b794b61..fd1454e5 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/dialog_dropdown.dart'; -import 'package:syncrow_web/common/dialog_textfield_dropdown.dart'; +import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart'; @@ -26,6 +26,7 @@ class AssignTagDialog extends StatelessWidget { final String spaceName; final String title; final Function(List, List?)? onSave; + final List projectTags; const AssignTagDialog( {Key? key, @@ -37,18 +38,17 @@ class AssignTagDialog extends StatelessWidget { this.allTags, required this.spaceName, required this.title, - this.onSave}) + this.onSave, + required this.projectTags}) : super(key: key); @override Widget build(BuildContext context) { - final List locations = (subspaces ?? []) - .map((subspace) => subspace.subspaceName) - .toList() - ..add('Main Space'); + final List locations = + (subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space'); return BlocProvider( - create: (_) => AssignTagBloc(allTags ?? []) + create: (_) => AssignTagBloc(projectTags) ..add(InitializeTags( initialTags: initialTags, addedProducts: addedProducts, @@ -70,8 +70,7 @@ class AssignTagDialog extends StatelessWidget { ClipRRect( borderRadius: BorderRadius.circular(20), child: DataTable( - headingRowColor: WidgetStateProperty.all( - ColorsManager.dataHeaderGrey), + headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey), key: ValueKey(state.tags.length), border: TableBorder.all( color: ColorsManager.dataHeaderGrey, @@ -80,22 +79,15 @@ class AssignTagDialog extends StatelessWidget { ), columns: [ DataColumn( - label: Text('#', - style: - Theme.of(context).textTheme.bodyMedium)), + label: Text('#', style: Theme.of(context).textTheme.bodyMedium)), DataColumn( - label: Text('Device', - style: - Theme.of(context).textTheme.bodyMedium)), + label: Text('Device', style: Theme.of(context).textTheme.bodyMedium)), DataColumn( numeric: false, - label: Text('Tag', - style: - Theme.of(context).textTheme.bodyMedium)), + label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)), DataColumn( - label: Text('Location', - style: - Theme.of(context).textTheme.bodyMedium)), + label: + Text('Location', style: Theme.of(context).textTheme.bodyMedium)), ], rows: state.tags.isEmpty ? [ @@ -103,12 +95,8 @@ class AssignTagDialog extends StatelessWidget { DataCell( Center( child: Text('No Data Available', - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith( - color: ColorsManager - .lightGrayColor, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: ColorsManager.lightGrayColor, )), ), ), @@ -126,8 +114,7 @@ class AssignTagDialog extends StatelessWidget { DataCell(Text((index + 1).toString())), DataCell( Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( @@ -141,31 +128,25 @@ class AssignTagDialog extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: ColorsManager - .lightGrayColor, + color: ColorsManager.lightGrayColor, width: 1.0, ), ), child: IconButton( icon: const Icon( Icons.close, - color: ColorsManager - .lightGreyColor, + color: ColorsManager.lightGreyColor, size: 16, ), onPressed: () { - context - .read() - .add(DeleteTag( - tagToDelete: tag, - tags: state.tags)); + context.read().add( + DeleteTag(tagToDelete: tag, tags: state.tags)); controllers.removeAt(index); }, tooltip: 'Delete Tag', padding: EdgeInsets.zero, - constraints: - const BoxConstraints(), + constraints: const BoxConstraints(), ), ), ], @@ -173,23 +154,20 @@ class AssignTagDialog extends StatelessWidget { ), DataCell( Container( - alignment: Alignment - .centerLeft, // Align cell content to the left + alignment: + Alignment.centerLeft, // Align cell content to the left child: SizedBox( - width: double - .infinity, // Ensure full width for dropdown - child: DialogTextfieldDropdown( - key: ValueKey( - 'dropdown_${Uuid().v4()}_${index}'), + width: double.infinity, + child: TagDialogTextfieldDropdown( + key: ValueKey('dropdown_${const Uuid().v4()}_$index'), items: state.updatedTags, - initialValue: tag.tag, + product: tag.product?.uuid ?? 'Unknown', + initialValue: tag, onSelected: (value) { - controller.text = value; - context - .read() - .add(UpdateTagEvent( + controller.text = value.tag ?? ''; + context.read().add(UpdateTagEvent( index: index, - tag: value.trim(), + tag: value, )); }, ), @@ -201,12 +179,9 @@ class AssignTagDialog extends StatelessWidget { width: double.infinity, child: DialogDropdown( items: locations, - selectedValue: - tag.location ?? 'Main Space', + selectedValue: tag.location ?? 'Main Space', onSelected: (value) { - context - .read() - .add(UpdateLocation( + context.read().add(UpdateLocation( index: index, location: value, )); @@ -238,13 +213,11 @@ class AssignTagDialog extends StatelessWidget { label: 'Add New Device', onPressed: () async { final updatedTags = List.from(state.tags); - final result = - TagHelper.processTags(updatedTags, subspaces); + final result = TagHelper.processTags(updatedTags, subspaces); - final processedTags = - result['updatedTags'] as List; - final processedSubspaces = List.from( - result['subspaces'] as List); + final processedTags = result['updatedTags'] as List; + final processedSubspaces = + List.from(result['subspaces'] as List); Navigator.of(context).pop(); @@ -253,8 +226,9 @@ class AssignTagDialog extends StatelessWidget { builder: (context) => AddDeviceTypeWidget( products: products, subspaces: processedSubspaces, - initialSelectedProducts: TagHelper - .createInitialSelectedProductsForTags( + projectTags: projectTags, + initialSelectedProducts: + TagHelper.createInitialSelectedProductsForTags( processedTags, processedSubspaces), spaceName: spaceName, spaceTags: processedTags, @@ -278,14 +252,11 @@ class AssignTagDialog extends StatelessWidget { onPressed: state.isSaveEnabled ? () async { final updatedTags = List.from(state.tags); - final result = TagHelper.processTags( - updatedTags, subspaces); + final result = TagHelper.processTags(updatedTags, subspaces); - final processedTags = - result['updatedTags'] as List; + final processedTags = result['updatedTags'] as List; final processedSubspaces = - List.from( - result['subspaces'] as List); + List.from(result['subspaces'] as List); onSave?.call(processedTags, processedSubspaces); Navigator.of(context).pop(); } diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index d0e37f6a..7df82b5e 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -1,45 +1,40 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; -class AssignTagModelBloc - extends Bloc { - final List allTags; +class AssignTagModelBloc extends Bloc { + final List projectTags; - AssignTagModelBloc(this.allTags) : super(AssignTagModelInitial()) { + AssignTagModelBloc(this.projectTags) : super(AssignTagModelInitial()) { on((event, emit) { - final initialTags = event.initialTags ?? []; + final initialTags = event.initialTags; final existingTagCounts = {}; for (var tag in initialTags) { if (tag.product != null) { - existingTagCounts[tag.product!.uuid] = - (existingTagCounts[tag.product!.uuid] ?? 0) + 1; + existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1; } } - final tags = []; + final tags = []; for (var selectedProduct in event.addedProducts) { final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; - if (selectedProduct.count == 0 || - selectedProduct.count <= existingCount) { - tags.addAll(initialTags - .where((tag) => tag.product?.uuid == selectedProduct.productId)); + if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) { + tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId)); continue; } final missingCount = selectedProduct.count - existingCount; - tags.addAll(initialTags - .where((tag) => tag.product?.uuid == selectedProduct.productId)); + tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId)); if (missingCount > 0) { tags.addAll(List.generate( missingCount, - (index) => TagModel( + (index) => Tag( tag: '', product: selectedProduct.product, location: 'Main Space', @@ -48,7 +43,7 @@ class AssignTagModelBloc } } - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagModelLoaded( tags: tags, @@ -59,11 +54,20 @@ class AssignTagModelBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && - currentState.tags.isNotEmpty) { - final tags = List.from(currentState.tags); - tags[event.index] = tags[event.index].copyWith(tag: event.tag); - final updatedTags = _calculateAvailableTags(allTags, tags); + if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + + if (event.index < 0 || event.index >= tags.length) return; + + tags[event.index] = tags[event.index].copyWith( + tag: event.tag.tag, + uuid: event.tag.uuid, + product: event.tag.product, + internalId: event.tag.internalId, + location: event.tag.location, + ); + + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagModelLoaded( tags: tags, @@ -77,15 +81,12 @@ class AssignTagModelBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && - currentState.tags.isNotEmpty) { - final tags = List.from(currentState.tags); - + if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); // Use copyWith for immutability - tags[event.index] = - tags[event.index].copyWith(location: event.location); + tags[event.index] = tags[event.index].copyWith(location: event.location); - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagModelLoaded( tags: tags, @@ -99,13 +100,12 @@ class AssignTagModelBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && - currentState.tags.isNotEmpty) { - final tags = List.from(currentState.tags); + if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); emit(AssignTagModelLoaded( tags: tags, - updatedTags: _calculateAvailableTags(allTags, tags), + updatedTags: _calculateAvailableTags(projectTags, tags), isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -115,12 +115,10 @@ class AssignTagModelBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && - currentState.tags.isNotEmpty) { - final tags = List.from(currentState.tags) - ..remove(event.tagToDelete); + if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags)..remove(event.tagToDelete); - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagModelLoaded( tags: tags, @@ -128,24 +126,22 @@ class AssignTagModelBloc isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); - } + } }); } - bool _validateTags(List tags) { + bool _validateTags(List tags) { final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final isValid = uniqueTags.length == tags.length && !hasEmptyTag; return isValid; } - String? _getValidationError(List tags) { + String? _getValidationError(List tags) { // Check for duplicate tags - final nonEmptyTags = tags - .map((tag) => tag.tag?.trim() ?? '') - .where((tag) => tag.isNotEmpty) - .toList(); + final nonEmptyTags = + tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList(); final duplicateTags = nonEmptyTags .fold>({}, (map, tag) { @@ -164,15 +160,16 @@ class AssignTagModelBloc return null; } - List _calculateAvailableTags( - List allTags, List tags) { - final selectedTags = tags + List _calculateAvailableTags(List allTags, List selectedTags) { + final selectedTagSet = selectedTags .where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) .map((tag) => tag.tag!.trim()) .toSet(); - final availableTags = - allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); + final availableTags = allTags + .where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim())) + .toList(); + return availableTags; } } diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart index 38642d96..cb878bde 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; abstract class AssignTagModelEvent extends Equatable { @@ -10,7 +10,7 @@ abstract class AssignTagModelEvent extends Equatable { } class InitializeTagModels extends AssignTagModelEvent { - final List initialTags; + final List initialTags; final List addedProducts; const InitializeTagModels({ @@ -24,7 +24,7 @@ class InitializeTagModels extends AssignTagModelEvent { class UpdateTag extends AssignTagModelEvent { final int index; - final String tag; + final Tag tag; const UpdateTag({required this.index, required this.tag}); @@ -45,8 +45,8 @@ class UpdateLocation extends AssignTagModelEvent { class ValidateTagModels extends AssignTagModelEvent {} class DeleteTagModel extends AssignTagModelEvent { - final TagModel tagToDelete; - final List tags; + final Tag tagToDelete; + final List tags; const DeleteTagModel({required this.tagToDelete, required this.tags}); diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart index 167a6ac2..08168e6d 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; abstract class AssignTagModelState extends Equatable { const AssignTagModelState(); @@ -13,11 +13,11 @@ class AssignTagModelInitial extends AssignTagModelState {} class AssignTagModelLoading extends AssignTagModelState {} class AssignTagModelLoaded extends AssignTagModelState { - final List tags; + final List tags; final bool isSaveEnabled; final String? errorMessage; - final List updatedTags; + final List updatedTags; const AssignTagModelLoaded({ required this.tags, diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index d13766d4..85be3bf3 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/dialog_dropdown.dart'; -import 'package:syncrow_web/common/dialog_textfield_dropdown.dart'; +import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -23,8 +23,8 @@ class AssignTagModelsDialog extends StatelessWidget { final List? subspaces; final SpaceTemplateModel? spaceModel; - final List initialTags; - final ValueChanged>? onTagsAssigned; + final List initialTags; + final ValueChanged>? onTagsAssigned; final List addedProducts; final List? allTags; final String spaceName; @@ -32,6 +32,7 @@ class AssignTagModelsDialog extends StatelessWidget { final BuildContext? pageContext; final List? otherSpaceModels; final List? allSpaceModels; + final List projectTags; const AssignTagModelsDialog( {Key? key, @@ -46,18 +47,17 @@ class AssignTagModelsDialog extends StatelessWidget { this.pageContext, this.otherSpaceModels, this.spaceModel, - this.allSpaceModels}) + this.allSpaceModels, + required this.projectTags}) : super(key: key); @override Widget build(BuildContext context) { - final List locations = (subspaces ?? []) - .map((subspace) => subspace.subspaceName) - .toList() - ..add('Main Space'); + final List locations = + (subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space'); return BlocProvider( - create: (_) => AssignTagModelBloc(allTags ?? []) + create: (_) => AssignTagModelBloc(projectTags) ..add(InitializeTagModels( initialTags: initialTags, addedProducts: addedProducts, @@ -81,8 +81,7 @@ class AssignTagModelsDialog extends StatelessWidget { ClipRRect( borderRadius: BorderRadius.circular(20), child: DataTable( - headingRowColor: WidgetStateProperty.all( - ColorsManager.dataHeaderGrey), + headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey), key: ValueKey(state.tags.length), border: TableBorder.all( color: ColorsManager.dataHeaderGrey, @@ -91,26 +90,17 @@ class AssignTagModelsDialog extends StatelessWidget { ), columns: [ DataColumn( - label: Text('#', - style: Theme.of(context) - .textTheme - .bodyMedium)), + label: Text('#', style: Theme.of(context).textTheme.bodyMedium)), DataColumn( label: Text('Device', - style: Theme.of(context) - .textTheme - .bodyMedium)), + style: Theme.of(context).textTheme.bodyMedium)), DataColumn( numeric: false, - label: Text('Tag', - style: Theme.of(context) - .textTheme - .bodyMedium)), + label: + Text('Tag', style: Theme.of(context).textTheme.bodyMedium)), DataColumn( label: Text('Location', - style: Theme.of(context) - .textTheme - .bodyMedium)), + style: Theme.of(context).textTheme.bodyMedium)), ], rows: state.tags.isEmpty ? [ @@ -118,13 +108,10 @@ class AssignTagModelsDialog extends StatelessWidget { DataCell( Center( child: Text('No Devices Available', - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith( - color: ColorsManager - .lightGrayColor, - )), + style: + Theme.of(context).textTheme.bodyMedium?.copyWith( + color: ColorsManager.lightGrayColor, + )), ), ), const DataCell(SizedBox()), @@ -141,8 +128,7 @@ class AssignTagModelsDialog extends StatelessWidget { DataCell(Text((index + 1).toString())), DataCell( Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( @@ -156,31 +142,25 @@ class AssignTagModelsDialog extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: ColorsManager - .lightGrayColor, + color: ColorsManager.lightGrayColor, width: 1.0, ), ), child: IconButton( icon: const Icon( Icons.close, - color: ColorsManager - .lightGreyColor, + color: ColorsManager.lightGreyColor, size: 16, ), onPressed: () { - context - .read< - AssignTagModelBloc>() - .add(DeleteTagModel( - tagToDelete: tag, - tags: state.tags)); + context.read().add( + DeleteTagModel( + tagToDelete: tag, tags: state.tags)); controllers.removeAt(index); }, tooltip: 'Delete Tag', padding: EdgeInsets.zero, - constraints: - const BoxConstraints(), + constraints: const BoxConstraints(), ), ), ], @@ -191,19 +171,16 @@ class AssignTagModelsDialog extends StatelessWidget { alignment: Alignment .centerLeft, // Align cell content to the left child: SizedBox( - width: double - .infinity, // Ensure full width for dropdown - child: DialogTextfieldDropdown( - key: ValueKey( - 'dropdown_${Uuid().v4()}_${index}'), + width: double.infinity, + child: TagDialogTextfieldDropdown( + key: ValueKey( + 'dropdown_${const Uuid().v4()}_$index'), + product: tag.product?.uuid ?? 'Unknown', items: state.updatedTags, - initialValue: tag.tag, + initialValue: tag, onSelected: (value) { - controller.text = value; - context - .read< - AssignTagModelBloc>() - .add(UpdateTag( + controller.text = value.tag ?? ''; + context.read().add(UpdateTag( index: index, tag: value, )); @@ -217,12 +194,10 @@ class AssignTagModelsDialog extends StatelessWidget { width: double.infinity, child: DialogDropdown( items: locations, - selectedValue: tag.location ?? - 'Main Space', + selectedValue: tag.location ?? 'Main Space', onSelected: (value) { context - .read< - AssignTagModelBloc>() + .read() .add(UpdateLocation( index: index, location: value, @@ -254,17 +229,13 @@ class AssignTagModelsDialog extends StatelessWidget { builder: (buttonContext) => CancelButton( label: 'Add New Device', onPressed: () async { - final updatedTags = - List.from(state.tags); + final updatedTags = List.from(state.tags); final result = - TagHelper.updateSubspaceTagModels( - updatedTags, subspaces); + TagHelper.updateSubspaceTagModels(updatedTags, subspaces); - final processedTags = - result['updatedTags'] as List; - final processedSubspaces = - List.from( - result['subspaces'] as List); + final processedTags = result['updatedTags'] as List; + final processedSubspaces = List.from( + result['subspaces'] as List); if (context.mounted) { Navigator.of(context).pop(); @@ -272,28 +243,25 @@ class AssignTagModelsDialog extends StatelessWidget { await showDialog( barrierDismissible: false, context: context, - builder: (dialogContext) => - AddDeviceTypeModelWidget( - products: products, - subspaces: processedSubspaces, - isCreate: false, - initialSelectedProducts: TagHelper - .createInitialSelectedProducts( - processedTags, - processedSubspaces), - allTags: allTags, - spaceName: spaceName, - otherSpaceModels: otherSpaceModels, - spaceTagModels: processedTags, - pageContext: pageContext, - spaceModel: SpaceTemplateModel( - modelName: spaceName, - tags: updatedTags, - uuid: spaceModel?.uuid, - internalId: - spaceModel?.internalId, - subspaceModels: - processedSubspaces)), + builder: (dialogContext) => AddDeviceTypeModelWidget( + products: products, + subspaces: processedSubspaces, + isCreate: false, + initialSelectedProducts: + TagHelper.createInitialSelectedProducts( + processedTags, processedSubspaces), + allTags: allTags, + spaceName: spaceName, + otherSpaceModels: otherSpaceModels, + spaceTagModels: processedTags, + pageContext: pageContext, + projectTags: projectTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + tags: updatedTags, + uuid: spaceModel?.uuid, + internalId: spaceModel?.internalId, + subspaceModels: processedSubspaces)), ); } }, @@ -310,22 +278,16 @@ class AssignTagModelsDialog extends StatelessWidget { : ColorsManager.whiteColorsWithOpacity, onPressed: state.isSaveEnabled ? () async { - final updatedTags = - List.from(state.tags); + final updatedTags = List.from(state.tags); final result = - TagHelper.updateSubspaceTagModels( - updatedTags, subspaces); + TagHelper.updateSubspaceTagModels(updatedTags, subspaces); - final processedTags = - result['updatedTags'] as List; - final processedSubspaces = - List.from( - result['subspaces'] - as List); + final processedTags = result['updatedTags'] as List; + final processedSubspaces = List.from( + result['subspaces'] as List); - Navigator.of(context) - .popUntil((route) => route.isFirst); + Navigator.of(context).popUntil((route) => route.isFirst); await showDialog( context: context, @@ -334,16 +296,15 @@ class AssignTagModelsDialog extends StatelessWidget { products: products, allSpaceModels: allSpaceModels, allTags: allTags, + projectTags: projectTags, pageContext: pageContext, otherSpaceModels: otherSpaceModels, spaceModel: SpaceTemplateModel( modelName: spaceName, tags: processedTags, uuid: spaceModel?.uuid, - internalId: - spaceModel?.internalId, - subspaceModels: - processedSubspaces), + internalId: spaceModel?.internalId, + subspaceModels: processedSubspaces), ); }, ); diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index 3e3353a3..e1dc3cc3 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_mo import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; class TagHelper { static Map updateTags({ @@ -131,9 +130,9 @@ class TagHelper { } static List getAvailableTagModels( - List allTags, List currentTags, TagModel currentTag) { + List allTags, List currentTags, Tag currentTag) { List availableTagsForTagModel = - TagHelper.getAvailableTags( + TagHelper.getAvailableTags( allTags: allTags, currentTags: currentTags, currentTag: currentTag, @@ -142,11 +141,11 @@ class TagHelper { return availableTagsForTagModel; } - static List generateInitialTags({ - List? spaceTagModels, + static List generateInitialTags({ + List? spaceTagModels, List? subspaces, }) { - final List initialTags = []; + final List initialTags = []; if (spaceTagModels != null) { initialTags.addAll(spaceTagModels); @@ -212,7 +211,7 @@ class TagHelper { } static List createInitialSelectedProducts( - List? tags, List? subspaces) { + List? tags, List? subspaces) { final Map productCounts = {}; if (tags != null) { @@ -282,7 +281,7 @@ class TagHelper { } static int? checkTagExistInSubspaceModels( - TagModel tag, List? subspaces) { + Tag tag, List? subspaces) { if (subspaces == null) return null; for (int i = 0; i < subspaces.length; i++) { @@ -298,8 +297,8 @@ class TagHelper { } static Map updateSubspaceTagModels( - List updatedTags, List? subspaces) { - final result = TagHelper.updateTags( + List updatedTags, List? subspaces) { + final result = TagHelper.updateTags( updatedTags: updatedTags, subspaces: subspaces, getInternalId: (tag) => tag.internalId, @@ -311,7 +310,7 @@ class TagHelper { checkTagExistInSubspace: checkTagExistInSubspaceModels, ); - final processedTags = result['updatedTags'] as List; + final processedTags = result['updatedTags'] as List; final processedSubspaces = List.from(result['subspaces'] as List); diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart deleted file mode 100644 index aa9a446d..00000000 --- a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart'; - - -class SpaceModelBloc extends Bloc { - SpaceModelBloc() : super(SpaceModelInitial()) { - on((event, emit) { - emit(SpaceModelSelectedState(event.selectedIndex)); - }); - } -} \ No newline at end of file diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart deleted file mode 100644 index 8bff0202..00000000 --- a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart +++ /dev/null @@ -1,7 +0,0 @@ -abstract class SpaceModelEvent {} - -class SpaceModelSelectedEvent extends SpaceModelEvent { - final int selectedIndex; - - SpaceModelSelectedEvent(this.selectedIndex); -} diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart deleted file mode 100644 index cc745e4d..00000000 --- a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart +++ /dev/null @@ -1,9 +0,0 @@ -abstract class SpaceModelState {} - -class SpaceModelInitial extends SpaceModelState {} - -class SpaceModelSelectedState extends SpaceModelState { - final int selectedIndex; - - SpaceModelSelectedState(this.selectedIndex); -} diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart new file mode 100644 index 00000000..c789c2a9 --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart @@ -0,0 +1,104 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; +import 'package:syncrow_web/utils/navigation_service.dart'; + +class LinkSpaceToModelBloc + extends Bloc { + LinkSpaceToModelBloc() : super(SpaceModelInitial()) { + on(_getSpaceIds); + on(_handleLinkSpaceModel); + on(_validateLinkSpaceModel); + on((event, emit) { + emit(SpaceModelSelectedState(event.selectedIndex)); + }); + } + + List spacesListIds = []; + bool hasSelectedSpaces = false; + String validate = ''; + + Future _getSpaceIds(LinkSpaceModelSelectedIdsEvent event, + Emitter emit) async { + try { + BuildContext context = NavigationService.navigatorKey.currentContext!; + var spaceBloc = context.read(); + for (var communityId in spaceBloc.state.selectedCommunities) { + List spacesList = + spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + spacesListIds = spacesList; + } + hasSelectedSpaces = + spaceBloc.state.selectedCommunities.any((communityId) { + List spacesList = + spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + return spacesList.isNotEmpty; + }); + if (hasSelectedSpaces) { + debugPrint("At least one space is selected."); + } else { + debugPrint("No spaces selected."); + } + } catch (e) { + debugPrint("Error in _getSpaceIds: $e"); + } + } + + Future _handleLinkSpaceModel( + LinkSpaceModelEvent event, + Emitter emit, + ) async { + emit(SpaceModelLoading()); + try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + await SpaceModelManagementApi().linkSpaceModel( + spaceModelUuid: event.selectedSpaceMode!, + projectId: projectUuid, + spaceUuids: spacesListIds, + isOverWrite: event.isOverWrite); + emit(SpaceModelLinkSuccess()); + } on DioException catch (e) { + final errorMessage = _parseDioError(e); + emit(SpaceModelOperationFailure(errorMessage)); + } catch (e) { + emit(SpaceModelOperationFailure('Unexpected error: $e')); + } + } + + Future _validateLinkSpaceModel( + ValidateSpaceModelEvent event, + Emitter emit, + ) async { + emit(SpaceModelLoading()); + try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + await SpaceModelManagementApi().validateSpaceModel( + projectUuid, + spacesListIds, + ); + emit(SpaceValidationSuccess()); + } on DioException catch (e) { + final errorMessage = _parseDioError(e); + if (errorMessage == + 'Selected spaces already have linked space model / sub-spaces and devices') { + emit(const AlreadyHaveLinkedState()); + } else { + emit(SpaceModelOperationFailure(errorMessage)); + } + } catch (e) { + emit(SpaceModelOperationFailure('Unexpected error: $e')); + } + } + + String _parseDioError(DioException e) { + if (e.response?.data is Map) { + return e.response!.data['error']['message'] ?? 'Unknown error occurred'; + } + return e.message ?? 'Network request failed'; + } +} diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart new file mode 100644 index 00000000..694358db --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +abstract class LinkSpaceToModelEvent {} + +class LinkSpaceModelSelectedEvent extends LinkSpaceToModelEvent { + final int selectedIndex; + + LinkSpaceModelSelectedEvent(this.selectedIndex); +} + +class LinkSpaceModelSelectedIdsEvent extends LinkSpaceToModelEvent {} + +class LinkSpaceModelEvent extends LinkSpaceToModelEvent { + final String? selectedSpaceMode; + final bool isOverWrite; + LinkSpaceModelEvent({this.selectedSpaceMode, this.isOverWrite = false}); +} + +class ValidateSpaceModelEvent extends LinkSpaceToModelEvent { + BuildContext? context; + ValidateSpaceModelEvent({this.context}); +} diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart new file mode 100644 index 00000000..ad18b3c7 --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart @@ -0,0 +1,37 @@ +abstract class LinkSpaceToModelState { + const LinkSpaceToModelState(); +} + +class SpaceModelInitial extends LinkSpaceToModelState {} + +class SpaceModelLoading extends LinkSpaceToModelState {} +class LinkSpaceModelLoading extends LinkSpaceToModelState {} + + +class SpaceModelSelectedState extends LinkSpaceToModelState { + final int selectedIndex; + const SpaceModelSelectedState(this.selectedIndex); +} + +class SpaceModelSelectionUpdated extends LinkSpaceToModelState { + final bool hasSelectedSpaces; + const SpaceModelSelectionUpdated(this.hasSelectedSpaces); +} + +class SpaceValidationSuccess extends LinkSpaceToModelState {} + +class SpaceModelLinkSuccess extends LinkSpaceToModelState {} + +class ValidationError extends LinkSpaceToModelState { + final String message; + const ValidationError(this.message); +} + +class SpaceModelOperationFailure extends LinkSpaceToModelState { + final String message; + const SpaceModelOperationFailure(this.message); +} + +class AlreadyHaveLinkedState extends LinkSpaceToModelState { + const AlreadyHaveLinkedState(); +} diff --git a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart index 69023857..bbd0de36 100644 --- a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart +++ b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -24,13 +24,13 @@ class LinkSpaceModelDialog extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => SpaceModelBloc() + create: (context) => LinkSpaceToModelBloc() ..add( - SpaceModelSelectedEvent(initialSelectedIndex ?? -1), + LinkSpaceModelSelectedEvent(initialSelectedIndex ?? -1), ), child: Builder( builder: (context) { - final bloc = context.read(); + final bloc = context.read(); return AlertDialog( backgroundColor: ColorsManager.whiteColors, title: const Text('Link a space model'), @@ -39,7 +39,7 @@ class LinkSpaceModelDialog extends StatelessWidget { color: ColorsManager.textFieldGreyColor, width: MediaQuery.of(context).size.width * 0.7, height: MediaQuery.of(context).size.height * 0.6, - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { int selectedIndex = -1; if (state is SpaceModelSelectedState) { @@ -59,7 +59,7 @@ class LinkSpaceModelDialog extends StatelessWidget { final isSelected = selectedIndex == index; return GestureDetector( onTap: () { - bloc.add(SpaceModelSelectedEvent(index)); + bloc.add(LinkSpaceModelSelectedEvent(index)); }, child: Container( margin: const EdgeInsets.all(10.0), @@ -93,7 +93,7 @@ class LinkSpaceModelDialog extends StatelessWidget { label: 'Cancel', ), const SizedBox(width: 10), - BlocBuilder( + BlocBuilder( builder: (context, state) { final isEnabled = state is SpaceModelSelectedState && state.selectedIndex >= 0; diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 7c68e15c..5428c343 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -1,17 +1,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; -import 'package:syncrow_web/utils/constants/strings_manager.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; -import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; class CreateSpaceModelBloc extends Bloc { @@ -97,14 +94,9 @@ class CreateSpaceModelBloc orElse: () => subspace, ); - // Update the subspace's tags - final eventTagIds = matchingEventSubspace.tags - ?.map((e) => e.internalId) - .toSet() ?? - {}; final updatedTags = [ - ...?subspace.tags?.map((tag) { + ...?subspace.tags?.map((tag) { final matchingTag = matchingEventSubspace.tags?.firstWhere( (e) => e.internalId == tag.internalId, @@ -115,14 +107,14 @@ class CreateSpaceModelBloc ? tag.copyWith(tag: matchingTag?.tag) : tag; }) ?? - [], + [], ...?matchingEventSubspace.tags?.where( (e) => subspace.tags ?.every((t) => t.internalId != e.internalId) ?? true, ) ?? - [], + [], ]; return subspace.copyWith( subspaceName: matchingEventSubspace.subspaceName, @@ -247,7 +239,7 @@ class CreateSpaceModelBloc } if (newSubspaces != null) { - for (var newSubspace in newSubspaces!) { + for (var newSubspace in newSubspaces) { // Tag without UUID if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) { final List tagUpdates = []; @@ -256,7 +248,7 @@ class CreateSpaceModelBloc for (var tag in newSubspace.tags!) { tagUpdates.add(TagModelUpdate( action: Action.add, - uuid: tag.uuid == '' ? null : tag.uuid, + newTagUuid: tag.uuid == '' ? null : tag.uuid, tag: tag.tag, productUuid: tag.product?.uuid)); } @@ -271,7 +263,7 @@ class CreateSpaceModelBloc if (prevSubspaces != null && newSubspaces != null) { final newSubspaceMap = { - for (var subspace in newSubspaces!) subspace.uuid: subspace + for (var subspace in newSubspaces) subspace.uuid: subspace }; for (var prevSubspace in prevSubspaces) { @@ -312,8 +304,8 @@ class CreateSpaceModelBloc } List processTagUpdates( - List? prevTags, - List? newTags, + List? prevTags, + List? newTags, ) { final List tagUpdates = []; final processedTags = {}; @@ -323,7 +315,7 @@ class CreateSpaceModelBloc tagUpdates.add(TagModelUpdate( action: Action.add, tag: newTag.tag, - uuid: newTag.uuid, + newTagUuid: newTag.uuid, productUuid: newTag.product?.uuid, )); } @@ -335,7 +327,7 @@ class CreateSpaceModelBloc if (prevTags != null && newTags != null) { for (var prevTag in prevTags) { final existsInNew = - newTags!.any((newTag) => newTag.uuid == prevTag.uuid); + newTags.any((newTag) => newTag.uuid == prevTag.uuid); if (!existsInNew) { tagUpdates .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); @@ -352,14 +344,14 @@ class CreateSpaceModelBloc if (newTags != null) { final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {}; - for (var newTag in newTags!) { + for (var newTag in newTags) { // Tag without UUID if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) && !processedTags.contains(newTag.tag)) { tagUpdates.add(TagModelUpdate( action: Action.add, tag: newTag.tag, - uuid: newTag.uuid == '' ? null : newTag.uuid, + newTagUuid: newTag.uuid == '' ? null : newTag.uuid, productUuid: newTag.product?.uuid)); processedTags.add(newTag.tag); } @@ -368,14 +360,15 @@ class CreateSpaceModelBloc // Case 3: Tags updated if (prevTags != null && newTags != null) { - final newTagMap = {for (var tag in newTags!) tag.uuid: tag}; + final newTagMap = {for (var tag in newTags) tag.uuid: tag}; - for (var prevTag in prevTags!) { + for (var prevTag in prevTags) { final newTag = newTagMap[prevTag.uuid]; if (newTag != null) { tagUpdates.add(TagModelUpdate( action: Action.update, - uuid: newTag.uuid, + uuid: prevTag.uuid, + newTagUuid: newTag.uuid, tag: newTag.tag, )); } else {} diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index d0cd245c..0d2a3a4f 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; abstract class CreateSpaceModelEvent extends Equatable { const CreateSpaceModelEvent(); @@ -49,7 +49,7 @@ class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { } class AddTagsToSpaceTemplate extends CreateSpaceModelEvent { - final List tags; + final List tags; AddTagsToSpaceTemplate(this.tags); } diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart index facfe64f..37a8c0a9 100644 --- a/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart @@ -1,39 +1,38 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; -import 'package:syncrow_web/utils/constants/strings_manager.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; -import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; class SpaceModelBloc extends Bloc { final SpaceModelManagementApi api; + final SpaceTreeBloc _spaceTreeBloc; - SpaceModelBloc({ + SpaceModelBloc( + this._spaceTreeBloc, { required this.api, required List initialSpaceModels, }) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) { on(_onCreateSpaceModel); on(_onUpdateSpaceModel); + on(_onDeleteSpaceModel); } - Future _onCreateSpaceModel( - CreateSpaceModel event, Emitter emit) async { + Future _onCreateSpaceModel(CreateSpaceModel event, Emitter emit) async { final currentState = state; if (currentState is SpaceModelLoaded) { try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - final newSpaceModel = await api.getSpaceModel( - event.newSpaceModel.uuid ?? '', projectUuid); + final newSpaceModel = await api.getSpaceModel(event.newSpaceModel.uuid ?? '', projectUuid); if (newSpaceModel != null) { - final updatedSpaceModels = - List.from(currentState.spaceModels) - ..add(newSpaceModel); + final updatedSpaceModels = List.from(currentState.spaceModels) + ..add(newSpaceModel); emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); } } catch (e) { @@ -42,19 +41,40 @@ class SpaceModelBloc extends Bloc { } } - Future _onUpdateSpaceModel( - UpdateSpaceModel event, Emitter emit) async { + Future _onUpdateSpaceModel(UpdateSpaceModel event, Emitter emit) async { final currentState = state; if (currentState is SpaceModelLoaded) { try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - final newSpaceModel = - await api.getSpaceModel(event.spaceModelUuid ?? '', projectUuid); + final newSpaceModel = await api.getSpaceModel(event.spaceModelUuid, projectUuid); if (newSpaceModel != null) { final updatedSpaceModels = currentState.spaceModels.map((model) { return model.uuid == event.spaceModelUuid ? newSpaceModel : model; }).toList(); + _spaceTreeBloc.add(InitialEvent()); + emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); + } + } catch (e) { + emit(SpaceModelError(message: e.toString())); + } + } + } + + Future _onDeleteSpaceModel(DeleteSpaceModel event, Emitter emit) async { + final currentState = state; + + if (currentState is SpaceModelLoaded) { + try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + + final deletedSuccessfully = await api.deleteSpaceModel(event.spaceModelUuid, projectUuid); + + if (deletedSuccessfully) { + final updatedSpaceModels = currentState.spaceModels + .where((model) => model.uuid != event.spaceModelUuid) + .toList(); + _spaceTreeBloc.add(InitialEvent()); emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); } } catch (e) { diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/space_model_event.dart index 8f71e611..33856427 100644 --- a/lib/pages/spaces_management/space_model/bloc/space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/space_model_event.dart @@ -34,3 +34,12 @@ class UpdateSpaceModel extends SpaceModelEvent { @override List get props => [spaceModelUuid]; } + +class DeleteSpaceModel extends SpaceModelEvent { + final String spaceModelUuid; + + DeleteSpaceModel({required this.spaceModelUuid}); + + @override + List get props => [spaceModelUuid]; +} diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart index ad0770d5..161bbf3d 100644 --- a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -6,7 +6,7 @@ class TagBodyModel { Map toJson() { return { 'uuid': uuid, - 'tag': tag, + 'name': tag, 'productUuid': productUuid, }; } diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index 22378f1f..f8926051 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; @@ -9,8 +9,9 @@ class SpaceTemplateModel extends Equatable { String? uuid; String modelName; List? subspaceModels; - final List? tags; + final List? tags; String internalId; + DateTime? createdAt; @override List get props => [modelName, subspaceModels, tags]; @@ -21,24 +22,27 @@ class SpaceTemplateModel extends Equatable { required this.modelName, this.subspaceModels, this.tags, + this.createdAt, }) : internalId = internalId ?? const Uuid().v4(); - factory SpaceTemplateModel.fromJson(Map json) { final String internalId = json['internalId'] ?? const Uuid().v4(); return SpaceTemplateModel( uuid: json['uuid'] ?? '', + createdAt: json['createdAt'] != null + ? DateTime.tryParse(json['createdAt']) + : null, internalId: internalId, modelName: json['modelName'] ?? '', subspaceModels: (json['subspaceModels'] as List?) - ?.where((e) => e is Map) // Validate type + ?.where((e) => e is Map) .map((e) => SubspaceTemplateModel.fromJson(e as Map)) .toList() ?? [], tags: (json['tags'] as List?) ?.where((item) => item is Map) // Validate type - .map((item) => TagModel.fromJson(item as Map)) + .map((item) => Tag.fromJson(item as Map)) .toList() ?? [], ); @@ -47,7 +51,7 @@ class SpaceTemplateModel extends Equatable { String? uuid, String? modelName, List? subspaceModels, - List? tags, + List? tags, String? internalId, }) { return SpaceTemplateModel( diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index 9c69b4c8..130a477e 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -1,11 +1,11 @@ -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:uuid/uuid.dart'; class SubspaceTemplateModel { final String? uuid; String subspaceName; final bool disabled; - List? tags; + List? tags; String internalId; SubspaceTemplateModel({ @@ -25,7 +25,7 @@ class SubspaceTemplateModel { internalId: internalId, disabled: json['disabled'] ?? false, tags: (json['tags'] as List?) - ?.map((item) => TagModel.fromJson(item)) + ?.map((item) => Tag.fromJson(item)) .toList() ?? [], ); @@ -44,7 +44,7 @@ class SubspaceTemplateModel { String? uuid, String? subspaceName, bool? disabled, - List? tags, + List? tags, String? internalId, }) { return SubspaceTemplateModel( diff --git a/lib/pages/spaces_management/space_model/models/tag_body_model.dart b/lib/pages/spaces_management/space_model/models/tag_body_model.dart index d66e2884..49947831 100644 --- a/lib/pages/spaces_management/space_model/models/tag_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_body_model.dart @@ -4,7 +4,7 @@ class CreateTagBodyModel { Map toJson() { return { - 'tag': tag, + 'name': tag, 'productUuid': productUuid, }; } diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart deleted file mode 100644 index 20bd50e2..00000000 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; -import 'package:uuid/uuid.dart'; - -class TagModel extends BaseTag { - TagModel({ - String? uuid, - required String? tag, - ProductModel? product, - String? internalId, - String? location, - }) : super( - uuid: uuid, - tag: tag, - product: product, - internalId: internalId, - location: location, - ); - factory TagModel.fromJson(Map json) { - final String internalId = json['internalId'] ?? const Uuid().v4(); - - return TagModel( - uuid: json['uuid'] , - internalId: internalId, - tag: json['tag'] ?? '', - product: json['product'] != null - ? ProductModel.fromMap(json['product']) - : null, - ); - } - - @override - TagModel copyWith( - {String? tag, - ProductModel? product, - String? uuid, - String? location, - String? internalId}) { - return TagModel( - tag: tag ?? this.tag, - product: product ?? this.product, - location: location ?? this.location, - internalId: internalId ?? this.internalId, - uuid:uuid?? this.uuid - ); - } - - Map toJson() { - return { - 'uuid': uuid, - 'tag': tag, - 'product': product?.toMap(), - }; - } -} - -extension TagModelExtensions on TagModel { - TagBodyModel toTagBodyModel() { - return TagBodyModel() - ..uuid = uuid - ..tag = tag ?? '' - ..productUuid = product?.uuid; - } -} diff --git a/lib/pages/spaces_management/space_model/models/tag_update_model.dart b/lib/pages/spaces_management/space_model/models/tag_update_model.dart index c7190dc8..c2177058 100644 --- a/lib/pages/spaces_management/space_model/models/tag_update_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_update_model.dart @@ -5,12 +5,14 @@ class TagModelUpdate { final String? uuid; final String? tag; final String? productUuid; + final String? newTagUuid; TagModelUpdate({ required this.action, this.uuid, this.tag, this.productUuid, + this.newTagUuid, }); factory TagModelUpdate.fromJson(Map json) { @@ -26,9 +28,10 @@ class TagModelUpdate { Map toJson() { return { 'action': action.value, - 'uuid': uuid, // Nullable field - 'tag': tag, + 'tagUuid': uuid, + 'name': tag, 'productUuid': productUuid, + 'newTagUuid': newTagUuid }; } } diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 8f466a5e..e1aa7a92 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; @@ -12,8 +13,10 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelPage extends StatelessWidget { final List? products; final Function(List)? onSpaceModelsUpdated; + final List projectTags; - const SpaceModelPage({Key? key, this.products, this.onSpaceModelsUpdated}) + const SpaceModelPage( + {Key? key, this.products, this.onSpaceModelsUpdated, required this.projectTags}) : super(key: key); @override @@ -60,6 +63,7 @@ class SpaceModelPage extends StatelessWidget { allTags: allTagValues, pageContext: context, otherSpaceModels: allSpaceModelNames, + projectTags: projectTags, ); }, ); @@ -69,8 +73,7 @@ class SpaceModelPage extends StatelessWidget { } // Render existing space model final model = spaceModels[index]; - final otherModel = - List.from(allSpaceModelNames); + final otherModel = List.from(allSpaceModelNames); otherModel.remove(model.modelName); return GestureDetector( onTap: () { @@ -84,13 +87,18 @@ class SpaceModelPage extends StatelessWidget { otherSpaceModels: otherModel, pageContext: context, allSpaceModels: spaceModels, + projectTags: projectTags, ); }, ); }, child: Container( margin: const EdgeInsets.all(8.0), - child: SpaceModelCardWidget(model: model), + child: SpaceModelCardWidget( + model: model, + pageContext: context, + topActionsDisabled: false, + ), )); }, ), @@ -103,10 +111,8 @@ class SpaceModelPage extends StatelessWidget { return Center( child: Text( 'Error: ${state.message}', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.warningRed), + style: + Theme.of(context).textTheme.bodySmall?.copyWith(color: ColorsManager.warningRed), ), ); } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart new file mode 100644 index 00000000..2a39d67b --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ConfirmMergeDialog extends StatelessWidget { + const ConfirmMergeDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: Text( + 'Merge', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 30), + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Are you sure you want to merge?', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Cancel", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.secondaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Ok", + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 16, + color: ColorsManager.whiteColors, + ), + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart new file mode 100644 index 00000000..0497b570 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ConfirmOverwriteDialog extends StatelessWidget { + const ConfirmOverwriteDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: Text( + 'Overwrite', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 30), + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Are you sure you want to overwrite?', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + Center( + child: Text( + 'Selected spaces already have linked space \nmodel / sub-spaces and devices', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + color: ColorsManager.grayColor), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Cancel", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const LinkingSuccessful(); + }, + ); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.secondaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Ok", + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 16, + color: ColorsManager.whiteColors, + ), + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 9a89c452..98251382 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -2,7 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; @@ -23,6 +26,7 @@ class CreateSpaceModelDialog extends StatelessWidget { final BuildContext? pageContext; final List? otherSpaceModels; final List? allSpaceModels; + final List projectTags; const CreateSpaceModelDialog( {Key? key, @@ -31,7 +35,8 @@ class CreateSpaceModelDialog extends StatelessWidget { this.spaceModel, this.pageContext, this.otherSpaceModels, - this.allSpaceModels}) + this.allSpaceModels, + required this.projectTags}) : super(key: key); @override @@ -66,8 +71,7 @@ class CreateSpaceModelDialog extends StatelessWidget { spaceNameController.addListener(() { bloc.add(UpdateSpaceTemplateName( - name: spaceNameController.text, - allModels: otherSpaceModels ?? [])); + name: spaceNameController.text, allModels: otherSpaceModels ?? [])); }); return bloc; @@ -85,9 +89,7 @@ class CreateSpaceModelDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - spaceModel?.uuid == null - ? 'Create New Space Model' - : 'Edit Space Model', + spaceModel?.uuid == null ? 'Create New Space Model' : 'Edit Space Model', style: Theme.of(context) .textTheme .headlineLarge @@ -99,10 +101,8 @@ class CreateSpaceModelDialog extends StatelessWidget { child: TextField( controller: spaceNameController, onChanged: (value) { - context.read().add( - UpdateSpaceTemplateName( - name: value, - allModels: otherSpaceModels ?? [])); + context.read().add(UpdateSpaceTemplateName( + name: value, allModels: otherSpaceModels ?? [])); }, style: Theme.of(context) .textTheme @@ -155,6 +155,7 @@ class CreateSpaceModelDialog extends StatelessWidget { pageContext: pageContext, otherSpaceModels: otherSpaceModels, allSpaceModels: allSpaceModels, + projectTags: projectTags, ), const SizedBox(height: 20), SizedBox( @@ -177,73 +178,55 @@ class CreateSpaceModelDialog extends StatelessWidget { !isNameValid) ? null : () { - final updatedSpaceTemplate = - updatedSpaceModel.copyWith( - modelName: - spaceNameController.text.trim(), + final updatedSpaceTemplate = updatedSpaceModel.copyWith( + modelName: spaceNameController.text.trim(), ); if (updatedSpaceModel.uuid == null) { - context - .read() - .add( + context.read().add( CreateSpaceTemplate( - spaceTemplate: - updatedSpaceTemplate, + spaceTemplate: updatedSpaceTemplate, onCreate: (newModel) { if (pageContext != null) { + pageContext!.read().add( + CreateSpaceModel(newSpaceModel: newModel)); pageContext! - .read() - .add(CreateSpaceModel( - newSpaceModel: - newModel)); + .read() + .add(UpdateSpaceModelCache(newModel)); } - Navigator.of(context) - .pop(); // Close the dialog + Navigator.of(context).pop(); // Close the dialog }, ), ); } else { if (pageContext != null) { - final currentState = pageContext! - .read() - .state; - if (currentState - is SpaceModelLoaded) { - final spaceModels = - List.from( - currentState.spaceModels); + final currentState = + pageContext!.read().state; + if (currentState is SpaceModelLoaded) { + final spaceModels = List.from( + currentState.spaceModels); - final SpaceTemplateModel? - currentSpaceModel = spaceModels - .cast() - .firstWhere( - (sm) => - sm?.uuid == - updatedSpaceModel - .uuid, + final SpaceTemplateModel? currentSpaceModel = + spaceModels.cast().firstWhere( + (sm) => sm?.uuid == updatedSpaceModel.uuid, orElse: () => null, ); if (currentSpaceModel != null) { context .read() .add(ModifySpaceTemplate( - spaceTemplate: - currentSpaceModel, - updatedSpaceTemplate: - updatedSpaceTemplate, + spaceTemplate: currentSpaceModel, + updatedSpaceTemplate: updatedSpaceTemplate, onUpdate: (newModel) { - if (pageContext != - null) { - pageContext! - .read< - SpaceModelBloc>() - .add(UpdateSpaceModel( + if (pageContext != null) { + pageContext!.read().add( + UpdateSpaceModel( spaceModelUuid: - newModel.uuid ?? - '')); + newModel.uuid ?? '')); + pageContext! + .read() + .add(UpdateSpaceModelCache(newModel)); } - Navigator.of(context) - .pop(); + Navigator.of(context).pop(); })); } } @@ -252,11 +235,11 @@ class CreateSpaceModelDialog extends StatelessWidget { }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: ((state.errorMessage != null && - state.errorMessage != '') || - !isNameValid) - ? ColorsManager.whiteColorsWithOpacity - : ColorsManager.whiteColors, + foregroundColor: + ((state.errorMessage != null && state.errorMessage != '') || + !isNameValid) + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, child: const Text('OK'), ), ), diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart new file mode 100644 index 00000000..e0260887 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart @@ -0,0 +1,75 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart'; + + +class CustomLoadingIndicator extends StatefulWidget { + @override + _CustomLoadingIndicatorState createState() => _CustomLoadingIndicatorState(); +} + +class _CustomLoadingIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(seconds: 1), + )..repeat(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 50, + height: 50, + child: AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Transform.rotate( + angle: _controller.value * 2 * pi, + child: CustomPaint( + painter: LoadingPainter(), + ), + ); + }, + ), + ); + } +} + +class LoadingPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final Paint paint = Paint() + ..strokeWidth = 5 + ..strokeCap = StrokeCap.round + ..style = PaintingStyle.stroke; + + final double radius = size.width / 2; + final Offset center = Offset(size.width / 2, size.height / 2); + + for (int i = 0; i < 12; i++) { + final double angle = (i * 30) * (pi / 180); + final double startX = center.dx + radius * cos(angle); + final double startY = center.dy + radius * sin(angle); + final double endX = center.dx + (radius - 8) * cos(angle); + final double endY = center.dy + (radius - 8) * sin(angle); + + paint.color = Colors.blue.withOpacity(i / 12); // Gradient effect + canvas.drawLine(Offset(startX, startY), Offset(endX, endY), paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/delete_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/delete_space_model_dialog.dart new file mode 100644 index 00000000..8349baa4 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/delete_space_model_dialog.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DeleteSpaceModelDialog extends StatelessWidget { + final VoidCallback onConfirmDelete; + + const DeleteSpaceModelDialog({Key? key, required this.onConfirmDelete}) + : super(key: key); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: Text( + "Delete Space Model", + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: ColorsManager.blackColor), + ), + ), + content: SizedBox( + width: screenWidth * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Are you sure you want to delete?", + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 15), + Text( + "The existing sub-spaces, devices, and routines will remain associated with the spaces, but the connection will be removed.", + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(color: ColorsManager.lightGrayColor), + ), + ], + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 200, + child: CancelButton( + onPressed: () { + Navigator.of(context).pop(); // Close dialog + }, + label: "Cancel", + ), + ), + const SizedBox(width: 10), + SizedBox( + width: 200, + child: DefaultButton( + onPressed: () { + Navigator.of(context).pop(); // Close dialog + onConfirmDelete(); // Execute delete action + }, + backgroundColor: ColorsManager.semiTransparentRed, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('Delete'), + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart new file mode 100644 index 00000000..4da0c642 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart @@ -0,0 +1,251 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class LinkSpaceModelSpacesDialog extends StatefulWidget { + final SpaceTemplateModel spaceModel; + + const LinkSpaceModelSpacesDialog({super.key, required this.spaceModel}); + + @override + State createState() => + _LinkSpaceModelSpacesDialogState(); +} + +class _LinkSpaceModelSpacesDialogState + extends State { + @override + void initState() { + context.read().add(LinkSpaceModelSelectedIdsEvent()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + backgroundColor: Colors.white, + content: SizedBox( + width: MediaQuery.of(context).size.width * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDialogContent(), + _buildActionButtons(), + ], + ), + ), + ); + } + + Widget _buildDialogContent() { + widget.spaceModel.createdAt.toString(); + String formattedDate = + DateFormat('yyyy-MM-dd').format(widget.spaceModel.createdAt!); + String formattedTime = + DateFormat('HH:mm:ss').format(widget.spaceModel.createdAt!); + + return Expanded( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Center( + child: Text( + "Link Space Model to Spaces", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.blueAccent, + ), + ), + ), + const Divider(), + const SizedBox(height: 16), + _buildDetailRow( + "Space model name:", widget.spaceModel.modelName), + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + const Expanded( + child: Text( + "Creation date and time:", + style: const TextStyle( + fontWeight: FontWeight.bold), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + "$formattedDate $formattedTime", + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black), + ), + ), + ], + ), + ), + // _buildDetailRow("Creation date and time:", + // widget.spaceModel.createdAt.toString()), + _buildDetailRow("Created by:", "Admin"), + const SizedBox(height: 12), + const Text( + "Link to:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const Text( + "Please select all the spaces where you would like to link the Routine.", + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + const SizedBox(height: 8), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 7, + child: Container( + color: ColorsManager.whiteColors, + child: SpaceTreeView( + isSide: true, + onSelect: () { + context + .read() + .add( + LinkSpaceModelSelectedIdsEvent()); + }))) + ], + ), + ), + ), + const SizedBox( + height: 20, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildActionButtons() { + return Row( + children: [ + Expanded( + child: Container( + height: 50, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayColor, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayColor, + width: 1, + ), + ), + ), + child: _buildButton("Cancel", Colors.grey, () { + Navigator.of(context).pop(); + }), + ), + ), + Expanded( + child: Container( + height: 50, + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.grayColor, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayColor, + width: 1.0, + ), + ), + ), + child: _buildButton( + "Confirm", + ColorsManager.onSecondaryColor, + () { + final spaceModelBloc = context.read(); + if (!spaceModelBloc.hasSelectedSpaces) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Please select at least one space")), + ); + return; + } else { + spaceModelBloc.add(ValidateSpaceModelEvent(context: context)); + } + }, + ), + ), + ), + ], + ); + } +} + +Widget _buildDetailRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Expanded( + child: Text( + label, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + value, + style: const TextStyle( + fontWeight: FontWeight.bold, color: Colors.black), + ), + ), + ], + ), + ); +} + +Widget _buildButton(String text, Color color, VoidCallback onPressed) { + return InkWell( + onTap: onPressed, + child: Center( + child: Text( + text, + style: + TextStyle(color: color, fontWeight: FontWeight.w400, fontSize: 14), + ), + ), + ); +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/linking_attention_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/linking_attention_dialog.dart new file mode 100644 index 00000000..15d92029 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/linking_attention_dialog.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class LinkingAttentionDialog extends StatelessWidget { + const LinkingAttentionDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: Text( + 'Linking Attention', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 30), + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Do you want to merge or overwrite?', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + const SizedBox(height: 8), + Text( + 'Selected spaces already have commissioned Devices', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 14), + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // Cancel Button + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const ConfirmOverwriteDialog(); + }, + ); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Overwrite", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + const SizedBox(width: 10), + + // OK Button + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const ConfirmMergeDialog(); + }, + ); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Merge", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart b/lib/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart new file mode 100644 index 00000000..6a228fc1 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class LinkingSuccessful extends StatelessWidget { + const LinkingSuccessful({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: SvgPicture.asset( + Assets.successIcon, + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Linking successful', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + const SizedBox(height: 25), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart new file mode 100644 index 00000000..9f57a4b1 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +void showOverwriteDialog( + BuildContext context, LinkSpaceToModelBloc bloc, SpaceTemplateModel model) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return SizedBox( + child: Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + elevation: 10, + backgroundColor: Colors.white, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.3, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + "Overwrite", + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + const SizedBox(height: 15), + const Text( + "Are you sure you want to overwrite?", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + const SizedBox(height: 5), + Text( + "Selected spaces already have linked space model / sub-spaces and devices", + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: Colors.grey[200], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + ), + child: const Text( + "Cancel", + style: TextStyle( + fontSize: 16, + color: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton( + onPressed: () { + bloc.add(LinkSpaceModelEvent( + isOverWrite: true, + selectedSpaceMode: model.uuid)); + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: Colors.blue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: const Text( + "OK", + style: TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + }, + ); +} diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index 0056c96f..d028acba 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -1,13 +1,35 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/delete_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class SpaceModelCardWidget extends StatelessWidget { final SpaceTemplateModel model; + final BuildContext? pageContext; + final bool topActionsDisabled; - const SpaceModelCardWidget({Key? key, required this.model}) : super(key: key); + const SpaceModelCardWidget({ + Key? key, + required this.model, + this.pageContext, + this.topActionsDisabled = true, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -34,7 +56,7 @@ class SpaceModelCardWidget extends StatelessWidget { return LayoutBuilder( builder: (context, constraints) { bool showOnlyName = constraints.maxWidth < 250; - return Container( + return Container( padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( color: Colors.white, @@ -51,14 +73,140 @@ class SpaceModelCardWidget extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - model.modelName, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.black, - fontWeight: FontWeight.bold, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + model.modelName, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (!topActionsDisabled) + Row( + children: [ + InkWell( + onTap: () { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return BlocProvider( + create: (_) => LinkSpaceToModelBloc(), + child: BlocListener( + listenWhen: (previous, current) { + return previous != current; + }, + listener: (context, state) { + final _bloc = + BlocProvider.of( + context); + if (state is SpaceModelLoading) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular( + 20)), + elevation: 10, + backgroundColor: Colors.white, + child: Padding( + padding: + const EdgeInsets.symmetric( + vertical: 30, + horizontal: 50), + child: Column( + mainAxisSize: + MainAxisSize.min, + children: [ + CustomLoadingIndicator(), + ], + ), + ), + ); + }, + ); + } else if (state + is AlreadyHaveLinkedState) { + Navigator.of(dialogContext).pop(); + showOverwriteDialog( + context, _bloc, model); + } else if (state + is SpaceValidationSuccess) { + _bloc.add(LinkSpaceModelEvent( + isOverWrite: false, + selectedSpaceMode: model.uuid)); + + Future.delayed( + const Duration(seconds: 1), () { + Navigator.of(dialogContext).pop(); + Navigator.of(dialogContext).pop(); + Navigator.of(dialogContext).pop(); + }); + + showDialog( + context: context, + builder: + (BuildContext dialogContext) { + return const LinkingSuccessful(); + }, + ).then((v) { + Future.delayed( + const Duration(seconds: 2), () { + Navigator.of(dialogContext).pop(); + }); + }); + } else if (state + is SpaceModelLinkSuccess) { + Navigator.of(dialogContext).pop(); + Navigator.of(dialogContext).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext + successDialogContext) { + Future.delayed( + const Duration(seconds: 2), () { + Navigator.of(successDialogContext) + .pop(); + }); + + return const LinkingSuccessful(); + }, + ); + } + }, + child: LinkSpaceModelSpacesDialog( + spaceModel: model, + ), + ), + ); + }, + ); + }, + child: SvgPicture.asset( + Assets.spaceLinkIcon, + fit: BoxFit.contain, + ), + ), + InkWell( + onTap: () { + _showDeleteDialog(context); + }, + child: SvgPicture.asset( + Assets.deleteSpaceLinkIcon, + fit: BoxFit.contain, + ), + ), + ], ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + ], ), if (!showOnlyName) ...[ const SizedBox(height: 10), @@ -117,4 +265,26 @@ class SpaceModelCardWidget extends StatelessWidget { }, ); } + + void _showDeleteDialog(BuildContext context) { + showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext dialogContext) { + return DeleteSpaceModelDialog( + onConfirmDelete: () { + if (pageContext != null) { + pageContext!.read().add( + DeleteSpaceModel(spaceModelUuid: model.uuid ?? ''), + ); + + pageContext!.read().add( + DeleteSpaceModelFromCache(model.uuid ?? ''), + ); + } + }, + ); + }, + ); + } } diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index d8e27bec..4ebd65df 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/common/edit_chip.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/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/space_model/widgets/subspace_name_label_widget.dart'; @@ -10,9 +10,9 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SubspaceModelCreate extends StatefulWidget { final List subspaces; final void Function( - List newSubspaces, List? tags)? + List newSubspaces, List? tags)? onSpaceModelUpdate; - final List tags; + final List tags; const SubspaceModelCreate({ Key? key, @@ -28,7 +28,7 @@ class SubspaceModelCreate extends StatefulWidget { class _SubspaceModelCreateState extends State { late List _subspaces; String? errorSubspaceId; - late List _tags; + late List _tags; @override void initState() { @@ -117,7 +117,7 @@ class _SubspaceModelCreateState extends State { .where((s) => !updatedIds.contains(s.internalId)) .toList(); - final List tagsToAppendToSpace = []; + final List tagsToAppendToSpace = []; for (var s in deletedSubspaces) { if (s.tags != null) { diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index a07f9b29..fc6a8c88 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.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'; @@ -20,6 +21,7 @@ class TagChipDisplay extends StatelessWidget { final BuildContext? pageContext; final List? otherSpaceModels; final List? allSpaceModels; + final List projectTags; const TagChipDisplay(BuildContext context, {Key? key, @@ -31,14 +33,14 @@ class TagChipDisplay extends StatelessWidget { required this.spaceNameController, this.pageContext, this.otherSpaceModels, - this.allSpaceModels}) + this.allSpaceModels, + required this.projectTags}) : super(key: key); @override Widget build(BuildContext context) { return (spaceModel?.tags?.isNotEmpty == true || - spaceModel?.subspaceModels - ?.any((subspace) => subspace.tags?.isNotEmpty == true) == + spaceModel?.subspaceModels?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) ? SizedBox( width: screenWidth * 0.25, @@ -59,8 +61,7 @@ class TagChipDisplay extends StatelessWidget { // Combine tags from spaceModel and subspaces ...TagHelper.groupTags([ ...?spaceModel?.tags, - ...?spaceModel?.subspaceModels - ?.expand((subspace) => subspace.tags ?? []) + ...?spaceModel?.subspaceModels?.expand((subspace) => subspace.tags ?? []) ]).entries.map( (entry) => Chip( avatar: SizedBox( @@ -76,9 +77,7 @@ class TagChipDisplay extends StatelessWidget { style: Theme.of(context) .textTheme .bodySmall! - .copyWith( - color: - ColorsManager.spaceColor), + .copyWith(color: ColorsManager.spaceColor), ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( @@ -105,13 +104,12 @@ class TagChipDisplay extends StatelessWidget { spaceModel: spaceModel, otherSpaceModels: otherSpaceModels, initialTags: TagHelper.generateInitialTags( - subspaces: subspaces, - spaceTagModels: spaceModel?.tags ?? []), + subspaces: subspaces, spaceTagModels: spaceModel?.tags ?? []), title: 'Edit Device', - addedProducts: - TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], subspaces), + addedProducts: TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], subspaces), spaceName: spaceModel?.modelName ?? '', + projectTags: projectTags, )); }) ], @@ -134,6 +132,7 @@ class TagChipDisplay extends StatelessWidget { isCreate: true, spaceModel: spaceModel, otherSpaceModels: otherSpaceModels, + projectTags: projectTags, ), ); }, diff --git a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart index 45a6aaf7..0f40ddbb 100644 --- a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart +++ b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart @@ -15,11 +15,11 @@ class CenterBodyWidget extends StatelessWidget { context.read().add(CommunityStructureSelectedEvent()); } if (state is CommunityStructureState) { - context.read().add(BlankStateEvent()); + context.read().add(BlankStateEvent(context)); } if (state is SpaceModelState) { - context.read().add(SpaceModelLoadEvent()); + context.read().add(SpaceModelLoadEvent(context)); } return Container( diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart index f45471cd..c4e27051 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; abstract class AddDeviceModelState extends Equatable { const AddDeviceModelState(); @@ -15,7 +15,7 @@ class AddDeviceModelLoading extends AddDeviceModelState {} class AddDeviceModelLoaded extends AddDeviceModelState { final List selectedProducts; - final List initialTag; + final List initialTag; const AddDeviceModelLoaded({ required this.selectedProducts, diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart index 9b3a8b1e..b9018b2b 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart @@ -1,7 +1,7 @@ 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/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; abstract class AddDeviceTypeModelEvent extends Equatable { const AddDeviceTypeModelEvent(); @@ -25,7 +25,7 @@ class UpdateProductCountEvent extends AddDeviceTypeModelEvent { class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent { - final List initialTags; + final List initialTags; final List addedProducts; const InitializeDeviceTypeModel({ diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 9d0eac96..c0226ba8 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.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/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/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; @@ -20,7 +20,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? products; final List? initialSelectedProducts; final List? subspaces; - final List? spaceTagModels; + final List? spaceTagModels; final List? allTags; final String spaceName; final bool isCreate; @@ -28,6 +28,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final BuildContext? pageContext; final SpaceTemplateModel? spaceModel; final List? allSpaceModels; + final List projectTags; const AddDeviceTypeModelWidget( {super.key, @@ -41,7 +42,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { this.pageContext, this.otherSpaceModels, this.spaceModel, - this.allSpaceModels}); + this.allSpaceModels, + required this.projectTags}); @override Widget build(BuildContext context) { @@ -78,8 +80,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { const SizedBox(height: 16), Expanded( child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 20.0), + padding: const EdgeInsets.symmetric(horizontal: 20.0), child: ScrollableGridViewWidget( isCreate: isCreate, products: products, @@ -112,6 +113,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { allSpaceModels: allSpaceModels, products: products, allTags: allTags, + projectTags: projectTags, pageContext: pageContext, otherSpaceModels: otherSpaceModels, spaceModel: SpaceTemplateModel( @@ -137,6 +139,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { subspaces: subspaces, addedProducts: initialSelectedProducts ?? [], allTags: allTags, + projectTags: projectTags, spaceName: spaceName, initialTags: initialTags, otherSpaceModels: otherSpaceModels, @@ -149,11 +152,10 @@ class AddDeviceTypeModelWidget extends StatelessWidget { ), SizedBox( width: 140, - child: - BlocBuilder( + child: BlocBuilder( builder: (context, state) { - final isDisabled = state is AddDeviceModelLoaded && - state.selectedProducts.isEmpty; + final isDisabled = + state is AddDeviceModelLoaded && state.selectedProducts.isEmpty; return DefaultButton( backgroundColor: ColorsManager.secondaryColor, @@ -166,15 +168,13 @@ class AddDeviceTypeModelWidget extends StatelessWidget { : () async { if (state is AddDeviceModelLoaded && state.selectedProducts.isNotEmpty) { - final initialTags = - TagHelper.generateInitialTags( + final initialTags = TagHelper.generateInitialTags( spaceTagModels: spaceTagModels, subspaces: subspaces, ); - final dialogTitle = initialTags.isNotEmpty - ? 'Edit Device' - : 'Assign Tags'; + final dialogTitle = + initialTags.isNotEmpty ? 'Edit Device' : 'Assign Tags'; Navigator.of(context).pop(); await showDialog( context: context, @@ -184,6 +184,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { subspaces: subspaces, addedProducts: state.selectedProducts, allTags: allTags, + projectTags: projectTags, spaceName: spaceName, initialTags: initialTags, otherSpaceModels: otherSpaceModels, diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 655c6a83..25a0177f 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -9,10 +9,10 @@ import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_ import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; class DevicesManagementApi { - Future> fetchDevices(String communityId, String spaceId, String projectId) async { + Future> fetchDevices( + String communityId, String spaceId, String projectId) async { try { final response = await HTTPService().get( path: communityId.isNotEmpty && spaceId.isNotEmpty diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 2361dd1d..4eced226 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -227,6 +227,7 @@ class CommunitySpaceManagementApi { required Offset position, List? tags, List? subspaces, + String? spaceModelUuid, required String projectId}) async { try { final body = { @@ -238,6 +239,7 @@ class CommunitySpaceManagementApi { 'icon': icon, 'subspace': subspaces, 'tags': tags, + 'spaceModelUuid': spaceModelUuid, }; if (parentId != null) { body['parentUuid'] = parentId; diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index d9e295e0..5ae3e4d9 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -1,8 +1,8 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; class SpaceModelManagementApi { Future> listSpaceModels( @@ -33,8 +33,8 @@ class SpaceModelManagementApi { return response; } - Future updateSpaceModel(CreateSpaceTemplateBodyModel spaceModel, - String spaceModelUuid, String projectId) async { + Future updateSpaceModel( + CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid, String projectId) async { final response = await HTTPService().put( path: ApiEndpoints.updateSpaceModel .replaceAll('{projectId}', projectId) @@ -47,8 +47,7 @@ class SpaceModelManagementApi { return response; } - Future getSpaceModel( - String spaceModelUuid, String projectId) async { + Future getSpaceModel(String spaceModelUuid, String projectId) async { final response = await HTTPService().get( path: ApiEndpoints.getSpaceModel .replaceAll('{projectId}', projectId) @@ -60,4 +59,60 @@ class SpaceModelManagementApi { ); return response; } + + Future linkSpaceModel( + {required String spaceModelUuid, + required String projectId, + required List spaceUuids, + required bool isOverWrite}) async { + final response = await HTTPService().post( + path: ApiEndpoints.linkSpaceModel + .replaceAll('{projectId}', projectId) + .replaceAll('{spaceModelUuid}', spaceModelUuid), + showServerMessage: true, + body: {"spaceUuids": spaceUuids, "overwrite": isOverWrite}, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + Future validateSpaceModel(String projectId, List spaceUuids) async { + final response = await HTTPService().post( + path: ApiEndpoints.validateSpaceModel + .replaceAll('{projectId}', projectId), + showServerMessage: true, + body: {"spacesUuids": spaceUuids}, + expectedResponseModel: (json) { + return json; + }); + return response; + } + + Future deleteSpaceModel(String spaceModelUuid, String projectId) async { + final response = await HTTPService().delete( + path: ApiEndpoints.getSpaceModel + .replaceAll('{projectId}', projectId) + .replaceAll('{spaceModelUuid}', spaceModelUuid), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } + + Future> listTags({required String projectId}) async { + final response = await HTTPService().get( + path: ApiEndpoints.listTags.replaceAll('{projectId}', projectId), + expectedResponseModel: (json) { + List jsonData = json['data']; + return jsonData.map((jsonItem) { + return Tag.fromJson(jsonItem); + }).toList(); + }, + ); + return response; + } } diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 4d3dbb0c..a4bcc0da 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -71,4 +71,5 @@ abstract class ColorsManager { static const Color lightGrayBorderColor = Color(0xB2D5D5D5); //background: #F8F8F8; static const Color vividBlue = Color(0xFF023DFE); + static const Color semiTransparentRed = Color(0x99FF0000); } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 32dfcc4c..ee848d3f 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -89,6 +89,13 @@ abstract class ApiEndpoints { static const String createSpaceModel = '/projects/{projectId}/space-models'; static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; static const String updateSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; + //tag + static const String listTags = '/projects/{projectId}/tags'; + + static const String linkSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}/spaces/link'; + + static const String validateSpaceModel = '/projects/{projectId}/spaces/validate'; static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index d5d216c5..b7a0115f 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -258,6 +258,7 @@ class Assets { static const String doorSensor = 'assets/icons/door_sensor.svg'; static const String delete = 'assets/icons/delete.svg'; + static const String deleteSpaceModel = 'assets/icons/delete_space_model.svg'; static const String edit = 'assets/icons/edit.svg'; static const String editSpace = 'assets/icons/edit_space.svg'; //assets/icons/routine/tab_to_run.svg @@ -401,5 +402,10 @@ class Assets { static const String link = 'assets/icons/link.svg'; static const String duplicate = 'assets/icons/duplicate.svg'; static const String spaceDelete = 'assets/icons/space_delete.svg'; + + static const String deleteSpaceLinkIcon = + 'assets/icons/delete_space_link_icon.svg'; + static const String spaceLinkIcon = 'assets/icons/space_link_icon.svg'; + static const String successIcon = 'assets/icons/success_icon.svg'; + } -//user_management.svg diff --git a/lib/utils/responsive_layout.dart b/lib/utils/responsive_layout.dart index efc1600b..c49fa0d9 100644 --- a/lib/utils/responsive_layout.dart +++ b/lib/utils/responsive_layout.dart @@ -1,18 +1,37 @@ import 'package:flutter/material.dart'; class ResponsiveLayout extends StatelessWidget { - final Widget desktopBody; final Widget mobileBody; - const ResponsiveLayout( - {super.key, required this.desktopBody, required this.mobileBody}); + final Widget? tablet; + 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 Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { - if (constraints.maxWidth < 600) { - return mobileBody; - } else { + if (constraints.maxWidth >= 1100) { return desktopBody; + } else if (constraints.maxWidth >= 650) { + return tablet!; + } else { + return mobileBody; } }, ); diff --git a/lib/utils/theme/responsive_text_theme.dart b/lib/utils/theme/responsive_text_theme.dart new file mode 100644 index 00000000..f5daed7f --- /dev/null +++ b/lib/utils/theme/responsive_text_theme.dart @@ -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 { + final TextStyle deviceManagementTitle; + + ResponsiveTextTheme({ + required this.deviceManagementTitle, + }); + + @override + ThemeExtension copyWith() => this; + + @override + ThemeExtension lerp( + ThemeExtension? other, double t) => + this; + + static ResponsiveTextTheme of(BuildContext context) { + final isMobile = ResponsiveLayout.isMobile(context); + return Theme.of(context).extension() ?? + ResponsiveTextTheme( + deviceManagementTitle: TextStyle( + fontSize: isMobile ? 20 : 30, + fontWeight: FontWeight.w700, + color: ColorsManager.whiteColors), + ); + } +} diff --git a/lib/web_layout/web_app_bar.dart b/lib/web_layout/web_app_bar.dart index 777b0931..02b81522 100644 --- a/lib/web_layout/web_app_bar.dart +++ b/lib/web_layout/web_app_bar.dart @@ -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/utils/color_manager.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'; -class WebAppBar extends StatefulWidget { +class WebAppBar extends StatelessWidget { final Widget? title; final Widget? centerBody; final Widget? rightBody; @@ -16,178 +16,234 @@ class WebAppBar extends StatefulWidget { const WebAppBar({super.key, this.title, this.centerBody, this.rightBody}); @override - State createState() => _WebAppBarState(); + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final user = context.read().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 with HelperResponsiveLayout { - @override - void initState() { - super.initState(); - } +class DesktopAppBar extends StatelessWidget { + final Widget? title; + final Widget? centerBody; + final Widget? rightBody; + final dynamic user; + + const DesktopAppBar({ + super.key, + this.title, + this.centerBody, + this.rightBody, + required this.user, + }); @override Widget build(BuildContext context) { - bool isSmallScreen = isSmallScreenSize(context); - bool isHalfMediumScreen = isHafMediumScreenSize(context); - return BlocBuilder(builder: (context, state) { - final user = context.read().user; - return Container( - height: (isSmallScreen || isHalfMediumScreen) ? 130 : 100, - decoration: const BoxDecoration(color: ColorsManager.secondaryColor), - padding: const EdgeInsets.all(10), - child: isSmallScreen || isHalfMediumScreen - ? 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, - 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: [ - Expanded( - child: Row( - children: [ - widget.title!, - if (widget.centerBody != null) - Padding( - padding: const EdgeInsets.only(left: 80), - child: widget.centerBody!, - ), - ], - ), + return Container( + height: 100, + decoration: const BoxDecoration(color: ColorsManager.secondaryColor), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + if (title != null) title!, + if (centerBody != null) + Padding( + padding: const EdgeInsets.only(left: 80), + child: centerBody!, ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.rightBody != null) - Align( - alignment: Alignment.centerRight, - child: widget.rightBody, - ), - 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) - Text( - '${user.firstName} ${user.lastName}', - style: Theme.of(context).textTheme.bodyLarge, - ), - const SizedBox( - width: 10, - ), - UserDropdownMenu(user: user), - // GestureDetector( - // onTap: () { - // showCustomDialog( - // context: context, - // barrierDismissible: true, - // title: 'Logout', - // message: 'Are you sure you want to logout?', - // actions: [ - // GestureDetector( - // onTap: () { - // AuthBloc.logout(); - // context.go(RoutesConst.auth); - // }, - // child: DefaultButton( - // child: Text( - // 'Ok', - // style: Theme.of(context) - // .textTheme - // .bodyMedium! - // .copyWith(fontSize: 12, color: Colors.white), - // ), - // ), - // ), - // const SizedBox( - // height: 10, - // ), - // GestureDetector( - // onTap: () { - // context.pop(); - // }, - // child: DefaultButton( - // child: Text( - // 'Cancel', - // style: Theme.of(context) - // .textTheme - // .bodyMedium! - // .copyWith(fontSize: 12, color: Colors.white), - // ), - // ), - // ), - // ], - // ); - // }, - // child: const Icon( - // Icons.logout, - // color: ColorsManager.whiteColors, - // ), - // ) - ], - ), - ], - ), - ); - }); + ], + ), + ), + _buildUserSection(context), + ], + ), + ); + } + + Widget _buildUserSection(BuildContext context) { + return Row( + children: [ + if (rightBody != null) rightBody!, + const SizedBox(width: 24), + _UserAvatar(), + const SizedBox(width: 12), + if (user != null) + Text( + '${user.firstName} ${user.lastName}', + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(width: 12), + UserDropdownMenu(user: user), + ], + ); + } +} + +class TabletAppBar extends StatelessWidget { + final Widget? title; + final Widget? centerBody; + final Widget? rightBody; + final dynamic user; + + const TabletAppBar({ + super.key, + this.title, + this.centerBody, + this.rightBody, + required this.user, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: 100, + decoration: const BoxDecoration(color: ColorsManager.secondaryColor), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (title != null) Expanded(child: title!), + _buildUserSection(context), + ], + ), + if (centerBody != null) Expanded(child: centerBody!), + ], + ), + ); + } + + Widget _buildUserSection(BuildContext context) { + return Row( + children: [ + if (rightBody != null) rightBody!, + const SizedBox(width: 16), + _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, + ), + ), + ), + ); } } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 51aae316..e6f3527d 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,20 @@ import FlutterMacOS import Foundation +import firebase_analytics +import firebase_core +import firebase_crashlytics +import firebase_database import flutter_secure_storage_macos import path_provider_foundation import shared_preferences_foundation import url_launcher_macos 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")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index eec16af6..7bfb45ff 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ /* Begin PBXBuildFile section */ 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 */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.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 = ""; }; 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 996A2A515D007C9FED5396A5 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; 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 */, D73912EC22F37F3D000D13A0 /* Frameworks */, 75DCDFECC7757C5159E8F0C5 /* Pods */, + 996A2A515D007C9FED5396A5 /* GoogleService-Info.plist */, ); sourceTree = ""; }; @@ -241,6 +244,7 @@ 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */, + 7E188D2155D07A3E9E027C0F /* FlutterFire: "flutterfire upload-crashlytics-symbols" */, ); buildRules = ( ); @@ -317,6 +321,7 @@ files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + 2901225E5FAB0C696EE79F77 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -361,6 +366,24 @@ shellPath = /bin/sh; 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 */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/macos/Runner/GoogleService-Info.plist b/macos/Runner/GoogleService-Info.plist new file mode 100644 index 00000000..9cdebed0 --- /dev/null +++ b/macos/Runner/GoogleService-Info.plist @@ -0,0 +1,32 @@ + + + + + API_KEY + AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw + GCM_SENDER_ID + 427332280600 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.syncrowWeb + PROJECT_ID + test2-8a3d2 + STORAGE_BUCKET + test2-8a3d2.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:427332280600:ios:14346b200780dc760c7e6d + DATABASE_URL + https://test2-8a3d2-default-rtdb.firebaseio.com + + \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 7833f1ec..a0cbfaad 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: e051259913915ea5bc8fe18664596bea08592fd123930605d562969cd7315fcd + url: "https://pub.dev" + source: hosted + version: "1.3.51" args: dependency: transitive description: @@ -153,6 +161,94 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -572,10 +668,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936" + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.2" shared_preferences_windows: dependency: transitive description: @@ -777,10 +873,10 @@ packages: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.1.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f4108d5c..fd7ed797 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,6 +56,11 @@ dependencies: number_pagination: ^1.1.6 url_launcher: ^6.2.5 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: flutter_test: diff --git a/web/index.html b/web/index.html index a8b2aa25..d099103c 100644 --- a/web/index.html +++ b/web/index.html @@ -40,6 +40,8 @@ + + diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 2048c455..0e0afee0 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,10 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index de626cc8..9efea82a 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + firebase_core flutter_secure_storage_windows url_launcher_windows )