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_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/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/dialog_textfield_dropdown.dart index 807f3417..ac88e5dc 100644 --- a/lib/common/dialog_textfield_dropdown.dart +++ b/lib/common/dialog_textfield_dropdown.dart @@ -20,15 +20,22 @@ class DialogTextfieldDropdown extends StatefulWidget { class _DialogTextfieldDropdownState extends State { bool _isOpen = false; - late OverlayEntry _overlayEntry; + OverlayEntry? _overlayEntry; final TextEditingController _controller = TextEditingController(); - late List _filteredItems; // Filtered items list + final FocusNode _focusNode = FocusNode(); + List _filteredItems = []; @override void initState() { super.initState(); - _controller.text = widget.initialValue ?? 'Select Tag'; - _filteredItems = List.from(widget.items); // Initialize filtered items + _controller.text = widget.initialValue ?? ''; + _filteredItems = List.from(widget.items); + + _focusNode.addListener(() { + if (!_focusNode.hasFocus) { + _closeDropdown(); + } + }); } void _toggleDropdown() { @@ -41,13 +48,16 @@ class _DialogTextfieldDropdownState extends State { void _openDropdown() { _overlayEntry = _createOverlayEntry(); - Overlay.of(context).insert(_overlayEntry); + Overlay.of(context).insert(_overlayEntry!); _isOpen = true; } void _closeDropdown() { - _overlayEntry.remove(); - _isOpen = false; + if (_isOpen && _overlayEntry != null) { + _overlayEntry!.remove(); + _overlayEntry = null; + _isOpen = false; + } } OverlayEntry _createOverlayEntry() { @@ -58,9 +68,7 @@ class _DialogTextfieldDropdownState extends State { return OverlayEntry( builder: (context) { return GestureDetector( - onTap: () { - _closeDropdown(); - }, + onTap: _closeDropdown, behavior: HitTestBehavior.translucent, child: Stack( children: [ @@ -72,40 +80,44 @@ class _DialogTextfieldDropdownState extends State { elevation: 4.0, child: Container( color: ColorsManager.whiteColors, - constraints: const BoxConstraints( - maxHeight: 200.0, - ), - child: ListView.builder( - shrinkWrap: true, - itemCount: _filteredItems.length, - itemBuilder: (context, index) { - final item = _filteredItems[index]; - return Container( - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: ColorsManager.lightGrayBorderColor, - width: 1.0, + constraints: const BoxConstraints(maxHeight: 200.0), + child: StatefulBuilder( + builder: (context, setStateDropdown) { + return ListView.builder( + shrinkWrap: true, + itemCount: _filteredItems.length, + itemBuilder: (context, index) { + final item = _filteredItems[index]; + + return Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: ColorsManager.lightGrayBorderColor, + width: 1.0, + ), + ), ), - ), - ), - child: ListTile( - title: Text(item, - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith( - color: ColorsManager.textPrimaryColor)), - onTap: () { - _controller.text = item; - widget.onSelected(item); - setState(() { - _filteredItems - .remove(item); // Remove selected item - }); - _closeDropdown(); - }, - ), + child: ListTile( + title: Text(item, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: ColorsManager + .textPrimaryColor)), + onTap: () { + _controller.text = item; + widget.onSelected(item); + setState(() { + _filteredItems + .remove(item); // Remove selected item + }); + _closeDropdown(); + }, + ), + ); + }, ); }, ), @@ -122,7 +134,8 @@ class _DialogTextfieldDropdownState extends State { @override Widget build(BuildContext context) { return GestureDetector( - onTap: _toggleDropdown, + onTap: () => FocusScope.of(context).unfocus(), + behavior: HitTestBehavior.opaque, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), decoration: BoxDecoration( @@ -135,23 +148,26 @@ class _DialogTextfieldDropdownState extends State { Expanded( child: TextFormField( controller: _controller, - onChanged: (value) { - setState(() { - _filteredItems = widget.items - .where((item) => - item.toLowerCase().contains(value.toLowerCase())) - .toList(); // Filter items dynamically - }); + focusNode: _focusNode, + onFieldSubmitted: (value) { widget.onSelected(value); + _closeDropdown(); + }, + onTapOutside: (event) { + widget.onSelected(_controller.text); + _closeDropdown(); }, style: Theme.of(context).textTheme.bodyMedium, decoration: const InputDecoration( - hintText: 'Enter or Select tag', + hintText: 'Enter or Select a tag', border: InputBorder.none, ), ), ), - const Icon(Icons.arrow_drop_down), + GestureDetector( + onTap: _toggleDropdown, + child: const Icon(Icons.arrow_drop_down), + ), ], ), ), 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..20b0311e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,11 @@ +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/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 +21,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 +56,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/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index c60b4bb2..752b3255 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -18,6 +18,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 +34,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 c2aa01a4..b22dae7b 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -10,9 +10,12 @@ import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/model/token.dart'; import 'package:syncrow_web/pages/auth/model/user_model.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/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 { @@ -32,8 +35,7 @@ class AuthBloc extends Bloc { ////////////////////////////// forget password ////////////////////////////////// final TextEditingController forgetEmailController = TextEditingController(); - final TextEditingController forgetPasswordController = - TextEditingController(); + final TextEditingController forgetPasswordController = TextEditingController(); final TextEditingController forgetOtp = TextEditingController(); final forgetFormKey = GlobalKey(); final forgetEmailKey = GlobalKey(); @@ -50,8 +52,7 @@ class AuthBloc extends Bloc { return; } _remainingTime = 1; - add(UpdateTimerEvent( - remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); try { forgetEmailValidate = ''; _remainingTime = (await AuthenticationAPI.sendOtp( @@ -88,8 +89,7 @@ class AuthBloc extends Bloc { _timer?.cancel(); add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); } else { - add(UpdateTimerEvent( - remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); } }); } @@ -99,8 +99,7 @@ class AuthBloc extends Bloc { emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); } - Future changePassword( - ChangePasswordEvent event, Emitter emit) async { + Future changePassword(ChangePasswordEvent event, Emitter emit) async { emit(LoadingForgetState()); try { var response = await AuthenticationAPI.verifyOtp( @@ -116,8 +115,7 @@ class AuthBloc extends Bloc { } } on DioException catch (e) { final errorData = e.response!.data; - String errorMessage = - errorData['error']['message'] ?? 'something went wrong'; + String errorMessage = errorData['error']['message'] ?? 'something went wrong'; validate = errorMessage; emit(AuthInitialState()); } @@ -131,9 +129,7 @@ class AuthBloc extends Bloc { } void _onUpdateTimer(UpdateTimerEvent event, Emitter emit) { - emit(TimerState( - isButtonEnabled: event.isButtonEnabled, - remainingTime: event.remainingTime)); + emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime)); } ///////////////////////////////////// login ///////////////////////////////////// @@ -183,15 +179,13 @@ class AuthBloc extends Bloc { if (token.accessTokenIsNotEmpty) { FlutterSecureStorage storage = const FlutterSecureStorage(); - await storage.write( - key: Token.loginAccessTokenKey, value: token.accessToken); + await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken); const FlutterSecureStorage().write( key: UserModel.userUuidKey, value: Token.decodeToken(token.accessToken)['uuid'].toString()); user = UserModel.fromToken(token); loginEmailController.clear(); loginPasswordController.clear(); - debugPrint("token " + token.accessToken); emit(LoginSuccess()); } else { emit(const LoginFailure(error: 'Something went wrong')); @@ -342,14 +336,12 @@ class AuthBloc extends Bloc { static Future getTokenAndValidate() async { try { const storage = FlutterSecureStorage(); - final firstLaunch = await SharedPreferencesHelper.readBoolFromSP( - StringsManager.firstLaunch) ?? - true; + final firstLaunch = + await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true; if (firstLaunch) { storage.deleteAll(); } - await SharedPreferencesHelper.saveBoolToSP( - StringsManager.firstLaunch, false); + await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false); final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; if (value.isEmpty) { return 'Token not found'; @@ -402,9 +394,7 @@ class AuthBloc extends Bloc { final String formattedTime = [ if (days > 0) '${days}d', // Append 'd' for days if (days > 0 || hours > 0) - hours - .toString() - .padLeft(2, '0'), // Show hours if there are days or hours + hours.toString().padLeft(2, '0'), // Show hours if there are days or hours minutes.toString().padLeft(2, '0'), seconds.toString().padLeft(2, '0'), ].join(':'); @@ -445,6 +435,7 @@ class AuthBloc extends Bloc { static Future logout(BuildContext context) async { final storage = FlutterSecureStorage(); ProjectManager.clearProjectUUID(); + context.read().add(ClearAllData()); storage.deleteAll(); } } 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/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index 1bc4d071..81a21046 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 @@ -10,6 +10,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 +26,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 +48,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 +61,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 +80,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 +96,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 f23e87ed..584a6e17 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -2,20 +2,19 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:go_router/go_router.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -// import 'package:graphview/GraphView.dart'; import 'package:syncrow_web/pages/auth/model/user_model.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/home_model/home_item_model.dart'; import 'package:syncrow_web/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/services/home_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/routes_const.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'; class HomeBloc extends Bloc { // final Graph graph = Graph()..isTree = true; @@ -52,12 +51,12 @@ class HomeBloc extends Bloc { Future _fetchUserInfo(FetchUserInfo event, Emitter emit) async { try { - var uuid = - await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); if (user != null && user!.project != null) { await ProjectManager.setProjectUUID(user!.project!.uuid); + NavigationService.navigatorKey.currentContext!.read().add(InitialEvent()); } add(FetchTermEvent()); add(FetchPolicyEvent()); @@ -73,8 +72,6 @@ class HomeBloc extends Bloc { emit(LoadingHome()); terms = await HomeApi().fetchTerms(); emit(HomeInitial()); - - // emit(PolicyAgreement()); } catch (e) { return; } @@ -84,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"); @@ -93,12 +88,10 @@ class HomeBloc extends Bloc { } } - Future _confirmUserAgreement( - ConfirmUserAgreementEvent event, Emitter emit) async { + Future _confirmUserAgreement(ConfirmUserAgreementEvent event, Emitter emit) async { try { emit(LoadingHome()); - var uuid = - await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); policy = await HomeApi().confirmUserAgreements(uuid); emit(PolicyAgreement()); } catch (e) { @@ -118,10 +111,11 @@ class HomeBloc extends Bloc { List homeItems = [ HomeItemModel( - title: 'Access', + title: 'Access Management', icon: Assets.accessIcon, active: true, onPress: (context) { + context.read().add(ClearCachedData()); context.go(RoutesConst.accessManagementPage); }, color: null, @@ -131,21 +125,24 @@ class HomeBloc extends Bloc { icon: Assets.spaseManagementIcon, active: true, onPress: (context) { + context.read().add(ClearCachedData()); context.go(RoutesConst.spacesManagementPage); }, color: ColorsManager.primaryColor, ), HomeItemModel( - title: 'Devices', + title: 'Devices Management', icon: Assets.devicesIcon, active: true, onPress: (context) { + context.read().add(ClearCachedData()); BlocProvider.of(context) .add(const TriggerSwitchTabsEvent(isRoutineTab: false)); context.go(RoutesConst.deviceManagementPage); }, 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/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 a94756f1..b5455646 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'; @@ -25,8 +27,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( @@ -59,8 +60,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, @@ -84,15 +84,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 @@ -160,6 +157,7 @@ class UsersPage extends StatelessWidget { const SizedBox(width: 20), InkWell( onTap: () { + context.read().add(ClearCachedData()); showDialog( context: context, barrierDismissible: false, @@ -198,14 +196,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()); }, ); } @@ -214,9 +208,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( @@ -256,9 +249,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, @@ -278,10 +270,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; @@ -296,14 +287,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()); }, ); } @@ -312,9 +299,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, @@ -352,9 +338,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, @@ -391,14 +376,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()); }, ); } @@ -425,23 +406,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, ), @@ -452,12 +427,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) { @@ -478,13 +453,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; @@ -514,20 +486,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/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/space_tree/bloc/space_tree_bloc.dart b/lib/pages/space_tree/bloc/space_tree_bloc.dart index 3325a721..e7fb4dc1 100644 --- a/lib/pages/space_tree/bloc/space_tree_bloc.dart +++ b/lib/pages/space_tree/bloc/space_tree_bloc.dart @@ -1,4 +1,3 @@ -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_event.dart'; @@ -6,18 +5,8 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.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/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 SpaceTreeBloc extends Bloc { - // String selectedCommunityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9'; - // String selectedSpaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; - final TextEditingController textController = TextEditingController(); - - // String selectedCommunityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9'; - // String selectedSpaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; - SpaceTreeBloc() : super(const SpaceTreeState()) { on(_fetchSpaces); on(_onCommunityExpanded); @@ -25,6 +14,8 @@ class SpaceTreeBloc extends Bloc { on(_onCommunitySelected); on(_onSpaceSelected); on(_onSearch); + on(_clearAllData); + on(_clearCachedData); } _fetchSpaces(InitialEvent event, Emitter emit) async { @@ -37,8 +28,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, @@ -53,19 +44,15 @@ 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 { + _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); @@ -97,17 +84,14 @@ 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); @@ -115,14 +99,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, @@ -138,12 +124,11 @@ 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; @@ -158,16 +143,17 @@ class SpaceTreeBloc extends Bloc { !updatedSoldChecks.contains(event.spaceId)) { // First click: Select the space and all its children updatedSelectedSpaces.add(event.spaceId); - updatedSelectedCommunities.add(event.communityId); + updatedSelectedCommunities.add(event.communityModel.uuid); + selectedSpacesInCommunity.add(event.spaceId); + if (childrenIds.isNotEmpty) { updatedSelectedSpaces.addAll(childrenIds); + selectedSpacesInCommunity.addAll(childrenIds); } - List spaces = - _getThePathToChild(event.communityId, 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); } } @@ -175,29 +161,42 @@ 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.communityId, event.spaceId); - if (!_parentSelected(parents, updatedSelectedSpaces)) { + _getThePathToChild(event.communityModel.uuid, event.spaceId).toSet().toList(); + + if (updatedSelectedSpaces.isEmpty) { updatedSoldChecks.removeWhere(parents.contains); - } - if (!_anySpacesSelectedInCommunity( - event.communityId, updatedSelectedSpaces, updatedSoldChecks)) { - updatedSelectedCommunities.remove(event.communityId); + updatedSelectedCommunities.remove(event.communityModel.uuid); + } else { + // Check if any parent has selected children + for (String space in parents) { + if (!_noChildrenSelected(event.communityModel, space, updatedSelectedSpaces, parents)) { + updatedSoldChecks.remove(space); + } + } + + if (!_anySpacesSelectedInCommunity( + event.communityModel, updatedSelectedSpaces, updatedSoldChecks)) { + updatedSelectedCommunities.remove(event.communityModel.uuid); + } } } - communityAndSpaces[event.communityId] = updatedSelectedSpaces; + communityAndSpaces[event.communityModel.uuid] = selectedSpacesInCommunity; emit(state.copyWith( selectedCommunities: updatedSelectedCommunities.toSet().toList(), @@ -210,12 +209,24 @@ class SpaceTreeBloc extends Bloc { } } - _parentSelected(List parents, List selectedSpaces) { - for (String space in parents) { - if (selectedSpaces.contains(space)) { - return true; + _noChildrenSelected( + CommunityModel community, String spaceId, List selectedSpaces, List parents) { + if (selectedSpaces.contains(spaceId)) { + return true; + } + + List children = _getAllChildSpaces(community.spaces); + for (var child in children) { + if (spaceId == child.uuid) { + List ids = _getAllChildIds(child.children); + for (var id in ids) { + if (selectedSpaces.contains(id)) { + return true; + } + } } } + return false; } @@ -226,11 +237,10 @@ 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(); @@ -244,11 +254,47 @@ class SpaceTreeBloc extends Bloc { } } + _clearAllData(ClearAllData event, Emitter emit) async { + try { + emit(state.copyWith( + communitiesList: [], + filteredCommunity: [], + isSearching: false, + soldCheck: [], + selectedSpaces: [], + selectedCommunities: [], + selectedCommunityAndSpaces: {}, + searchQuery: '', + expandedSpaces: [], + expandedCommunity: [])); + } catch (e) { + emit(const SpaceTreeErrorState('Something went wrong')); + } + } + + _clearCachedData(ClearCachedData event, Emitter emit) async { + try { + emit(state.copyWith( + communitiesList: state.communityList, + filteredCommunity: [], + isSearching: false, + soldCheck: [], + selectedSpaces: [], + selectedCommunities: [], + selectedCommunityAndSpaces: {}, + searchQuery: '', + expandedSpaces: [], + expandedCommunity: [])); + } catch (e) { + emit(const SpaceTreeErrorState('Something went wrong')); + } + } + // 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; } @@ -259,22 +305,26 @@ class SpaceTreeBloc extends Bloc { ids.add(child.uuid!); ids.addAll(_getAllChildIds(child.children)); } - return ids; + return ids.toSet().toList(); } - bool _anySpacesSelectedInCommunity(String communityId, - List selectedSpaces, List partialCheckedList) { + List _getAllChildSpaces(List spaces) { + List children = []; + for (var child in spaces) { + children.add(child); + children.addAll(_getAllChildSpaces(child.children)); + } + return children; + } + + bool _anySpacesSelectedInCommunity( + CommunityModel community, List selectedSpaces, List partialCheckedList) { bool result = false; - for (var community in state.communityList) { - if (community.uuid == communityId) { - List ids = _getAllChildIds(community.spaces); - for (var id in ids) { - result = - selectedSpaces.contains(id) || partialCheckedList.contains(id); - if (result) { - return result; - } - } + List ids = _getAllChildIds(community.spaces); + for (var id in ids) { + result = selectedSpaces.contains(id) || partialCheckedList.contains(id); + if (result) { + return result; } } return result; @@ -297,8 +347,7 @@ 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 ?? ''); @@ -322,7 +371,6 @@ class SpaceTreeBloc extends Bloc { @override Future close() async { - textController.dispose(); super.close(); } } diff --git a/lib/pages/space_tree/bloc/space_tree_event.dart b/lib/pages/space_tree/bloc/space_tree_event.dart index 7b1b550c..e8fa996f 100644 --- a/lib/pages/space_tree/bloc/space_tree_event.dart +++ b/lib/pages/space_tree/bloc/space_tree_event.dart @@ -1,4 +1,5 @@ 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/space_model.dart'; class SpaceTreeEvent extends Equatable { @@ -49,14 +50,14 @@ class OnSpaceExpanded extends SpaceTreeEvent { } class OnSpaceSelected extends SpaceTreeEvent { - final String communityId; final String spaceId; final List children; + final CommunityModel communityModel; - const OnSpaceSelected(this.communityId, this.spaceId, this.children); + const OnSpaceSelected(this.communityModel, this.spaceId, this.children); @override - List get props => [communityId, spaceId, children]; + List get props => [communityModel, spaceId, children]; } class SearchQueryEvent extends SpaceTreeEvent { @@ -67,3 +68,7 @@ class SearchQueryEvent extends SpaceTreeEvent { @override List get props => [searchQuery]; } + +class ClearAllData extends SpaceTreeEvent {} + +class ClearCachedData extends SpaceTreeEvent {} diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 3954f200..9e5f9725 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -172,11 +172,8 @@ class _SpaceTreeViewState extends State { .expandedSpaces .contains(space.uuid), onItemSelected: () { - context - .read() - .add(OnSpaceSelected( - community.uuid, - space.uuid ?? '', + context.read().add( + OnSpaceSelected(community, space.uuid ?? '', space.children)); widget.onSelect(); }, @@ -196,10 +193,7 @@ class _SpaceTreeViewState extends State { isSoldCheck: state.soldCheck .contains(space.uuid), children: _buildNestedSpaces( - context, - state, - space, - community.uuid), + context, state, space, community), ); }).toList(), ), @@ -281,8 +275,8 @@ class _SpaceTreeViewState extends State { }); } - List _buildNestedSpaces(BuildContext context, SpaceTreeState state, - SpaceModel space, String communityId) { + List _buildNestedSpaces( + BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) { return space.children.map((child) { return CustomExpansionTileSpaceTree( isSelected: state.selectedSpaces.contains(child.uuid) || @@ -291,16 +285,15 @@ class _SpaceTreeViewState extends State { title: child.name, isExpanded: state.expandedSpaces.contains(child.uuid), onItemSelected: () { - context.read().add( - OnSpaceSelected(communityId, child.uuid ?? '', child.children)); + context + .read() + .add(OnSpaceSelected(community, child.uuid ?? '', child.children)); widget.onSelect(); }, onExpansionChanged: () { - context - .read() - .add(OnSpaceExpanded(communityId, child.uuid ?? '')); + context.read().add(OnSpaceExpanded(community.uuid, child.uuid ?? '')); }, - children: _buildNestedSpaces(context, state, child, communityId), + children: _buildNestedSpaces(context, state, child, community), ); }).toList(); } 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..42eb3ab7 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 @@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/spaces_management/structure_selector/view/cent import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/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 { @@ -39,8 +40,10 @@ class SpaceManagementPageState extends State { ), ], 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(), 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..16ba41c0 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 @@ -4,9 +4,6 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model 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; @@ -17,6 +14,7 @@ class SpaceModelBloc extends Bloc { }) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) { on(_onCreateSpaceModel); on(_onUpdateSpaceModel); + on(_onDeleteSpaceModel); } Future _onCreateSpaceModel( @@ -50,7 +48,7 @@ class SpaceModelBloc extends Bloc { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final newSpaceModel = - await api.getSpaceModel(event.spaceModelUuid ?? '', projectUuid); + await api.getSpaceModel(event.spaceModelUuid, projectUuid); if (newSpaceModel != null) { final updatedSpaceModels = currentState.spaceModels.map((model) { return model.uuid == event.spaceModelUuid ? newSpaceModel : model; @@ -62,4 +60,28 @@ class SpaceModelBloc extends Bloc { } } } + + 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(); + + emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); + } + } catch (e) { + emit(SpaceModelError(message: e.toString())); + } + } + } } 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/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 8f466a5e..35611868 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 @@ -90,7 +90,11 @@ class SpaceModelPage extends StatelessWidget { }, child: Container( margin: const EdgeInsets.all(8.0), - child: SpaceModelCardWidget(model: model), + child: SpaceModelCardWidget( + model: model, + pageContext: context, + topActionsDisabled: false, + ), )); }, ), 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/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index c563a6e2..51386cb4 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 @@ -10,6 +10,9 @@ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/l import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_attention_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/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/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'; @@ -17,8 +20,15 @@ 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) { @@ -193,7 +203,48 @@ class SpaceModelCardWidget extends StatelessWidget { ), ), ], - ) + ), + Expanded( + child: Text( + model.modelName, + style: + Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (!topActionsDisabled) + GestureDetector( + onTap: () => _showDeleteDialog(context), + child: Container( + width: 36, // Adjust size as needed + height: 36, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 2), + ), + ], + ), + child: Center( + child: SvgPicture.asset( + Assets.deleteSpaceModel, // Your actual SVG path + width: 20, + height: 20, + colorFilter: const ColorFilter.mode( + Colors.grey, BlendMode.srcIn), + ), + ), + ), + ), ], ), if (!showOnlyName) ...[ @@ -253,4 +304,22 @@ 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 ?? ''), + ); + } + }, + ); + }, + ); + } } diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 4af8caa9..d4fa06f4 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -86,12 +86,23 @@ class SpaceModelManagementApi { Future validateSpaceModel(String projectId, List spaceUuids) async { final response = await HTTPService().post( - path: - ApiEndpoints.validateSpaceModel.replaceAll('{projectId}', projectId), + path: ApiEndpoints.validateSpaceModel + .replaceAll('{projectId}', projectId), + showServerMessage: true, + body: {"spacesUuids": spaceUuids}, + expectedResponseModel: (json) { + return json; + }); + } + + Future deleteSpaceModel(String spaceModelUuid, String projectId) async { + final response = await HTTPService().delete( + path: ApiEndpoints.getSpaceModel + .replaceAll('{projectId}', projectId) + .replaceAll('{spaceModelUuid}', spaceModelUuid), showServerMessage: true, - body: {"spacesUuids": spaceUuids}, expectedResponseModel: (json) { - return json; + return json['success'] ?? false; }, ); return response; diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index a3fb2d96..3f02663d 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -14,7 +14,7 @@ class UserPermissionApi { Future> fetchUsers(String projectId) async { try { final response = await _httpService.get( - path: ApiEndpoints.getUsers.replaceAll('{projectUuid}', projectId), + path: ApiEndpoints.getUsers.replaceAll('{projectId}', projectId), showServerMessage: true, expectedResponseModel: (json) { debugPrint('fetchUsers Response: $json'); diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 63456ff7..4e506093 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 d3cd6001..630f59c0 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -29,7 +29,7 @@ abstract class ApiEndpoints { ////// Devices Management //////////////// - static const String getAllDevices = '/projects/{projectId}/device'; + static const String getAllDevices = '/projects/{projectId}/devices'; static const String getSpaceDevices = '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices'; static const String getDeviceStatus = '/device/{uuid}/functions/status'; @@ -120,8 +120,8 @@ abstract class ApiEndpoints { static const String inviteUser = '/invite-user'; static const String checkEmail = '/invite-user/check-email'; - static const String getUsers = '/projects/{projectUuid}/user'; - static const String getUserById = '/projects/{projectUuid}/user/{userUuid}'; + static const String getUsers = '/projects/{projectId}/user'; + static const String getUserById = '/projects/{projectId}/user/{userUuid}'; static const String editUser = '/invite-user/{inviteUserUuid}'; static const String deleteUser = '/invite-user/{inviteUserUuid}'; static const String changeUserStatus = diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 79efdaaf..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 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 )