From df34ded1536aeffc1aea744465ba04d14371be69 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 24 Jun 2025 11:35:03 +0300 Subject: [PATCH 01/37] Add responsive input fields and radio groups for visitor password setup --- .../view/access_type_radio_group.dart | 120 ++++ .../view/responsive_fields_row.dart | 73 +++ .../view/usage_frequency_radio_group.dart | 91 +++ .../view/visitor_password_dialog.dart | 589 +++++++++--------- 4 files changed, 574 insertions(+), 299 deletions(-) create mode 100644 lib/pages/visitor_password/view/access_type_radio_group.dart create mode 100644 lib/pages/visitor_password/view/responsive_fields_row.dart create mode 100644 lib/pages/visitor_password/view/usage_frequency_radio_group.dart diff --git a/lib/pages/visitor_password/view/access_type_radio_group.dart b/lib/pages/visitor_password/view/access_type_radio_group.dart new file mode 100644 index 00000000..be4adb9d --- /dev/null +++ b/lib/pages/visitor_password/view/access_type_radio_group.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; + +class AccessTypeRadioGroup extends StatelessWidget { + final String? selectedType; + final String? accessTypeSelected; + final Function(String) onTypeSelected; + final VisitorPasswordBloc visitorBloc; + + const AccessTypeRadioGroup({ + super.key, + required this.selectedType, + required this.accessTypeSelected, + required this.onTypeSelected, + required this.visitorBloc, + }); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + final text = Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black, fontSize: 13); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + Text('Access Type', style: text), + ], + ), + const SizedBox(height: 8), + if (size.width < 800) + Column( + children: [ + _buildRadioTile( + context, + 'Online Password', + selectedType ?? accessTypeSelected, + onTypeSelected, + ), + const SizedBox(height: 8), + _buildRadioTile( + context, + 'Offline Password', + selectedType ?? accessTypeSelected, + onTypeSelected, + ), + ], + ) + else + Row( + children: [ + Expanded( + flex: 2, + child: Row( + children: [ + _buildRadioTile( + context, + 'Online Password', + selectedType ?? accessTypeSelected, + onTypeSelected, + width: size.width * 0.12, + ), + _buildRadioTile( + context, + 'Offline Password', + selectedType ?? accessTypeSelected, + onTypeSelected, + width: size.width * 0.12, + ), + ], + ), + ), + const Spacer(flex: 2), + ], + ), + ], + ); + } + + Widget _buildRadioTile( + BuildContext context, + String value, + String? groupValue, + Function(String) onChanged, { + double? width, + }) { + return SizedBox( + width: width, + child: RadioListTile( + contentPadding: EdgeInsets.zero, + title: Text(value, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black, + fontSize: 13, + )), + value: value, + groupValue: groupValue, + onChanged: (value) { + if (value != null) { + onChanged(value); + if (value == 'Dynamic Password') { + visitorBloc.usageFrequencySelected = ''; + } + } + }, + ), + ); + } +} diff --git a/lib/pages/visitor_password/view/responsive_fields_row.dart b/lib/pages/visitor_password/view/responsive_fields_row.dart new file mode 100644 index 00000000..92a79276 --- /dev/null +++ b/lib/pages/visitor_password/view/responsive_fields_row.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart'; + +class NameAndEmailFields extends StatelessWidget { + final TextEditingController nameController; + final TextEditingController emailController; + final String? Function(String?)? nameValidator; + final String? Function(String?)? emailValidator; + + const NameAndEmailFields({ + super.key, + required this.nameController, + required this.emailController, + required this.nameValidator, + required this.emailValidator, + }); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + return Container( + width: size.width, + child: size.width < 800 + ? Column( + children: [ + CustomWebTextField( + validator: nameValidator, + controller: nameController, + isRequired: true, + textFieldName: 'Name', + description: '', + ), + const SizedBox(height: 15), + CustomWebTextField( + validator: emailValidator, + controller: emailController, + isRequired: true, + textFieldName: 'Email Address', + description: + 'The password will be sent to the visitor’s email address.', + ), + ], + ) + : Row( + children: [ + Expanded( + flex: 2, + child: CustomWebTextField( + validator: nameValidator, + controller: nameController, + isRequired: true, + textFieldName: 'Name', + description: '', + ), + ), + const Spacer(), + Expanded( + flex: 2, + child: CustomWebTextField( + validator: emailValidator, + controller: emailController, + isRequired: true, + textFieldName: 'Email Address', + description: + 'The password will be sent to the visitor’s email address.', + ), + ), + const Spacer(), + ], + ), + ); + } +} diff --git a/lib/pages/visitor_password/view/usage_frequency_radio_group.dart b/lib/pages/visitor_password/view/usage_frequency_radio_group.dart new file mode 100644 index 00000000..aebebefe --- /dev/null +++ b/lib/pages/visitor_password/view/usage_frequency_radio_group.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; + +class UsageFrequencyRadioGroup extends StatelessWidget { + final String? selectedFrequency; + final String? usageFrequencySelected; + final Function(String) onFrequencySelected; + + const UsageFrequencyRadioGroup({ + super.key, + required this.selectedFrequency, + required this.usageFrequencySelected, + required this.onFrequencySelected, + }); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + final text = Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black, fontSize: 13); + + return size.width < 600 + ? Column( + children: [ + _buildRadioTile( + context, + 'One-Time', + selectedFrequency ?? usageFrequencySelected, + onFrequencySelected, + text: text, + fullWidth: true, + ), + const SizedBox(height: 8), + _buildRadioTile( + context, + 'Periodic', + selectedFrequency ?? usageFrequencySelected, + onFrequencySelected, + text: text, + fullWidth: true, + ), + ], + ) + : Row( + children: [ + _buildRadioTile( + context, + 'One-Time', + selectedFrequency ?? usageFrequencySelected, + onFrequencySelected, + width: size.width * 0.12, + text: text, + ), + _buildRadioTile( + context, + 'Periodic', + selectedFrequency ?? usageFrequencySelected, + onFrequencySelected, + width: size.width * 0.12, + text: text, + ), + ], + ); + } + + Widget _buildRadioTile( + BuildContext context, + String value, + String? groupValue, + Function(String) onChanged, { + double? width, + required TextStyle text, + bool fullWidth = false, + }) { + return SizedBox( + width: fullWidth ? double.infinity : width, + child: RadioListTile( + contentPadding: EdgeInsets.zero, + title: Text(value, style: text), + value: value, + groupValue: groupValue, + onChanged: (String? value) { + if (value != null) { + onChanged(value); + } + }, + ), + ); + } +} diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 1e43af46..4b5cb0e2 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -9,8 +9,11 @@ import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/pages/visitor_password/view/access_type_radio_group.dart'; import 'package:syncrow_web/pages/visitor_password/view/add_device_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/view/repeat_widget.dart'; +import 'package:syncrow_web/pages/visitor_password/view/responsive_fields_row.dart'; +import 'package:syncrow_web/pages/visitor_password/view/usage_frequency_radio_group.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -21,7 +24,10 @@ class VisitorPasswordDialog extends StatelessWidget { @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; - var text = Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black, fontSize: 13); + var text = Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black, fontSize: 13); return BlocProvider( create: (context) => VisitorPasswordBloc(), child: BlocListener( @@ -35,7 +41,8 @@ class VisitorPasswordDialog extends StatelessWidget { title: 'Sent Successfully', widgeta: Column( children: [ - if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty) + if (visitorBloc + .passwordStatus!.failedOperations.isNotEmpty) Column( children: [ const Text('Failed Devices'), @@ -45,7 +52,8 @@ class VisitorPasswordDialog extends StatelessWidget { child: ListView.builder( scrollDirection: Axis.horizontal, shrinkWrap: true, - itemCount: visitorBloc.passwordStatus!.failedOperations.length, + itemCount: visitorBloc + .passwordStatus!.failedOperations.length, itemBuilder: (context, index) { return Container( margin: EdgeInsets.all(5), @@ -53,14 +61,17 @@ class VisitorPasswordDialog extends StatelessWidget { height: 45, child: Center( child: Text(visitorBloc - .passwordStatus!.failedOperations[index].deviceUuid)), + .passwordStatus! + .failedOperations[index] + .deviceUuid)), ); }, ), ), ], ), - if (visitorBloc.passwordStatus!.successOperations.isNotEmpty) + if (visitorBloc + .passwordStatus!.successOperations.isNotEmpty) Column( children: [ const Text('Success Devices'), @@ -70,15 +81,18 @@ class VisitorPasswordDialog extends StatelessWidget { child: ListView.builder( scrollDirection: Axis.horizontal, shrinkWrap: true, - itemCount: visitorBloc.passwordStatus!.successOperations.length, + itemCount: visitorBloc + .passwordStatus!.successOperations.length, itemBuilder: (context, index) { return Container( margin: EdgeInsets.all(5), decoration: containerDecoration, height: 45, child: Center( - child: Text(visitorBloc.passwordStatus! - .successOperations[index].deviceUuid)), + child: Text(visitorBloc + .passwordStatus! + .successOperations[index] + .deviceUuid)), ); }, ), @@ -89,7 +103,6 @@ class VisitorPasswordDialog extends StatelessWidget { )) .then((v) { Navigator.of(context).pop(true); - }); } else if (state is FailedState) { visitorBloc.stateDialog( @@ -102,15 +115,16 @@ class VisitorPasswordDialog extends StatelessWidget { child: BlocBuilder( builder: (BuildContext context, VisitorPasswordState state) { final visitorBloc = BlocProvider.of(context); - bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat; + bool isRepeat = + state is IsRepeatState ? state.repeat : visitorBloc.repeat; return AlertDialog( backgroundColor: Colors.white, title: Text( 'Create visitor password', - style: Theme.of(context) - .textTheme - .headlineLarge! - .copyWith(fontWeight: FontWeight.w400, fontSize: 24, color: Colors.black), + style: Theme.of(context).textTheme.headlineLarge!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 24, + color: Colors.black), ), content: state is LoadingInitialState ? const Center(child: CircularProgressIndicator()) @@ -121,34 +135,11 @@ class VisitorPasswordDialog extends StatelessWidget { padding: const EdgeInsets.all(5.0), child: ListBody( children: [ - Container( - child: Row( - children: [ - Expanded( - flex: 2, - child: CustomWebTextField( - validator: visitorBloc.validate, - controller: visitorBloc.userNameController, - isRequired: true, - textFieldName: 'Name', - description: '', - ), - ), - const Spacer(), - Expanded( - flex: 2, - child: CustomWebTextField( - validator: visitorBloc.validateEmail, - controller: visitorBloc.emailController, - isRequired: true, - textFieldName: 'Email Address', - description: - 'The password will be sent to the visitor’s email address.', - ), - ), - const Spacer(), - ], - ), + NameAndEmailFields( + nameController: visitorBloc.userNameController, + emailController: visitorBloc.emailController, + nameValidator: visitorBloc.validate, + emailValidator: visitorBloc.validateEmail, ), const SizedBox( height: 15, @@ -156,107 +147,43 @@ class VisitorPasswordDialog extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - Text('Access Type', style: text), - ], + AccessTypeRadioGroup( + selectedType: state is PasswordTypeSelected + ? state.selectedType + : null, + accessTypeSelected: + visitorBloc.accessTypeSelected, + onTypeSelected: (value) { + context + .read() + .add(SelectPasswordType(value)); + }, + visitorBloc: visitorBloc, ), - Row( - children: [ - Expanded( - flex: 2, - child: Row( - children: [ - SizedBox( - width: size.width * 0.12, - child: RadioListTile( - contentPadding: EdgeInsets.zero, - title: Text( - 'Online Password', - style: text, - ), - value: 'Online Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context - .read() - .add(SelectPasswordType(value)); - } - }, - ), - ), - SizedBox( - width: size.width * 0.12, - child: RadioListTile( - contentPadding: EdgeInsets.zero, - title: Text('Offline Password', style: text), - value: 'Offline Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context - .read() - .add(SelectPasswordType(value)); - } - }, - ), - ), - // SizedBox( - // width: size.width * 0.12, - // child: RadioListTile( - // contentPadding: EdgeInsets.zero, - // title: Text( - // 'Dynamic Password', - // style: text, - // ), - // value: 'Dynamic Password', - // groupValue: (state is PasswordTypeSelected) - // ? state.selectedType - // : visitorBloc.accessTypeSelected, - // onChanged: (String? value) { - // if (value != null) { - // context - // .read() - // .add(SelectPasswordType(value)); - // visitorBloc.usageFrequencySelected = ''; - // } - // }, - // ), - // ), - ], - )), - const Spacer( - flex: 2, - ), - ], - ), - if (visitorBloc.accessTypeSelected == 'Online Password') + + if (visitorBloc.accessTypeSelected == + 'Online Password') Text( 'Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.grayColor, - fontSize: 9), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.grayColor, + fontSize: 9), ), - if (visitorBloc.accessTypeSelected == 'Offline Password') + if (visitorBloc.accessTypeSelected == + 'Offline Password') Text( 'Unaffected by the online status of the device, you can select online or offline device, and the system randomly generates a digital password', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.grayColor, - fontSize: 9), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.grayColor, + fontSize: 9), ), // if (visitorBloc.accessTypeSelected == 'Dynamic Password') // Text( @@ -271,143 +198,170 @@ class VisitorPasswordDialog extends StatelessWidget { ) ], ), - visitorBloc.accessTypeSelected == 'Dynamic Password' - ? const SizedBox() - : Column( - crossAxisAlignment: CrossAxisAlignment.start, + if (visitorBloc.accessTypeSelected == + 'Dynamic Password') + const SizedBox() + else + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - Text( - 'Usage Frequency', - style: text, - ), - ], + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), ), - Row( - children: [ - SizedBox( - width: size.width * 0.12, - child: RadioListTile( - contentPadding: EdgeInsets.zero, - title: Text( - 'One-Time', - style: text, - ), - value: 'One-Time', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context - .read() - .add(SelectUsageFrequency(value)); - } - }, - ), - ), - SizedBox( - width: size.width * 0.12, - child: RadioListTile( - contentPadding: EdgeInsets.zero, - title: Text('Periodic', style: text), - value: 'Periodic', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context.read() - .add(SelectUsageFrequency(value)); - } - }, - ), - ), - ], + Text( + 'Usage Frequency', + style: text, ), - - //One-Time - if (visitorBloc.usageFrequencySelected == 'One-Time' && - visitorBloc.accessTypeSelected == 'Online Password') - Text( - 'Within the validity period, each device can be unlocked only once.', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, fontSize: 9), - ), - if (visitorBloc.usageFrequencySelected == 'One-Time' && - visitorBloc.accessTypeSelected == 'Offline Password') - Text( - 'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, fontSize: 9), - ), - - // Periodic - if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Offline Password') - Text( - 'Within the validity period, there is no limit to the number of times each device can be unlocked, and it should be used at least once within 24 hours after the entry into force.', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, fontSize: 9), - ), - - if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Online Password') - Text( - 'Within the validity period, there is no limit to the number of times each device can be unlocked.', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, fontSize: 9), - ), ], ), + UsageFrequencyRadioGroup( + selectedFrequency: + state is UsageFrequencySelected + ? state.selectedFrequency + : null, + usageFrequencySelected: + visitorBloc.usageFrequencySelected, + onFrequencySelected: (value) { + context + .read() + .add(SelectUsageFrequency(value)); + }, + ), + + //One-Time + if (visitorBloc.usageFrequencySelected == + 'One-Time' && + visitorBloc.accessTypeSelected == + 'Online Password') + Text( + 'Within the validity period, each device can be unlocked only once.', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.grayColor, + fontSize: 9), + ), + if (visitorBloc.usageFrequencySelected == + 'One-Time' && + visitorBloc.accessTypeSelected == + 'Offline Password') + Text( + 'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.grayColor, + fontSize: 9), + ), + + // Periodic + if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Offline Password') + Text( + 'Within the validity period, there is no limit to the number of times each device can be unlocked, and it should be used at least once within 24 hours after the entry into force.', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.grayColor, + fontSize: 9), + ), + + if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Online Password') + Text( + 'Within the validity period, there is no limit to the number of times each device can be unlocked.', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.grayColor, + fontSize: 9), + ), + ], + ), const SizedBox( height: 20, ), - if ((visitorBloc.usageFrequencySelected != 'One-Time' || - visitorBloc.accessTypeSelected != 'Offline Password') && + if ((visitorBloc.usageFrequencySelected != + 'One-Time' || + visitorBloc.accessTypeSelected != + 'Offline Password') && (visitorBloc.usageFrequencySelected != '')) DateTimeWebWidget( isRequired: true, title: 'Access Period', size: size, endTime: () { - if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Offline Password') { - visitorBloc.add(SelectTimeEvent(context: context, isEffective: false)); + if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Offline Password') { + visitorBloc.add(SelectTimeEvent( + context: context, + isEffective: false)); } else { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false, isRepeat: false)); + visitorBloc.add( + SelectTimeVisitorPassword( + context: context, + isStart: false, + isRepeat: false)); } }, startTime: () { - if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Offline Password') { - visitorBloc.add( - SelectTimeEvent(context: context, isEffective: true)); + if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Offline Password') { + visitorBloc.add(SelectTimeEvent( + context: context, + isEffective: true)); } else { - visitorBloc.add(SelectTimeVisitorPassword( - context: context, isStart: true, isRepeat: false)); + visitorBloc.add( + SelectTimeVisitorPassword( + context: context, + isStart: true, + isRepeat: false)); } }, - firstString: (visitorBloc.usageFrequencySelected == - 'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password') + firstString: (visitorBloc + .usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Offline Password') ? visitorBloc.effectiveTime - : visitorBloc.startTimeAccess.toString(), - secondString: (visitorBloc.usageFrequencySelected == - 'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password') + : visitorBloc.startTimeAccess + .toString(), + secondString: (visitorBloc + .usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Offline Password') ? visitorBloc.expirationTime : visitorBloc.endTimeAccess.toString(), icon: Assets.calendarIcon), - const SizedBox(height: 10,), - Text(visitorBloc.accessPeriodValidate, - style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: ColorsManager.red),), + const SizedBox( + height: 10, + ), + Text( + visitorBloc.accessPeriodValidate, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.red), + ), const SizedBox( height: 20, ), @@ -431,16 +385,21 @@ class VisitorPasswordDialog extends StatelessWidget { ), Text( 'Within the validity period, each device can be unlocked only once.', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.grayColor, - fontSize: 9), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.grayColor, + fontSize: 9), ), const SizedBox( height: 20, ), - if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Online Password') + if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Online Password') SizedBox( width: 100, child: Column( @@ -451,7 +410,8 @@ class VisitorPasswordDialog extends StatelessWidget { child: CupertinoSwitch( value: visitorBloc.repeat, onChanged: (value) { - visitorBloc.add(ToggleRepeatEvent()); + visitorBloc + .add(ToggleRepeatEvent()); }, applyTheme: true, ), @@ -459,12 +419,16 @@ class VisitorPasswordDialog extends StatelessWidget { ], ), ), - if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Online Password') - isRepeat ? const RepeatWidget() : const SizedBox(), + if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Online Password') + isRepeat + ? const RepeatWidget() + : const SizedBox(), Container( decoration: containerDecoration, - width: size.width / 9, + width: size.width / 6, child: DefaultButton( onPressed: () { showDialog( @@ -472,22 +436,28 @@ class VisitorPasswordDialog extends StatelessWidget { barrierDismissible: false, builder: (BuildContext context) { return AddDeviceDialog( - selectedDeviceIds: visitorBloc.selectedDevices, + selectedDeviceIds: + visitorBloc.selectedDevices, ); }, ).then((listDevice) { if (listDevice != null) { - visitorBloc.selectedDevices = listDevice; + visitorBloc.selectedDevices = + listDevice; } }); }, borderRadius: 8, child: Text( '+ Add Device', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.whiteColors, - fontSize: 12), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontWeight: FontWeight.w400, + color: + ColorsManager.whiteColors, + fontSize: 12), ), ), ), @@ -525,30 +495,37 @@ class VisitorPasswordDialog extends StatelessWidget { onPressed: () { if (visitorBloc.forgetFormKey.currentState!.validate()) { if (visitorBloc.selectedDevices.isNotEmpty) { - if (visitorBloc.usageFrequencySelected == 'One-Time' && - visitorBloc.accessTypeSelected == 'Offline Password') { + if (visitorBloc.usageFrequencySelected == + 'One-Time' && + visitorBloc.accessTypeSelected == + 'Offline Password') { setPasswordFunction(context, size, visitorBloc); - } else if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Offline Password') { + } else if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Offline Password') { if (visitorBloc.expirationTime != 'End Time' && - visitorBloc.effectiveTime != 'Start Time' ) { + visitorBloc.effectiveTime != 'Start Time') { setPasswordFunction(context, size, visitorBloc); - }else{ + } else { visitorBloc.stateDialog( context: context, - message: 'Please select Access Period to continue', + message: + 'Please select Access Period to continue', title: 'Access Period'); } - } else if( - visitorBloc.endTimeAccess.toString()!='End Time' - &&visitorBloc.startTimeAccess.toString()!='Start Time') { + } else if (visitorBloc.endTimeAccess.toString() != + 'End Time' && + visitorBloc.startTimeAccess.toString() != + 'Start Time') { if (visitorBloc.effectiveTimeTimeStamp != null && visitorBloc.expirationTimeTimeStamp != null) { if (isRepeat == true) { if (visitorBloc.expirationTime != 'End Time' && visitorBloc.effectiveTime != 'Start Time' && visitorBloc.selectedDays.isNotEmpty) { - setPasswordFunction(context, size, visitorBloc); + setPasswordFunction( + context, size, visitorBloc); } else { visitorBloc.stateDialog( context: context, @@ -562,14 +539,16 @@ class VisitorPasswordDialog extends StatelessWidget { } else { visitorBloc.stateDialog( context: context, - message: 'Please select Access Period to continue', + message: + 'Please select Access Period to continue', title: 'Access Period'); } - }else{ - visitorBloc.stateDialog( - context: context, - message: 'Please select Access Period to continue', - title: 'Access Period'); + } else { + visitorBloc.stateDialog( + context: context, + message: + 'Please select Access Period to continue', + title: 'Access Period'); } } else { visitorBloc.stateDialog( @@ -615,7 +594,8 @@ class VisitorPasswordDialog extends StatelessWidget { content: SizedBox( height: size.height * 0.25, child: Center( - child: CircularProgressIndicator(), // Display a loading spinner + child: + CircularProgressIndicator(), // Display a loading spinner ), ), ); @@ -639,7 +619,10 @@ class VisitorPasswordDialog extends StatelessWidget { ), Text( 'Set Password', - style: Theme.of(context).textTheme.headlineLarge!.copyWith( + style: Theme.of(context) + .textTheme + .headlineLarge! + .copyWith( fontSize: 30, fontWeight: FontWeight.w400, color: Colors.black, @@ -689,37 +672,45 @@ class VisitorPasswordDialog extends StatelessWidget { onPressed: () { Navigator.pop(context); if (visitorBloc.usageFrequencySelected == 'One-Time' && - visitorBloc.accessTypeSelected == 'Online Password') { + visitorBloc.accessTypeSelected == + 'Online Password') { visitorBloc.add(OnlineOneTimePasswordEvent( context: context, passwordName: visitorBloc.userNameController.text, email: visitorBloc.emailController.text, )); - } - else if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Online Password') { + } else if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Online Password') { visitorBloc.add(OnlineMultipleTimePasswordEvent( passwordName: visitorBloc.userNameController.text, email: visitorBloc.emailController.text, - effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), - invalidTime: visitorBloc.expirationTimeTimeStamp.toString(), + effectiveTime: + visitorBloc.effectiveTimeTimeStamp.toString(), + invalidTime: + visitorBloc.expirationTimeTimeStamp.toString(), )); - } - else if (visitorBloc.usageFrequencySelected == 'One-Time' && - visitorBloc.accessTypeSelected == 'Offline Password') { + } else if (visitorBloc.usageFrequencySelected == + 'One-Time' && + visitorBloc.accessTypeSelected == + 'Offline Password') { visitorBloc.add(OfflineOneTimePasswordEvent( context: context, passwordName: visitorBloc.userNameController.text, email: visitorBloc.emailController.text, )); - } - else if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Offline Password') { + } else if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Offline Password') { visitorBloc.add(OfflineMultipleTimePasswordEvent( passwordName: visitorBloc.userNameController.text, email: visitorBloc.emailController.text, - effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), - invalidTime: visitorBloc.expirationTimeTimeStamp.toString(), + effectiveTime: + visitorBloc.effectiveTimeTimeStamp.toString(), + invalidTime: + visitorBloc.expirationTimeTimeStamp.toString(), )); } }, From 6c268754a928c086f9e649de8824ed5194266cab Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Wed, 25 Jun 2025 16:26:57 +0300 Subject: [PATCH 02/37] add icons and the basic route to show curtain module --- assets/icons/close_curtain.svg | 8 ++ assets/icons/open_curtain.svg | 8 ++ assets/icons/pause_curtain.svg | 6 + assets/icons/reverse_arrows.svg | 10 ++ .../helper/route_controls_based_code.dart | 113 ++++++++++++++---- 5 files changed, 123 insertions(+), 22 deletions(-) create mode 100644 assets/icons/close_curtain.svg create mode 100644 assets/icons/open_curtain.svg create mode 100644 assets/icons/pause_curtain.svg create mode 100644 assets/icons/reverse_arrows.svg diff --git a/assets/icons/close_curtain.svg b/assets/icons/close_curtain.svg new file mode 100644 index 00000000..53f9e03b --- /dev/null +++ b/assets/icons/close_curtain.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/open_curtain.svg b/assets/icons/open_curtain.svg new file mode 100644 index 00000000..715773a5 --- /dev/null +++ b/assets/icons/open_curtain.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/pause_curtain.svg b/assets/icons/pause_curtain.svg new file mode 100644 index 00000000..8f90ea4f --- /dev/null +++ b/assets/icons/pause_curtain.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/reverse_arrows.svg b/assets/icons/reverse_arrows.svg new file mode 100644 index 00000000..fe119c39 --- /dev/null +++ b/assets/icons/reverse_arrows.svg @@ -0,0 +1,10 @@ + + + + + + + + + + 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 5586a310..cc09260e 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 @@ -7,6 +7,8 @@ import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_s import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart'; import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_status_view.dart'; import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/view/curtain_module_batch.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/view/curtain_module_items.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart'; @@ -18,6 +20,7 @@ import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dar import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart'; import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart'; @@ -39,8 +42,6 @@ import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heate import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_control_view.dart'; -import '../../one_g_glass_switch/view/one_gang_glass_switch_control_view.dart'; - mixin RouteControlsBasedCode { Widget routeControlsWidgets({required AllDevicesModel device}) { switch (device.productType) { @@ -84,6 +85,10 @@ mixin RouteControlsBasedCode { return CurtainStatusControlsView( deviceId: device.uuid!, ); + case 'CUR_2': + return CurtainModuleItems( + deviceId: device.uuid!, + ); case 'AC': return AcDeviceControlsView(device: device); case 'WH': @@ -107,7 +112,7 @@ mixin RouteControlsBasedCode { case 'SOS': return SosDeviceControlsView(device: device); - case 'NCPS': + case 'NCPS': return FlushMountedPresenceSensorControlView(device: device); default: return const SizedBox(); @@ -132,76 +137,140 @@ mixin RouteControlsBasedCode { switch (devices.first.productType) { case '1G': return WallLightBatchControlView( - deviceIds: devices.where((e) => (e.productType == '1G')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == '1G') + .map((e) => e.uuid!) + .toList(), ); case '2G': return TwoGangBatchControlView( - deviceIds: devices.where((e) => (e.productType == '2G')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == '2G') + .map((e) => e.uuid!) + .toList(), ); case '3G': return LivingRoomBatchControlsView( - deviceIds: devices.where((e) => (e.productType == '3G')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == '3G') + .map((e) => e.uuid!) + .toList(), ); case '1GT': return OneGangGlassSwitchBatchControlView( - deviceIds: devices.where((e) => (e.productType == '1GT')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == '1GT') + .map((e) => e.uuid!) + .toList(), ); case '2GT': return TwoGangGlassSwitchBatchControlView( - deviceIds: devices.where((e) => (e.productType == '2GT')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == '2GT') + .map((e) => e.uuid!) + .toList(), ); case '3GT': return ThreeGangGlassSwitchBatchControlView( - deviceIds: devices.where((e) => (e.productType == '3GT')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == '3GT') + .map((e) => e.uuid!) + .toList(), ); case 'GW': return GatewayBatchControlView( - gatewayIds: devices.where((e) => (e.productType == 'GW')).map((e) => e.uuid!).toList(), + gatewayIds: devices + .where((e) => e.productType == 'GW') + .map((e) => e.uuid!) + .toList(), ); case 'DL': return DoorLockBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'DL')).map((e) => e.uuid!).toList()); + devicesIds: devices + .where((e) => e.productType == 'DL') + .map((e) => e.uuid!) + .toList()); case 'WPS': return WallSensorBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'WPS')).map((e) => e.uuid!).toList()); + devicesIds: devices + .where((e) => e.productType == 'WPS') + .map((e) => e.uuid!) + .toList()); case 'CPS': return CeilingSensorBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'CPS')).map((e) => e.uuid!).toList(), + devicesIds: devices + .where((e) => e.productType == 'CPS') + .map((e) => e.uuid!) + .toList(), ); case 'CUR': return CurtainBatchStatusView( - devicesIds: devices.where((e) => (e.productType == 'CUR')).map((e) => e.uuid!).toList(), + devicesIds: devices + .where((e) => e.productType == 'CUR') + .map((e) => e.uuid!) + .toList(), + ); + case 'CUR_2': + return CurtainModuleBatchView( + devicesIds: devices + .where((e) => e.productType == 'AC') + .map((e) => e.uuid!) + .toList(), ); case 'AC': return AcDeviceBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'AC')).map((e) => e.uuid!).toList()); + devicesIds: devices + .where((e) => e.productType == 'AC') + .map((e) => e.uuid!) + .toList()); case 'WH': return WaterHEaterBatchControlView( - deviceIds: devices.where((e) => (e.productType == 'WH')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == 'WH') + .map((e) => e.uuid!) + .toList(), ); case 'DS': return MainDoorSensorBatchView( - devicesIds: devices.where((e) => (e.productType == 'DS')).map((e) => e.uuid!).toList(), + devicesIds: devices + .where((e) => e.productType == 'DS') + .map((e) => e.uuid!) + .toList(), ); case 'GD': return GarageDoorBatchControlView( - deviceIds: devices.where((e) => (e.productType == 'GD')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == 'GD') + .map((e) => e.uuid!) + .toList(), ); case 'WL': return WaterLeakBatchControlView( - deviceIds: devices.where((e) => (e.productType == 'WL')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == 'WL') + .map((e) => e.uuid!) + .toList(), ); case 'PC': return PowerClampBatchControlView( - deviceIds: devices.where((e) => (e.productType == 'PC')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == 'PC') + .map((e) => e.uuid!) + .toList(), ); case 'SOS': return SOSBatchControlView( - deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => e.productType == 'SOS') + .map((e) => e.uuid!) + .toList(), ); case 'NCPS': return FlushMountedPresenceSensorBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'NCPS')).map((e) => e.uuid!).toList(), + devicesIds: devices + .where((e) => e.productType == 'NCPS') + .map((e) => e.uuid!) + .toList(), ); default: return const SizedBox(); From 03ba50629427bf2c160b3cf7f63e7ee931a6d862 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Wed, 25 Jun 2025 16:27:15 +0300 Subject: [PATCH 03/37] add bloc nd logic --- .../bloc/batch/curtain_module_batch_bloc.dart | 45 ++++ .../bloc/curtain_module_bloc.dart | 232 ++++++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_bloc.dart create mode 100644 lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart diff --git a/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_bloc.dart b/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_bloc.dart new file mode 100644 index 00000000..87dd53f7 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_bloc.dart @@ -0,0 +1,45 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +part 'curtain_module_batch_event.dart'; +part 'curtain_module_batch_state.dart'; + +class CurtainModuleBatchBloc + extends Bloc { + final ControlDeviceService controlDeviceService; + StreamSubscription? _firebaseSubscription; + + CurtainModuleBatchBloc(this.controlDeviceService) + : super(CurtainModuleBatchInitial()) { + on(_onFetchAcBatchStatus); + } + + Future _onFetchAcBatchStatus( + CutrainModuleFetchBatchStatusEvent event, + Emitter emit, + ) async { + emit(CurtainModuleBatchLoadingState()); + try { + final status = + await DevicesManagementApi().getBatchStatus(event.devicesIds); + status.status.forEach( + (element) => print( + 'this is code ${element.code} - this is value ${element.value}'), + ); + + emit( + CurtainModuleBatchLoadedState( + curtainModuleStatusModel: CurtainModuleStatusModel.fromJson({}), + ), + ); + } catch (e) { + emit(CurtainModuleBatchFailedState(error: e.toString())); + } + } +} diff --git a/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart b/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart new file mode 100644 index 00000000..d79380cf --- /dev/null +++ b/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart @@ -0,0 +1,232 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +part 'curtain_module_event.dart'; +part 'curtain_module_state.dart'; + +class CurtainModuleBloc extends Bloc { + final ControlDeviceService controlDeviceService; + StreamSubscription? _firebaseSubscription; + + CurtainModuleBloc(this.controlDeviceService) : super(CurtainModuleInitial()) { + on(_onFetchCurtainModuleStatusEvent); + on(_onSendCurtainPercentToApiEvent); + on(_onOpenCurtainEvent); + on(_onCloseCurtainEvent); + on(_onStopCurtainEvent); + on(_onChangeTimerControlEvent); + on(_onChageCurCalibrationEvent); + on(_onChangeElecMachineryModeEvent); + on(_onChangeControlBackEvent); + on(_onChangeControlBackModeEvent); + on(_onChangeCurtainModuleStatusEvent); + } + + Future _onFetchCurtainModuleStatusEvent( + FetchCurtainModuleStatusEvent event, + Emitter emit, + ) async { + emit(CurtainModuleLoading()); + final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); + final result = Map.fromEntries( + status.status.map((element) => MapEntry(element.code, element.value)), + ); + + emit(CurtainModuleStatusLoaded( + curtainModuleStatus: CurtainModuleStatusModel.fromJson(result), + )); + Map statusMap = {}; + final ref = + FirebaseDatabase.instance.ref('device-status/${event.deviceId}'); + final stream = ref.onValue; + + stream.listen((DatabaseEvent DatabaseEvent) async { + if (DatabaseEvent.snapshot.value == null) return; + + Map usersMap = + DatabaseEvent.snapshot.value as Map; + + List statusList = []; + + usersMap['status'].forEach((element) { + statusList.add(Status(code: element['code'], value: element['value'])); + }); + + statusMap = { + for (final element in statusList) element.code: element.value, + }; + if (!isClosed) { + add( + ChangeCurtainModuleStatusEvent( + deviceId: event.deviceId, + status: CurtainModuleStatusModel.fromJson(statusMap), + ), + ); + } + }); + } + + Future _onChangeCurtainModuleStatusEvent( + ChangeCurtainModuleStatusEvent event, + Emitter emit, + ) async { + emit(CurtainModuleLoading()); + emit(CurtainModuleStatusLoaded(curtainModuleStatus: event.status)); + } + + Future _onSendCurtainPercentToApiEvent( + SendCurtainPercentToApiEvent event, + Emitter emit, + ) async { + try { + await controlDeviceService.controlDevice( + deviceUuid: event.deviceId, + status: event.status, + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to send control command: $e')); + } + } + + Future _onOpenCurtainEvent( + OpenCurtainEvent event, + Emitter emit, + ) async { + try { + await controlDeviceService.controlDevice( + deviceUuid: event.deviceId, + status: Status(code: 'control', value: 'open'), + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to open curtain: $e')); + } + } + + Future _onCloseCurtainEvent( + CloseCurtainEvent event, + Emitter emit, + ) async { + try { + await controlDeviceService.controlDevice( + deviceUuid: event.deviceId, + status: Status(code: 'control', value: 'close'), + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to close curtain: $e')); + } + } + + Future _onStopCurtainEvent( + StopCurtainEvent event, + Emitter emit, + ) async { + try { + await controlDeviceService.controlDevice( + deviceUuid: event.deviceId, + status: Status(code: 'control', value: 'stop'), + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to stop curtain: $e')); + } + } + + Future _onChangeTimerControlEvent( + ChangeTimerControlEvent event, + Emitter emit, + ) async { + try { + if (event.timControl < 10 || event.timControl > 120) { + emit(const CurtainModuleError( + message: 'Timer control value must be between 10 and 120')); + return; + } + await controlDeviceService.controlDevice( + deviceUuid: event.deviceId, + status: Status( + code: 'tr_timecon', + value: event.timControl, + ), + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to change timer control: $e')); + } + } + + Future _onChageCurCalibrationEvent( + CurCalibrationEvent event, + Emitter emit, + ) async { + try { + await controlDeviceService.controlDevice( + deviceUuid: event.deviceId, + status: Status(code: 'cur_calibration', value: 'start'), + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to start calibration: $e')); + } + } + + Future _onChangeElecMachineryModeEvent( + ChangeElecMachineryModeEvent event, + Emitter emit, + ) async { + try { + await controlDeviceService.controlDevice( + deviceUuid: event.deviceId, + status: Status( + code: 'elec_machinery_mode', + value: event.elecMachineryMode, + ), + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to change mode: $e')); + } + } + + Future _onChangeControlBackEvent( + ChangeControlBackEvent event, + Emitter emit, + ) async { + try { + await controlDeviceService.controlDevice( + deviceUuid: event.deviceId, + status: Status( + code: 'control_back', + value: event.controlBack, + ), + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to change control back: $e')); + } + } + + Future _onChangeControlBackModeEvent( + ChangeControlBackModeEvent event, + Emitter emit, + ) async { + try { + await controlDeviceService.controlDevice( + deviceUuid: event.deviceId, + status: Status( + code: 'control_back_mode', + value: event.controlBackMode, + ), + ); + } catch (e) { + emit(CurtainModuleError( + message: 'Failed to change control back mode: $e')); + } + } + + @override + Future close() async { + await _firebaseSubscription?.cancel(); + return super.close(); + } +} From eee6a80c50aa58b467aceee07506bedb2f04f1d9 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Wed, 25 Jun 2025 16:27:40 +0300 Subject: [PATCH 04/37] add events and states and models --- .../batch/curtain_module_batch_event.dart | 19 +++ .../batch/curtain_module_batch_state.dart | 28 ++++ .../bloc/curtain_module_event.dart | 132 ++++++++++++++++++ .../bloc/curtain_module_state.dart | 37 +++++ .../models/curtain_module_model.dart | 53 +++++++ 5 files changed, 269 insertions(+) create mode 100644 lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_event.dart create mode 100644 lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_state.dart create mode 100644 lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart create mode 100644 lib/pages/device_managment/curtain_module/bloc/curtain_module_state.dart create mode 100644 lib/pages/device_managment/curtain_module/models/curtain_module_model.dart diff --git a/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_event.dart b/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_event.dart new file mode 100644 index 00000000..351773f9 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_event.dart @@ -0,0 +1,19 @@ +part of 'curtain_module_batch_bloc.dart'; + +sealed class CurtainModuleBatchEvent extends Equatable { + const CurtainModuleBatchEvent(); + + @override + List get props => []; +} + +class CutrainModuleFetchBatchStatusEvent extends CurtainModuleBatchEvent { + final List devicesIds; + + const CutrainModuleFetchBatchStatusEvent({ + required this.devicesIds, + }); + + @override + List get props => [devicesIds]; +} diff --git a/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_state.dart b/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_state.dart new file mode 100644 index 00000000..bb8bb7d0 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_state.dart @@ -0,0 +1,28 @@ +part of 'curtain_module_batch_bloc.dart'; + +sealed class CurtainModuleBatchState extends Equatable { + const CurtainModuleBatchState(); + + @override + List get props => []; +} + +final class CurtainModuleBatchInitial extends CurtainModuleBatchState {} + +final class CurtainModuleBatchLoadingState extends CurtainModuleBatchState {} + +final class CurtainModuleBatchLoadedState extends CurtainModuleBatchState { + final CurtainModuleStatusModel curtainModuleStatusModel; + const CurtainModuleBatchLoadedState({ + required this.curtainModuleStatusModel, + }); +} + +final class CurtainModuleBatchFailedState extends CurtainModuleBatchState { + final String error; + + const CurtainModuleBatchFailedState({required this.error}); + + @override + List get props => [error]; +} diff --git a/lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart b/lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart new file mode 100644 index 00000000..6b0d89ae --- /dev/null +++ b/lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart @@ -0,0 +1,132 @@ +part of 'curtain_module_bloc.dart'; + +sealed class CurtainModuleEvent extends Equatable { + const CurtainModuleEvent(); + + @override + List get props => []; +} + +class FetchCurtainModuleStatusEvent extends CurtainModuleEvent { + final String deviceId; + const FetchCurtainModuleStatusEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class SendCurtainPercentToApiEvent extends CurtainModuleEvent { + final String deviceId; + final Status status; + + const SendCurtainPercentToApiEvent({ + required this.deviceId, + required this.status, + }); + + @override + List get props => [deviceId, status]; +} + +class OpenCurtainEvent extends CurtainModuleEvent { + final String deviceId; + + const OpenCurtainEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class CloseCurtainEvent extends CurtainModuleEvent { + final String deviceId; + + const CloseCurtainEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class StopCurtainEvent extends CurtainModuleEvent { + final String deviceId; + + const StopCurtainEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class ChangeTimerControlEvent extends CurtainModuleEvent { + final String deviceId; + final int timControl; + + const ChangeTimerControlEvent({ + required this.deviceId, + required this.timControl, + }); + + @override + List get props => [deviceId, timControl]; +} + +class CurCalibrationEvent extends CurtainModuleEvent { + final String deviceId; + + const CurCalibrationEvent({ + required this.deviceId, + }); + + @override + List get props => [deviceId]; +} + +class ChangeElecMachineryModeEvent extends CurtainModuleEvent { + final String deviceId; + final String elecMachineryMode; + + const ChangeElecMachineryModeEvent({ + required this.deviceId, + required this.elecMachineryMode, + }); + + @override + List get props => [deviceId, elecMachineryMode]; +} + +class ChangeControlBackEvent extends CurtainModuleEvent { + final String deviceId; + final String controlBack; + + const ChangeControlBackEvent({ + required this.deviceId, + required this.controlBack, + }); + + @override + List get props => [deviceId, controlBack]; +} + +class ChangeControlBackModeEvent extends CurtainModuleEvent { + final String deviceId; + final String controlBackMode; + + const ChangeControlBackModeEvent({ + required this.deviceId, + required this.controlBackMode, + }); + + @override + List get props => [deviceId, controlBackMode]; +} + +class ChangeCurtainModuleStatusEvent extends CurtainModuleEvent { + final String deviceId; + final CurtainModuleStatusModel status; + + const ChangeCurtainModuleStatusEvent({ + required this.deviceId, + required this.status, + }); + + @override + List get props => [deviceId, status]; +} \ No newline at end of file diff --git a/lib/pages/device_managment/curtain_module/bloc/curtain_module_state.dart b/lib/pages/device_managment/curtain_module/bloc/curtain_module_state.dart new file mode 100644 index 00000000..02ef9279 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/bloc/curtain_module_state.dart @@ -0,0 +1,37 @@ +part of 'curtain_module_bloc.dart'; + +sealed class CurtainModuleState extends Equatable { + const CurtainModuleState(); + + @override + List get props => []; +} + +class CurtainModuleInitial extends CurtainModuleState {} + +class CurtainModuleLoading extends CurtainModuleState {} + +class CurtainModuleError extends CurtainModuleState { + final String message; + const CurtainModuleError({required this.message}); + + @override + List get props => [message]; +} + +class CurtainModuleStatusLoaded extends CurtainModuleState { + final CurtainModuleStatusModel curtainModuleStatus; + + const CurtainModuleStatusLoaded({required this.curtainModuleStatus}); + + @override + List get props => [curtainModuleStatus]; +} +class CurtainModuleStatusUpdated extends CurtainModuleState { + final CurtainModuleStatusModel curtainModuleStatus; + + const CurtainModuleStatusUpdated({required this.curtainModuleStatus}); + + @override + List get props => [curtainModuleStatus]; +} diff --git a/lib/pages/device_managment/curtain_module/models/curtain_module_model.dart b/lib/pages/device_managment/curtain_module/models/curtain_module_model.dart new file mode 100644 index 00000000..0b6d23fb --- /dev/null +++ b/lib/pages/device_managment/curtain_module/models/curtain_module_model.dart @@ -0,0 +1,53 @@ + +enum CurtainModuleControl { + open, + close, + stop, +} + +// enum CurtainControlBackMode { +// foward, +// backward, +// } + +class CurtainModuleStatusModel { + CurtainModuleControl control; + int percentControl; + String curCalibration; + // CurtainControlBackMode controlBackmode; + int trTimeControl; + String elecMachineryMode; + String controlBack; + CurtainModuleStatusModel({ + required this.control, + required this.percentControl, + required this.curCalibration, + // required this.controlBackmode, + required this.trTimeControl, + required this.controlBack, + required this.elecMachineryMode, + }); + factory CurtainModuleStatusModel.zero() => CurtainModuleStatusModel( + control: CurtainModuleControl.stop, + percentControl: 0, + // controlBackmode: CurtainControlBackMode.foward, + curCalibration: '', + trTimeControl: 0, + controlBack: '', + elecMachineryMode: '', + ); + + factory CurtainModuleStatusModel.fromJson(Map json) { + return CurtainModuleStatusModel( + control: CurtainModuleControl.values.firstWhere( + (e) => e.toString() == json['control'] as String, + orElse: () => CurtainModuleControl.stop, + ), + percentControl: json['percent_control'] as int? ?? 0, + curCalibration: json['cur_calibration'] as String? ?? '', + trTimeControl: json['tr_timecon'] as int? ?? 0, + elecMachineryMode: json['elec_machinery_mode'] as String? ?? '', + controlBack: json['control_back'] as String? ?? '', + ); + } +} From 4e9bcbdcea3fab23c024d64196c7b2f069137bae Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Wed, 25 Jun 2025 16:28:15 +0300 Subject: [PATCH 05/37] build UI and integrate with back --- .../view/curtain_module_batch.dart | 71 +++++++ .../view/curtain_module_items.dart | 98 +++++++++ .../widgets/accurate_calibrating_dialog.dart | 48 +++++ .../widgets/accurate_calibration_dialog.dart | 40 ++++ .../widgets/accurate_dialog_widget.dart | 97 +++++++++ .../widgets/calibrate_completed_dialog.dart | 77 +++++++ .../widgets/curtain_action_widget.dart | 41 ++++ .../widgets/curtain_movment_widget.dart | 190 ++++++++++++++++++ .../widgets/normal_text_body_for_dialog.dart | 42 ++++ .../widgets/number_input_textfield.dart | 27 +++ .../widgets/pref_revers_card_widget.dart | 79 ++++++++ .../widgets/prefrences_dialog.dart | 137 +++++++++++++ .../widgets/quick_calibrating_dialog.dart | 122 +++++++++++ .../widgets/quick_calibration_dialog.dart | 44 ++++ lib/utils/constants/assets.dart | 6 +- 15 files changed, 1118 insertions(+), 1 deletion(-) create mode 100644 lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart create mode 100644 lib/pages/device_managment/curtain_module/view/curtain_module_items.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/curtain_action_widget.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart create mode 100644 lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart new file mode 100644 index 00000000..0e0dfdc3 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class CurtainModuleBatchView extends StatelessWidget { + final List devicesIds; + const CurtainModuleBatchView({ + super.key, + required this.devicesIds, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => CurtainModuleBatchBloc(RemoteControlDeviceService()) + ..add(CutrainModuleFetchBatchStatusEvent(devicesIds: devicesIds)), + child: _buildStatusControls(context), + ); + } + + Widget _buildStatusControls(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ControlCurtainMovementWidget( + deviceId: devicesIds.first, + ), + const SizedBox( + height: 10, + ), + SizedBox( + height: 120, + width: 350, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: IconNameStatusContainer( + isFullIcon: false, + name: 'Factory Reset', + icon: Assets.factoryReset, + onTap: () {}, + status: false, + textColor: ColorsManager.blackColor, + ), + ), + Expanded( + child: IconNameStatusContainer( + isFullIcon: false, + name: 'Firmware Update', + icon: Assets.firmware, + onTap: () {}, + status: false, + textColor: ColorsManager.blackColor, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart new file mode 100644 index 00000000..95366e49 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { + final String deviceId; + const CurtainModuleItems({ + super.key, + required this.deviceId, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => CurtainModuleBloc(RemoteControlDeviceService()) + ..add(FetchCurtainModuleStatusEvent(deviceId: deviceId)), + child: _buildStatusControls(context), + ); + } + + Widget _buildStatusControls(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ControlCurtainMovementWidget( + deviceId: deviceId, + ), + const SizedBox( + height: 10, + ), + SizedBox( + height: 120, + width: 350, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: IconNameStatusContainer( + isFullIcon: false, + name: 'Schedules', + icon: Assets.schedule, + onTap: () {}, + status: false, + textColor: ColorsManager.blackColor, + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: BlocBuilder( + builder: (context, state) { + if (state is CurtainModuleLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } else if (state is CurtainModuleStatusLoaded) { + return IconNameStatusContainer( + isFullIcon: false, + name: 'Preferences', + icon: Assets.preferences, + onTap: () => showDialog( + context: context, + builder: (_) => BlocProvider.value( + value: context.watch(), + child: CurtainModulePrefrencesDialog( + deviceId: deviceId, + curtainModuleStatusModel: + state.curtainModuleStatus, + ), + ), + ), + status: false, + textColor: ColorsManager.blackColor, + ); + } else { + return const SizedBox(); + } + }, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart new file mode 100644 index 00000000..54107420 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; + +class AccurteCalibratingDialog extends StatelessWidget { + final String deviceId; + final BuildContext parentContext; + const AccurteCalibratingDialog({ + super.key, + required this.deviceId, + required this.parentContext, + }); + + @override + Widget build(_) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: AccurateDialogWidget( + title: 'Calibrating', + body: const NormalTextBodyForDialog( + title: '', + step1: + '1. Click Close Button to make the Curtain run to Full Close and Position.', + step2: '2. click Next to complete the Calibration.', + ), + leftOnTap: () => Navigator.of(parentContext).pop(), + rightOnTap: () { + parentContext.read().add( + CurCalibrationEvent( + deviceId: deviceId, + ), + ); + Navigator.of(parentContext).pop(); + showDialog( + context: parentContext, + builder: (_) => CalibrateCompletedDialog( + parentContext: parentContext, + deviceId: deviceId, + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart new file mode 100644 index 00000000..a9d1b010 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; + +class AccurateCalibrationDialog extends StatelessWidget { + final String deviceId; + final BuildContext parentContext; + const AccurateCalibrationDialog({ + super.key, + required this.deviceId, + required this.parentContext, + }); + + @override + Widget build(_) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: AccurateDialogWidget( + title: 'Accurate Calibration', + body: const NormalTextBodyForDialog( + title: 'Prepare Calibration:', + step1: '1. Run The Curtain to the Fully Open Position,and pause.', + step2: '2. click Next to Start accurate calibration.', + ), + leftOnTap: () => Navigator.of(parentContext).pop(), + rightOnTap: () { + Navigator.of(parentContext).pop(); + showDialog( + context: parentContext, + builder: (_) => AccurteCalibratingDialog( + deviceId: deviceId, + parentContext: parentContext, + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart new file mode 100644 index 00000000..5be376ae --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AccurateDialogWidget extends StatelessWidget { + final String title; + final Widget body; + final void Function() leftOnTap; + final void Function() rightOnTap; + const AccurateDialogWidget({ + super.key, + required this.title, + required this.body, + required this.leftOnTap, + required this.rightOnTap, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 300, + width: 400, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Text( + title, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorsManager.blueColor, + ), + ), + ), + const SizedBox(height: 5), + const Divider( + indent: 10, + endIndent: 10, + ), + Padding( + padding: const EdgeInsets.all(10), + child: body, + ), + const SizedBox(height: 20), + const Spacer(), + const Divider(), + Row( + children: [ + Expanded( + child: InkWell( + onTap: leftOnTap, + child: Container( + height: 60, + alignment: Alignment.center, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayBorder, + ), + ), + ), + child: const Text( + 'Cancel', + style: TextStyle(color: ColorsManager.grayBorder), + ), + ), + ), + ), + Expanded( + child: InkWell( + onTap: rightOnTap, + child: Container( + height: 60, + alignment: Alignment.center, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayBorder, + ), + ), + ), + child: const Text( + 'Next', + style: TextStyle( + color: ColorsManager.blueColor, + ), + ), + ), + ), + ) + ], + ) + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart new file mode 100644 index 00000000..9b2b5ea9 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CalibrateCompletedDialog extends StatelessWidget { + final BuildContext parentContext; + final String deviceId; + const CalibrateCompletedDialog({ + super.key, + required this.parentContext, + required this.deviceId, + }); + + @override + Widget build(_) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: SizedBox( + height: 250, + width: 400, + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Calibration Completed', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorsManager.blueColor, + ), + ), + ), + const SizedBox(height: 5), + const Divider( + indent: 10, + endIndent: 10, + ), + const Icon( + Icons.check_circle, + size: 100, + color: ColorsManager.blueColor, + ), + const Spacer(), + const Divider( + indent: 10, + endIndent: 10, + ), + InkWell( + onTap: () { + parentContext.read().add( + FetchCurtainModuleStatusEvent( + deviceId: deviceId, + ), + ); + Navigator.of(parentContext).pop(); + Navigator.of(parentContext).pop(); + }, + child: Container( + height: 40, + width: double.infinity, + alignment: Alignment.center, + child: const Text( + 'Close', + style: TextStyle( + color: ColorsManager.grayBorder, + ), + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/curtain_action_widget.dart b/lib/pages/device_managment/curtain_module/widgets/curtain_action_widget.dart new file mode 100644 index 00000000..8c2ff81c --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/curtain_action_widget.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CurtainActionWidget extends StatelessWidget { + final String icon; + final void Function() onTap; + const CurtainActionWidget({ + super.key, + required this.icon, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: ClipOval( + child: Container( + height: 60, + width: 60, + padding: const EdgeInsets.all(8), + color: ColorsManager.whiteColors, + child: ClipOval( + child: Container( + height: 60, + width: 60, + padding: const EdgeInsets.all(8), + color: ColorsManager.graysColor, + child: SvgPicture.asset( + icon, + width: 35, + height: 35, + fit: BoxFit.contain, + ), + ), + ), + )), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart b/lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart new file mode 100644 index 00000000..076c5a9c --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_action_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class ControlCurtainMovementWidget extends StatelessWidget { + final String deviceId; + const ControlCurtainMovementWidget({ + super.key, + required this.deviceId, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 550, + child: DeviceControlsContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CurtainActionWidget( + icon: Assets.openCurtain, + onTap: () { + context.read().add( + OpenCurtainEvent(deviceId: deviceId), + ); + }, + ), + const SizedBox( + width: 30, + ), + CurtainActionWidget( + icon: Assets.pauseCurtain, + onTap: () { + context.read().add( + StopCurtainEvent(deviceId: deviceId), + ); + }, + ), + const SizedBox( + width: 30, + ), + CurtainActionWidget( + icon: Assets.closeCurtain, + onTap: () { + context.read().add( + CloseCurtainEvent(deviceId: deviceId), + ); + }, + ), + BlocBuilder( + builder: (context, state) { + if (state is CurtainModuleError) { + return Center( + child: Text( + state.message, + style: const TextStyle( + color: ColorsManager.minBlueDot, + fontSize: 16, + ), + ), + ); + } else if (state is CurtainModuleLoading) { + return const Center( + child: CircularProgressIndicator( + color: ColorsManager.minBlueDot, + ), + ); + } else if (state is CurtainModuleInitial) { + return const Center( + child: Text( + 'No data available', + style: TextStyle( + color: ColorsManager.minBlueDot, + fontSize: 16, + ), + ), + ); + } else if (state is CurtainModuleStatusLoaded) { + return CurtainSliderWidget( + status: state.curtainModuleStatus, + deviceId: deviceId, + ); + } else { + return const Center( + child: Text( + 'Unknown state', + style: TextStyle( + color: ColorsManager.minBlueDot, + fontSize: 16, + ), + ), + ); + } + }, + ) + ], + ), + ), + ); + } +} + +class CurtainSliderWidget extends StatefulWidget { + final CurtainModuleStatusModel status; + final String deviceId; + + const CurtainSliderWidget({ + super.key, + required this.status, + required this.deviceId, + }); + + @override + State createState() => _CurtainSliderWidgetState(); +} + +class _CurtainSliderWidgetState extends State { + double? _localValue; // For temporary drag state + + @override + Widget build(BuildContext context) { + // If user is dragging, use local value. Otherwise, use Firebase-synced state + final double currentSliderValue = + _localValue ?? widget.status.percentControl / 100; + + return Column( + children: [ + Text( + '${(currentSliderValue * 100).round()}%', + style: const TextStyle( + color: ColorsManager.minBlueDot, + fontSize: 25, + fontWeight: FontWeight.bold, + ), + ), + Slider( + value: currentSliderValue, + min: 0, + max: 1, + divisions: 10, // 10% step + activeColor: ColorsManager.minBlueDot, + thumbColor: ColorsManager.primaryColor, + inactiveColor: ColorsManager.whiteColors, + + // Start dragging — use local control + onChangeStart: (_) { + setState(() { + _localValue = currentSliderValue; + }); + }, + + // While dragging — update temporary value + onChanged: (value) { + final steppedValue = (value * 10).roundToDouble() / 10; + setState(() { + _localValue = steppedValue; + }); + }, + + // On release — send API and return to Firebase-controlled state + onChangeEnd: (value) { + final int targetPercent = (value * 100).round(); + + // Dispatch API call + context.read().add( + SendCurtainPercentToApiEvent( + deviceId: widget.deviceId, + status: Status( + code: 'percent_control', + value: targetPercent, + ), + ), + ); + + // Revert back to Firebase-synced stream + setState(() { + _localValue = null; + }); + }, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart new file mode 100644 index 00000000..8818cb7b --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class NormalTextBodyForDialog extends StatelessWidget { + final String title; + final String step1; + final String step2; + + const NormalTextBodyForDialog({ + super.key, + required this.title, + required this.step1, + required this.step2, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + color: ColorsManager.grayColor, + ), + ), + Text( + step1, + style: const TextStyle( + color: ColorsManager.grayColor, + ), + ), + Text( + step2, + style: const TextStyle( + color: ColorsManager.grayColor, + ), + ) + ], + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart b/lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart new file mode 100644 index 00000000..ea95f838 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class NumberInputField extends StatelessWidget { + final TextEditingController controller; + + const NumberInputField({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + decoration: const InputDecoration( + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.zero, + ), + style: const TextStyle( + fontSize: 20, + color: ColorsManager.blackColor, + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart b/lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart new file mode 100644 index 00000000..81912e80 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/web_layout/default_container.dart'; + +class PrefReversCardWidget extends StatelessWidget { + final void Function() onTap; + final String title; + final String body; + const PrefReversCardWidget({ + super.key, + required this.title, + required this.body, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return DefaultContainer( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 8, + child: Text( + title, + style: const TextStyle( + color: ColorsManager.grayBorder, + fontSize: 15, + ), + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + flex: 2, + child: InkWell( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.horizontal( + left: Radius.circular(10), + right: Radius.circular(10)), + border: Border.all(color: ColorsManager.grayBorder)), + child: SvgPicture.asset( + Assets.reverseArrows, + height: 15, + ), + ), + ), + ) + ], + ), + SizedBox( + width: 100, + child: Text( + body, + style: const TextStyle( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w500, + fontSize: 18, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart new file mode 100644 index 00000000..c5fcf4ba --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/web_layout/default_container.dart'; + +class CurtainModulePrefrencesDialog extends StatelessWidget { + final CurtainModuleStatusModel curtainModuleStatusModel; + final String deviceId; + + const CurtainModulePrefrencesDialog({ + super.key, + required this.curtainModuleStatusModel, + required this.deviceId, + }); + + @override + Widget build(_) { + return AlertDialog( + backgroundColor: ColorsManager.CircleImageBackground, + contentPadding: const EdgeInsets.all(30), + title: const Center( + child: Text( + 'Preferences', + style: TextStyle( + color: ColorsManager.blueColor, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + )), + content: BlocBuilder( + builder: (context, state) { + if (state is CurtainModuleLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } else if (state is CurtainModuleStatusLoaded) { + return SizedBox( + height: 300, + width: 400, + child: GridView( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1.5, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + children: [ + PrefReversCardWidget( + title: state.curtainModuleStatus.controlBack, + body: 'Motor Steering', + onTap: () { + context.read().add( + ChangeControlBackEvent( + deviceId: deviceId, + controlBack: + state.curtainModuleStatus.controlBack == + 'forward' + ? 'back' + : 'forward', + ), + ); + }, + ), + PrefReversCardWidget( + title: state.curtainModuleStatus.elecMachineryMode, + body: 'Motor Mode', + onTap: () => context.read().add( + ChangeElecMachineryModeEvent( + deviceId: deviceId, + elecMachineryMode: + state.curtainModuleStatus.elecMachineryMode == + 'dry_contact' + ? 'strong_power' + : 'dry_contact', + ), + ), + ), + DefaultContainer( + padding: const EdgeInsets.all(12), + child: InkWell( + onTap: () => showDialog( + context: context, + builder: (_) => AccurateCalibrationDialog( + deviceId: deviceId, + parentContext: context, + ), + ), + child: const Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text('Accurte Calibration', + style: TextStyle( + fontSize: 18, + color: ColorsManager.blackColor, + )), + ], + ), + ), + ), + DefaultContainer( + padding: const EdgeInsets.all(12), + child: InkWell( + onTap: () => showDialog( + context: context, + builder: (_) => QuickCalibrationDialog( + timControl: state.curtainModuleStatus.trTimeControl, + deviceId: deviceId, + parentContext: context), + ), + child: const Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text('Quick Calibration', + style: TextStyle( + fontSize: 18, + color: ColorsManager.blackColor, + )), + ], + ), + ), + ), + ], + ), + ); + } else { + return const SizedBox(); + } + }, + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart new file mode 100644 index 00000000..0b86c96e --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/number_input_textfield.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class QuickCalibratingDialog extends StatefulWidget { + final int timControl; + final String deviceId; + final BuildContext parentContext; + const QuickCalibratingDialog({ + super.key, + required this.timControl, + required this.deviceId, + required this.parentContext, + }); + + @override + State createState() => _QuickCalibratingDialogState(); +} + +class _QuickCalibratingDialogState extends State { + late TextEditingController _controller; + String? _errorText; + + void _onRightTap() { + final value = int.tryParse(_controller.text); + + if (value == null || value < 10 || value > 120) { + setState(() { + _errorText = 'Number should be between 10 and 120'; + }); + return; + } + + setState(() { + _errorText = null; + }); + widget.parentContext.read().add( + ChangeTimerControlEvent( + deviceId: widget.deviceId, + timControl: value, + ), + ); + Navigator.of(widget.parentContext).pop(); + showDialog( + context: widget.parentContext, + builder: (_) => CalibrateCompletedDialog( + parentContext: widget.parentContext, + deviceId: widget.deviceId, + ), + ); + } + + @override + void initState() { + _controller = TextEditingController(text: widget.timControl.toString()); + super.initState(); + } + + @override + Widget build(_) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: AccurateDialogWidget( + title: 'Calibrating', + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '1. please Enter the Travel Time:', + style: TextStyle(color: ColorsManager.grayBorder), + ), + const SizedBox(height: 10), + Container( + width: 150, + height: 40, + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: NumberInputField(controller: _controller), + ), + const Expanded( + child: Text( + 'seconds', + style: TextStyle( + fontSize: 15, + color: ColorsManager.blueColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + if (_errorText != null) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + _errorText!, + style: const TextStyle( + color: ColorsManager.red, + fontSize: 14, + ), + ), + ), + ], + ), + leftOnTap: () => Navigator.of(widget.parentContext).pop(), + rightOnTap: _onRightTap, + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart new file mode 100644 index 00000000..803d904f --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart'; + +class QuickCalibrationDialog extends StatelessWidget { + final int timControl; + final String deviceId; + final BuildContext parentContext; + const QuickCalibrationDialog({ + super.key, + required this.timControl, + required this.deviceId, + required this.parentContext, + }); + + @override + Widget build(_) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: AccurateDialogWidget( + title: 'Quick Calibration', + body: const NormalTextBodyForDialog( + title: 'Prepare Calibration:', + step1: + '1. Confirm that the curtain is in the fully closed and suspended state.', + step2: '2. click Next to Start calibration.', + ), + leftOnTap: () => Navigator.of(parentContext).pop(), + rightOnTap: () { + Navigator.of(parentContext).pop(); + showDialog( + context: parentContext, + builder: (_) => QuickCalibratingDialog( + timControl: timControl, + deviceId: deviceId, + parentContext: parentContext, + ), + ); + }, + ), + ); + } +} diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index dfc0b394..331a4285 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -124,7 +124,11 @@ class Assets { //assets/icons/AC.svg static const String ac = "assets/icons/AC.svg"; //assets/icons/Curtain.svg - static const String curtain = "assets/icons/Curtain.svg"; + static const String curtain = 'assets/icons/Curtain.svg'; + static const String openCurtain = 'assets/icons/open_curtain.svg'; + static const String pauseCurtain = 'assets/icons/pause_curtain.svg'; + static const String closeCurtain = 'assets/icons/close_curtain.svg'; + static const String reverseArrows = 'assets/icons/reverse_arrows.svg'; //assets/icons/doorLock.svg static const String doorLock = "assets/icons/doorLock.svg"; //assets/icons/Gateway.svg From 0b372e1ed8746218c950cda7dc72521618510e0f Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Wed, 25 Jun 2025 16:49:11 +0300 Subject: [PATCH 06/37] use read instead of watch --- .../curtain_module/view/curtain_module_items.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart index 95366e49..15175bd5 100644 --- a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart @@ -71,7 +71,7 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { onTap: () => showDialog( context: context, builder: (_) => BlocProvider.value( - value: context.watch(), + value: context.read(), child: CurtainModulePrefrencesDialog( deviceId: deviceId, curtainModuleStatusModel: From f43826a82489e88581b2850e87c4fb3ad5053671 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Wed, 25 Jun 2025 17:07:59 +0300 Subject: [PATCH 07/37] now it is rendering the chages for motors and control back --- .../curtain_module/view/curtain_module_items.dart | 13 ++++++------- .../curtain_module/widgets/prefrences_dialog.dart | 4 +++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart index 15175bd5..22ccf90e 100644 --- a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart @@ -70,13 +70,12 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { icon: Assets.preferences, onTap: () => showDialog( context: context, - builder: (_) => BlocProvider.value( - value: context.read(), - child: CurtainModulePrefrencesDialog( - deviceId: deviceId, - curtainModuleStatusModel: - state.curtainModuleStatus, - ), + builder: (_) => CurtainModulePrefrencesDialog( + curtainModuleBloc: + context.watch(), + deviceId: deviceId, + curtainModuleStatusModel: + state.curtainModuleStatus, ), ), status: false, diff --git a/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart index c5fcf4ba..bf500792 100644 --- a/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart @@ -11,11 +11,12 @@ import 'package:syncrow_web/web_layout/default_container.dart'; class CurtainModulePrefrencesDialog extends StatelessWidget { final CurtainModuleStatusModel curtainModuleStatusModel; final String deviceId; - + final CurtainModuleBloc curtainModuleBloc; const CurtainModulePrefrencesDialog({ super.key, required this.curtainModuleStatusModel, required this.deviceId, + required this.curtainModuleBloc, }); @override @@ -33,6 +34,7 @@ class CurtainModulePrefrencesDialog extends StatelessWidget { ), )), content: BlocBuilder( + bloc: curtainModuleBloc, builder: (context, state) { if (state is CurtainModuleLoading) { return const Center( From 396ce3dad8c55bd1a5757fc3de0f85dd24af0c17 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Fri, 27 Jun 2025 16:26:39 +0300 Subject: [PATCH 08/37] now batch is working --- .../device_managment/ac/bloc/ac_bloc.dart | 15 ++- .../helper/route_controls_based_code.dart | 2 +- .../bloc/batch/curtain_module_batch_bloc.dart | 45 ------- .../batch/curtain_module_batch_event.dart | 19 --- .../batch/curtain_module_batch_state.dart | 28 ----- .../bloc/curtain_module_bloc.dart | 118 +++++++++++++++++- .../bloc/curtain_module_event.dart | 63 +++++++++- .../view/curtain_module_batch.dart | 57 +++++---- .../view/curtain_module_items.dart | 22 ++-- .../widgets/curtain_movment_widget.dart | 75 +++++++---- .../shared/device_batch_control_dialog.dart | 5 +- .../batch_control_devices_service.dart | 3 +- 12 files changed, 294 insertions(+), 158 deletions(-) delete mode 100644 lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_bloc.dart delete mode 100644 lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_event.dart delete mode 100644 lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_state.dart diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index af5a7b0a..9a8e18a2 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -45,7 +45,8 @@ class AcBloc extends Bloc { ) async { emit(AcsLoadingState()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); if (deviceStatus.countdown1 != 0) { final totalMinutes = deviceStatus.countdown1 * 6; @@ -82,10 +83,12 @@ class AcBloc extends Bloc { List statusList = []; usersMap['status'].forEach((element) { - statusList.add(Status(code: element['code'], value: element['value'])); + statusList + .add(Status(code: element['code'], value: element['value'])); }); - deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList); + deviceStatus = + AcStatusModel.fromJson(usersMap['productUuid'], statusList); if (!isClosed) { add(AcStatusUpdated(deviceStatus)); } @@ -129,8 +132,10 @@ class AcBloc extends Bloc { ) async { emit(AcsLoadingState()); try { - final status = await DevicesManagementApi().getBatchStatus(event.devicesIds); - deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status); + final status = + await DevicesManagementApi().getBatchStatus(event.devicesIds); + deviceStatus = + AcStatusModel.fromJson(event.devicesIds.first, status.status); emit(ACStatusLoaded(status: deviceStatus)); } catch (e) { emit(AcsFailedState(error: e.toString())); 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 cc09260e..08bca73c 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 @@ -213,7 +213,7 @@ mixin RouteControlsBasedCode { case 'CUR_2': return CurtainModuleBatchView( devicesIds: devices - .where((e) => e.productType == 'AC') + .where((e) => e.productType == 'CUR_2') .map((e) => e.uuid!) .toList(), ); diff --git a/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_bloc.dart b/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_bloc.dart deleted file mode 100644 index 87dd53f7..00000000 --- a/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_bloc.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:async'; - -import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; -import 'package:firebase_database/firebase_database.dart'; -import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart'; -import 'package:syncrow_web/services/control_device_service.dart'; -import 'package:syncrow_web/services/devices_mang_api.dart'; - -part 'curtain_module_batch_event.dart'; -part 'curtain_module_batch_state.dart'; - -class CurtainModuleBatchBloc - extends Bloc { - final ControlDeviceService controlDeviceService; - StreamSubscription? _firebaseSubscription; - - CurtainModuleBatchBloc(this.controlDeviceService) - : super(CurtainModuleBatchInitial()) { - on(_onFetchAcBatchStatus); - } - - Future _onFetchAcBatchStatus( - CutrainModuleFetchBatchStatusEvent event, - Emitter emit, - ) async { - emit(CurtainModuleBatchLoadingState()); - try { - final status = - await DevicesManagementApi().getBatchStatus(event.devicesIds); - status.status.forEach( - (element) => print( - 'this is code ${element.code} - this is value ${element.value}'), - ); - - emit( - CurtainModuleBatchLoadedState( - curtainModuleStatusModel: CurtainModuleStatusModel.fromJson({}), - ), - ); - } catch (e) { - emit(CurtainModuleBatchFailedState(error: e.toString())); - } - } -} diff --git a/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_event.dart b/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_event.dart deleted file mode 100644 index 351773f9..00000000 --- a/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_event.dart +++ /dev/null @@ -1,19 +0,0 @@ -part of 'curtain_module_batch_bloc.dart'; - -sealed class CurtainModuleBatchEvent extends Equatable { - const CurtainModuleBatchEvent(); - - @override - List get props => []; -} - -class CutrainModuleFetchBatchStatusEvent extends CurtainModuleBatchEvent { - final List devicesIds; - - const CutrainModuleFetchBatchStatusEvent({ - required this.devicesIds, - }); - - @override - List get props => [devicesIds]; -} diff --git a/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_state.dart b/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_state.dart deleted file mode 100644 index bb8bb7d0..00000000 --- a/lib/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_state.dart +++ /dev/null @@ -1,28 +0,0 @@ -part of 'curtain_module_batch_bloc.dart'; - -sealed class CurtainModuleBatchState extends Equatable { - const CurtainModuleBatchState(); - - @override - List get props => []; -} - -final class CurtainModuleBatchInitial extends CurtainModuleBatchState {} - -final class CurtainModuleBatchLoadingState extends CurtainModuleBatchState {} - -final class CurtainModuleBatchLoadedState extends CurtainModuleBatchState { - final CurtainModuleStatusModel curtainModuleStatusModel; - const CurtainModuleBatchLoadedState({ - required this.curtainModuleStatusModel, - }); -} - -final class CurtainModuleBatchFailedState extends CurtainModuleBatchState { - final String error; - - const CurtainModuleBatchFailedState({required this.error}); - - @override - List get props => [error]; -} diff --git a/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart b/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart index d79380cf..c3cd0b92 100644 --- a/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart +++ b/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart @@ -3,7 +3,9 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; import 'package:syncrow_web/services/control_device_service.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; @@ -12,9 +14,13 @@ part 'curtain_module_state.dart'; class CurtainModuleBloc extends Bloc { final ControlDeviceService controlDeviceService; + final BatchControlDevicesService batchControlDevicesService; StreamSubscription? _firebaseSubscription; - CurtainModuleBloc(this.controlDeviceService) : super(CurtainModuleInitial()) { + CurtainModuleBloc({ + required this.controlDeviceService, + required this.batchControlDevicesService, + }) : super(CurtainModuleInitial()) { on(_onFetchCurtainModuleStatusEvent); on(_onSendCurtainPercentToApiEvent); on(_onOpenCurtainEvent); @@ -26,6 +32,13 @@ class CurtainModuleBloc extends Bloc { on(_onChangeControlBackEvent); on(_onChangeControlBackModeEvent); on(_onChangeCurtainModuleStatusEvent); + //batch + on(_onFetchCurtainModuleBatchStatus); + on(_onSendCurtainBatchPercentToApiEvent); + on(_onOpenCurtainBatchEvent); + on(_onCloseCurtainBatchEvent); + on(_onStopCurtainBatchEvent); + on(_onFactoryReset); } Future _onFetchCurtainModuleStatusEvent( @@ -224,6 +237,109 @@ class CurtainModuleBloc extends Bloc { } } + FutureOr _onFetchCurtainModuleBatchStatus( + CurtainModuleFetchBatchStatusEvent event, + Emitter emit, + ) async { + emit(CurtainModuleLoading()); + try { + final status = + await DevicesManagementApi().getBatchStatus(event.devicesIds); + + final result = Map.fromEntries( + status.status.map((element) => MapEntry(element.code, element.value)), + ); + + emit(CurtainModuleStatusLoaded( + curtainModuleStatus: CurtainModuleStatusModel.fromJson(result), + )); + } catch (e) { + emit(CurtainModuleError(message: e.toString())); + } + } + + Future _onSendCurtainBatchPercentToApiEvent( + SendCurtainBatchPercentToApiEvent event, + Emitter emit, + ) async { + try { + await batchControlDevicesService.batchControlDevices( + uuids: event.devicesId, + code: event.status.code, + value: event.status.value, + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to send control command: $e')); + } + } + + Future _onOpenCurtainBatchEvent( + OpenCurtainBatchEvent event, + Emitter emit, + ) async { + try { + await batchControlDevicesService.batchControlDevices( + uuids: event.devicesId, + code: 'control', + value: 'open', + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to open curtain: $e')); + } + } + + Future _onCloseCurtainBatchEvent( + CloseCurtainBatchEvent event, + Emitter emit, + ) async { + try { + await batchControlDevicesService.batchControlDevices( + uuids: event.devicesId, + code: 'control', + value: 'close', + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to close curtain: $e')); + } + } + + Future _onStopCurtainBatchEvent( + StopCurtainBatchEvent event, + Emitter emit, + ) async { + try { + await batchControlDevicesService.batchControlDevices( + uuids: event.devicesId, + code: 'control', + value: 'stop', + ); + } catch (e) { + emit(CurtainModuleError(message: 'Failed to stop curtain: $e')); + } + } + + Future _onFactoryReset( + CurtainModuleFactoryReset event, + Emitter emit, + ) async { + emit(CurtainModuleLoading()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (!response) { + emit(const CurtainModuleError(message: 'Failed')); + } else { + add( + FetchCurtainModuleStatusEvent(deviceId: event.deviceId), + ); + } + } catch (e) { + emit(CurtainModuleError(message: e.toString())); + } + } + @override Future close() async { await _firebaseSubscription?.cancel(); diff --git a/lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart b/lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart index 6b0d89ae..4eec030d 100644 --- a/lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart +++ b/lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart @@ -129,4 +129,65 @@ class ChangeCurtainModuleStatusEvent extends CurtainModuleEvent { @override List get props => [deviceId, status]; -} \ No newline at end of file +} + +///batch +class CurtainModuleFetchBatchStatusEvent extends CurtainModuleEvent { + final List devicesIds; + + const CurtainModuleFetchBatchStatusEvent(this.devicesIds); + + @override + List get props => [devicesIds]; +} + +class SendCurtainBatchPercentToApiEvent extends CurtainModuleEvent { + final List devicesId; + final Status status; + + const SendCurtainBatchPercentToApiEvent({ + required this.devicesId, + required this.status, + }); + + @override + List get props => [devicesId, status]; +} + +class OpenCurtainBatchEvent extends CurtainModuleEvent { + final List devicesId; + + const OpenCurtainBatchEvent({required this.devicesId}); + + @override + List get props => [devicesId]; +} + +class CloseCurtainBatchEvent extends CurtainModuleEvent { + final List devicesId; + + const CloseCurtainBatchEvent({required this.devicesId}); + + @override + List get props => [devicesId]; +} + +class StopCurtainBatchEvent extends CurtainModuleEvent { + final List devicesId; + + const StopCurtainBatchEvent({required this.devicesId}); + + @override + List get props => [devicesId]; +} + +class CurtainModuleFactoryReset extends CurtainModuleEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + const CurtainModuleFactoryReset( + {required this.deviceId, required this.factoryReset}); + + @override + List get props => [deviceId, factoryReset]; +} diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart index 0e0dfdc3..bd28cd8a 100644 --- a/lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/batch/curtain_module_batch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; import 'package:syncrow_web/services/control_device_service.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -17,8 +20,10 @@ class CurtainModuleBatchView extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => CurtainModuleBatchBloc(RemoteControlDeviceService()) - ..add(CutrainModuleFetchBatchStatusEvent(devicesIds: devicesIds)), + create: (context) => CurtainModuleBloc( + controlDeviceService: RemoteControlDeviceService(), + batchControlDevicesService: RemoteBatchControlDevicesService()) + ..add(CurtainModuleFetchBatchStatusEvent(devicesIds)), child: _buildStatusControls(context), ); } @@ -30,37 +35,41 @@ class CurtainModuleBatchView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ ControlCurtainMovementWidget( - deviceId: devicesIds.first, + devicesId: devicesIds, ), const SizedBox( height: 10, ), SizedBox( height: 120, - width: 350, + // width: 350, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: IconNameStatusContainer( - isFullIcon: false, - name: 'Factory Reset', - icon: Assets.factoryReset, - onTap: () {}, - status: false, - textColor: ColorsManager.blackColor, - ), - ), - Expanded( - child: IconNameStatusContainer( - isFullIcon: false, - name: 'Firmware Update', - icon: Assets.firmware, - onTap: () {}, - status: false, - textColor: ColorsManager.blackColor, - ), + // Expanded( + // child: + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + CurtainModuleFactoryReset( + deviceId: devicesIds.first, + factoryReset: + FactoryResetModel(devicesUuid: devicesIds), + ), + ); + }, ), + // ), + // Expanded( + // child: IconNameStatusContainer( + // isFullIcon: false, + // name: 'Firmware Update', + // icon: Assets.firmware, + // onTap: () {}, + // status: false, + // textColor: ColorsManager.blackColor, + // ), + // ) ], ), ), diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart index 22ccf90e..198c8713 100644 --- a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart @@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_m import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; import 'package:syncrow_web/services/control_device_service.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -19,7 +20,9 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => CurtainModuleBloc(RemoteControlDeviceService()) + create: (context) => CurtainModuleBloc( + controlDeviceService: RemoteControlDeviceService(), + batchControlDevicesService: RemoteBatchControlDevicesService()) ..add(FetchCurtainModuleStatusEvent(deviceId: deviceId)), child: _buildStatusControls(context), ); @@ -32,7 +35,7 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { crossAxisAlignment: CrossAxisAlignment.start, children: [ ControlCurtainMovementWidget( - deviceId: deviceId, + devicesId: [deviceId], ), const SizedBox( height: 10, @@ -70,12 +73,15 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { icon: Assets.preferences, onTap: () => showDialog( context: context, - builder: (_) => CurtainModulePrefrencesDialog( - curtainModuleBloc: - context.watch(), - deviceId: deviceId, - curtainModuleStatusModel: - state.curtainModuleStatus, + builder: (_) => BlocProvider.value( + value: context.read(), + child: CurtainModulePrefrencesDialog( + curtainModuleBloc: + context.watch(), + deviceId: deviceId, + curtainModuleStatusModel: + state.curtainModuleStatus, + ), ), ), status: false, diff --git a/lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart b/lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart index 076c5a9c..e98ff11d 100644 --- a/lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart +++ b/lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart @@ -9,10 +9,10 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class ControlCurtainMovementWidget extends StatelessWidget { - final String deviceId; + final List devicesId; const ControlCurtainMovementWidget({ super.key, - required this.deviceId, + required this.devicesId, }); @override @@ -26,9 +26,15 @@ class ControlCurtainMovementWidget extends StatelessWidget { CurtainActionWidget( icon: Assets.openCurtain, onTap: () { - context.read().add( - OpenCurtainEvent(deviceId: deviceId), - ); + if (devicesId.length == 1) { + context.read().add( + OpenCurtainEvent(deviceId: devicesId.first), + ); + } else { + context.read().add( + OpenCurtainBatchEvent(devicesId: devicesId), + ); + } }, ), const SizedBox( @@ -37,9 +43,15 @@ class ControlCurtainMovementWidget extends StatelessWidget { CurtainActionWidget( icon: Assets.pauseCurtain, onTap: () { - context.read().add( - StopCurtainEvent(deviceId: deviceId), - ); + if (devicesId.length == 1) { + context.read().add( + StopCurtainEvent(deviceId: devicesId.first), + ); + } else { + context.read().add( + StopCurtainBatchEvent(devicesId: devicesId), + ); + } }, ), const SizedBox( @@ -48,9 +60,15 @@ class ControlCurtainMovementWidget extends StatelessWidget { CurtainActionWidget( icon: Assets.closeCurtain, onTap: () { - context.read().add( - CloseCurtainEvent(deviceId: deviceId), - ); + if (devicesId.length == 1) { + context.read().add( + CloseCurtainEvent(deviceId: devicesId.first), + ); + } else { + context.read().add( + CloseCurtainBatchEvent(devicesId: devicesId), + ); + } }, ), BlocBuilder( @@ -84,7 +102,7 @@ class ControlCurtainMovementWidget extends StatelessWidget { } else if (state is CurtainModuleStatusLoaded) { return CurtainSliderWidget( status: state.curtainModuleStatus, - deviceId: deviceId, + devicesId: devicesId, ); } else { return const Center( @@ -108,12 +126,12 @@ class ControlCurtainMovementWidget extends StatelessWidget { class CurtainSliderWidget extends StatefulWidget { final CurtainModuleStatusModel status; - final String deviceId; + final List devicesId; const CurtainSliderWidget({ super.key, required this.status, - required this.deviceId, + required this.devicesId, }); @override @@ -167,16 +185,27 @@ class _CurtainSliderWidgetState extends State { onChangeEnd: (value) { final int targetPercent = (value * 100).round(); - // Dispatch API call - context.read().add( - SendCurtainPercentToApiEvent( - deviceId: widget.deviceId, - status: Status( - code: 'percent_control', - value: targetPercent, + if (widget.devicesId.length == 1) { + context.read().add( + SendCurtainPercentToApiEvent( + deviceId: widget.devicesId.first, + status: Status( + code: 'percent_control', + value: targetPercent, + ), ), - ), - ); + ); + } else { + context.read().add( + SendCurtainBatchPercentToApiEvent( + devicesId: widget.devicesId, + status: Status( + code: 'percent_control', + value: targetPercent, + ), + ), + ); + } // Revert back to Firebase-synced stream setState(() { diff --git a/lib/pages/device_managment/shared/device_batch_control_dialog.dart b/lib/pages/device_managment/shared/device_batch_control_dialog.dart index f2dc68f5..c7ea6c71 100644 --- a/lib/pages/device_managment/shared/device_batch_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_batch_control_dialog.dart @@ -4,7 +4,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class DeviceBatchControlDialog extends StatelessWidget with RouteControlsBasedCode { +class DeviceBatchControlDialog extends StatelessWidget + with RouteControlsBasedCode { final List devices; const DeviceBatchControlDialog({super.key, required this.devices}); @@ -18,7 +19,7 @@ class DeviceBatchControlDialog extends StatelessWidget with RouteControlsBasedCo borderRadius: BorderRadius.circular(20), ), child: SizedBox( - width: devices.length < 2 ? 500 : 800, + width: devices.length < 2 ? 600 : 800, // height: context.screenHeight * 0.7, child: SingleChildScrollView( child: Padding( diff --git a/lib/services/batch_control_devices_service.dart b/lib/services/batch_control_devices_service.dart index f78cdef4..16542c8c 100644 --- a/lib/services/batch_control_devices_service.dart +++ b/lib/services/batch_control_devices_service.dart @@ -11,7 +11,8 @@ abstract interface class BatchControlDevicesService { }); } -final class RemoteBatchControlDevicesService implements BatchControlDevicesService { +final class RemoteBatchControlDevicesService + implements BatchControlDevicesService { @override Future batchControlDevices({ required List uuids, From 812c51400b984a5fe553f0bb57fe987a9e4b419c Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Fri, 27 Jun 2025 16:53:40 +0300 Subject: [PATCH 09/37] add listener to batch --- .../bloc/curtain_module_bloc.dart | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart b/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart index c3cd0b92..b40d7ea6 100644 --- a/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart +++ b/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart @@ -253,6 +253,37 @@ class CurtainModuleBloc extends Bloc { emit(CurtainModuleStatusLoaded( curtainModuleStatus: CurtainModuleStatusModel.fromJson(result), )); + + Map statusMap = {}; + final ref = FirebaseDatabase.instance + .ref('device-status/${event.devicesIds.first}'); + final stream = ref.onValue; + + stream.listen((DatabaseEvent DatabaseEvent) async { + if (DatabaseEvent.snapshot.value == null) return; + + Map usersMap = + DatabaseEvent.snapshot.value as Map; + + List statusList = []; + + usersMap['status'].forEach((element) { + statusList + .add(Status(code: element['code'], value: element['value'])); + }); + + statusMap = { + for (final element in statusList) element.code: element.value, + }; + if (!isClosed) { + add( + ChangeCurtainModuleStatusEvent( + deviceId: event.devicesIds.first, + status: CurtainModuleStatusModel.fromJson(statusMap), + ), + ); + } + }); } catch (e) { emit(CurtainModuleError(message: e.toString())); } From 0c0bf96c073dfd090cfb93c8a1a48c477cd3dbf8 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Fri, 27 Jun 2025 17:16:41 +0300 Subject: [PATCH 10/37] add bloc builder to use the context --- .../view/curtain_module_items.dart | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart index 198c8713..07dc4158 100644 --- a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart @@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart'; import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart'; import 'package:syncrow_web/services/batch_control_devices_service.dart'; import 'package:syncrow_web/services/control_device_service.dart'; @@ -24,7 +26,11 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { controlDeviceService: RemoteControlDeviceService(), batchControlDevicesService: RemoteBatchControlDevicesService()) ..add(FetchCurtainModuleStatusEvent(deviceId: deviceId)), - child: _buildStatusControls(context), + child: BlocBuilder( + builder: (context, state) { + return _buildStatusControls(context); + }, + ), ); } @@ -41,19 +47,28 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { height: 10, ), SizedBox( - height: 120, + height: 140, width: 350, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: IconNameStatusContainer( - isFullIcon: false, - name: 'Schedules', - icon: Assets.schedule, - onTap: () {}, - status: false, - textColor: ColorsManager.blackColor, + child: ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: + BlocProvider.of(context), + child: BuildScheduleView( + deviceUuid: deviceId, + category: 'CUR_2', + ), + )); + }, + mainText: '', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, ), ), const SizedBox( From 26e8ff7ee2adfe95fbcb70105245b4011ad4e217 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Fri, 27 Jun 2025 17:55:27 +0300 Subject: [PATCH 11/37] use dynamic instead of bool to accept mant types and fix schedual view to accept curtain code and value --- .../schedule_widgets/schedual_view.dart | 16 +++++++++++++--- .../helper/add_schedule_dialog_helper.dart | 6 +++++- lib/services/devices_mang_api.dart | 3 +-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart index 47534d37..3b7bc138 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart @@ -12,10 +12,17 @@ import 'package:syncrow_web/pages/device_managment/water_heater/helper/add_sched import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; class BuildScheduleView extends StatelessWidget { - const BuildScheduleView( - {super.key, required this.deviceUuid, required this.category}); + const BuildScheduleView({ + super.key, + required this.deviceUuid, + required this.category, + this.code, + this.value, + }); final String deviceUuid; final String category; + final String? code; + final String? value; @override Widget build(BuildContext context) { @@ -59,11 +66,14 @@ class BuildScheduleView extends StatelessWidget { context, schedule: null, isEdit: false, + code: code, + value: value, ); if (entry != null) { context.read().add( ScheduleAddEvent( category: entry.category, + code: entry.function.code, time: entry.time, functionOn: entry.function.value, selectedDays: entry.days, @@ -74,7 +84,7 @@ class BuildScheduleView extends StatelessWidget { ), if (state.scheduleMode == ScheduleModes.countdown || state.scheduleMode == ScheduleModes.inching) - CountdownInchingView( + CountdownInchingView( deviceId: deviceUuid, ), const SizedBox(height: 20), diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index ae7feac9..b8059402 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -17,6 +17,8 @@ class ScheduleDialogHelper { BuildContext context, { ScheduleEntry? schedule, bool isEdit = false, + String? code, + String? value, }) { final initialTime = schedule != null ? _convertStringToTimeOfDay(schedule.time) @@ -118,7 +120,9 @@ class ScheduleDialogHelper { final entry = ScheduleEntry( category: schedule?.category ?? 'switch_1', time: _formatTimeOfDayToISO(selectedTime), - function: Status(code: 'switch_1', value: functionOn), + function: Status( + code: code ?? 'switch_1', + value: value ?? functionOn), days: _convertSelectedDaysToStrings(selectedDays), scheduleId: schedule?.scheduleId, ); diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 6fb27daf..963738a7 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -393,7 +393,7 @@ class DevicesManagementApi { required String deviceId, required String time, required String code, - required bool value, + required dynamic value, required List days, }) async { final response = await HTTPService().post( @@ -416,5 +416,4 @@ class DevicesManagementApi { ); return response; } - } From e365aa3faac702c29430f5cde7755f3bebdadc0b Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Fri, 27 Jun 2025 17:56:12 +0300 Subject: [PATCH 12/37] edite event and block of schdual to accept code and functionOn as dynamic --- .../schedule_device/bloc/schedule_bloc.dart | 2 +- .../schedule_device/bloc/schedule_event.dart | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart b/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart index fbf7ae64..0ec55e39 100644 --- a/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart +++ b/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart @@ -265,7 +265,7 @@ class ScheduleBloc extends Bloc { category: event.category, deviceId: deviceId, time: getTimeStampWithoutSeconds(dateTime).toString(), - code: event.category, + code: event.code ?? event.category, value: event.functionOn, days: event.selectedDays); if (success) { diff --git a/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart b/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart index 0b9ec581..a28b8757 100644 --- a/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart +++ b/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart @@ -70,17 +70,19 @@ class ScheduleAddEvent extends ScheduleEvent { final String category; final String time; final List selectedDays; - final bool functionOn; + final dynamic functionOn; + final String? code; const ScheduleAddEvent({ required this.category, required this.time, required this.selectedDays, required this.functionOn, + required this.code, }); @override - List get props => [category, time, selectedDays, functionOn]; + List get props => [category, time, selectedDays, functionOn, code]; } class ScheduleEditEvent extends ScheduleEvent { From 1d95915f57be5788db5b458d2aa989ede7d67ac8 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Fri, 27 Jun 2025 17:56:41 +0300 Subject: [PATCH 13/37] fix the string for motor without underscore --- .../curtain_module/widgets/prefrences_dialog.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart index bf500792..1e4f932c 100644 --- a/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart @@ -69,7 +69,8 @@ class CurtainModulePrefrencesDialog extends StatelessWidget { }, ), PrefReversCardWidget( - title: state.curtainModuleStatus.elecMachineryMode, + title: formatDeviceType( + state.curtainModuleStatus.elecMachineryMode), body: 'Motor Mode', onTap: () => context.read().add( ChangeElecMachineryModeEvent( @@ -136,4 +137,13 @@ class CurtainModulePrefrencesDialog extends StatelessWidget { ), ); } + + String formatDeviceType(String raw) { + return raw + .split('_') + .map((word) => word.isNotEmpty + ? '${word[0].toUpperCase()}${word.substring(1)}' + : '') + .join(' '); + } } From 32208c1e81976a09d1311a072b7bc0dc39a5a94f Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Fri, 27 Jun 2025 17:57:04 +0300 Subject: [PATCH 14/37] send code and value to schdual from curtain module --- .../curtain_module/view/curtain_module_items.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart index 07dc4158..48b5ab2a 100644 --- a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart @@ -63,6 +63,8 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { child: BuildScheduleView( deviceUuid: deviceId, category: 'CUR_2', + code: 'control', + value: 'open', ), )); }, From bd53388438f04f3a15b87562f0770d169cf91a81 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Sun, 29 Jun 2025 09:17:22 +0300 Subject: [PATCH 15/37] make one API with new QP to filter on spacesId --- .../device_managment_bloc.dart | 14 ++-- .../bloc/routine_bloc/routine_bloc.dart | 80 +++++++++---------- lib/services/devices_mang_api.dart | 13 ++- lib/utils/constants/api_const.dart | 2 +- 4 files changed, 51 insertions(+), 58 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index 98b0c195..6fd23374 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -41,18 +41,17 @@ class DeviceManagementBloc _devices.clear(); var spaceBloc = event.context.read(); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - if (spaceBloc.state.selectedCommunities.isEmpty) { - devices = - await DevicesManagementApi().fetchDevices('', '', projectUuid); + devices = await DevicesManagementApi().fetchDevices(projectUuid); } else { for (var community in spaceBloc.state.selectedCommunities) { List spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? []; - for (var space in spacesList) { - devices.addAll(await DevicesManagementApi() - .fetchDevices(community, space, projectUuid)); - } + spacesList.forEach( + (element) => print(element), + ); + devices.addAll(await DevicesManagementApi() + .fetchDevices(projectUuid, spacesId: spacesList)); } } @@ -270,6 +269,7 @@ class DeviceManagementBloc return 'All'; } } + void _onSearchDevices( SearchDevices event, Emitter emit) { if ((event.community == null || event.community!.isEmpty) && diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index 3fd07834..f38ea994 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -170,45 +170,45 @@ class RoutineBloc extends Bloc { } } -Future _onLoadScenes( - LoadScenes event, Emitter emit) async { - emit(state.copyWith(isLoading: true, errorMessage: null)); - List scenes = []; - try { - BuildContext context = NavigationService.navigatorKey.currentContext!; - var createRoutineBloc = context.read(); - final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - if (createRoutineBloc.selectedSpaceId == '' && - createRoutineBloc.selectedCommunityId == '') { - var spaceBloc = context.read(); - for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = - spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - for (var spaceId in spacesList) { - scenes.addAll( - await SceneApi.getScenes(spaceId, communityId, projectUuid)); + Future _onLoadScenes( + LoadScenes event, Emitter emit) async { + emit(state.copyWith(isLoading: true, errorMessage: null)); + List scenes = []; + try { + BuildContext context = NavigationService.navigatorKey.currentContext!; + var createRoutineBloc = context.read(); + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + if (createRoutineBloc.selectedSpaceId == '' && + createRoutineBloc.selectedCommunityId == '') { + var spaceBloc = context.read(); + for (var communityId in spaceBloc.state.selectedCommunities) { + List spacesList = + spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + for (var spaceId in spacesList) { + scenes.addAll( + await SceneApi.getScenes(spaceId, communityId, projectUuid)); + } } + } else { + scenes.addAll(await SceneApi.getScenes( + createRoutineBloc.selectedSpaceId, + createRoutineBloc.selectedCommunityId, + projectUuid)); } - } else { - scenes.addAll(await SceneApi.getScenes( - createRoutineBloc.selectedSpaceId, - createRoutineBloc.selectedCommunityId, - projectUuid)); - } - emit(state.copyWith( - scenes: scenes, - isLoading: false, - )); - } catch (e) { - emit(state.copyWith( + emit(state.copyWith( + scenes: scenes, isLoading: false, - loadScenesErrorMessage: 'Failed to load scenes', - errorMessage: '', - loadAutomationErrorMessage: '', - scenes: scenes)); + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + loadScenesErrorMessage: 'Failed to load scenes', + errorMessage: '', + loadAutomationErrorMessage: '', + scenes: scenes)); + } } -} Future _onLoadAutomation( LoadAutomation event, Emitter emit) async { @@ -936,16 +936,12 @@ Future _onLoadScenes( for (var communityId in spaceBloc.state.selectedCommunities) { List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - for (var spaceId in spacesList) { - devices.addAll(await DevicesManagementApi() - .fetchDevices(communityId, spaceId, projectUuid)); - } + devices.addAll(await DevicesManagementApi() + .fetchDevices(projectUuid, spacesId: spacesList)); } } else { - devices.addAll(await DevicesManagementApi().fetchDevices( - createRoutineBloc.selectedCommunityId, - createRoutineBloc.selectedSpaceId, - projectUuid)); + devices.addAll(await DevicesManagementApi().fetchDevices(projectUuid, + spacesId: [createRoutineBloc.selectedSpaceId])); } emit(state.copyWith(isLoading: false, devices: devices)); diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 6fb27daf..a6b4a278 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -13,15 +13,13 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class DevicesManagementApi { Future> fetchDevices( - String communityId, String spaceId, String projectId) async { + String projectId, { + List? spacesId, + }) async { try { final response = await HTTPService().get( - path: communityId.isNotEmpty && spaceId.isNotEmpty - ? ApiEndpoints.getSpaceDevices - .replaceAll('{spaceUuid}', spaceId) - .replaceAll('{communityUuid}', communityId) - .replaceAll('{projectId}', projectId) - : ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId), + queryParameters: {if (spacesId != null) 'spaces': spacesId}, + path: ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId), showServerMessage: true, expectedResponseModel: (json) { List jsonData = json['data']; @@ -416,5 +414,4 @@ class DevicesManagementApi { ); return response; } - } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index eb7b6a3e..6dda1108 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -18,7 +18,7 @@ abstract class ApiEndpoints { static const String getAllDevices = '/projects/{projectId}/devices'; static const String getSpaceDevices = - '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices'; + '/projects/{projectId}/devices'; static const String getDeviceStatus = '/devices/{uuid}/functions/status'; static const String getBatchStatus = '/devices/batch'; From 1828ffb87a2903c8d2340cb4758fef81e0bb23a8 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Sun, 29 Jun 2025 09:17:57 +0300 Subject: [PATCH 16/37] remove print statment --- .../bloc/device_mgmt_bloc/device_managment_bloc.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index 6fd23374..4063692e 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -47,9 +47,6 @@ class DeviceManagementBloc for (var community in spaceBloc.state.selectedCommunities) { List spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? []; - spacesList.forEach( - (element) => print(element), - ); devices.addAll(await DevicesManagementApi() .fetchDevices(projectUuid, spacesId: spacesList)); } From b96f65d2c2b0cc577996e94101eb5a57212063c4 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Sun, 29 Jun 2025 10:42:18 +0300 Subject: [PATCH 17/37] fix the open close states when curtain module --- .../curtain_module/view/curtain_module_items.dart | 2 +- .../schedule_widgets/schedual_view.dart | 15 ++++++++++----- .../schedule_widgets/schedule_table.dart | 14 ++++++++++++-- .../helper/add_schedule_dialog_helper.dart | 14 +++++++++++--- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart index 48b5ab2a..82c812ce 100644 --- a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart @@ -64,7 +64,7 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { deviceUuid: deviceId, category: 'CUR_2', code: 'control', - value: 'open', + ), )); }, diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart index 3b7bc138..c511b8bd 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart'; @@ -9,6 +10,7 @@ import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widg import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; class BuildScheduleView extends StatelessWidget { @@ -17,12 +19,10 @@ class BuildScheduleView extends StatelessWidget { required this.deviceUuid, required this.category, this.code, - this.value, }); final String deviceUuid; final String category; final String? code; - final String? value; @override Widget build(BuildContext context) { @@ -64,15 +64,20 @@ class BuildScheduleView extends StatelessWidget { final entry = await ScheduleDialogHelper .showAddScheduleDialog( context, - schedule: null, + schedule: ScheduleEntry( + category: category, + time: '', + function: Status( + code: code.toString(), value: null), + days: [], + ), isEdit: false, code: code, - value: value, ); if (entry != null) { context.read().add( ScheduleAddEvent( - category: entry.category, + category: category, code: entry.function.code, time: entry.time, functionOn: entry.function.value, diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart index 98ae0515..b23e48df 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart @@ -162,11 +162,18 @@ class _ScheduleTableView extends StatelessWidget { child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { + bool temp; + if (schedule.category == 'CUR_2') { + temp = schedule.function.value == 'open' ? true : false; + } else { + temp = schedule.function.value as bool; + } context.read().add( ScheduleUpdateEntryEvent( category: schedule.category, scheduleId: schedule.scheduleId, - functionOn: schedule.function.value, + functionOn: temp, + // schedule.function.value, enable: !schedule.enable, ), ); @@ -188,7 +195,10 @@ class _ScheduleTableView extends StatelessWidget { child: Text(_getSelectedDays( ScheduleModel.parseSelectedDays(schedule.days)))), Center(child: Text(formatIsoStringToTime(schedule.time, context))), - Center(child: Text(schedule.function.value ? 'On' : 'Off')), + schedule.category == 'CUR_2' + ? Center( + child: Text(schedule.function.value == true ? 'open' : 'close')) + : Center(child: Text(schedule.function.value ? 'On' : 'Off')), Center( child: Wrap( runAlignment: WrapAlignment.center, diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index b8059402..51087704 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -18,7 +18,6 @@ class ScheduleDialogHelper { ScheduleEntry? schedule, bool isEdit = false, String? code, - String? value, }) { final initialTime = schedule != null ? _convertStringToTimeOfDay(schedule.time) @@ -117,12 +116,21 @@ class ScheduleDialogHelper { width: 100, child: ElevatedButton( onPressed: () { + dynamic temp; + if (schedule?.category == 'CUR_2') { + temp = functionOn! ? 'open' : 'close'; + } else { + temp = functionOn; + } + print(temp); final entry = ScheduleEntry( category: schedule?.category ?? 'switch_1', time: _formatTimeOfDayToISO(selectedTime), function: Status( - code: code ?? 'switch_1', - value: value ?? functionOn), + code: code ?? 'switch_1', + value: temp, + // functionOn, + ), days: _convertSelectedDaysToStrings(selectedDays), scheduleId: schedule?.scheduleId, ); From ec1bb5b609bc54f6fab908b212aa8c297ffedda4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 29 Jun 2025 10:49:00 +0300 Subject: [PATCH 18/37] added curtain icons. --- lib/pages/visitor_password/model/device_model.dart | 4 ++++ lib/utils/enum/device_types.dart | 2 ++ 2 files changed, 6 insertions(+) diff --git a/lib/pages/visitor_password/model/device_model.dart b/lib/pages/visitor_password/model/device_model.dart index f9711eed..75d00350 100644 --- a/lib/pages/visitor_password/model/device_model.dart +++ b/lib/pages/visitor_password/model/device_model.dart @@ -80,6 +80,10 @@ class DeviceModel { tempIcon = Assets.openedDoor; } else if (type == DeviceType.WaterLeak) { tempIcon = Assets.waterLeakNormal; + } else if (type == DeviceType.Curtain2) { + tempIcon = Assets.curtainIcon; + } else if (type == DeviceType.Curtain) { + tempIcon = Assets.curtainIcon; } else { tempIcon = Assets.blackLogo; } diff --git a/lib/utils/enum/device_types.dart b/lib/utils/enum/device_types.dart index 9bfd322f..947e63aa 100644 --- a/lib/utils/enum/device_types.dart +++ b/lib/utils/enum/device_types.dart @@ -3,6 +3,7 @@ enum DeviceType { LightBulb, DoorLock, Curtain, + Curtain2, Blind, OneGang, TwoGang, @@ -44,6 +45,7 @@ enum DeviceType { Map devicesTypesMap = { "AC": DeviceType.AC, + "CUR_2": DeviceType.Curtain2, "GW": DeviceType.Gateway, "CPS": DeviceType.CeilingSensor, "DL": DeviceType.DoorLock, From a1562110d57e47f5d583c507b0f37b3abf3b6d97 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Sun, 29 Jun 2025 10:50:51 +0300 Subject: [PATCH 19/37] add close open if it is curtain module for schdule --- .../water_heater/helper/add_schedule_dialog_helper.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index 51087704..389eac3f 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -97,7 +97,8 @@ class ScheduleDialogHelper { setState(() => selectedDays[i] = v); }), const SizedBox(height: 16), - _buildFunctionSwitch(ctx, functionOn!, (v) { + _buildFunctionSwitch(schedule!.category, ctx, functionOn!, + (v) { setState(() => functionOn = v); }), ], @@ -197,7 +198,7 @@ class ScheduleDialogHelper { } static Widget _buildFunctionSwitch( - BuildContext ctx, bool isOn, Function(bool) onChanged) { + String categor, BuildContext ctx, bool isOn, Function(bool) onChanged) { return Row( children: [ Text( @@ -211,14 +212,14 @@ class ScheduleDialogHelper { groupValue: isOn, onChanged: (val) => onChanged(true), ), - const Text('On'), + Text(categor == 'CUR_2' ? 'open' : 'On'), const SizedBox(width: 10), Radio( value: false, groupValue: isOn, onChanged: (val) => onChanged(false), ), - const Text('Off'), + Text(categor == 'CUR_2' ? 'close' : 'Off'), ], ); } From 23cfee14901c7b176b4a20ba0a76fbc65c35d702 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Sun, 29 Jun 2025 11:12:28 +0300 Subject: [PATCH 20/37] fix curtain name in curtain if then containers dialogs --- .../routines/widgets/routine_dialogs/curtain_dialog.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/curtain_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/curtain_dialog.dart index bdf8660d..64295e2a 100644 --- a/lib/pages/routines/widgets/routine_dialogs/curtain_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/curtain_dialog.dart @@ -58,7 +58,9 @@ class CurtainHelper { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const DialogHeader('AC Functions'), + DialogHeader(dialogType == 'THEN' + ? 'Curtain Functions' + : 'Curtain Conditions'), Expanded( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, From 388391eec4b900326aac70776f22b175c0bd19cd Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Sun, 29 Jun 2025 11:29:43 +0300 Subject: [PATCH 21/37] stop stacking snackbars --- .../all_devices/widgets/device_managment_body.dart | 2 ++ 1 file changed, 2 insertions(+) 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 c865a5dc..4f86c8e3 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 @@ -111,6 +111,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { onPressed: isControlButtonEnabled ? () { if (isAnyDeviceOffline) { + ScaffoldMessenger.of(context) + .clearSnackBars(); ScaffoldMessenger.of(context) .showSnackBar( const SnackBar( From 19cdd371f8bb212696585e6467bb2d28ed8839be Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Sun, 29 Jun 2025 12:43:23 +0300 Subject: [PATCH 22/37] fix edit problem and funtion name in dialog was olways keep close now it is really take the real value --- .../schedule_device/schedule_widgets/schedule_table.dart | 8 ++++---- .../water_heater/helper/add_schedule_dialog_helper.dart | 8 +++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart index b23e48df..21f404ff 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart @@ -195,10 +195,10 @@ class _ScheduleTableView extends StatelessWidget { child: Text(_getSelectedDays( ScheduleModel.parseSelectedDays(schedule.days)))), Center(child: Text(formatIsoStringToTime(schedule.time, context))), - schedule.category == 'CUR_2' - ? Center( - child: Text(schedule.function.value == true ? 'open' : 'close')) - : Center(child: Text(schedule.function.value ? 'On' : 'Off')), + if (schedule.category == 'CUR_2') + Center(child: Text(schedule.function.value)) + else + Center(child: Text(schedule.function.value ? 'On' : 'Off')), Center( child: Wrap( runAlignment: WrapAlignment.center, diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index 389eac3f..f55b32ab 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -19,13 +19,19 @@ class ScheduleDialogHelper { bool isEdit = false, String? code, }) { + bool temp; + if (schedule?.category == 'CUR_2') { + temp = schedule!.function.value == 'open' ? true : false; + } else { + temp = schedule!.function.value; + } final initialTime = schedule != null ? _convertStringToTimeOfDay(schedule.time) : TimeOfDay.now(); final initialDays = schedule != null ? _convertDaysStringToBooleans(schedule.days) : List.filled(7, false); - bool? functionOn = schedule?.function.value ?? true; + bool? functionOn = temp; TimeOfDay selectedTime = initialTime; List selectedDays = List.of(initialDays); From 01f55c14de682d1cccc1e061b93fa4bcaae7f3b7 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 29 Jun 2025 13:03:33 +0300 Subject: [PATCH 23/37] Add update events for device and subspace names implement copyWith methods in models --- .../device_managment_bloc.dart | 98 +++++++++++++++++++ .../device_managment_event.dart | 18 ++++ .../models/device_subspace.model.dart | 16 +++ .../all_devices/models/devices_model.dart | 68 +++++++++++++ .../widgets/device_managment_body.dart | 3 +- .../device_management_content.dart | 5 + .../device_setting/device_settings_panel.dart | 11 ++- 7 files changed, 216 insertions(+), 3 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index 98b0c195..ecb2b207 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -21,6 +21,7 @@ class DeviceManagementBloc String currentProductName = ''; String? currentCommunity; String? currentUnitName; + String subSpaceName = ''; DeviceManagementBloc() : super(DeviceManagementInitial()) { on(_onFetchDevices); @@ -31,6 +32,8 @@ class DeviceManagementBloc on(_onResetFilters); on(_onResetSelectedDevices); on(_onUpdateSelection); + on(_onUpdateDeviceName); + on(_onUpdateSubSpaceName); } Future _onFetchDevices( @@ -343,5 +346,100 @@ class DeviceManagementBloc } } + void _onUpdateDeviceName( + UpdateDeviceName event, Emitter emit) { + _devices = _devices.map((device) { + if (device.uuid == event.deviceId) { + return device.copyWith(name: event.newName); + } + return device; + }).toList(); + + if (state is DeviceManagementLoaded) { + final loaded = state as DeviceManagementLoaded; + emit(DeviceManagementLoaded( + devices: _devices, + selectedIndex: loaded.selectedIndex, + onlineCount: loaded.onlineCount, + offlineCount: loaded.offlineCount, + lowBatteryCount: loaded.lowBatteryCount, + selectedDevice: loaded.selectedDevice, + isControlButtonEnabled: loaded.isControlButtonEnabled, + )); + } else if (state is DeviceManagementFiltered) { + final filtered = state as DeviceManagementFiltered; + emit(DeviceManagementFiltered( + filteredDevices: _filteredDevices, + selectedIndex: filtered.selectedIndex, + onlineCount: filtered.onlineCount, + offlineCount: filtered.offlineCount, + lowBatteryCount: filtered.lowBatteryCount, + selectedDevice: filtered.selectedDevice, + isControlButtonEnabled: filtered.isControlButtonEnabled, + )); + } + } + + void _onUpdateSubSpaceName( + UpdateSubSpaceName event, Emitter emit) { + _devices = _devices.map((device) { + print('before update: ${device.subspace?.subspaceName}'); + if (device.uuid == event.deviceId) { + print('Updating subspace name for device: ${device.uuid}'); + print('New subspace name: ${event.newSubSpaceName}'); + final updatedSubspace = device.subspace?.copyWith( + subspaceName: event.newSubSpaceName, + ); + final s = device.copyWith(subspace: updatedSubspace); + + return s; + } + subSpaceName = device.subspace!.subspaceName; + + return device; + }).toList(); + print('After update:'); + for (final device in _devices) { + if (device.uuid == event.deviceId) { + print( + 'Device: ${device.uuid}, subspace: ${device.subspace?.uuid}, subspaceName: ${device.subspace?.subspaceName}', + ); + } + } + print('Subspace name updated to: $subSpaceName'); + if (state is DeviceManagementLoaded) { + final loaded = state as DeviceManagementLoaded; + emit(DeviceManagementLoaded( + devices: _devices, + selectedIndex: loaded.selectedIndex, + onlineCount: loaded.onlineCount, + offlineCount: loaded.offlineCount, + lowBatteryCount: loaded.lowBatteryCount, + selectedDevice: loaded.selectedDevice, + isControlButtonEnabled: loaded.isControlButtonEnabled, + )); + } else if (state is DeviceManagementFiltered) { + // final filtered = state as DeviceManagementFiltered; + // emit(DeviceManagementFiltered( + // filteredDevices: _filteredDevices, + // selectedIndex: filtered.selectedIndex, + // onlineCount: filtered.onlineCount, + // offlineCount: filtered.offlineCount, + // lowBatteryCount: filtered.lowBatteryCount, + // selectedDevice: filtered.selectedDevice, + // isControlButtonEnabled: filtered.isControlButtonEnabled, + // )); + } + } + + void changeSubspaceName( + String deviceId, String newSubSpaceName, String subspaceId) { + add(UpdateSubSpaceName( + deviceId: deviceId, + newSubSpaceName: newSubSpaceName, + subspaceId: subspaceId, + )); + } + List get selectedDevices => _selectedDevices; } diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart index 5292de0e..e3b3acac 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart @@ -70,3 +70,21 @@ class UpdateSelection extends DeviceManagementEvent { const UpdateSelection(this.selectedRows); } + +class UpdateDeviceName extends DeviceManagementEvent { + final String deviceId; + final String newName; + + const UpdateDeviceName({required this.deviceId, required this.newName}); +} + +class UpdateSubSpaceName extends DeviceManagementEvent { + final String deviceId; + final String newSubSpaceName; + final String subspaceId; + + const UpdateSubSpaceName( + {required this.deviceId, + required this.newSubSpaceName, + required this.subspaceId}); +} diff --git a/lib/pages/device_managment/all_devices/models/device_subspace.model.dart b/lib/pages/device_managment/all_devices/models/device_subspace.model.dart index dc2386de..5d5f44bf 100644 --- a/lib/pages/device_managment/all_devices/models/device_subspace.model.dart +++ b/lib/pages/device_managment/all_devices/models/device_subspace.model.dart @@ -44,4 +44,20 @@ class DeviceSubspace { static List> listToJson(List subspaces) { return subspaces.map((subspace) => subspace.toJson()).toList(); } + + DeviceSubspace copyWith({ + String? uuid, + DateTime? createdAt, + DateTime? updatedAt, + String? subspaceName, + bool? disabled, + }) { + return DeviceSubspace( + uuid: uuid ?? this.uuid, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + subspaceName: subspaceName ?? this.subspaceName, + disabled: disabled ?? this.disabled, + ); + } } 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 e491214d..21fd1193 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -588,4 +588,72 @@ SOS "NCPS": DeviceType.NCPS, "PC": DeviceType.PC, }; + + AllDevicesModel copyWith({ + DevicesModelRoom? room, + DeviceSubspace? subspace, + DevicesModelUnit? unit, + DeviceCommunityModel? community, + String? productUuid, + String? productType, + String? permissionType, + int? activeTime, + String? category, + String? categoryName, + int? createTime, + String? gatewayId, + String? icon, + String? ip, + String? lat, + String? localKey, + String? lon, + String? model, + String? name, + String? nodeId, + bool? online, + String? ownerId, + bool? sub, + String? timeZone, + int? updateTime, + String? uuid, + int? batteryLevel, + String? productName, + List? spaces, + List? deviceTags, + DeviceSubSpace? deviceSubSpace, + }) { + return AllDevicesModel( + room: room ?? this.room, + subspace: subspace ?? this.subspace, + unit: unit ?? this.unit, + community: community ?? this.community, + productUuid: productUuid ?? this.productUuid, + productType: productType ?? this.productType, + permissionType: permissionType ?? this.permissionType, + activeTime: activeTime ?? this.activeTime, + category: category ?? this.category, + categoryName: categoryName ?? this.categoryName, + createTime: createTime ?? this.createTime, + gatewayId: gatewayId ?? this.gatewayId, + icon: icon ?? this.icon, + ip: ip ?? this.ip, + lat: lat ?? this.lat, + localKey: localKey ?? this.localKey, + lon: lon ?? this.lon, + model: model ?? this.model, + name: name ?? this.name, + nodeId: nodeId ?? this.nodeId, + online: online ?? this.online, + ownerId: ownerId ?? this.ownerId, + sub: sub ?? this.sub, + timeZone: timeZone ?? this.timeZone, + updateTime: updateTime ?? this.updateTime, + uuid: uuid ?? this.uuid, + batteryLevel: batteryLevel ?? this.batteryLevel, + productName: productName ?? this.productName, + spaces: spaces ?? this.spaces, + deviceTags: deviceTags ?? this.deviceTags, + deviceSubSpace: deviceSubSpace ?? this.deviceSubSpace, + ); + } } 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 f4baad0c..75dda30b 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 @@ -31,7 +31,6 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { int lowBatteryCount = 0; bool isControlButtonEnabled = false; List selectedDevices = []; - if (state is DeviceManagementLoaded) { devicesToShow = state.devices; selectedIndex = state.selectedIndex; @@ -223,7 +222,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { .map((device) => device.uuid!) .toList(), isEmpty: devicesToShow.isEmpty, - onSettingsPressed: (rowIndex) { + onSettingsPressed: (rowIndex) async { final device = devicesToShow[rowIndex]; showDeviceSettingsSidebar(context, device); }, diff --git a/lib/pages/device_managment/device_setting/device_management_content.dart b/lib/pages/device_managment/device_setting/device_management_content.dart index a087e5bb..c2cdb9a3 100644 --- a/lib/pages/device_managment/device_setting/device_management_content.dart +++ b/lib/pages/device_managment/device_setting/device_management_content.dart @@ -87,6 +87,11 @@ class DeviceManagementContent extends StatelessWidget { ), ); }); + + context.read().add(UpdateSubSpaceName( + subspaceId: selectedSubSpace.id!, + deviceId: device.uuid!, + newSubSpaceName: selectedSubSpace.name ?? '')); } }, child: infoRow( diff --git a/lib/pages/device_managment/device_setting/device_settings_panel.dart b/lib/pages/device_managment/device_setting/device_settings_panel.dart index 48458b3b..0436b971 100644 --- a/lib/pages/device_managment/device_setting/device_settings_panel.dart +++ b/lib/pages/device_managment/device_setting/device_settings_panel.dart @@ -1,6 +1,7 @@ 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/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/device_icon_type_helper.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/device_management_content.dart'; @@ -134,8 +135,16 @@ class DeviceSettingsPanel extends StatelessWidget { onFieldSubmitted: (value) { _bloc.add(const ChangeNameEvent( value: false)); + context + .read< + DeviceManagementBloc>() + .add(UpdateDeviceName( + deviceId: device.uuid!, + newName: _bloc + .nameController + .text)); }, - decoration: InputDecoration( + decoration:const InputDecoration( isDense: true, contentPadding: EdgeInsets.zero, border: InputBorder.none, From 814cbf787f1cd3ebeac588b5621d3c189be9cbdb Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Sun, 29 Jun 2025 13:58:57 +0300 Subject: [PATCH 24/37] edit the UI as wanted in ticket (note: in figma is not updated yet to the requested ticket) --- .../view/visitor_password_dialog.dart | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index d1fb172a..f2c85169 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -602,8 +602,9 @@ class VisitorPasswordDialog extends StatelessWidget { } else { return AlertDialog( alignment: Alignment.center, + backgroundColor: Colors.white, content: SizedBox( - height: size.height * 0.25, + height: size.height * 0.13, child: Column( children: [ Column( @@ -617,13 +618,16 @@ class VisitorPasswordDialog extends StatelessWidget { width: 35, ), ), + const SizedBox( + height: 20, + ), Text( 'Set Password', style: Theme.of(context) .textTheme .headlineLarge! .copyWith( - fontSize: 30, + fontSize: 24, fontWeight: FontWeight.w400, color: Colors.black, ), @@ -631,15 +635,6 @@ class VisitorPasswordDialog extends StatelessWidget { ], ), const SizedBox(width: 15), - Text( - 'This action will update all of the selected\n door locks passwords in the property.\n\nAre you sure you want to continue?', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.w400, - fontSize: 18, - ), - ), ], ), ), @@ -668,6 +663,7 @@ class VisitorPasswordDialog extends StatelessWidget { decoration: containerDecoration, width: size.width * 0.1, child: DefaultButton( + backgroundColor: Color(0xff023DFE), borderRadius: 8, onPressed: () { Navigator.pop(context); @@ -715,7 +711,7 @@ class VisitorPasswordDialog extends StatelessWidget { } }, child: Text( - 'Ok', + 'Confirm', style: Theme.of(context).textTheme.bodySmall!.copyWith( fontWeight: FontWeight.w400, color: ColorsManager.whiteColors, From e0cfe541dded683cda71eff0b481866f412f4233 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 29 Jun 2025 14:13:25 +0300 Subject: [PATCH 25/37] name changes in table when changed. --- .../device_managment_bloc.dart | 28 +++++++++---------- .../widgets/device_managment_body.dart | 1 + .../device_setting/device_settings_panel.dart | 18 ++++++++---- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index ecb2b207..4c3e5b39 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -348,17 +348,27 @@ class DeviceManagementBloc void _onUpdateDeviceName( UpdateDeviceName event, Emitter emit) { - _devices = _devices.map((device) { + final devices = _devices.map((device) { if (device.uuid == event.deviceId) { return device.copyWith(name: event.newName); } return device; }).toList(); + final filteredDevices = _filteredDevices.map((device) { + if (device.uuid == event.deviceId) { + return device.copyWith(name: event.newName); + } + return device; + }).toList(); + + _devices = devices; + _filteredDevices = filteredDevices; + if (state is DeviceManagementLoaded) { final loaded = state as DeviceManagementLoaded; emit(DeviceManagementLoaded( - devices: _devices, + devices: devices, selectedIndex: loaded.selectedIndex, onlineCount: loaded.onlineCount, offlineCount: loaded.offlineCount, @@ -369,7 +379,7 @@ class DeviceManagementBloc } else if (state is DeviceManagementFiltered) { final filtered = state as DeviceManagementFiltered; emit(DeviceManagementFiltered( - filteredDevices: _filteredDevices, + filteredDevices: filteredDevices, selectedIndex: filtered.selectedIndex, onlineCount: filtered.onlineCount, offlineCount: filtered.offlineCount, @@ -383,10 +393,7 @@ class DeviceManagementBloc void _onUpdateSubSpaceName( UpdateSubSpaceName event, Emitter emit) { _devices = _devices.map((device) { - print('before update: ${device.subspace?.subspaceName}'); if (device.uuid == event.deviceId) { - print('Updating subspace name for device: ${device.uuid}'); - print('New subspace name: ${event.newSubSpaceName}'); final updatedSubspace = device.subspace?.copyWith( subspaceName: event.newSubSpaceName, ); @@ -398,15 +405,6 @@ class DeviceManagementBloc return device; }).toList(); - print('After update:'); - for (final device in _devices) { - if (device.uuid == event.deviceId) { - print( - 'Device: ${device.uuid}, subspace: ${device.subspace?.uuid}, subspaceName: ${device.subspace?.subspaceName}', - ); - } - } - print('Subspace name updated to: $subSpaceName'); if (state is DeviceManagementLoaded) { final loaded = state as DeviceManagementLoaded; emit(DeviceManagementLoaded( 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 75dda30b..8d108671 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 @@ -254,6 +254,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { child: DeviceSettingsPanel( device: device, onClose: () => Navigator.of(context).pop(), + deviceManagementBloc: context.read(), ), ), ), diff --git a/lib/pages/device_managment/device_setting/device_settings_panel.dart b/lib/pages/device_managment/device_setting/device_settings_panel.dart index 0436b971..96e48f11 100644 --- a/lib/pages/device_managment/device_setting/device_settings_panel.dart +++ b/lib/pages/device_managment/device_setting/device_settings_panel.dart @@ -3,12 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/device_icon_type_helper.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/device_management_content.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/remove_device_widget.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart'; -import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -18,7 +18,13 @@ import 'package:syncrow_web/web_layout/default_container.dart'; class DeviceSettingsPanel extends StatelessWidget { final VoidCallback? onClose; final AllDevicesModel device; - const DeviceSettingsPanel({super.key, this.onClose, required this.device}); + final DeviceManagementBloc deviceManagementBloc; + const DeviceSettingsPanel({ + super.key, + this.onClose, + required this.device, + required this.deviceManagementBloc, + }); @override Widget build(BuildContext context) { @@ -72,10 +78,10 @@ class DeviceSettingsPanel extends StatelessWidget { 'Device Settings', style: context.theme.textTheme.titleLarge! .copyWith( - fontWeight: FontWeight.w700, + fontWeight: FontWeight.w700, color: ColorsManager.vividBlue .withOpacity(0.7), - fontSize: 24), + fontSize: 24), ), ], ), @@ -166,7 +172,7 @@ class DeviceSettingsPanel extends StatelessWidget { onTap: () { _bloc.add( const ChangeNameEvent( - value: true)); + value: true)); }, child: SvgPicture.asset( Assets From 57bd4b8527819020fdcf817110b19bb341af6dba Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Sun, 29 Jun 2025 14:45:54 +0300 Subject: [PATCH 26/37] Revert "Sp 1589 fe when user navigates to devices page the devices are already listed although no community is selected also when we select a community the api is being called repeatedly too many times (#305)" This reverts commit 034a5ef90827c56ec51ba0ac0b7d3727b20f5c27, reversing changes made to b97183fb61baef13f4018c6202ede44814f0f8ce. --- .../device_managment_bloc.dart | 11 ++- .../bloc/routine_bloc/routine_bloc.dart | 82 ++++++++++--------- lib/services/devices_mang_api.dart | 13 +-- lib/utils/constants/api_const.dart | 2 +- 4 files changed, 59 insertions(+), 49 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index 4063692e..98b0c195 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -41,14 +41,18 @@ class DeviceManagementBloc _devices.clear(); var spaceBloc = event.context.read(); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + if (spaceBloc.state.selectedCommunities.isEmpty) { - devices = await DevicesManagementApi().fetchDevices(projectUuid); + devices = + await DevicesManagementApi().fetchDevices('', '', projectUuid); } else { for (var community in spaceBloc.state.selectedCommunities) { List spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? []; - devices.addAll(await DevicesManagementApi() - .fetchDevices(projectUuid, spacesId: spacesList)); + for (var space in spacesList) { + devices.addAll(await DevicesManagementApi() + .fetchDevices(community, space, projectUuid)); + } } } @@ -266,7 +270,6 @@ class DeviceManagementBloc return 'All'; } } - void _onSearchDevices( SearchDevices event, Emitter emit) { if ((event.community == null || event.community!.isEmpty) && diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index f38ea994..3fd07834 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -170,45 +170,45 @@ class RoutineBloc extends Bloc { } } - Future _onLoadScenes( - LoadScenes event, Emitter emit) async { - emit(state.copyWith(isLoading: true, errorMessage: null)); - List scenes = []; - try { - BuildContext context = NavigationService.navigatorKey.currentContext!; - var createRoutineBloc = context.read(); - final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - if (createRoutineBloc.selectedSpaceId == '' && - createRoutineBloc.selectedCommunityId == '') { - var spaceBloc = context.read(); - for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = - spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - for (var spaceId in spacesList) { - scenes.addAll( - await SceneApi.getScenes(spaceId, communityId, projectUuid)); - } +Future _onLoadScenes( + LoadScenes event, Emitter emit) async { + emit(state.copyWith(isLoading: true, errorMessage: null)); + List scenes = []; + try { + BuildContext context = NavigationService.navigatorKey.currentContext!; + var createRoutineBloc = context.read(); + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + if (createRoutineBloc.selectedSpaceId == '' && + createRoutineBloc.selectedCommunityId == '') { + var spaceBloc = context.read(); + for (var communityId in spaceBloc.state.selectedCommunities) { + List spacesList = + spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + for (var spaceId in spacesList) { + scenes.addAll( + await SceneApi.getScenes(spaceId, communityId, projectUuid)); } - } else { - scenes.addAll(await SceneApi.getScenes( - createRoutineBloc.selectedSpaceId, - createRoutineBloc.selectedCommunityId, - projectUuid)); } - - emit(state.copyWith( - scenes: scenes, - isLoading: false, - )); - } catch (e) { - emit(state.copyWith( - isLoading: false, - loadScenesErrorMessage: 'Failed to load scenes', - errorMessage: '', - loadAutomationErrorMessage: '', - scenes: scenes)); + } else { + scenes.addAll(await SceneApi.getScenes( + createRoutineBloc.selectedSpaceId, + createRoutineBloc.selectedCommunityId, + projectUuid)); } + + emit(state.copyWith( + scenes: scenes, + isLoading: false, + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + loadScenesErrorMessage: 'Failed to load scenes', + errorMessage: '', + loadAutomationErrorMessage: '', + scenes: scenes)); } +} Future _onLoadAutomation( LoadAutomation event, Emitter emit) async { @@ -936,12 +936,16 @@ class RoutineBloc extends Bloc { for (var communityId in spaceBloc.state.selectedCommunities) { List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - devices.addAll(await DevicesManagementApi() - .fetchDevices(projectUuid, spacesId: spacesList)); + for (var spaceId in spacesList) { + devices.addAll(await DevicesManagementApi() + .fetchDevices(communityId, spaceId, projectUuid)); + } } } else { - devices.addAll(await DevicesManagementApi().fetchDevices(projectUuid, - spacesId: [createRoutineBloc.selectedSpaceId])); + devices.addAll(await DevicesManagementApi().fetchDevices( + createRoutineBloc.selectedCommunityId, + createRoutineBloc.selectedSpaceId, + projectUuid)); } emit(state.copyWith(isLoading: false, devices: devices)); diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index dd54cfef..709d6855 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -13,13 +13,15 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class DevicesManagementApi { Future> fetchDevices( - String projectId, { - List? spacesId, - }) async { + String communityId, String spaceId, String projectId) async { try { final response = await HTTPService().get( - queryParameters: {if (spacesId != null) 'spaces': spacesId}, - path: ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId), + path: communityId.isNotEmpty && spaceId.isNotEmpty + ? ApiEndpoints.getSpaceDevices + .replaceAll('{spaceUuid}', spaceId) + .replaceAll('{communityUuid}', communityId) + .replaceAll('{projectId}', projectId) + : ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId), showServerMessage: true, expectedResponseModel: (json) { List jsonData = json['data']; @@ -414,4 +416,5 @@ class DevicesManagementApi { ); return response; } + } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 6dda1108..eb7b6a3e 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -18,7 +18,7 @@ abstract class ApiEndpoints { static const String getAllDevices = '/projects/{projectId}/devices'; static const String getSpaceDevices = - '/projects/{projectId}/devices'; + '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices'; static const String getDeviceStatus = '/devices/{uuid}/functions/status'; static const String getBatchStatus = '/devices/batch'; From 8916efcebb43ee235ddbda857035692960d2eb4e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 29 Jun 2025 15:39:30 +0300 Subject: [PATCH 27/37] fixed aqi filter bugs. --- .../air_quality_distribution_bloc.dart | 6 +++--- .../blocs/range_of_aqi/range_of_aqi_bloc.dart | 2 +- .../widgets/aqi_distribution_chart_title.dart | 1 + .../widgets/aqi_type_dropdown.dart | 20 +++++++++---------- .../widgets/range_of_aqi_chart_title.dart | 6 +++--- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart index 40d51d2b..455dff23 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart @@ -46,11 +46,11 @@ class AirQualityDistributionBloc } } - Future _onClearAirQualityDistribution( + void _onClearAirQualityDistribution( ClearAirQualityDistribution event, Emitter emit, - ) async { - emit(const AirQualityDistributionState()); + ) { + emit(AirQualityDistributionState(selectedAqiType: state.selectedAqiType)); } void _onUpdateAqiTypeEvent( diff --git a/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart b/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart index 88c3715e..326a87a2 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart @@ -75,6 +75,6 @@ class RangeOfAqiBloc extends Bloc { ClearRangeOfAqiEvent event, Emitter emit, ) { - emit(const RangeOfAqiState()); + emit(RangeOfAqiState(selectedAqiType: state.selectedAqiType)); } } diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart index f7be6ee3..7b6b113a 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart @@ -34,6 +34,7 @@ class AqiDistributionChartTitle extends StatelessWidget { alignment: AlignmentDirectional.centerEnd, fit: BoxFit.scaleDown, child: AqiTypeDropdown( + selectedAqiType: context.watch().state.selectedAqiType, onChanged: (value) { if (value != null) { final bloc = context.read(); diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart index 6640c717..8233fe5a 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart @@ -18,19 +18,20 @@ enum AqiType { } class AqiTypeDropdown extends StatefulWidget { - const AqiTypeDropdown({super.key, required this.onChanged}); + const AqiTypeDropdown({ + required this.onChanged, + this.selectedAqiType, + super.key, + }); final ValueChanged onChanged; + final AqiType? selectedAqiType; @override State createState() => _AqiTypeDropdownState(); } class _AqiTypeDropdownState extends State { - AqiType? _selectedItem = AqiType.aqi; - - void _updateSelectedItem(AqiType? item) => setState(() => _selectedItem = item); - @override Widget build(BuildContext context) { return Container( @@ -41,8 +42,8 @@ class _AqiTypeDropdownState extends State { width: 1, ), ), - child: DropdownButton( - value: _selectedItem, + child: DropdownButton( + value: widget.selectedAqiType, isDense: true, borderRadius: BorderRadius.circular(16), dropdownColor: ColorsManager.whiteColors, @@ -59,10 +60,7 @@ class _AqiTypeDropdownState extends State { items: AqiType.values .map((e) => DropdownMenuItem(value: e, child: Text(e.value))) .toList(), - onChanged: (value) { - _updateSelectedItem(value); - widget.onChanged(value); - }, + onChanged: widget.onChanged, ), ); } diff --git a/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart b/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart index 1b0da288..421fbb13 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart @@ -63,15 +63,15 @@ class RangeOfAqiChartTitle extends StatelessWidget { fit: BoxFit.scaleDown, alignment: AlignmentDirectional.centerEnd, child: AqiTypeDropdown( + selectedAqiType: context.watch().state.selectedAqiType, onChanged: (value) { final spaceTreeState = context.read().state; final spaceUuid = spaceTreeState.selectedSpaces.firstOrNull; - - if (spaceUuid == null) return; - if (value != null) { context.read().add(UpdateAqiTypeEvent(value)); } + + if (spaceUuid == null) return; }, ), ), From 354d61dfa24dbf62d627ee92c8e9444ee0aeca8d Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Sun, 29 Jun 2025 15:50:37 +0300 Subject: [PATCH 28/37] UI Enhancement --- .../widgets/accurate_calibrating_dialog.dart | 4 +- .../widgets/accurate_calibration_dialog.dart | 4 +- .../widgets/accurate_dialog_widget.dart | 131 ++++++++++-------- .../widgets/calibrate_completed_dialog.dart | 96 +++++++------ .../widgets/normal_text_body_for_dialog.dart | 86 +++++++++--- .../widgets/number_input_textfield.dart | 2 +- .../widgets/pref_revers_card_widget.dart | 2 +- .../widgets/prefrences_dialog.dart | 6 +- .../widgets/quick_calibrating_dialog.dart | 102 ++++++++------ .../widgets/quick_calibration_dialog.dart | 4 +- lib/utils/color_manager.dart | 4 +- lib/utils/constants/assets.dart | 4 +- 12 files changed, 266 insertions(+), 179 deletions(-) diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart index 54107420..64044b94 100644 --- a/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart @@ -23,8 +23,8 @@ class AccurteCalibratingDialog extends StatelessWidget { body: const NormalTextBodyForDialog( title: '', step1: - '1. Click Close Button to make the Curtain run to Full Close and Position.', - step2: '2. click Next to complete the Calibration.', + 'Click Close Button to make the Curtain run to Full Close and Position.', + step2: 'click Next to complete the Calibration.', ), leftOnTap: () => Navigator.of(parentContext).pop(), rightOnTap: () { diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart index a9d1b010..997e70cf 100644 --- a/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart @@ -20,8 +20,8 @@ class AccurateCalibrationDialog extends StatelessWidget { title: 'Accurate Calibration', body: const NormalTextBodyForDialog( title: 'Prepare Calibration:', - step1: '1. Run The Curtain to the Fully Open Position,and pause.', - step2: '2. click Next to Start accurate calibration.', + step1: 'Run The Curtain to the Fully Open Position,and pause.', + step2: 'click Next to Start accurate calibration.', ), leftOnTap: () => Navigator.of(parentContext).pop(), rightOnTap: () { diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart index 5be376ae..433608ac 100644 --- a/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart @@ -17,78 +17,87 @@ class AccurateDialogWidget extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox( - height: 300, - width: 400, + height: 250, + width: 500, child: Column( children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Text( - title, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorsManager.blueColor, - ), + Expanded( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Text( + title, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorsManager.dialogBlueTitle, + ), + ), + ), + const Divider( + indent: 60, + endIndent: 60, + ), + ], ), ), - const SizedBox(height: 5), - const Divider( - indent: 10, - endIndent: 10, - ), - Padding( - padding: const EdgeInsets.all(10), + Expanded( child: body, ), - const SizedBox(height: 20), - const Spacer(), - const Divider(), - Row( - children: [ - Expanded( - child: InkWell( - onTap: leftOnTap, - child: Container( - height: 60, - alignment: Alignment.center, - decoration: const BoxDecoration( - border: Border( - right: BorderSide( - color: ColorsManager.grayBorder, + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const Divider(), + Row( + children: [ + Expanded( + child: InkWell( + onTap: leftOnTap, + child: Container( + height: 40, + alignment: Alignment.center, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayBorder, + ), + ), + ), + child: const Text( + 'Cancel', + style: TextStyle(color: ColorsManager.grayBorder), + ), ), ), ), - child: const Text( - 'Cancel', - style: TextStyle(color: ColorsManager.grayBorder), - ), - ), - ), - ), - Expanded( - child: InkWell( - onTap: rightOnTap, - child: Container( - height: 60, - alignment: Alignment.center, - decoration: const BoxDecoration( - border: Border( - right: BorderSide( - color: ColorsManager.grayBorder, + Expanded( + child: InkWell( + onTap: rightOnTap, + child: Container( + height: 40, + alignment: Alignment.center, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayBorder, + ), + ), + ), + child: const Text( + 'Next', + style: TextStyle( + color: ColorsManager.blueColor, + ), + ), ), ), - ), - child: const Text( - 'Next', - style: TextStyle( - color: ColorsManager.blueColor, - ), - ), - ), - ), - ) - ], + ) + ], + ) + ], + ), ) ], ), diff --git a/lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart index 9b2b5ea9..bd0cbb37 100644 --- a/lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class CalibrateCompletedDialog extends StatelessWidget { final BuildContext parentContext; @@ -21,52 +23,62 @@ class CalibrateCompletedDialog extends StatelessWidget { width: 400, child: Column( children: [ - const Padding( - padding: EdgeInsets.all(10), - child: Text( - 'Calibration Completed', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: ColorsManager.blueColor, - ), + Expanded( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Text( + 'Calibration Completed', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorsManager.dialogBlueTitle, + ), + ), + ), + const SizedBox(height: 5), + const Divider( + indent: 10, + endIndent: 10, + ), + ], ), ), - const SizedBox(height: 5), - const Divider( - indent: 10, - endIndent: 10, + Expanded( + child: SvgPicture.asset(Assets.completedDoneIcon), ), - const Icon( - Icons.check_circle, - size: 100, - color: ColorsManager.blueColor, - ), - const Spacer(), - const Divider( - indent: 10, - endIndent: 10, - ), - InkWell( - onTap: () { - parentContext.read().add( - FetchCurtainModuleStatusEvent( - deviceId: deviceId, - ), - ); - Navigator.of(parentContext).pop(); - Navigator.of(parentContext).pop(); - }, - child: Container( - height: 40, - width: double.infinity, - alignment: Alignment.center, - child: const Text( - 'Close', - style: TextStyle( - color: ColorsManager.grayBorder, + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const Divider( + indent: 10, + endIndent: 10, ), - ), + InkWell( + onTap: () { + parentContext.read().add( + FetchCurtainModuleStatusEvent( + deviceId: deviceId, + ), + ); + Navigator.of(parentContext).pop(); + Navigator.of(parentContext).pop(); + }, + child: Container( + height: 40, + width: double.infinity, + alignment: Alignment.center, + child: const Text( + 'Close', + style: TextStyle( + color: ColorsManager.grayBorder, + ), + ), + ), + ) + ], ), ) ], diff --git a/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart index 8818cb7b..c322fe9d 100644 --- a/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart @@ -15,28 +15,72 @@ class NormalTextBodyForDialog extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: const TextStyle( - color: ColorsManager.grayColor, + return Padding( + padding: EdgeInsetsGeometry.only(left: 15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (title.isEmpty) + const SizedBox() + else + Expanded( + child: Text( + title, + style: const TextStyle( + color: ColorsManager.grayColor, + fontSize: 17, + ), + ), + ), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + width: 10, + ), + const Text('1. ', + style: TextStyle( + color: ColorsManager.grayColor, + fontSize: 17, + )), + SizedBox( + width: 450, + child: Text( + step1, + style: const TextStyle( + color: ColorsManager.grayColor, + fontSize: 17, + ), + ), + ), + ], + ), ), - ), - Text( - step1, - style: const TextStyle( - color: ColorsManager.grayColor, - ), - ), - Text( - step2, - style: const TextStyle( - color: ColorsManager.grayColor, - ), - ) - ], + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + width: 10, + ), + const Text('2. ', + style: TextStyle( + color: ColorsManager.grayColor, + fontSize: 17, + )), + Text( + step2, + style: const TextStyle( + color: ColorsManager.grayColor, + fontSize: 17, + ), + ), + ], + ), + ) + ], + ), ); } } diff --git a/lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart b/lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart index ea95f838..428f6531 100644 --- a/lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart +++ b/lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart @@ -19,7 +19,7 @@ class NumberInputField extends StatelessWidget { contentPadding: EdgeInsets.zero, ), style: const TextStyle( - fontSize: 20, + fontSize: 15, color: ColorsManager.blackColor, ), ); diff --git a/lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart b/lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart index 81912e80..85c45d27 100644 --- a/lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart +++ b/lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart @@ -18,7 +18,7 @@ class PrefReversCardWidget extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultContainer( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(18), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart index 1e4f932c..35844c05 100644 --- a/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart @@ -23,12 +23,12 @@ class CurtainModulePrefrencesDialog extends StatelessWidget { Widget build(_) { return AlertDialog( backgroundColor: ColorsManager.CircleImageBackground, - contentPadding: const EdgeInsets.all(30), - title: const Center( + contentPadding: const EdgeInsets.all(20), + title: Center( child: Text( 'Preferences', style: TextStyle( - color: ColorsManager.blueColor, + color: ColorsManager.dialogBlueTitle, fontSize: 24, fontWeight: FontWeight.bold, ), diff --git a/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart index 0b86c96e..8514d432 100644 --- a/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart @@ -69,49 +69,71 @@ class _QuickCalibratingDialogState extends State { body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - '1. please Enter the Travel Time:', - style: TextStyle(color: ColorsManager.grayBorder), - ), - const SizedBox(height: 10), - Container( - width: 150, - height: 40, - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: NumberInputField(controller: _controller), - ), - const Expanded( - child: Text( - 'seconds', - style: TextStyle( - fontSize: 15, - color: ColorsManager.blueColor, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - if (_errorText != null) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - _errorText!, - style: const TextStyle( - color: ColorsManager.red, - fontSize: 14, + const Expanded( + child: Align( + alignment: Alignment.center, + child: Padding( + padding: EdgeInsets.only(right: 75), + child: Text( + '1.please Enter the Travel Time:', + style: TextStyle(color: ColorsManager.lightGrayColor), ), ), ), + ), + Expanded( + child: Align( + alignment: Alignment.center, + child: Container( + width: 110, + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: NumberInputField(controller: _controller), + ), + Expanded( + child: Text( + 'seconds', + style: TextStyle( + fontSize: 12, + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ), + ), + if (_errorText != null) + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + _errorText!, + style: const TextStyle( + color: ColorsManager.red, + fontSize: 14, + ), + ), + ), + ), + const Expanded( + child: Align( + alignment: Alignment.center, + child: Text( + '2.click Next to Complete the calibration', + style: TextStyle(color: ColorsManager.lightGrayColor), + ), + ), + ) ], ), leftOnTap: () => Navigator.of(widget.parentContext).pop(), diff --git a/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart index 803d904f..6c776293 100644 --- a/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart @@ -23,8 +23,8 @@ class QuickCalibrationDialog extends StatelessWidget { body: const NormalTextBodyForDialog( title: 'Prepare Calibration:', step1: - '1. Confirm that the curtain is in the fully closed and suspended state.', - step2: '2. click Next to Start calibration.', + 'Confirm that the curtain is in the fully closed and suspended state.', + step2: 'click Next to Start calibration.', ), leftOnTap: () => Navigator.of(parentContext).pop(), rightOnTap: () { diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 50170ed9..40fca1fa 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -83,7 +83,5 @@ abstract class ColorsManager { static const Color maxPurpleDot = Color(0xFF5F00BD); static const Color minBlue = Color(0xFF93AAFD); static const Color minBlueDot = Color(0xFF023DFE); - static const Color grey25 = Color(0xFFF9F9F9); - - + static const Color grey25 = Color(0xFFF9F9F9); } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 8979c446..821df6e3 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -394,6 +394,7 @@ class Assets { static const String emptyBox = 'assets/icons/empty_box.png'; static const String completeProcessIcon = 'assets/icons/compleate_process_icon.svg'; + static const String completedDoneIcon = 'assets/images/completed_done.svg'; static const String currentProcessIcon = 'assets/icons/current_process_icon.svg'; static const String uncomplete_ProcessIcon = @@ -505,5 +506,6 @@ class Assets { static const String aqiAirQuality = 'assets/icons/aqi_air_quality.svg'; static const String temperatureAqiSidebar = 'assets/icons/thermometer.svg'; static const String humidityAqiSidebar = 'assets/icons/humidity.svg'; - static const String autocadOccupancyImage = 'assets/images/autocad_occupancy_image.png'; + static const String autocadOccupancyImage = + 'assets/images/autocad_occupancy_image.png'; } From d8bb234537434b261ce5565b3f5b3f2811aeec5a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 29 Jun 2025 16:00:15 +0300 Subject: [PATCH 29/37] SP-1771 --- .../device_managment_bloc.dart | 157 +++++++++++------- .../widgets/device_managment_body.dart | 5 +- .../device_management_content.dart | 5 +- .../device_setting/device_settings_panel.dart | 10 +- 4 files changed, 108 insertions(+), 69 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index b7f04a58..da039a8e 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -16,7 +16,7 @@ class DeviceManagementBloc int _onlineCount = 0; int _offlineCount = 0; int _lowBatteryCount = 0; - List _selectedDevices = []; + final List _selectedDevices = []; List _filteredDevices = []; String currentProductName = ''; String? currentCommunity; @@ -40,15 +40,15 @@ class DeviceManagementBloc FetchDevices event, Emitter emit) async { emit(DeviceManagementLoading()); try { - List devices = []; + var devices = []; _devices.clear(); - var spaceBloc = event.context.read(); + final spaceBloc = event.context.read(); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; if (spaceBloc.state.selectedCommunities.isEmpty) { devices = await DevicesManagementApi().fetchDevices(projectUuid); } else { - for (var community in spaceBloc.state.selectedCommunities) { - List spacesList = + for (final community in spaceBloc.state.selectedCommunities) { + final spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? []; devices.addAll(await DevicesManagementApi() .fetchDevices(projectUuid, spacesId: spacesList)); @@ -73,7 +73,7 @@ class DeviceManagementBloc } } - void _onFilterDevices( + Future _onFilterDevices( FilterDevices event, Emitter emit) async { if (_devices.isNotEmpty) { _filteredDevices = List.from(_devices.where((device) { @@ -155,8 +155,7 @@ class DeviceManagementBloc 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)) { @@ -165,9 +164,9 @@ class DeviceManagementBloc _selectedDevices.add(event.selectedDevice); } - List clonedSelectedDevices = List.from(_selectedDevices); + final clonedSelectedDevices = List.from(_selectedDevices); - bool isControlButtonEnabled = + final isControlButtonEnabled = _checkIfControlButtonEnabled(clonedSelectedDevices); if (state is DeviceManagementLoaded) { @@ -197,8 +196,8 @@ class DeviceManagementBloc void _onUpdateSelection( UpdateSelection event, Emitter emit) { - List selectedDevices = []; - List devicesToSelectFrom = []; + final selectedDevices = []; + var devicesToSelectFrom = []; if (state is DeviceManagementLoaded) { devicesToSelectFrom = (state as DeviceManagementLoaded).devices; @@ -206,7 +205,7 @@ class DeviceManagementBloc devicesToSelectFrom = (state as DeviceManagementFiltered).filteredDevices; } - for (int i = 0; i < event.selectedRows.length; i++) { + for (var i = 0; i < event.selectedRows.length; i++) { if (event.selectedRows[i]) { selectedDevices.add(devicesToSelectFrom[i]); } @@ -252,8 +251,7 @@ class DeviceManagementBloc _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) + .where((device) => device.batteryLevel != null && device.batteryLevel! < 20) .length; } @@ -270,8 +268,7 @@ class DeviceManagementBloc } } - void _onSearchDevices( - SearchDevices event, Emitter emit) { + void _onSearchDevices(SearchDevices event, Emitter emit) { if ((event.community == null || event.community!.isEmpty) && (event.unitName == null || event.unitName!.isEmpty) && (event.deviceNameOrProductName == null || @@ -300,7 +297,7 @@ class DeviceManagementBloc currentCommunity = event.community; currentUnitName = event.unitName; - List devicesToSearch = _devices; + final devicesToSearch = _devices; if (devicesToSearch.isNotEmpty) { final searchText = event.deviceNameOrProductName?.toLowerCase() ?? ''; @@ -347,14 +344,84 @@ class DeviceManagementBloc UpdateDeviceName event, Emitter emit) { final devices = _devices.map((device) { if (device.uuid == event.deviceId) { - return device.copyWith(name: event.newName); + final modifiedDevice = device.copyWith(name: event.newName); + _selectedDevices.removeWhere((device) => device.uuid == event.deviceId); + _selectedDevices.add(modifiedDevice); + return modifiedDevice; } return device; }).toList(); final filteredDevices = _filteredDevices.map((device) { if (device.uuid == event.deviceId) { - return device.copyWith(name: event.newName); + final modifiedDevice = device.copyWith(name: event.newName); + _selectedDevices.removeWhere((device) => device.uuid == event.deviceId); + _selectedDevices.add(modifiedDevice); + return modifiedDevice; + } + return device; + }).toList(); + + _devices = devices; + _filteredDevices = filteredDevices; + + + + if (state is DeviceManagementLoaded) { + final loaded = state as DeviceManagementLoaded; + final selectedDevices01 = _selectedDevices.map((device) { + if (device.uuid == event.deviceId) { + final modifiedDevice = device.copyWith(name: event.newName); + return modifiedDevice; + } + return device; + }).toList(); + emit(DeviceManagementLoaded( + devices: devices, + selectedIndex: loaded.selectedIndex, + onlineCount: loaded.onlineCount, + offlineCount: loaded.offlineCount, + lowBatteryCount: loaded.lowBatteryCount, + selectedDevice: selectedDevices01, + isControlButtonEnabled: loaded.isControlButtonEnabled, + )); + } else if (state is DeviceManagementFiltered) { + final filtered = state as DeviceManagementFiltered; + final selectedDevices01 = filtered.selectedDevice?.map((device) { + if (device.uuid == event.deviceId) { + final modifiedDevice = device.copyWith(name: event.newName); + return modifiedDevice; + } + return device; + }).toList(); + emit(DeviceManagementFiltered( + filteredDevices: filteredDevices, + selectedIndex: filtered.selectedIndex, + onlineCount: filtered.onlineCount, + offlineCount: filtered.offlineCount, + lowBatteryCount: filtered.lowBatteryCount, + selectedDevice: selectedDevices01, + isControlButtonEnabled: filtered.isControlButtonEnabled, + )); + } + } + + void _onUpdateSubSpaceName( + UpdateSubSpaceName event, Emitter emit) { + final devices = _devices.map((device) { + if (device.uuid == event.deviceId) { + return device.copyWith( + subspace: + device.subspace?.copyWith(subspaceName: event.newSubSpaceName)); + } + return device; + }).toList(); + + final filteredDevices = _filteredDevices.map((device) { + if (device.uuid == event.deviceId) { + return device.copyWith( + subspace: + device.subspace?.copyWith(subspaceName: event.newSubSpaceName)); } return device; }).toList(); @@ -364,53 +431,21 @@ class DeviceManagementBloc if (state is DeviceManagementLoaded) { final loaded = state as DeviceManagementLoaded; - emit(DeviceManagementLoaded( - devices: devices, - selectedIndex: loaded.selectedIndex, - onlineCount: loaded.onlineCount, - offlineCount: loaded.offlineCount, - lowBatteryCount: loaded.lowBatteryCount, - selectedDevice: loaded.selectedDevice, - isControlButtonEnabled: loaded.isControlButtonEnabled, - )); - } else if (state is DeviceManagementFiltered) { - final filtered = state as DeviceManagementFiltered; - emit(DeviceManagementFiltered( - filteredDevices: filteredDevices, - selectedIndex: filtered.selectedIndex, - onlineCount: filtered.onlineCount, - offlineCount: filtered.offlineCount, - lowBatteryCount: filtered.lowBatteryCount, - selectedDevice: filtered.selectedDevice, - isControlButtonEnabled: filtered.isControlButtonEnabled, - )); - } - } - - void _onUpdateSubSpaceName( - UpdateSubSpaceName event, Emitter emit) { - _devices = _devices.map((device) { - if (device.uuid == event.deviceId) { - final updatedSubspace = device.subspace?.copyWith( - subspaceName: event.newSubSpaceName, - ); - final s = device.copyWith(subspace: updatedSubspace); - - return s; - } - subSpaceName = device.subspace!.subspaceName; - - return device; - }).toList(); - if (state is DeviceManagementLoaded) { - final loaded = state as DeviceManagementLoaded; + final selectedDevices = loaded.selectedDevice?.map((device) { + if (device.uuid == event.deviceId) { + return device.copyWith( + subspace: + device.subspace?.copyWith(subspaceName: event.newSubSpaceName)); + } + return device; + }).toList(); emit(DeviceManagementLoaded( devices: _devices, selectedIndex: loaded.selectedIndex, onlineCount: loaded.onlineCount, offlineCount: loaded.offlineCount, lowBatteryCount: loaded.lowBatteryCount, - selectedDevice: loaded.selectedDevice, + selectedDevice: selectedDevices, isControlButtonEnabled: loaded.isControlButtonEnabled, )); } else if (state is DeviceManagementFiltered) { 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 b4eb60e6..7ca33d1d 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 @@ -23,6 +23,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { @override Widget build(BuildContext context) { return BlocBuilder( + buildWhen: (previous, current) => previous != current, builder: (context, state) { List devicesToShow = []; int selectedIndex = 0; @@ -191,7 +192,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { 'Product Name', 'Device ID', 'Space Name', - 'location', + 'Location', 'Battery Level', 'Installation Date and Time', 'Status', @@ -265,7 +266,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { barrierDismissible: true, barrierLabel: "Device Settings", transitionDuration: const Duration(milliseconds: 300), - pageBuilder: (context, anim1, anim2) { + pageBuilder: (_, anim1, anim2) { return Align( alignment: Alignment.centerRight, child: Material( diff --git a/lib/pages/device_managment/device_setting/device_management_content.dart b/lib/pages/device_managment/device_setting/device_management_content.dart index c2cdb9a3..c73899fc 100644 --- a/lib/pages/device_managment/device_setting/device_management_content.dart +++ b/lib/pages/device_managment/device_setting/device_management_content.dart @@ -19,11 +19,14 @@ class DeviceManagementContent extends StatelessWidget { required this.device, required this.subSpaces, required this.deviceInfo, + required this.deviceManagementBloc, }); final AllDevicesModel device; final List subSpaces; final DeviceInfoModel deviceInfo; + final DeviceManagementBloc deviceManagementBloc; + @override Widget build(BuildContext context) { @@ -88,7 +91,7 @@ class DeviceManagementContent extends StatelessWidget { ); }); - context.read().add(UpdateSubSpaceName( + deviceManagementBloc.add(UpdateSubSpaceName( subspaceId: selectedSubSpace.id!, deviceId: device.uuid!, newSubSpaceName: selectedSubSpace.name ?? '')); diff --git a/lib/pages/device_managment/device_setting/device_settings_panel.dart b/lib/pages/device_managment/device_setting/device_settings_panel.dart index 96e48f11..0856b5d0 100644 --- a/lib/pages/device_managment/device_setting/device_settings_panel.dart +++ b/lib/pages/device_managment/device_setting/device_settings_panel.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; @@ -141,14 +142,12 @@ class DeviceSettingsPanel extends StatelessWidget { onFieldSubmitted: (value) { _bloc.add(const ChangeNameEvent( value: false)); - context - .read< - DeviceManagementBloc>() - .add(UpdateDeviceName( + deviceManagementBloc + ..add(UpdateDeviceName( deviceId: device.uuid!, newName: _bloc .nameController - .text)); + .text))..add(ResetSelectedDevices()); }, decoration:const InputDecoration( isDense: true, @@ -205,6 +204,7 @@ class DeviceSettingsPanel extends StatelessWidget { device: device, subSpaces: subSpaces.cast(), deviceInfo: deviceInfo, + deviceManagementBloc: deviceManagementBloc, ), const SizedBox(height: 32), RemoveDeviceWidget(bloc: _bloc), From e87dffd76bd3e9c98bd473b40a910ecfdf49064b Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Mon, 30 Jun 2025 08:28:19 +0300 Subject: [PATCH 30/37] when it is CUR module there is no countdown and other selector --- .../schedule_device/schedule_widgets/schedual_view.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart index c511b8bd..52a5c56f 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart @@ -52,9 +52,12 @@ class BuildScheduleView extends StatelessWidget { children: [ const ScheduleHeader(), const SizedBox(height: 20), - ScheduleModeSelector( - currentMode: state.scheduleMode, - ), + if (category == 'CUR_2') + const SizedBox() + else + ScheduleModeSelector( + currentMode: state.scheduleMode, + ), const SizedBox(height: 20), if (state.scheduleMode == ScheduleModes.schedule) ScheduleManagementUI( From d4625a8f0484b7dafc9605793785d24bbe42f811 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Mon, 30 Jun 2025 08:45:18 +0300 Subject: [PATCH 31/37] fix edit to accept string of cur module --- .../schedule_widgets/schedule_table.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart index 21f404ff..213afd61 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart @@ -212,12 +212,21 @@ class _ScheduleTableView extends StatelessWidget { isEdit: true, ).then((updatedSchedule) { if (updatedSchedule != null) { + bool temp; + if (schedule.category == 'CUR_2') { + updatedSchedule.function.value == 'open' + ? temp = true + : temp = false; + } else { + temp = updatedSchedule.function.value; + } context.read().add( ScheduleEditEvent( scheduleId: schedule.scheduleId, category: schedule.category, time: updatedSchedule.time, - functionOn: updatedSchedule.function.value, + functionOn: temp, + // updatedSchedule.function.value, selectedDays: updatedSchedule.days), ); } From 0cfd58d82083c1ef3c017bd11260228f71109a53 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Mon, 30 Jun 2025 08:56:42 +0300 Subject: [PATCH 32/37] fix to send fit data to integrate with API (was true and false)now cur module send close and open with control key --- .../all_devices/models/device_status.dart | 4 ++-- .../schedule_device/bloc/schedule_bloc.dart | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/device_status.dart b/lib/pages/device_managment/all_devices/models/device_status.dart index b78f2a30..b3d582f1 100644 --- a/lib/pages/device_managment/all_devices/models/device_status.dart +++ b/lib/pages/device_managment/all_devices/models/device_status.dart @@ -35,8 +35,8 @@ class DeviceStatus { } class Status { - final String code; - final dynamic value; + String code; + dynamic value; Status({ required this.code, diff --git a/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart b/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart index 0ec55e39..f84f95e7 100644 --- a/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart +++ b/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart @@ -286,11 +286,20 @@ class ScheduleBloc extends Bloc { try { if (state is ScheduleLoaded) { final dateTime = DateTime.parse(event.time); + Status status = Status(code: '', value: ''); + if (event.category == 'CUR_2') { + status.code = 'control'; + status.value = event.functionOn == true ? 'open' : 'close'; + } else { + status.code = event.category; + status.value = event.functionOn; + } final updatedSchedule = ScheduleEntry( scheduleId: event.scheduleId, category: event.category, time: getTimeStampWithoutSeconds(dateTime).toString(), - function: Status(code: event.category, value: event.functionOn), + function: status, + // Status(code: event.category, value: event.functionOn), days: event.selectedDays, ); final success = await DevicesManagementApi().editScheduleRecord( From 32938404dd65d20e8d3f8e3ce2b7d9baf020a889 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Mon, 30 Jun 2025 09:28:09 +0300 Subject: [PATCH 33/37] PR requested changes --- .../all_devices/models/device_status.dart | 14 ++++++++++++-- .../schedule_device/bloc/schedule_bloc.dart | 10 +++++----- .../schedule_widgets/schedule_table.dart | 1 - 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/device_status.dart b/lib/pages/device_managment/all_devices/models/device_status.dart index b3d582f1..f4efe36b 100644 --- a/lib/pages/device_managment/all_devices/models/device_status.dart +++ b/lib/pages/device_managment/all_devices/models/device_status.dart @@ -35,8 +35,8 @@ class DeviceStatus { } class Status { - String code; - dynamic value; + final String code; + final dynamic value; Status({ required this.code, @@ -57,6 +57,16 @@ class Status { }; } + Status copyWith({ + String? code, + dynamic value, + }) { + return Status( + code: code ?? this.code, + value: value ?? this.value, + ); + } + factory Status.fromJson(String source) => Status.fromMap(json.decode(source)); String toJson() => json.encode(toMap()); diff --git a/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart b/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart index f84f95e7..0db1445f 100644 --- a/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart +++ b/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart @@ -288,18 +288,18 @@ class ScheduleBloc extends Bloc { final dateTime = DateTime.parse(event.time); Status status = Status(code: '', value: ''); if (event.category == 'CUR_2') { - status.code = 'control'; - status.value = event.functionOn == true ? 'open' : 'close'; + status = status.copyWith( + code: 'control', + value: event.functionOn == true ? 'open' : 'close'); } else { - status.code = event.category; - status.value = event.functionOn; + status = + status.copyWith(code: event.category, value: event.functionOn); } final updatedSchedule = ScheduleEntry( scheduleId: event.scheduleId, category: event.category, time: getTimeStampWithoutSeconds(dateTime).toString(), function: status, - // Status(code: event.category, value: event.functionOn), days: event.selectedDays, ); final success = await DevicesManagementApi().editScheduleRecord( diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart index 213afd61..84d8e1f5 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart @@ -226,7 +226,6 @@ class _ScheduleTableView extends StatelessWidget { category: schedule.category, time: updatedSchedule.time, functionOn: temp, - // updatedSchedule.function.value, selectedDays: updatedSchedule.days), ); } From 4d51321675605f50691287e198145d0f379ddd29 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Mon, 30 Jun 2025 10:05:15 +0300 Subject: [PATCH 34/37] add the new devices to mapIconToProduct func --- .../spaces_management/all_spaces/model/product_model.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages/spaces_management/all_spaces/model/product_model.dart b/lib/pages/spaces_management/all_spaces/model/product_model.dart index 8f905032..a7b23d0f 100644 --- a/lib/pages/spaces_management/all_spaces/model/product_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/product_model.dart @@ -58,11 +58,14 @@ class ProductModel { '3G': Assets.Gang3SwitchIcon, '3GT': Assets.threeTouchSwitch, 'CUR': Assets.curtain, + 'CUR_2': Assets.curtain, 'GD': Assets.garageDoor, 'GW': Assets.SmartGatewayIcon, 'DL': Assets.DoorLockIcon, 'WL': Assets.waterLeakSensor, 'WH': Assets.waterHeater, + 'WM': Assets.waterLeakSensor, + 'SOS': Assets.sos, 'AC': Assets.ac, 'CPS': Assets.presenceSensor, 'PC': Assets.powerClamp, From b90f25f7b03ff0e51039195462a85537e43c47ac Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Mon, 30 Jun 2025 10:39:23 +0300 Subject: [PATCH 35/37] fix all UI notes make white dialogs with fix calibrating textfield for seconds && fix icon of completed action dialog --- assets/images/completed_done.svg | 4 ++++ .../widgets/accurate_calibrating_dialog.dart | 2 ++ .../widgets/accurate_calibration_dialog.dart | 2 ++ .../widgets/accurate_dialog_widget.dart | 5 ++++- .../widgets/normal_text_body_for_dialog.dart | 10 +++++----- .../widgets/quick_calibrating_dialog.dart | 15 ++++++++++----- .../widgets/quick_calibration_dialog.dart | 2 ++ 7 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 assets/images/completed_done.svg diff --git a/assets/images/completed_done.svg b/assets/images/completed_done.svg new file mode 100644 index 00000000..759f0cba --- /dev/null +++ b/assets/images/completed_done.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart index 64044b94..0d3a1a92 100644 --- a/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart @@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_m import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class AccurteCalibratingDialog extends StatelessWidget { final String deviceId; @@ -17,6 +18,7 @@ class AccurteCalibratingDialog extends StatelessWidget { @override Widget build(_) { return AlertDialog( + backgroundColor: ColorsManager.whiteColors, contentPadding: EdgeInsets.zero, content: AccurateDialogWidget( title: 'Calibrating', diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart index 997e70cf..7124639d 100644 --- a/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class AccurateCalibrationDialog extends StatelessWidget { final String deviceId; @@ -15,6 +16,7 @@ class AccurateCalibrationDialog extends StatelessWidget { @override Widget build(_) { return AlertDialog( + backgroundColor: ColorsManager.whiteColors, contentPadding: EdgeInsets.zero, content: AccurateDialogWidget( title: 'Accurate Calibration', diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart index 433608ac..d13ebca0 100644 --- a/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart @@ -22,6 +22,7 @@ class AccurateDialogWidget extends StatelessWidget { child: Column( children: [ Expanded( + flex: 3, child: Column( children: [ Padding( @@ -43,13 +44,15 @@ class AccurateDialogWidget extends StatelessWidget { ), ), Expanded( + flex: 5, child: body, ), Expanded( + flex: 2, child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - const Divider(), + const Expanded(child: Divider()), Row( children: [ Expanded( diff --git a/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart index c322fe9d..fa293ec6 100644 --- a/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart @@ -28,7 +28,7 @@ class NormalTextBodyForDialog extends StatelessWidget { title, style: const TextStyle( color: ColorsManager.grayColor, - fontSize: 17, + fontSize: 15, ), ), ), @@ -42,7 +42,7 @@ class NormalTextBodyForDialog extends StatelessWidget { const Text('1. ', style: TextStyle( color: ColorsManager.grayColor, - fontSize: 17, + fontSize: 15, )), SizedBox( width: 450, @@ -50,7 +50,7 @@ class NormalTextBodyForDialog extends StatelessWidget { step1, style: const TextStyle( color: ColorsManager.grayColor, - fontSize: 17, + fontSize: 15, ), ), ), @@ -67,13 +67,13 @@ class NormalTextBodyForDialog extends StatelessWidget { const Text('2. ', style: TextStyle( color: ColorsManager.grayColor, - fontSize: 17, + fontSize: 15, )), Text( step2, style: const TextStyle( color: ColorsManager.grayColor, - fontSize: 17, + fontSize: 15, ), ), ], diff --git a/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart index 8514d432..6fc9adf2 100644 --- a/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart @@ -63,6 +63,7 @@ class _QuickCalibratingDialogState extends State { @override Widget build(_) { return AlertDialog( + backgroundColor: ColorsManager.whiteColors, contentPadding: EdgeInsets.zero, content: AccurateDialogWidget( title: 'Calibrating', @@ -71,7 +72,7 @@ class _QuickCalibratingDialogState extends State { children: [ const Expanded( child: Align( - alignment: Alignment.center, + alignment: Alignment.topCenter, child: Padding( padding: EdgeInsets.only(right: 75), child: Text( @@ -85,17 +86,21 @@ class _QuickCalibratingDialogState extends State { child: Align( alignment: Alignment.center, child: Container( - width: 110, + width: 130, padding: const EdgeInsets.all(5), decoration: BoxDecoration( - color: ColorsManager.whiteColors, + color: ColorsManager.neutralGray.withValues( + alpha: 0.5, + ), borderRadius: BorderRadius.circular(12), ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( - child: NumberInputField(controller: _controller), + child: Padding( + padding: const EdgeInsetsGeometry.only(left: 5), + child: NumberInputField(controller: _controller)), ), Expanded( child: Text( @@ -127,7 +132,7 @@ class _QuickCalibratingDialogState extends State { ), const Expanded( child: Align( - alignment: Alignment.center, + alignment: Alignment.bottomCenter, child: Text( '2.click Next to Complete the calibration', style: TextStyle(color: ColorsManager.lightGrayColor), diff --git a/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart index 6c776293..06b386c8 100644 --- a/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart +++ b/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class QuickCalibrationDialog extends StatelessWidget { final int timControl; @@ -17,6 +18,7 @@ class QuickCalibrationDialog extends StatelessWidget { @override Widget build(_) { return AlertDialog( + backgroundColor: ColorsManager.whiteColors, contentPadding: EdgeInsets.zero, content: AccurateDialogWidget( title: 'Quick Calibration', From 8c3861e83ca6edc37927d758aca27163f3a9b617 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Mon, 30 Jun 2025 10:44:16 +0300 Subject: [PATCH 36/37] fix the button border Raduis when hovering --- .../widgets/accurate_dialog_widget.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart index d13ebca0..0d6ea90c 100644 --- a/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart @@ -57,6 +57,9 @@ class AccurateDialogWidget extends StatelessWidget { children: [ Expanded( child: InkWell( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(26), + ), onTap: leftOnTap, child: Container( height: 40, @@ -67,6 +70,9 @@ class AccurateDialogWidget extends StatelessWidget { color: ColorsManager.grayBorder, ), ), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(26), + ), ), child: const Text( 'Cancel', @@ -77,6 +83,9 @@ class AccurateDialogWidget extends StatelessWidget { ), Expanded( child: InkWell( + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(26), + ), onTap: rightOnTap, child: Container( height: 40, @@ -87,6 +96,9 @@ class AccurateDialogWidget extends StatelessWidget { color: ColorsManager.grayBorder, ), ), + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(26), + ), ), child: const Text( 'Next', From cdc76c2c8eb564140a317b16b9d3606ad8fd3e23 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 30 Jun 2025 11:04:22 +0300 Subject: [PATCH 37/37] handle more cases when decoding analytics devices. --- lib/pages/analytics/models/analytics_device.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/pages/analytics/models/analytics_device.dart b/lib/pages/analytics/models/analytics_device.dart index 869de23f..6571eae4 100644 --- a/lib/pages/analytics/models/analytics_device.dart +++ b/lib/pages/analytics/models/analytics_device.dart @@ -39,8 +39,12 @@ class AnalyticsDevice { ? ProductDevice.fromJson(json['productDevice'] as Map) : null, spaceUuid: json['spaceUuid'] as String?, - latitude: json['lat'] != null ? double.parse(json['lat'] as String? ?? '0.0') : null, - longitude: json['lon'] != null ? double.parse(json['lon'] as String? ?? '0.0') : null, + latitude: json['lat'] != null && json['lat'] != '' + ? double.tryParse(json['lat']?.toString() ?? '0.0') + : null, + longitude: json['lon'] != null && json['lon'] != '' + ? double.tryParse(json['lon']?.toString() ?? '0.0') + : null, ); } }