diff --git a/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml b/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml index a600bd1e..95e9346d 100644 --- a/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml +++ b/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml @@ -57,4 +57,4 @@ jobs: uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_BUSH_01E607F10 }} - action: "close" + action: "close" \ No newline at end of file diff --git a/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml b/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml new file mode 100644 index 00000000..e28d1bb2 --- /dev/null +++ b/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml @@ -0,0 +1,60 @@ +name: Azure Static Web Apps CI/CD + +on: + push: + branches: + - dev + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - dev + +jobs: + build_and_deploy_job: + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + name: Build and Deploy Job + steps: + + - name: Checkout Code + uses: actions/checkout@v3 + with: + submodules: true + lfs: false + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.22.2' # Specify the Flutter version you want to use + + - name: Install dependencies + run: flutter pub get + + - name: Build Flutter Web App + run: flutter build web --release --dart-define=FLAVOR=development + + - name: Build And Deploy + id: builddeploy + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_POLITE_SMOKE_017C65C10 }} + repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### + # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig + app_location: "/build/web" # App source code path + api_location: "" # Api source code path - optional + output_location: "/build/web" # Built app content directory - optional + ###### End of Repository/Build Configurations ###### + + close_pull_request_job: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + name: Close Pull Request Job + steps: + - name: Close Pull Request + id: closepullrequest + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_POLITE_SMOKE_017C65C10 }} + action: "close" \ No newline at end of file diff --git a/README.md b/README.md index 301f90fd..e6fef581 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,11 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. + + +## USEFUL COMMANDS + +Run on chrome: flutter run -d chrome --dart-define=FLAVOR='ENV_NAME' + +Build: flutter build web --release --dart-define=FLAVOR='ENV_NAME' + diff --git a/assets/icons/bathroom.svg b/assets/icons/bathroom.svg index 51fc8b6a..8a75f646 100644 --- a/assets/icons/bathroom.svg +++ b/assets/icons/bathroom.svg @@ -1,29 +1,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/bedroom.svg b/assets/icons/bedroom.svg index d579b003..6797009c 100644 --- a/assets/icons/bedroom.svg +++ b/assets/icons/bedroom.svg @@ -1,34 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - + + + + + + diff --git a/assets/icons/dyi.svg b/assets/icons/dyi.svg index 7da61e8e..938d2ba2 100644 --- a/assets/icons/dyi.svg +++ b/assets/icons/dyi.svg @@ -1,14 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/assets/icons/office.svg b/assets/icons/office.svg index 03a2badd..479352c6 100644 --- a/assets/icons/office.svg +++ b/assets/icons/office.svg @@ -1,40 +1,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/parlour.svg b/assets/icons/parlour.svg index 3298393a..52562dd2 100644 --- a/assets/icons/parlour.svg +++ b/assets/icons/parlour.svg @@ -1,30 +1,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/main.dart b/lib/main.dart index eeaa4685..3b861d10 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dotenv/flutter_dotenv.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/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:syncrow_web/services/locator.dart'; @@ -13,7 +14,8 @@ import 'package:syncrow_web/utils/theme/theme.dart'; Future main() async { try { - const environment = String.fromEnvironment('FLAVOR', defaultValue: 'production'); + const environment = + String.fromEnvironment('FLAVOR', defaultValue: 'development'); await dotenv.load(fileName: '.env.$environment'); WidgetsFlutterBinding.ensureInitialized(); initialSetup(); @@ -43,9 +45,11 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { + //HomeBloc.fetchUserInfo(); return MultiBlocProvider( providers: [ - BlocProvider(create: (context) => HomeBloc()), + BlocProvider( + create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider( create: (context) => VisitorPasswordBloc(), ) diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 0d4e3a19..f66c8a99 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -59,7 +59,6 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { builder: (context, state) { final accessBloc = BlocProvider.of(context); final filteredData = accessBloc.filteredData; - return state is AccessLoaded ? const Center(child: CircularProgressIndicator()) : Container( @@ -87,6 +86,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { const SizedBox(height: 20), Expanded( child: DynamicTable( + tableName: 'AccessManagement', uuidIndex: 1, withSelectAll: true, isEmpty: filteredData.isEmpty, diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 4e4a7b0e..9e4fb1d1 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -31,8 +31,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(); late bool checkValidate = false; @@ -41,15 +40,13 @@ class AuthBloc extends Bloc { int _remainingTime = 0; List? regionList = [RegionModel(name: 'name', id: 'id')]; - Future _onStartTimer( - StartTimerEvent event, Emitter emit) async { + Future _onStartTimer(StartTimerEvent event, Emitter emit) async { if (_validateInputs(emit)) return; if (_timer != null && _timer!.isActive) { return; } _remainingTime = 1; - add(UpdateTimerEvent( - remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); _remainingTime = (await AuthenticationAPI.sendOtp( email: forgetEmailController.text, regionUuid: regionUuid))!; _timer = Timer.periodic(const Duration(seconds: 1), (timer) { @@ -58,8 +55,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)); } }); } @@ -69,36 +65,32 @@ 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 { try { emit(LoadingForgetState()); var response = await AuthenticationAPI.verifyOtp( email: forgetEmailController.text, otpCode: forgetOtp.text); if (response == true) { await AuthenticationAPI.forgetPassword( - password: forgetPasswordController.text, - email: forgetEmailController.text); + password: forgetPasswordController.text, email: forgetEmailController.text); _timer?.cancel(); emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); emit(SuccessForgetState()); } + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + if (errorMessage == 'this email is not registered') { + validate = 'Invalid Credentials!'; + emit(AuthInitialState()); + } else if (errorMessage == "You entered wrong otp") { + forgetValidate = 'Wrong one time password.'; + emit(AuthInitialState()); + } else if (errorMessage == "OTP expired") { + forgetValidate = 'One time password has been expired.'; + emit(AuthInitialState()); + } } - on DioException catch (e) { - final errorData = e.response!.data; - String errorMessage = errorData['message']; - if(errorMessage=='this email is not registered'){ - validate='Invalid Credentials!'; - emit(AuthInitialState()); - }else if (errorMessage == "You entered wrong otp") { - forgetValidate = 'Wrong one time password.'; - emit(AuthInitialState()); - } else if (errorMessage == "OTP expired") { - forgetValidate = 'One time password has been expired.'; - emit(AuthInitialState()); - } - - } } String? validateCode(String? value) { @@ -109,9 +101,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 ///////////////////////////////////// @@ -132,7 +122,6 @@ class AuthBloc extends Bloc { void _login(LoginButtonPressed event, Emitter emit) async { emit(AuthLoading()); - if (isChecked) { try { if (event.username.isEmpty || event.password.isEmpty) { @@ -143,20 +132,17 @@ class AuthBloc extends Bloc { token = await AuthenticationAPI.loginWithEmail( model: LoginWithEmailModel( - email: event.username, - password: event.password, - regionUuid: event.regionUuid), + email: event.username, password: event.password, regionUuid: event.regionUuid), ); } catch (failure) { validate = 'Invalid Credentials!'; - emit(const LoginFailure(error: 'Invalid Credentials!')); + emit(LoginInitial()); return; } 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()); @@ -311,14 +297,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'; @@ -370,9 +354,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(':'); @@ -410,4 +392,9 @@ class AuthBloc extends Bloc { forgetValidate = ''; emit(LoginInitial()); } + + static logout() { + const storage = FlutterSecureStorage(); + storage.deleteAll(); + } } diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index efc580b3..a342a9a3 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -138,15 +138,6 @@ class ForgetPasswordWebPage extends StatelessWidget { CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - Text( - "Country/Region", - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( - fontSize: 14, - fontWeight: FontWeight.w400), - ), const SizedBox(height: 10), SizedBox( child: _buildDropdownField(context, forgetBloc, size) @@ -171,6 +162,7 @@ class ForgetPasswordWebPage extends StatelessWidget { const SizedBox(height: 10), SizedBox( child: TextFormField( + controller:forgetBloc.forgetEmailController , validator: forgetBloc.validateEmail, decoration: textBoxDecoration()! .copyWith( diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index af211308..a728e850 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -410,38 +410,39 @@ class _LoginWebPageState extends State }, ), ), - SizedBox( - width: 220, - child: RichText( - text: TextSpan( - text: 'Agree to ', - style: const TextStyle(color: Colors.white), - children: [ - TextSpan( - text: '(Terms of Service)', - style: const TextStyle(color: Colors.black), - recognizer: TapGestureRecognizer() - ..onTap = () { - loginBloc.launchURL('https://example.com/terms'); - }, - ), - TextSpan( - text: ' (Legal Statement)', - style: const TextStyle(color: Colors.black), - recognizer: TapGestureRecognizer() - ..onTap = () { - loginBloc.launchURL('https://example.com/legal'); - }, - ), - TextSpan( - text: ' (Privacy Statement)', - style: const TextStyle(color: Colors.black), - recognizer: TapGestureRecognizer() - ..onTap = () { - loginBloc.launchURL('https://example.com/privacy'); - }, - ), - ], + Expanded( + child: SizedBox( + child: RichText( + text: TextSpan( + text: 'Agree to ', + style: const TextStyle(color: Colors.white), + children: [ + TextSpan( + text: '(Terms of Service)', + style: const TextStyle(color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL('https://example.com/terms'); + }, + ), + TextSpan( + text: ' (Legal Statement)', + style: const TextStyle(color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL('https://example.com/legal'); + }, + ), + TextSpan( + text: ' (Privacy Statement)', + style: const TextStyle(color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL('https://example.com/privacy'); + }, + ), + ], + ), ), ), ), diff --git a/lib/pages/common/custom_dialog.dart b/lib/pages/common/custom_dialog.dart index e75c1e90..a40ef10f 100644 --- a/lib/pages/common/custom_dialog.dart +++ b/lib/pages/common/custom_dialog.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; Future showCustomDialog({ required BuildContext context, @@ -13,7 +12,7 @@ Future showCustomDialog({ double? iconWidth, VoidCallback? onOkPressed, bool barrierDismissible = false, - required actions, + required List actions, }) { return showDialog( context: context, @@ -21,59 +20,43 @@ Future showCustomDialog({ builder: (BuildContext context) { final size = MediaQuery.of(context).size; return AlertDialog( - alignment: Alignment.center, - content: SizedBox( - height: dialogHeight ?? size.height * 0.15, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (iconPath != null) - SvgPicture.asset( - iconPath, - height: iconHeight ?? 35, - width: iconWidth ?? 35, - ), - if (title != null) + alignment: Alignment.center, + content: SizedBox( + height: dialogHeight ?? size.height * 0.15, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (iconPath != null) + SvgPicture.asset( + iconPath, + height: iconHeight ?? 35, + width: iconWidth ?? 35, + ), + if (title != null) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + title, + style: Theme.of(context) + .textTheme + .headlineLarge! + .copyWith(fontSize: 20, fontWeight: FontWeight.w400, color: Colors.black), + ), + ), Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( - title, - style: Theme.of(context).textTheme.headlineLarge!.copyWith( - fontSize: 20, - fontWeight: FontWeight.w400, - color: Colors.black), + message, + style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Colors.black), + textAlign: TextAlign.center, ), ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - message, - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.black), - textAlign: TextAlign.center, - ), - ), - if(widget!=null) - Expanded(child:widget) - ], - ), - ), - actionsAlignment: MainAxisAlignment.center, - actions: [ - TextButton( - onPressed: onOkPressed ?? () => Navigator.of(context).pop(), - child: Text( - 'OK', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, - fontSize: 16), + if (widget != null) Expanded(child: widget) + ], ), ), - ], - ); + actionsAlignment: MainAxisAlignment.center, + actions: actions); }, ); } diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart index d64599f1..381c7969 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -1,19 +1,19 @@ -import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; - part 'device_managment_event.dart'; part 'device_managment_state.dart'; -class DeviceManagementBloc - extends Bloc { +class DeviceManagementBloc extends Bloc { int _selectedIndex = 0; List _devices = []; int _onlineCount = 0; int _offlineCount = 0; int _lowBatteryCount = 0; List _selectedDevices = []; + List _filteredDevices = []; + String productName = ''; DeviceManagementBloc() : super(DeviceManagementInitial()) { on(_onFetchDevices); @@ -23,13 +23,13 @@ class DeviceManagementBloc on(_onSelectDevice); } - Future _onFetchDevices( - FetchDevices event, Emitter emit) async { + Future _onFetchDevices(FetchDevices event, Emitter emit) async { emit(DeviceManagementLoading()); try { final devices = await DevicesManagementApi().fetchDevices(); _selectedDevices.clear(); _devices = devices; + _filteredDevices = devices; _calculateDeviceCounts(); emit(DeviceManagementLoaded( devices: devices, @@ -44,10 +44,9 @@ class DeviceManagementBloc } } - void _onFilterDevices( - FilterDevices event, Emitter emit) { + void _onFilterDevices(FilterDevices event, Emitter emit) async { if (_devices.isNotEmpty) { - final filteredDevices = _devices.where((device) { + _filteredDevices = _devices.where((device) { switch (event.filter) { case 'Online': return device.online == true; @@ -60,25 +59,25 @@ class DeviceManagementBloc } }).toList(); emit(DeviceManagementFiltered( - filteredDevices: filteredDevices, + filteredDevices: _filteredDevices, selectedIndex: _selectedIndex, onlineCount: _onlineCount, offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, - selectedDevice: - _selectedDevices.isNotEmpty ? _selectedDevices.first : null, + selectedDevice: _selectedDevices.isNotEmpty ? _selectedDevices.first : null, )); + if (productName.isNotEmpty) { + add(SearchDevices(productName: productName)); + } } } - void _onSelectedFilterChanged( - SelectedFilterChanged event, Emitter emit) { + void _onSelectedFilterChanged(SelectedFilterChanged event, Emitter emit) { _selectedIndex = event.selectedIndex; add(FilterDevices(_getFilterFromIndex(_selectedIndex))); } - void _onSelectDevice( - SelectDevice event, Emitter emit) { + void _onSelectDevice(SelectDevice event, Emitter emit) { final selectedUuid = event.selectedDevice.uuid; if (_selectedDevices.any((device) => device.uuid == selectedUuid)) { @@ -113,10 +112,8 @@ class DeviceManagementBloc void _calculateDeviceCounts() { _onlineCount = _devices.where((device) => device.online == true).length; _offlineCount = _devices.where((device) => device.online == false).length; - _lowBatteryCount = _devices - .where((device) => - device.batteryLevel != null && device.batteryLevel! < 20) - .length; + _lowBatteryCount = + _devices.where((device) => device.batteryLevel != null && device.batteryLevel! < 20).length; } String _getFilterFromIndex(int index) { @@ -132,36 +129,46 @@ class DeviceManagementBloc } } - void _onSearchDevices( - SearchDevices event, Emitter emit) { - if (_devices.isNotEmpty) { - _selectedDevices.clear(); - _selectedIndex = 0; + void _onSearchDevices(SearchDevices event, Emitter emit) { + // If the search fields are all empty, restore the last filtered devices + if ((event.community == null || event.community!.isEmpty) && + (event.unitName == null || event.unitName!.isEmpty) && + (event.productName == null || event.productName!.isEmpty)) { + productName = ''; + // If the current state is filtered, re-emit the filtered state + if (state is DeviceManagementFiltered) { + add(FilterDevices(_getFilterFromIndex(_selectedIndex))); + } + } - final filteredDevices = _devices.where((device) { + productName = event.productName ?? ''; + List devicesToSearch = _filteredDevices; + + if (devicesToSearch.isNotEmpty) { + _selectedDevices.clear(); + _selectedIndex = _selectedIndex; + + final filteredDevices = devicesToSearch.where((device) { final matchesCommunity = event.community == null || event.community!.isEmpty || - (device.room?.name - ?.toLowerCase() - .contains(event.community!.toLowerCase()) ?? - false); + (device.room?.name?.toLowerCase().contains(event.community!.toLowerCase()) ?? false); final matchesUnit = event.unitName == null || event.unitName!.isEmpty || - (device.unit?.name - ?.toLowerCase() - .contains(event.unitName!.toLowerCase()) ?? - false); + (device.unit?.name?.toLowerCase().contains(event.unitName!.toLowerCase()) ?? false); final matchesProductName = event.productName == null || event.productName!.isEmpty || - (device.name - ?.toLowerCase() - .contains(event.productName!.toLowerCase()) ?? + (device.name?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false); + final matchesDeviceName = event.productName == null || + event.productName!.isEmpty || + (device.categoryName?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false); - return matchesCommunity && matchesUnit && matchesProductName; + + return matchesCommunity && matchesUnit && (matchesProductName || matchesDeviceName); }).toList(); + emit(DeviceManagementFiltered( filteredDevices: filteredDevices, - selectedIndex: 0, + selectedIndex: _selectedIndex, onlineCount: _onlineCount, offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 48e99a1a..13d3dd2e 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -119,7 +119,7 @@ class AllDevicesModel { timeZone = json['timeZone']?.toString(); updateTime = int.tryParse(json['updateTime']?.toString() ?? ''); uuid = json['uuid']?.toString(); - batteryLevel = int.tryParse(json['batteryLevel']?.toString() ?? ''); + batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); } Map toJson() { final data = {}; @@ -151,7 +151,7 @@ class AllDevicesModel { data['timeZone'] = timeZone; data['updateTime'] = updateTime; data['uuid'] = uuid; - data['batteryLevel'] = batteryLevel; + data['battery'] = batteryLevel; return data; } } diff --git a/lib/pages/device_managment/shared/sensors_widgets/presence_space_type.dart b/lib/pages/device_managment/shared/sensors_widgets/presence_space_type.dart index 2f593abe..e1ca0586 100644 --- a/lib/pages/device_managment/shared/sensors_widgets/presence_space_type.dart +++ b/lib/pages/device_managment/shared/sensors_widgets/presence_space_type.dart @@ -53,18 +53,19 @@ class PresenceSpaceType extends StatelessWidget { return GestureDetector( onTap: () => action(spaceType.name), child: Container( + width: 40, + height: 40, + padding: const EdgeInsets.all(8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(100), - border: Border.all( - color: value == spaceType - ? ColorsManager.blueColor - : Colors.transparent, - ), + color: value == spaceType + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, ), child: SvgPicture.asset( icon, - width: 40, - height: 40, + width: 25, + height: 22, ), ), ); diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index cc40b8fe..98320a88 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -50,6 +50,16 @@ class HomeBloc extends Bloc { } } +// static Future fetchUserInfo() async { +// try { +// var uuid = +// await const FlutterSecureStorage().read(key: UserModel.userUuidKey); +// user = await HomeApi().fetchUserInfo(uuid); +// } catch (e) { +// return; +// } +// } + List homeItems = [ HomeItemModel( title: 'Access', diff --git a/lib/pages/home/view/home_page.dart b/lib/pages/home/view/home_page.dart index 75107e84..9159011f 100644 --- a/lib/pages/home/view/home_page.dart +++ b/lib/pages/home/view/home_page.dart @@ -1,25 +1,11 @@ import 'package:flutter/cupertino.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; -import 'package:syncrow_web/pages/home/bloc/home_event.dart'; import 'package:syncrow_web/pages/home/view/home_page_mobile.dart'; import 'package:syncrow_web/pages/home/view/home_page_web.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class HomePage extends StatefulWidget { +class HomePage extends StatelessWidget with HelperResponsiveLayout { const HomePage({super.key}); - @override - State createState() => _HomePageState(); -} - -class _HomePageState extends State with HelperResponsiveLayout { - @override - void initState() { - super.initState(); - context.read().add(const FetchUserInfo()); - } - @override Widget build(BuildContext context) { final isSmallScreen = isSmallScreenSize(context); diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 44a4e7cf..6fd34050 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -37,13 +37,17 @@ class AuthenticationAPI { expectedResponseModel: (json) { return json['data']['cooldown']; }); - return response; } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + print('sendOtp=$errorMessage'); if (e.response != null) { if (e.response!.statusCode == 400) { final errorData = e.response!.data; String errorMessage = errorData['message']; + print('sendOtp=$errorMessage'); + if (errorMessage == 'User not found') { return 1; } else { diff --git a/lib/web_layout/web_app_bar.dart b/lib/web_layout/web_app_bar.dart index 69f88f09..1052a23d 100644 --- a/lib/web_layout/web_app_bar.dart +++ b/lib/web_layout/web_app_bar.dart @@ -1,17 +1,32 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/common/custom_dialog.dart'; 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/routes_const.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class WebAppBar extends StatelessWidget with HelperResponsiveLayout { +class WebAppBar extends StatefulWidget { final Widget? title; final Widget? centerBody; final Widget? rightBody; const WebAppBar({super.key, this.title, this.centerBody, this.rightBody}); + @override + State createState() => _WebAppBarState(); +} + +class _WebAppBarState extends State with HelperResponsiveLayout { + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { bool isSmallScreen = isSmallScreenSize(context); @@ -26,21 +41,21 @@ class WebAppBar extends StatelessWidget with HelperResponsiveLayout { ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (title != null) + if (widget.title != null) Align( alignment: Alignment.centerLeft, - child: title!, + child: widget.title!, ), - if (centerBody != null) + if (widget.centerBody != null) Padding( padding: const EdgeInsets.only(top: 8.0), - child: centerBody, + child: widget.centerBody, ), - if (rightBody != null || user != null) + if (widget.rightBody != null || user != null) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (rightBody != null) rightBody!, + if (widget.rightBody != null) widget.rightBody!, Row( children: [ const SizedBox.square( @@ -77,11 +92,11 @@ class WebAppBar extends StatelessWidget with HelperResponsiveLayout { Expanded( child: Row( children: [ - title!, - if (centerBody != null) + widget.title!, + if (widget.centerBody != null) Padding( padding: const EdgeInsets.only(left: 80), - child: centerBody!, + child: widget.centerBody!, ), ], ), @@ -89,10 +104,10 @@ class WebAppBar extends StatelessWidget with HelperResponsiveLayout { Row( mainAxisSize: MainAxisSize.min, children: [ - if (rightBody != null) + if (widget.rightBody != null) Align( alignment: Alignment.centerRight, - child: rightBody, + child: widget.rightBody, ), const SizedBox( width: 10, @@ -118,6 +133,57 @@ class WebAppBar extends StatelessWidget with HelperResponsiveLayout { '${user.firstName} ${user.lastName}', style: Theme.of(context).textTheme.bodyLarge, ), + const SizedBox( + width: 10, + ), + 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, + ), + ) ], ), ],