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