diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 2d18d8e3..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 ///////////////////////////////////// @@ -142,9 +132,7 @@ 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!'; @@ -154,8 +142,7 @@ 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()); @@ -310,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'; @@ -369,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(':'); @@ -409,4 +392,9 @@ class AuthBloc extends Bloc { forgetValidate = ''; emit(LoginInitial()); } + + static logout() { + const storage = FlutterSecureStorage(); + storage.deleteAll(); + } } 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 c11e05e1..3f2e9f2a 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 @@ -6,14 +6,14 @@ 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 = []; + String productName = ''; DeviceManagementBloc() : super(DeviceManagementInitial()) { on(_onFetchDevices); @@ -23,8 +23,7 @@ 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(); @@ -44,8 +43,7 @@ class DeviceManagementBloc } } - void _onFilterDevices( - FilterDevices event, Emitter emit) { + void _onFilterDevices(FilterDevices event, Emitter emit) async { if (_devices.isNotEmpty) { final filteredDevices = _devices.where((device) { switch (event.filter) { @@ -65,20 +63,20 @@ class DeviceManagementBloc 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 +111,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,18 +128,20 @@ class DeviceManagementBloc } } - void _onSearchDevices( - SearchDevices event, Emitter emit) { + 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))); } } + productName = event.productName ?? ''; + List devicesToSearch = _devices; if (state is DeviceManagementFiltered) { @@ -157,32 +155,19 @@ class DeviceManagementBloc 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()) ?? - false); + (device.name?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false); final matchesDeviceName = event.productName == null || event.productName!.isEmpty || - (device.categoryName - ?.toLowerCase() - .contains(event.productName!.toLowerCase()) ?? + (device.categoryName?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false); - return matchesCommunity && - matchesUnit && - (matchesProductName || matchesDeviceName); + return matchesCommunity && matchesUnit && (matchesProductName || matchesDeviceName); }).toList(); emit(DeviceManagementFiltered( diff --git a/lib/web_layout/web_app_bar.dart b/lib/web_layout/web_app_bar.dart index b3236aae..1052a23d 100644 --- a/lib/web_layout/web_app_bar.dart +++ b/lib/web_layout/web_app_bar.dart @@ -1,8 +1,13 @@ 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 StatefulWidget { @@ -128,6 +133,57 @@ class _WebAppBarState extends State 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, + ), + ) ], ), ],