From 85a04e504f6f07fa7ce8df3981953b186f683d3e Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 8 Sep 2024 17:09:32 +0300 Subject: [PATCH 1/6] curtain and forget password and select all check box and fix bugs --- assets/images/curtain.svg | 17 + lib/pages/auth/bloc/auth_bloc.dart | 109 +++- .../auth/view/forget_password_web_page.dart | 512 +++++++++--------- lib/pages/common/curtain_toggle.dart | 83 +++ lib/pages/common/hour_picker_dialog.dart | 4 +- .../helper/route_controls_based_code.dart | 5 + .../widgets/device_managment_body.dart | 2 +- .../curtain/bloc/curtain_bloc.dart | 40 ++ .../curtain/bloc/curtain_event.dart | 32 ++ .../curtain/bloc/curtain_state.dart | 41 ++ .../curtain/model/curtain_model.dart | 32 ++ .../curtain/view/curtain_control.dart | 72 +++ .../view/living_room_device_control.dart | 68 +-- lib/services/auth_api.dart | 41 +- lib/services/devices_mang_api.dart | 1 + lib/utils/constants/assets.dart | 1 + 16 files changed, 708 insertions(+), 352 deletions(-) create mode 100644 assets/images/curtain.svg create mode 100644 lib/pages/common/curtain_toggle.dart create mode 100644 lib/pages/device_managment/curtain/bloc/curtain_bloc.dart create mode 100644 lib/pages/device_managment/curtain/bloc/curtain_event.dart create mode 100644 lib/pages/device_managment/curtain/bloc/curtain_state.dart create mode 100644 lib/pages/device_managment/curtain/model/curtain_model.dart create mode 100644 lib/pages/device_managment/curtain/view/curtain_control.dart diff --git a/assets/images/curtain.svg b/assets/images/curtain.svg new file mode 100644 index 00000000..34fb387a --- /dev/null +++ b/assets/images/curtain.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 9e4fb1d1..aee4224b 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -31,7 +31,8 @@ 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; @@ -40,22 +41,55 @@ 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)); - _remainingTime = (await AuthenticationAPI.sendOtp( - email: forgetEmailController.text, regionUuid: regionUuid))!; + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); + try { + forgetEmailValidate = ''; + _remainingTime = (await AuthenticationAPI.sendOtp( + email: forgetEmailController.text, regionUuid: regionUuid))!; + } on DioException catch (e) { + if (e.response!.statusCode == 400) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + print('sendOtp=$errorMessage'); + if (errorMessage == 'User not found') { + validate='Invalid Credential'; + emit(AuthInitialState()); + return 1; + } else { + validate=''; + _remainingTime = errorData['data']['cooldown'] ?? 1; + emit(AuthInitialState()); + } + } else { + emit(AuthInitialState()); + + return 1; + } + emit(AuthInitialState()); + + } catch (e) { + emit(AuthInitialState()); + + return 1; + } + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { _remainingTime--; if (_remainingTime <= 0) { _timer?.cancel(); add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); } else { - add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); } }); } @@ -65,14 +99,16 @@ 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()); @@ -89,7 +125,12 @@ class AuthBloc extends Bloc { } else if (errorMessage == "OTP expired") { forgetValidate = 'One time password has been expired.'; emit(AuthInitialState()); + }else{ + validate=''; + emit(AuthInitialState()); + } + } } @@ -101,7 +142,9 @@ 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 ///////////////////////////////////// @@ -115,6 +158,7 @@ class AuthBloc extends Bloc { String otpCode = ''; String validate = ''; String forgetValidate = ''; + String forgetEmailValidate = ''; String regionUuid = ''; static Token token = Token.emptyConstructor(); static UserModel? user; @@ -132,7 +176,9 @@ 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!'; @@ -142,7 +188,8 @@ 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()); @@ -160,9 +207,9 @@ class AuthBloc extends Bloc { } checkBoxToggle( - CheckBoxEvent event, - Emitter emit, - ) { + CheckBoxEvent event, + Emitter emit, + ) { emit(AuthLoading()); isChecked = event.newValue!; add(CheckEnableEvent()); @@ -218,7 +265,9 @@ class AuthBloc extends Bloc { emit(LoadingForgetState()); final nameError = validateEmail(forgetEmailController.text); if (nameError != null) { - emit(FailureForgetState(error: nameError)); + forgetEmailValidate = nameError; + emit(AuthInitialState()); + // emit(FailureForgetState(error: nameError)); return true; } return false; @@ -297,12 +346,14 @@ 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'; @@ -354,7 +405,9 @@ 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(':'); @@ -363,9 +416,9 @@ class AuthBloc extends Bloc { } bool checkEnable( - CheckEnableEvent event, - Emitter emit, - ) { + CheckEnableEvent event, + Emitter emit, + ) { emit(AuthLoading()); checkValidate = isChecked == true && loginPasswordController.text.isNotEmpty && @@ -376,18 +429,18 @@ class AuthBloc extends Bloc { } changeValidate( - ChangeValidateEvent event, - Emitter emit, - ) { + ChangeValidateEvent event, + Emitter emit, + ) { emit(AuthLoading()); validate = ''; emit(LoginInitial()); } changeForgetValidate( - ChangeValidateEvent event, - Emitter emit, - ) { + ChangeValidateEvent event, + Emitter emit, + ) { emit(AuthLoading()); forgetValidate = ''; emit(LoginInitial()); diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index a342a9a3..60270908 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -64,307 +64,310 @@ class ForgetPasswordWebPage extends StatelessWidget { Size size = MediaQuery.of(context).size; return FirstLayer( second: Center( - child: Stack( - children: [ - if (state is AuthLoading) - const Center(child: CircularProgressIndicator()), - ListView( - shrinkWrap: true, - controller: _scrollController, + child: Stack( children: [ - Container( - padding: EdgeInsets.all(size.width * 0.02), - margin: EdgeInsets.all(size.width * 0.09), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - child: Center( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Spacer(), - Expanded( - flex: 3, - child: SvgPicture.asset( - Assets.loginLogo, - ), - ), - const Spacer(), - Expanded( - flex: 3, - child: Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: - const BorderRadius.all(Radius.circular(30)), - border: Border.all( - color: - ColorsManager.graysColor.withOpacity(0.2)), + if (state is AuthLoading) + const Center(child: CircularProgressIndicator()), + ListView( + shrinkWrap: true, + controller: _scrollController, + children: [ + Container( + padding: EdgeInsets.all(size.width * 0.02), + margin: EdgeInsets.all(size.width * 0.09), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + child: Center( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + Expanded( + flex: 3, + child: SvgPicture.asset( + Assets.loginLogo, + ), ), - child: Form( - key: forgetBloc.forgetFormKey, - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: size.width * 0.02, - vertical: size.width * 0.003), - child: Column( - mainAxisAlignment: + const Spacer(), + Expanded( + flex: 3, + child: Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: + const BorderRadius.all(Radius.circular(30)), + border: Border.all( + color: + ColorsManager.graysColor.withOpacity(0.2)), + ), + child: Form( + key: forgetBloc.forgetFormKey, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: size.width * 0.02, + vertical: size.width * 0.003), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - const Text( - 'Forget Password', - style: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - Text( - 'Please fill in your account information to\nretrieve your password', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + const Text( + 'Forget Password', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + Text( + 'Please fill in your account information to\nretrieve your password', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( fontSize: 14, fontWeight: FontWeight.w400), - ), - const SizedBox(height: 10), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ + ), const SizedBox(height: 10), - SizedBox( - child: _buildDropdownField(context, forgetBloc, size) - ) - ], - ), - const SizedBox(height: 20), - Column( - crossAxisAlignment: + Column( + crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Account", - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(height: 10), + SizedBox( + child: _buildDropdownField(context, forgetBloc, size) + ) + ], + ), + const SizedBox(height: 20), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Account", + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( fontSize: 14, fontWeight: FontWeight.w400), + ), + const SizedBox(height: 10), + SizedBox( + child: TextFormField( + controller:forgetBloc.forgetEmailController , + validator: forgetBloc.validateEmail, + decoration: textBoxDecoration()!.copyWith( + hintText: 'Enter your email', + hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400), + ), + style: const TextStyle( + color: Colors.black), + ), + ), + if(forgetBloc.forgetEmailValidate!='') + SizedBox( + child: Text( + forgetBloc.forgetEmailValidate, + style: const TextStyle( + fontWeight: FontWeight.w700, + color: ColorsManager.red), + ), + ), + const SizedBox(height: 10.0), + + ], ), - const SizedBox(height: 10), - SizedBox( - child: TextFormField( - controller:forgetBloc.forgetEmailController , - validator: forgetBloc.validateEmail, - decoration: textBoxDecoration()! - .copyWith( - hintText: 'Enter your email', - hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.w400)), - style: const TextStyle( - color: Colors.black), - ), - ), - ], - ), - const SizedBox(height: 20.0), - Column( - crossAxisAlignment: + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "One Time Password", - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "One Time Password", + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( fontSize: 14, fontWeight: FontWeight.w400), - ), - const SizedBox(height: 10), - SizedBox( - child: TextFormField( - validator: forgetBloc.validateCode, - keyboardType: + ), + const SizedBox(height: 10), + SizedBox( + child: TextFormField( + validator: forgetBloc.validateCode, + keyboardType: TextInputType.visiblePassword, - controller: forgetBloc.forgetOtp, - decoration: + controller: forgetBloc.forgetOtp, + decoration: textBoxDecoration()!.copyWith( - hintText: 'Enter Code', - hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.w400), - suffixIcon: SizedBox( - width: 100, - child: Center( - child: InkWell( - onTap: state is TimerState && + hintText: 'Enter Code', + hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400), + suffixIcon: SizedBox( + width: 100, + child: Center( + child: InkWell( + onTap: state is TimerState && !state.isButtonEnabled && - state.remainingTime != - 1 - ? null - : () { - forgetBloc.add( - StartTimerEvent()); - }, - child: Text( - 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', - style: TextStyle( - color: state is TimerState && + state.remainingTime != 1 + ? null : () { + forgetBloc.add(StartTimerEvent()); + + }, + child: Text( + 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', + style: TextStyle( + color: state is TimerState && !state.isButtonEnabled - ? Colors.grey - : ColorsManager - .btnColor, + ? Colors.grey + : ColorsManager.btnColor, + ), + ), ), ), ), ), + style: const TextStyle( + color: Colors.black), ), ), - style: const TextStyle( - color: Colors.black), - ), - ), - if (forgetBloc.forgetValidate != '') // Check if there is a validation message - Padding( - padding: + if (forgetBloc.forgetValidate != '') // Check if there is a validation message + Padding( + padding: const EdgeInsets.only(top: 8.0), - child: Text( - forgetBloc.forgetValidate, - style: const TextStyle( - color: ColorsManager.red, - fontSize: 10, - fontWeight: FontWeight.w700), - ), - ), - ], - ), - const SizedBox(height: 20.0), - Column( - crossAxisAlignment: + child: Text( + forgetBloc.forgetValidate, + style: const TextStyle( + color: ColorsManager.red, + fontSize: 10, + fontWeight: FontWeight.w700), + ), + ), + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Password", - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Password", + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( fontSize: 14, fontWeight: FontWeight.w400), - ), - const SizedBox(height: 10), - SizedBox( - child: TextFormField( - validator: forgetBloc.passwordValidator, - keyboardType: TextInputType.visiblePassword, - controller: forgetBloc.forgetPasswordController, - decoration: textBoxDecoration()!.copyWith( - hintText: 'At least 8 characters', + ), + const SizedBox(height: 10), + SizedBox( + child: TextFormField( + validator: forgetBloc.passwordValidator, + keyboardType: TextInputType.visiblePassword, + controller: forgetBloc.forgetPasswordController, + decoration: textBoxDecoration()!.copyWith( + hintText: 'At least 8 characters', hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( color: ColorsManager.grayColor, fontWeight: FontWeight.w400), + ), + style: const TextStyle(color: Colors.black), + ), ), - style: const TextStyle(color: Colors.black), - ), + ], ), - ], - ), - const SizedBox( - height: 10, - ), - const SizedBox(height: 20.0), - Row( - crossAxisAlignment: + const SizedBox( + height: 10, + ), + const SizedBox(height: 20.0), + Row( + crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: size.width * 0.2, - child: DefaultButton( - backgroundColor: + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: size.width * 0.2, + child: DefaultButton( + backgroundColor: ColorsManager.btnColor, - child: const Text('Submit'), - onPressed: () { - if (forgetBloc - .forgetFormKey.currentState! - .validate()) { - forgetBloc.add(ChangePasswordEvent()); - } - }, + child: const Text('Submit'), + onPressed: () { + if (forgetBloc.forgetFormKey.currentState!.validate()) { + forgetBloc.add(ChangePasswordEvent()); + } + }, + ), + ), + ], + ), + const SizedBox(height: 10.0), + Center( + child: SizedBox( + child: Text( + forgetBloc.validate, + style: const TextStyle( + fontWeight: FontWeight.w700, + color: ColorsManager.red), + ), ), ), + const SizedBox( + height: 10, + ), + SizedBox( + child: Center( + child: Wrap( + children: [ + const Text( + "Do you have an account? ", + style: TextStyle(color: Colors.white), + ), + InkWell( + onTap: () { + forgetBloc.add(StopTimerEvent()); + Navigator.pop(context); + }, + child: const Text( + "Sign in", + ), + ), + ], + ), + ), + ), + const SizedBox(height: 15.0), ], ), - const SizedBox(height: 10.0), - Center( - child: SizedBox( - child: Text( - forgetBloc.validate, - style: const TextStyle( - fontWeight: FontWeight.w700, - color: ColorsManager.red), - ), - ), - ), - SizedBox( - height: 10, - ), - SizedBox( - width: size.width * 0.2, - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - const Flexible( - child: Text( - "Do you have an account? ", - style: TextStyle(color: Colors.white), - )), - InkWell( - onTap: () { - forgetBloc.add(StopTimerEvent()); - Navigator.pop(context); - }, - child: const Flexible( - child: Text( - "Sign in", - )), - ), - ], - ), - ), - const SizedBox(height: 15.0), - ], + ), ), ), ), - ), + const Spacer(), + ], ), - const Spacer(), - ], + ), ), - ), + ], ), ], ), - ], - ), - )); + )); } + + Widget _buildDropdownField( BuildContext context, AuthBloc loginBloc, Size size) { final TextEditingController textEditingController = TextEditingController(); @@ -467,6 +470,7 @@ class ForgetPasswordWebPage extends StatelessWidget { ), ), ), + ], ); } diff --git a/lib/pages/common/curtain_toggle.dart b/lib/pages/common/curtain_toggle.dart new file mode 100644 index 00000000..3c8bf5b1 --- /dev/null +++ b/lib/pages/common/curtain_toggle.dart @@ -0,0 +1,83 @@ + + + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class CurtainToggle extends StatelessWidget { + final bool value; + final String code; + final String deviceId; + final String label; + + const CurtainToggle({ + super.key, + required this.value, + required this.code, + required this.deviceId, + required this.label, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: ColorsManager.greyColor.withOpacity(0.2), + border: Border.all(color: ColorsManager.boxDivider), + ), + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + Assets.curtainIcon, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + )), + SizedBox( + height: 20, + width: 35, + child: CupertinoSwitch( + value: value, + activeColor: ColorsManager.dialogBlueTitle, + onChanged: (newValue) { + context.read().add( + LivingRoomControl( + deviceId: deviceId, + code: code, + value: newValue, + ), + ); + }, + ), + ), + ], + ), + const Spacer(), + Text( + label, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/common/hour_picker_dialog.dart b/lib/pages/common/hour_picker_dialog.dart index 2c89b710..091fca26 100644 --- a/lib/pages/common/hour_picker_dialog.dart +++ b/lib/pages/common/hour_picker_dialog.dart @@ -48,11 +48,11 @@ class _HourPickerDialogState extends State { value: _isPm, items: const [ DropdownMenuItem( - value: false, + value: true, child: Text('AM'), ), DropdownMenuItem( - value: true, + value:false , child: Text('PM'), ), ], diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 14ac7c5a..8c3941db 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_control.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_status_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart'; @@ -27,6 +28,10 @@ mixin RouteControlsBasedCode { return CeilingSensorControls( device: device, ); + case 'CUR': + return CurtainControl( + deviceId: device.uuid!, + ); case 'AC': return AcDeviceControl(device: device); default: 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 ab2f07fc..28a1d356 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 @@ -116,7 +116,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ? const EdgeInsets.all(30) : const EdgeInsets.all(15), child: DynamicTable( - withSelectAll: false, + withSelectAll: true, cellDecoration: containerDecoration, onRowSelected: (index, isSelected, row) { final selectedDevice = devicesToShow[index]; diff --git a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart new file mode 100644 index 00000000..a969b2c2 --- /dev/null +++ b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart @@ -0,0 +1,40 @@ + + +import 'dart:async'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class CurtainBloc extends Bloc { + late bool deviceStatus; + final String deviceId; + + CurtainBloc({required this.deviceId}) : super(CurtainInitial()) { + on(_onFetchDeviceStatus); + } + + FutureOr _onFetchDeviceStatus( + CurtainFetchDeviceStatus event, Emitter emit) async { + emit(CurtainStatusLoading()); + try { + final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus =checkStatus(status.status[0].value) ; // Assuming this is a Map + emit(CurtainStatusLoaded(deviceStatus)); + } catch (e) { + emit(CurtainError(e.toString())); + } + } + + bool checkStatus(String command) { + if (command.toLowerCase() == 'open') { + return true; + } else { + return false; + } + } + + + + +} diff --git a/lib/pages/device_managment/curtain/bloc/curtain_event.dart b/lib/pages/device_managment/curtain/bloc/curtain_event.dart new file mode 100644 index 00000000..386addf8 --- /dev/null +++ b/lib/pages/device_managment/curtain/bloc/curtain_event.dart @@ -0,0 +1,32 @@ + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + + +sealed class CurtainEvent extends Equatable { + const CurtainEvent(); + + @override + List get props => []; +} + +class CurtainFetchDeviceStatus extends CurtainEvent { + final String deviceId; + + const CurtainFetchDeviceStatus(this.deviceId); + + @override + List get props => [deviceId]; +} + +class CurtainControl extends CurtainEvent { + final String deviceId; + final String code; + final bool value; + + const CurtainControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} diff --git a/lib/pages/device_managment/curtain/bloc/curtain_state.dart b/lib/pages/device_managment/curtain/bloc/curtain_state.dart new file mode 100644 index 00000000..70fa154b --- /dev/null +++ b/lib/pages/device_managment/curtain/bloc/curtain_state.dart @@ -0,0 +1,41 @@ + +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/model/curtain_model.dart'; + +sealed class CurtainState extends Equatable { + const CurtainState(); + + @override + List get props => []; +} + +final class CurtainInitial extends CurtainState {} + +class CurtainStatusLoading extends CurtainState {} + +class CurtainStatusLoaded extends CurtainState { + final bool status; + + const CurtainStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class CurtainError extends CurtainState { + final String message; + + const CurtainError(this.message); + + @override + List get props => [message]; +} + +class CurtainControlError extends CurtainState { + final String message; + + const CurtainControlError(this.message); + + @override + List get props => [message]; +} diff --git a/lib/pages/device_managment/curtain/model/curtain_model.dart b/lib/pages/device_managment/curtain/model/curtain_model.dart new file mode 100644 index 00000000..426f4fe5 --- /dev/null +++ b/lib/pages/device_managment/curtain/model/curtain_model.dart @@ -0,0 +1,32 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class CurtainModel { + final String productUuid; + final String productType; + final List status; + + CurtainModel({ + required this.productUuid, + required this.productType, + required this.status, + }); + + factory CurtainModel.fromJson(dynamic json) { + var statusList = json['status'] as List; + List status = statusList.map((i) => Status.fromJson(i)).toList(); + + return CurtainModel( + productUuid: json['productUuid'], + productType: json['productType'], + status: status, + ); + } + + Map toJson() { + return { + 'productUuid': productUuid, + 'productType': productType, + 'status': status.map((s) => s!.toJson()).toList(), + }; + } +} diff --git a/lib/pages/device_managment/curtain/view/curtain_control.dart b/lib/pages/device_managment/curtain/view/curtain_control.dart new file mode 100644 index 00000000..c7c06681 --- /dev/null +++ b/lib/pages/device_managment/curtain/view/curtain_control.dart @@ -0,0 +1,72 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/curtain_toggle.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/model/curtain_model.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class CurtainControl extends StatelessWidget with HelperResponsiveLayout { + final String deviceId; + + const CurtainControl({super.key, required this.deviceId}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + CurtainBloc(deviceId: deviceId)..add(CurtainFetchDeviceStatus(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is CurtainStatusLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is CurtainStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is CurtainError || state is CurtainControlError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls(BuildContext context, bool status) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return Container( + child: GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + CurtainToggle( + value: status, + code: 'Curtains', + deviceId: deviceId, + label: 'Curtains', + ), + + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart index e3a42a7a..0cf7ffa9 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart @@ -35,40 +35,42 @@ class LivingRoomDeviceControl extends StatelessWidget with HelperResponsiveLayou final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); - return GridView( - padding: const EdgeInsets.symmetric(horizontal: 50), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: isLarge || isExtraLarge - ? 3 - : isMedium - ? 2 - : 1, - mainAxisExtent: 140, - crossAxisSpacing: 12, - mainAxisSpacing: 12, + return Container( + child: GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceId, + label: 'Wall Light', + ), + ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceId, + label: 'Ceiling Light', + ), + ToggleWidget( + value: status.switch3, + code: 'switch_3', + deviceId: deviceId, + label: 'Spotlight', + ), + ], ), - children: [ - ToggleWidget( - value: status.switch1, - code: 'switch_1', - deviceId: deviceId, - label: 'Wall Light', - ), - ToggleWidget( - value: status.switch2, - code: 'switch_2', - deviceId: deviceId, - label: 'Ceiling Light', - ), - ToggleWidget( - value: status.switch3, - code: 'switch_3', - deviceId: deviceId, - label: 'Spotlight', - ), - ], ); } } diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 6fd34050..5d5dcd57 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -29,40 +29,13 @@ class AuthenticationAPI { } static Future sendOtp({required String email, required String regionUuid}) async { - try { - final response = await HTTPService().post( - path: ApiEndpoints.sendOtp, - body: {"email": email, "type": "PASSWORD", "regionUuid": regionUuid}, - showServerMessage: true, - 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 { - int cooldown = errorData['data']['cooldown'] ?? 1; - return cooldown; - } - } else { - return 1; - } - } else { - return 1; - } - } catch (e) { - return 1; - } + final response = await HTTPService().post( + path: ApiEndpoints.sendOtp, + body: {"email": email, "type": "PASSWORD", "regionUuid": regionUuid}, + showServerMessage: true, + expectedResponseModel: (json) { + return json['data']['cooldown']; + }); } static Future verifyOtp({required String email, required String otpCode}) async { diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 7e2dc4f8..93ace830 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -33,6 +33,7 @@ class DevicesManagementApi { path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', uuid), showServerMessage: true, expectedResponseModel: (json) { + print('getDeviceStatus$json'); return DeviceStatus.fromJson(json); }, ); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 85a06ea3..e775fd84 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -124,4 +124,5 @@ class Assets { static const String office = 'assets/icons/office.svg'; static const String parlour = 'assets/icons/parlour.svg'; static const String grid = "assets/images/grid.svg"; + static const String curtainIcon = "assets/images/curtain.svg"; } From ce861efa3f4969aed43c46950cde76fa9d1e8313 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 8 Sep 2024 20:12:51 +0300 Subject: [PATCH 2/6] connect curtain controller --- lib/pages/common/curtain_toggle.dart | 39 +++++----- .../helper/route_controls_based_code.dart | 4 +- .../curtain/bloc/curtain_bloc.dart | 71 ++++++++++++++++--- .../curtain/bloc/curtain_event.dart | 1 - .../curtain/bloc/curtain_state.dart | 1 - .../curtain/model/curtain_model.dart | 2 +- ..._control.dart => curtain_status_view.dart} | 61 +++++++--------- 7 files changed, 110 insertions(+), 69 deletions(-) rename lib/pages/device_managment/curtain/view/{curtain_control.dart => curtain_status_view.dart} (53%) diff --git a/lib/pages/common/curtain_toggle.dart b/lib/pages/common/curtain_toggle.dart index 3c8bf5b1..371f8833 100644 --- a/lib/pages/common/curtain_toggle.dart +++ b/lib/pages/common/curtain_toggle.dart @@ -1,11 +1,9 @@ - - - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -40,15 +38,16 @@ class CurtainToggle extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ ClipOval( - child: Container( - color: ColorsManager.whiteColors, - child: SvgPicture.asset( - Assets.curtainIcon, - width: 60, - height: 60, - fit: BoxFit.cover, - ), - )), + child: Container( + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + Assets.curtainIcon, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + ), + ), SizedBox( height: 20, width: 35, @@ -56,13 +55,13 @@ class CurtainToggle extends StatelessWidget { value: value, activeColor: ColorsManager.dialogBlueTitle, onChanged: (newValue) { - context.read().add( - LivingRoomControl( - deviceId: deviceId, - code: code, - value: newValue, - ), - ); + context.read().add( + CurtainControl( + deviceId: deviceId, + code: code, + value: newValue, + ), + ); }, ), ), diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 8c3941db..3b228192 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart'; -import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_control.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_status_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart'; @@ -29,7 +29,7 @@ mixin RouteControlsBasedCode { device: device, ); case 'CUR': - return CurtainControl( + return CurtainStatusView( deviceId: device.uuid!, ); case 'AC': diff --git a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart index a969b2c2..fe6dd0b8 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart @@ -1,7 +1,6 @@ - - import 'dart:async'; 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'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; @@ -9,32 +8,84 @@ import 'package:syncrow_web/services/devices_mang_api.dart'; class CurtainBloc extends Bloc { late bool deviceStatus; final String deviceId; + Timer? _timer; CurtainBloc({required this.deviceId}) : super(CurtainInitial()) { on(_onFetchDeviceStatus); + on(_onCurtainControl); } FutureOr _onFetchDeviceStatus( CurtainFetchDeviceStatus event, Emitter emit) async { emit(CurtainStatusLoading()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus =checkStatus(status.status[0].value) ; // Assuming this is a Map + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + + deviceStatus = _checkStatus(status.status[0].value); + emit(CurtainStatusLoaded(deviceStatus)); } catch (e) { emit(CurtainError(e.toString())); } } - bool checkStatus(String command) { - if (command.toLowerCase() == 'open') { - return true; - } else { - return false; - } + FutureOr _onCurtainControl( + CurtainControl event, Emitter emit) async { + final oldValue = deviceStatus; + + _updateLocalValue(event.value, emit); + + emit(CurtainStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + ); } + Future _runDebounce({ + required String deviceId, + required String code, + required bool value, + required bool oldValue, + required Emitter emit, + }) async { + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + try { + final controlValue = value ? 'open' : 'stop'; + final response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: controlValue)); + if (!response) { + _revertValueAndEmit(deviceId, oldValue, emit); + } + } catch (e) { + _revertValueAndEmit(deviceId, oldValue, emit); + } + }); + } + void _revertValueAndEmit( + String deviceId, bool oldValue, Emitter emit) { + _updateLocalValue(oldValue, emit); + emit(CurtainStatusLoaded(deviceStatus)); + emit(const CurtainControlError('Failed to control the device.')); + } + + void _updateLocalValue(bool value, Emitter emit) { + deviceStatus = value; + emit(CurtainStatusLoaded(deviceStatus)); + } + + bool _checkStatus(String command) { + return command.toLowerCase() == 'open'; + } } diff --git a/lib/pages/device_managment/curtain/bloc/curtain_event.dart b/lib/pages/device_managment/curtain/bloc/curtain_event.dart index 386addf8..23bb2e45 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_event.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_event.dart @@ -1,6 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; sealed class CurtainEvent extends Equatable { diff --git a/lib/pages/device_managment/curtain/bloc/curtain_state.dart b/lib/pages/device_managment/curtain/bloc/curtain_state.dart index 70fa154b..dfe11c2a 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_state.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_state.dart @@ -1,6 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/device_managment/curtain/model/curtain_model.dart'; sealed class CurtainState extends Equatable { const CurtainState(); diff --git a/lib/pages/device_managment/curtain/model/curtain_model.dart b/lib/pages/device_managment/curtain/model/curtain_model.dart index 426f4fe5..908415d5 100644 --- a/lib/pages/device_managment/curtain/model/curtain_model.dart +++ b/lib/pages/device_managment/curtain/model/curtain_model.dart @@ -26,7 +26,7 @@ class CurtainModel { return { 'productUuid': productUuid, 'productType': productType, - 'status': status.map((s) => s!.toJson()).toList(), + 'status': status.map((s) => s.toJson()).toList(), }; } } diff --git a/lib/pages/device_managment/curtain/view/curtain_control.dart b/lib/pages/device_managment/curtain/view/curtain_status_view.dart similarity index 53% rename from lib/pages/device_managment/curtain/view/curtain_control.dart rename to lib/pages/device_managment/curtain/view/curtain_status_view.dart index c7c06681..c9af04fb 100644 --- a/lib/pages/device_managment/curtain/view/curtain_control.dart +++ b/lib/pages/device_managment/curtain/view/curtain_status_view.dart @@ -1,27 +1,21 @@ - - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/curtain_toggle.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; -import 'package:syncrow_web/pages/device_managment/curtain/model/curtain_model.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class CurtainControl extends StatelessWidget with HelperResponsiveLayout { +class CurtainStatusView extends StatelessWidget with HelperResponsiveLayout { final String deviceId; - const CurtainControl({super.key, required this.deviceId}); + const CurtainStatusView({super.key, required this.deviceId}); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => - CurtainBloc(deviceId: deviceId)..add(CurtainFetchDeviceStatus(deviceId)), + create: (context) => CurtainBloc(deviceId: deviceId) + ..add(CurtainFetchDeviceStatus(deviceId)), child: BlocBuilder( builder: (context, state) { if (state is CurtainStatusLoading) { @@ -42,31 +36,30 @@ class CurtainControl extends StatelessWidget with HelperResponsiveLayout { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); - return Container( - child: GridView( - padding: const EdgeInsets.symmetric(horizontal: 50), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: isLarge || isExtraLarge - ? 3 - : isMedium - ? 2 - : 1, - mainAxisExtent: 140, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - ), - children: [ - CurtainToggle( - value: status, - code: 'Curtains', - deviceId: deviceId, - label: 'Curtains', - ), - - ], + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, ), + children: [ + const SizedBox.shrink(), + CurtainToggle( + value: status, + code: 'control', + deviceId: deviceId, + label: 'Curtains', + ), + const SizedBox.shrink(), + ], ); } } From e0f7c7ab39b758b9bcb739fb56abd53514d64d5e Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 8 Sep 2024 21:02:48 +0300 Subject: [PATCH 3/6] fix search filter tabs and checkbox no data selection --- lib/pages/common/custom_table.dart | 34 ++++++--- .../bloc/device_managment_bloc.dart | 73 +++++++++++++++---- .../bloc/device_managment_event.dart | 2 + .../widgets/device_search_filters.dart | 4 +- .../curtain/bloc/curtain_bloc.dart | 2 +- 5 files changed, 85 insertions(+), 30 deletions(-) diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 9d2fe949..317f7381 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -22,7 +22,7 @@ class DynamicTable extends StatefulWidget { required this.headers, required this.data, required this.size, - this.tableName, + this.tableName, required this.isEmpty, required this.withCheckBox, required this.withSelectAll, @@ -57,13 +57,19 @@ class _DynamicTableState extends State { } void _initializeSelection() { - _selected = List.generate(widget.data.length, (index) { - // Check if the initialSelectedIds contains the deviceUuid - // uuidIndex is the index of the column containing the deviceUuid - final deviceUuid = widget.data[index][widget.uuidIndex]; - return widget.initialSelectedIds != null && - widget.initialSelectedIds!.contains(deviceUuid); - }); + if (widget.data.isEmpty) { + _selected = []; + _selectAll = false; + } else { + _selected = List.generate(widget.data.length, (index) { + // Check if the initialSelectedIds contains the deviceUuid + // uuidIndex is the index of the column containing the deviceUuid + final deviceUuid = widget.data[index][widget.uuidIndex]; + return widget.initialSelectedIds != null && + widget.initialSelectedIds!.contains(deviceUuid); + }); + _selectAll = _selected.every((element) => element == true); + } } void _toggleRowSelection(int index) { @@ -76,7 +82,6 @@ class _DynamicTableState extends State { }); } - void _toggleSelectAll(bool? value) { setState(() { _selectAll = value ?? false; @@ -125,7 +130,9 @@ class _DynamicTableState extends State { ), Text( // no password - widget.tableName=='AccessManagement'? 'No Password ' : 'No Devices', + widget.tableName == 'AccessManagement' + ? 'No Password ' + : 'No Devices', style: Theme.of(context) .textTheme .bodySmall! @@ -178,8 +185,11 @@ class _DynamicTableState extends State { ), ), child: Checkbox( - value: _selected.every((element) => element == true), - onChanged:widget.withSelectAll?_toggleSelectAll:null, + value: widget.data.isNotEmpty && + _selected.every((element) => element == true), + onChanged: widget.withSelectAll && widget.data.isNotEmpty + ? _toggleSelectAll + : null, ), ); } 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 381c7969..37447cc3 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 @@ -5,7 +5,8 @@ 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; @@ -21,9 +22,11 @@ class DeviceManagementBloc extends Bloc(_onSelectedFilterChanged); on(_onSearchDevices); on(_onSelectDevice); + on(_onResetFilters); } - Future _onFetchDevices(FetchDevices event, Emitter emit) async { + Future _onFetchDevices( + FetchDevices event, Emitter emit) async { emit(DeviceManagementLoading()); try { final devices = await DevicesManagementApi().fetchDevices(); @@ -44,9 +47,10 @@ class DeviceManagementBloc extends Bloc emit) async { + void _onFilterDevices( + FilterDevices event, Emitter emit) async { if (_devices.isNotEmpty) { - _filteredDevices = _devices.where((device) { + _filteredDevices = List.from(_devices.where((device) { switch (event.filter) { case 'Online': return device.online == true; @@ -57,27 +61,48 @@ class DeviceManagementBloc extends Bloc emit) { + Future _onResetFilters( + ResetFilters event, Emitter emit) async { + productName = ''; + _selectedDevices.clear(); + _filteredDevices = List.from(_devices); + _selectedIndex = 0; + emit(DeviceManagementLoaded( + devices: _devices, + selectedIndex: 0, + onlineCount: _onlineCount, + offlineCount: _offlineCount, + lowBatteryCount: _lowBatteryCount, + selectedDevice: null, + )); + } + + 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)) { @@ -112,8 +137,10 @@ class DeviceManagementBloc extends Bloc 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) { @@ -129,7 +156,8 @@ class DeviceManagementBloc extends Bloc 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) && @@ -151,19 +179,32 @@ class DeviceManagementBloc extends Bloc get props => [selectedDevice]; } + +class ResetFilters extends DeviceManagementEvent {} diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index e2d43b1a..51215c84 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -77,7 +77,9 @@ class _DeviceSearchFiltersState extends State communityController.clear(); unitNameController.clear(); productNameController.clear(); - context.read().add(FetchDevices()); + context.read() + ..add(ResetFilters()) + ..add(FetchDevices()); }, ); } diff --git a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart index fe6dd0b8..1b2f5864 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart @@ -59,7 +59,7 @@ class CurtainBloc extends Bloc { } _timer = Timer(const Duration(seconds: 1), () async { try { - final controlValue = value ? 'open' : 'stop'; + final controlValue = value ? 'open' : 'close'; final response = await DevicesManagementApi() .deviceControl(deviceId, Status(code: code, value: controlValue)); From f6cc19cad7ff44bdb92de6b4ee2460e9dc83369d Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 8 Sep 2024 21:27:32 +0300 Subject: [PATCH 4/6] add ac child unlock --- assets/icons/unlock_ic.svg | 5 +++++ lib/pages/device_managment/ac/view/ac_device_control.dart | 5 +++-- lib/utils/constants/assets.dart | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 assets/icons/unlock_ic.svg diff --git a/assets/icons/unlock_ic.svg b/assets/icons/unlock_ic.svg new file mode 100644 index 00000000..8ce3cc4d --- /dev/null +++ b/assets/icons/unlock_ic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 63d1799d..3e47885c 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -68,14 +68,15 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout { code: 'child_lock', deviceId: device.uuid!, description: 'Child Lock', - icon: Assets.childLock, + icon: + state.status.childLock ? Assets.unlock : Assets.childLock, ), ], ); } else if (state is AcsLoadingState) { return const Center(child: CircularProgressIndicator()); } else { - return const Center(child: Text('Error fetching status')); + return const Center(child: Text('Error fetching status')); } }, ), diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index e775fd84..5d2f5f2d 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -125,4 +125,5 @@ class Assets { static const String parlour = 'assets/icons/parlour.svg'; static const String grid = "assets/images/grid.svg"; static const String curtainIcon = "assets/images/curtain.svg"; + static const String unlock = 'assets/icons/unlock_ic.svg'; } From 1f22e5d32244017bb16b86a294b3b49b8b706cb1 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Sun, 8 Sep 2024 23:09:45 +0300 Subject: [PATCH 5/6] Removed prints and warnings --- lib/pages/auth/bloc/auth_bloc.dart | 78 +++++++++++------------------- lib/services/devices_mang_api.dart | 8 +-- 2 files changed, 31 insertions(+), 55 deletions(-) diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index aee4224b..851811e9 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,16 +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)); try { forgetEmailValidate = ''; _remainingTime = (await AuthenticationAPI.sendOtp( @@ -59,13 +55,12 @@ class AuthBloc extends Bloc { if (e.response!.statusCode == 400) { final errorData = e.response!.data; String errorMessage = errorData['message']; - print('sendOtp=$errorMessage'); if (errorMessage == 'User not found') { - validate='Invalid Credential'; + validate = 'Invalid Credential'; emit(AuthInitialState()); return 1; } else { - validate=''; + validate = ''; _remainingTime = errorData['data']['cooldown'] ?? 1; emit(AuthInitialState()); } @@ -75,7 +70,6 @@ class AuthBloc extends Bloc { return 1; } emit(AuthInitialState()); - } catch (e) { emit(AuthInitialState()); @@ -88,8 +82,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,16 +92,14 @@ 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()); @@ -125,12 +116,10 @@ class AuthBloc extends Bloc { } else if (errorMessage == "OTP expired") { forgetValidate = 'One time password has been expired.'; emit(AuthInitialState()); - }else{ - validate=''; + } else { + validate = ''; emit(AuthInitialState()); - } - } } @@ -142,9 +131,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 ///////////////////////////////////// @@ -176,9 +163,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!'; @@ -188,8 +173,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()); @@ -207,9 +191,9 @@ class AuthBloc extends Bloc { } checkBoxToggle( - CheckBoxEvent event, - Emitter emit, - ) { + CheckBoxEvent event, + Emitter emit, + ) { emit(AuthLoading()); isChecked = event.newValue!; add(CheckEnableEvent()); @@ -346,14 +330,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'; @@ -405,9 +387,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(':'); @@ -416,9 +396,9 @@ class AuthBloc extends Bloc { } bool checkEnable( - CheckEnableEvent event, - Emitter emit, - ) { + CheckEnableEvent event, + Emitter emit, + ) { emit(AuthLoading()); checkValidate = isChecked == true && loginPasswordController.text.isNotEmpty && @@ -429,18 +409,18 @@ class AuthBloc extends Bloc { } changeValidate( - ChangeValidateEvent event, - Emitter emit, - ) { + ChangeValidateEvent event, + Emitter emit, + ) { emit(AuthLoading()); validate = ''; emit(LoginInitial()); } changeForgetValidate( - ChangeValidateEvent event, - Emitter emit, - ) { + ChangeValidateEvent event, + Emitter emit, + ) { emit(AuthLoading()); forgetValidate = ''; emit(LoginInitial()); diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 93ace830..70ce96f1 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -33,7 +33,6 @@ class DevicesManagementApi { path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', uuid), showServerMessage: true, expectedResponseModel: (json) { - print('getDeviceStatus$json'); return DeviceStatus.fromJson(json); }, ); @@ -66,8 +65,7 @@ class DevicesManagementApi { } } - static Future> getDevicesByGatewayId( - String gatewayId) async { + static Future> getDevicesByGatewayId(String gatewayId) async { final response = await HTTPService().get( path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), showServerMessage: false, @@ -98,9 +96,7 @@ class DevicesManagementApi { static Future getDeviceReports(String uuid, String code) async { final response = await HTTPService().get( - path: ApiEndpoints.getDeviceLogs - .replaceAll('{uuid}', uuid) - .replaceAll('{code}', code), + path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code), showServerMessage: false, expectedResponseModel: (json) { return DeviceReport.fromJson(json); From a86c45a88ba0aa01f4d9a79800b6295f31b3df15 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Sun, 8 Sep 2024 23:10:22 +0300 Subject: [PATCH 6/6] Removed prints and warnings --- lib/services/auth_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 5d5dcd57..f84bed1a 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -1,4 +1,3 @@ -import 'package:dio/dio.dart'; import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/model/token.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -36,6 +35,7 @@ class AuthenticationAPI { expectedResponseModel: (json) { return json['data']['cooldown']; }); + return response; } static Future verifyOtp({required String email, required String otpCode}) async {