diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d290213..2e349a87 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,6 +7,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + errors: + constant_identifier_names: ignore include: package:flutter_lints/flutter.yaml linter: diff --git a/assets/fonts/fonnts.com-AftikaBlack.ttf b/assets/fonts/fonnts.com-AftikaBlack.ttf new file mode 100644 index 00000000..538c917d Binary files /dev/null and b/assets/fonts/fonnts.com-AftikaBlack.ttf differ diff --git a/assets/fonts/fonnts.com-AftikaBold.ttf b/assets/fonts/fonnts.com-AftikaBold.ttf new file mode 100644 index 00000000..2ff73322 Binary files /dev/null and b/assets/fonts/fonnts.com-AftikaBold.ttf differ diff --git a/assets/fonts/fonnts.com-AftikaRegular.ttf b/assets/fonts/fonnts.com-AftikaRegular.ttf new file mode 100644 index 00000000..5ca4dc50 Binary files /dev/null and b/assets/fonts/fonnts.com-AftikaRegular.ttf differ diff --git a/assets/icons/3GangSwitch.svg b/assets/icons/3GangSwitch.svg new file mode 100644 index 00000000..ecb9992b --- /dev/null +++ b/assets/icons/3GangSwitch.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/AC.svg b/assets/icons/AC.svg new file mode 100644 index 00000000..92f6fc59 --- /dev/null +++ b/assets/icons/AC.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/Curtain.svg b/assets/icons/Curtain.svg new file mode 100644 index 00000000..a2e4f235 --- /dev/null +++ b/assets/icons/Curtain.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/Gateway.svg b/assets/icons/Gateway.svg new file mode 100644 index 00000000..e293999e --- /dev/null +++ b/assets/icons/Gateway.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/Light.svg b/assets/icons/Light.svg new file mode 100644 index 00000000..c8cfff59 --- /dev/null +++ b/assets/icons/Light.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/icons/ac_air.svg b/assets/icons/ac_air.svg new file mode 100644 index 00000000..3c4fff0d --- /dev/null +++ b/assets/icons/ac_air.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/ac_cooling.svg b/assets/icons/ac_cooling.svg new file mode 100644 index 00000000..e95c0d4e --- /dev/null +++ b/assets/icons/ac_cooling.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/ac_device.svg b/assets/icons/ac_device.svg new file mode 100644 index 00000000..d5fbe2a6 --- /dev/null +++ b/assets/icons/ac_device.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/icons/ac_fan_auto.svg b/assets/icons/ac_fan_auto.svg new file mode 100644 index 00000000..0acacfef --- /dev/null +++ b/assets/icons/ac_fan_auto.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/ac_fan_high.svg b/assets/icons/ac_fan_high.svg new file mode 100644 index 00000000..d6131531 --- /dev/null +++ b/assets/icons/ac_fan_high.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/ac_fan_low.svg b/assets/icons/ac_fan_low.svg new file mode 100644 index 00000000..f4bf56b7 --- /dev/null +++ b/assets/icons/ac_fan_low.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/ac_fan_middle.svg b/assets/icons/ac_fan_middle.svg new file mode 100644 index 00000000..ee940238 --- /dev/null +++ b/assets/icons/ac_fan_middle.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/ac_heating.svg b/assets/icons/ac_heating.svg new file mode 100644 index 00000000..47a160c8 --- /dev/null +++ b/assets/icons/ac_heating.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/ac_power.svg b/assets/icons/ac_power.svg new file mode 100644 index 00000000..cc2127f0 --- /dev/null +++ b/assets/icons/ac_power.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/ac_power_off.svg b/assets/icons/ac_power_off.svg new file mode 100644 index 00000000..70f7f9aa --- /dev/null +++ b/assets/icons/ac_power_off.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/ac_sun.svg b/assets/icons/ac_sun.svg new file mode 100644 index 00000000..33c7e01a --- /dev/null +++ b/assets/icons/ac_sun.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/card_unlock.svg b/assets/icons/automation_functions/card_unlock.svg new file mode 100644 index 00000000..dd77680a --- /dev/null +++ b/assets/icons/automation_functions/card_unlock.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/current_temp.svg b/assets/icons/automation_functions/current_temp.svg new file mode 100644 index 00000000..42cceb23 --- /dev/null +++ b/assets/icons/automation_functions/current_temp.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/automation_functions/doorbell.svg b/assets/icons/automation_functions/doorbell.svg new file mode 100644 index 00000000..1dc515a9 --- /dev/null +++ b/assets/icons/automation_functions/doorbell.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/doorlock_normal_open.svg b/assets/icons/automation_functions/doorlock_normal_open.svg new file mode 100644 index 00000000..8f4a5901 --- /dev/null +++ b/assets/icons/automation_functions/doorlock_normal_open.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/double_lock.svg b/assets/icons/automation_functions/double_lock.svg new file mode 100644 index 00000000..d8ad971d --- /dev/null +++ b/assets/icons/automation_functions/double_lock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/fingerprint_unlock.svg b/assets/icons/automation_functions/fingerprint_unlock.svg new file mode 100644 index 00000000..f9f5b84c --- /dev/null +++ b/assets/icons/automation_functions/fingerprint_unlock.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/hijack_alarm.svg b/assets/icons/automation_functions/hijack_alarm.svg new file mode 100644 index 00000000..e32997fb --- /dev/null +++ b/assets/icons/automation_functions/hijack_alarm.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/lock_alarm.svg b/assets/icons/automation_functions/lock_alarm.svg new file mode 100644 index 00000000..8bd2deeb --- /dev/null +++ b/assets/icons/automation_functions/lock_alarm.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/motion.svg b/assets/icons/automation_functions/motion.svg new file mode 100644 index 00000000..8d69463b --- /dev/null +++ b/assets/icons/automation_functions/motion.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/password_unlock.svg b/assets/icons/automation_functions/password_unlock.svg new file mode 100644 index 00000000..1920b69f --- /dev/null +++ b/assets/icons/automation_functions/password_unlock.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/presence.svg b/assets/icons/automation_functions/presence.svg new file mode 100644 index 00000000..d71a474d --- /dev/null +++ b/assets/icons/automation_functions/presence.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/presence_state.svg b/assets/icons/automation_functions/presence_state.svg new file mode 100644 index 00000000..d5de48e1 --- /dev/null +++ b/assets/icons/automation_functions/presence_state.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/remote_unlock_req.svg b/assets/icons/automation_functions/remote_unlock_req.svg new file mode 100644 index 00000000..da128ff7 --- /dev/null +++ b/assets/icons/automation_functions/remote_unlock_req.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/remote_unlock_via_app.svg b/assets/icons/automation_functions/remote_unlock_via_app.svg new file mode 100644 index 00000000..39fc859b --- /dev/null +++ b/assets/icons/automation_functions/remote_unlock_via_app.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/residual_electricity.svg b/assets/icons/automation_functions/residual_electricity.svg new file mode 100644 index 00000000..6a5b6127 --- /dev/null +++ b/assets/icons/automation_functions/residual_electricity.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/automation_functions/self_test_result.svg b/assets/icons/automation_functions/self_test_result.svg new file mode 100644 index 00000000..8739327b --- /dev/null +++ b/assets/icons/automation_functions/self_test_result.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/automation_functions/temp_password_unlock.svg b/assets/icons/automation_functions/temp_password_unlock.svg new file mode 100644 index 00000000..98d7573c --- /dev/null +++ b/assets/icons/automation_functions/temp_password_unlock.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/bathroom.svg b/assets/icons/bathroom.svg new file mode 100644 index 00000000..51fc8b6a --- /dev/null +++ b/assets/icons/bathroom.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/bedroom.svg b/assets/icons/bedroom.svg new file mode 100644 index 00000000..d579b003 --- /dev/null +++ b/assets/icons/bedroom.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/celsius_degrees.svg b/assets/icons/celsius_degrees.svg new file mode 100644 index 00000000..7acbd6e7 --- /dev/null +++ b/assets/icons/celsius_degrees.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/icons/child_lock.svg b/assets/icons/child_lock.svg new file mode 100644 index 00000000..6b0138bf --- /dev/null +++ b/assets/icons/child_lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/doorLock.svg b/assets/icons/doorLock.svg new file mode 100644 index 00000000..6f27673f --- /dev/null +++ b/assets/icons/doorLock.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/door_un_look_ic.svg b/assets/icons/door_un_look_ic.svg new file mode 100644 index 00000000..b647ec84 --- /dev/null +++ b/assets/icons/door_un_look_ic.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/dyi.svg b/assets/icons/dyi.svg new file mode 100644 index 00000000..7da61e8e --- /dev/null +++ b/assets/icons/dyi.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/factory_reset.svg b/assets/icons/factory_reset.svg new file mode 100644 index 00000000..7a47f24b --- /dev/null +++ b/assets/icons/factory_reset.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/fan_speed.svg b/assets/icons/fan_speed.svg new file mode 100644 index 00000000..07a48834 --- /dev/null +++ b/assets/icons/fan_speed.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/far_detection.svg b/assets/icons/far_detection.svg new file mode 100644 index 00000000..2827d94a --- /dev/null +++ b/assets/icons/far_detection.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/far_detection_function.svg b/assets/icons/far_detection_function.svg new file mode 100644 index 00000000..894b84ed --- /dev/null +++ b/assets/icons/far_detection_function.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/freezing.svg b/assets/icons/freezing.svg new file mode 100644 index 00000000..6c02f2e4 --- /dev/null +++ b/assets/icons/freezing.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/help_description_ic.svg b/assets/icons/help_description_ic.svg new file mode 100644 index 00000000..5f86b69b --- /dev/null +++ b/assets/icons/help_description_ic.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/illuminance_record_ic.svg b/assets/icons/illuminance_record_ic.svg new file mode 100644 index 00000000..a92935ff --- /dev/null +++ b/assets/icons/illuminance_record_ic.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/indicator.svg b/assets/icons/indicator.svg new file mode 100644 index 00000000..b58a976e --- /dev/null +++ b/assets/icons/indicator.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/light_countdown.svg b/assets/icons/light_countdown.svg new file mode 100644 index 00000000..94f65b9a --- /dev/null +++ b/assets/icons/light_countdown.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/light_pulb.svg b/assets/icons/light_pulb.svg new file mode 100644 index 00000000..c442fdaf --- /dev/null +++ b/assets/icons/light_pulb.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/lockIcon.svg b/assets/icons/lockIcon.svg new file mode 100644 index 00000000..a78161ca --- /dev/null +++ b/assets/icons/lockIcon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/master_state.svg b/assets/icons/master_state.svg new file mode 100644 index 00000000..0aafae1a --- /dev/null +++ b/assets/icons/master_state.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/motion_detection.svg b/assets/icons/motion_detection.svg new file mode 100644 index 00000000..a9b2d685 --- /dev/null +++ b/assets/icons/motion_detection.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/motionless_detection.svg b/assets/icons/motionless_detection.svg new file mode 100644 index 00000000..25a767c1 --- /dev/null +++ b/assets/icons/motionless_detection.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/nobody_time.svg b/assets/icons/nobody_time.svg new file mode 100644 index 00000000..df80b517 --- /dev/null +++ b/assets/icons/nobody_time.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/office.svg b/assets/icons/office.svg new file mode 100644 index 00000000..03a2badd --- /dev/null +++ b/assets/icons/office.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/parlour.svg b/assets/icons/parlour.svg new file mode 100644 index 00000000..3298393a --- /dev/null +++ b/assets/icons/parlour.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence_record_ic.svg b/assets/icons/presence_record_ic.svg new file mode 100644 index 00000000..0bc133f7 --- /dev/null +++ b/assets/icons/presence_record_ic.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/reset_off.svg b/assets/icons/reset_off.svg new file mode 100644 index 00000000..eac88f2b --- /dev/null +++ b/assets/icons/reset_off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/scene_child_lock.svg b/assets/icons/scene_child_lock.svg new file mode 100644 index 00000000..7e56164a --- /dev/null +++ b/assets/icons/scene_child_lock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/scene_child_unlock.svg b/assets/icons/scene_child_unlock.svg new file mode 100644 index 00000000..4eafbdea --- /dev/null +++ b/assets/icons/scene_child_unlock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/scene_refresh.svg b/assets/icons/scene_refresh.svg new file mode 100644 index 00000000..c54ffb04 --- /dev/null +++ b/assets/icons/scene_refresh.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/sensitivity.svg b/assets/icons/sensitivity.svg new file mode 100644 index 00000000..b75ebd3e --- /dev/null +++ b/assets/icons/sensitivity.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/sensor_motion_ic.svg b/assets/icons/sensor_motion_ic.svg new file mode 100644 index 00000000..ceb4080a --- /dev/null +++ b/assets/icons/sensor_motion_ic.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/sensor_presence_ic.svg b/assets/icons/sensor_presence_ic.svg new file mode 100644 index 00000000..e30b109c --- /dev/null +++ b/assets/icons/sensor_presence_ic.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/sensor_vacant_ic.svg b/assets/icons/sensor_vacant_ic.svg new file mode 100644 index 00000000..8ee00e79 --- /dev/null +++ b/assets/icons/sensor_vacant_ic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/sensors.svg b/assets/icons/sensors.svg new file mode 100644 index 00000000..7fbb1506 --- /dev/null +++ b/assets/icons/sensors.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/sesitivity_operation_icon.svg b/assets/icons/sesitivity_operation_icon.svg new file mode 100644 index 00000000..612148c5 --- /dev/null +++ b/assets/icons/sesitivity_operation_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/switch_alarm_sound.svg b/assets/icons/switch_alarm_sound.svg new file mode 100644 index 00000000..db645338 --- /dev/null +++ b/assets/icons/switch_alarm_sound.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/tempreture.svg b/assets/icons/tempreture.svg new file mode 100644 index 00000000..448083a7 --- /dev/null +++ b/assets/icons/tempreture.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/devtools_options.yaml b/devtools_options.yaml index 2bc8e05f..6ee932ce 100644 --- a/devtools_options.yaml +++ b/devtools_options.yaml @@ -1,4 +1,4 @@ description: This file stores settings for Dart & Flutter DevTools. documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states extensions: - - provider: true \ No newline at end of file + - provider: true diff --git a/lib/core/extension/build_context_x.dart b/lib/core/extension/build_context_x.dart new file mode 100644 index 00000000..50bc5972 --- /dev/null +++ b/lib/core/extension/build_context_x.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +extension BuildContextExt on BuildContext { + ThemeData get theme => Theme.of(this); + + TextTheme get textTheme => Theme.of(this).textTheme; + + AppBarTheme get appBarTheme => Theme.of(this).appBarTheme; + + Size get screenSize => MediaQuery.of(this).size; + + double get screenWidth => MediaQuery.of(this).size.width; + + double get screenHeight => MediaQuery.of(this).size.height; + + double get textScale => MediaQuery.textScalerOf(this).scale(1); +} diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/lib/core/theme/app_theme.dart @@ -0,0 +1 @@ + diff --git a/lib/main.dart b/lib/main.dart index a4f4fcb8..90c643cc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,9 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/access_management/view/access_management.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; -import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; -import 'package:syncrow_web/pages/home/view/home_page.dart'; -import 'package:syncrow_web/pages/spaseManagementIcon.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; -import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:go_router/go_router.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/app_routes.dart'; @@ -19,14 +14,15 @@ Future main() async { WidgetsFlutterBinding.ensureInitialized(); initialSetup(); String checkToken = await AuthBloc.getTokenAndValidate(); - GoRouter router = GoRouter( - initialLocation: checkToken == 'Success' ? RoutesConst.home :RoutesConst.main , - routes: AppRoutes.getRoutes(), + GoRouter router = GoRouter( + initialLocation: checkToken == 'Success' ? RoutesConst.home : RoutesConst.auth, + routes: AppRoutes.getRoutes(), ); runApp(MyApp( router: router, )); } + class MyApp extends StatelessWidget { final GoRouter router; const MyApp({ @@ -37,48 +33,42 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiBlocProvider( - providers: [ - BlocProvider(create: (context) => HomeBloc()), - BlocProvider( - create: (context) => VisitorPasswordBloc(),) - ], - child: MaterialApp.router( - debugShowCheckedModeBanner: false, // Hide debug banner - scrollBehavior: const MaterialScrollBehavior().copyWith( - dragDevices: { - PointerDeviceKind.mouse, - PointerDeviceKind.touch, - PointerDeviceKind.stylus, - PointerDeviceKind.unknown, - }, - ), - - theme: ThemeData( - textTheme: const TextTheme( - bodySmall: TextStyle(fontSize: 13, color: ColorsManager.whiteColors, fontWeight: FontWeight.bold), - bodyMedium: TextStyle(color: Colors.black87, fontSize: 14), - bodyLarge: TextStyle(fontSize: 16, color: Colors.white), - headlineSmall: TextStyle(color: Colors.black87, fontSize: 18), - headlineMedium: TextStyle(color: Colors.black87, fontSize: 20), - headlineLarge: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold, + providers: [ + BlocProvider(create: (context) => HomeBloc()), + BlocProvider( + create: (context) => VisitorPasswordBloc(), + ) + ], + child: MaterialApp.router( + debugShowCheckedModeBanner: false, // Hide debug banner + scrollBehavior: const MaterialScrollBehavior().copyWith( + dragDevices: { + PointerDeviceKind.mouse, + PointerDeviceKind.touch, + PointerDeviceKind.stylus, + PointerDeviceKind.unknown, + }, ), - ), - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme - useMaterial3: true, // Enable Material 3 - ), - routeInformationProvider: router.routeInformationProvider, - routerDelegate: router.routerDelegate, - routeInformationParser: router.routeInformationParser, - - )); + theme: ThemeData( + fontFamily: 'Aftika', + textTheme: const TextTheme( + bodySmall: TextStyle( + fontSize: 13, color: ColorsManager.whiteColors, fontWeight: FontWeight.bold), + bodyMedium: TextStyle(color: Colors.black87, fontSize: 14), + bodyLarge: TextStyle(fontSize: 16, color: Colors.white), + headlineSmall: TextStyle(color: Colors.black87, fontSize: 18), + headlineMedium: TextStyle(color: Colors.black87, fontSize: 20), + headlineLarge: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + ), + routeInformationProvider: router.routeInformationProvider, + routerDelegate: router.routerDelegate, + routeInformationParser: router.routeInformationParser, + )); } } - - - - - diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index 0ba74a52..473d19bd 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -22,16 +22,15 @@ class AccessBloc extends Bloc { int? effectiveTimeTimeStamp; int? expirationTimeTimeStamp; - TextEditingController passwordName= TextEditingController(); + TextEditingController passwordName = TextEditingController(); List filteredData = []; - List data=[]; + List data = []; - Future _onFetchTableData( - FetchTableData event, Emitter emit) async { + Future _onFetchTableData(FetchTableData event, Emitter emit) async { try { emit(AccessLoaded()); - data = await AccessMangApi().fetchVisitorPassword(); - filteredData= data; + data = await AccessMangApi().fetchVisitorPassword(); + filteredData = data; updateTabsCount(); emit(TableLoaded(data)); } catch (e) { @@ -40,7 +39,8 @@ class AccessBloc extends Bloc { } void updateTabsCount() { - int toBeEffectiveCount = data.where((item) => item.passwordStatus.value== 'To be effective').length; + int toBeEffectiveCount = + data.where((item) => item.passwordStatus.value == 'To be effective').length; int effectiveCount = data.where((item) => item.passwordStatus.value == 'Effective').length; int expiredCount = data.where((item) => item.passwordStatus.value == 'Expired').length; tabs[1] = 'To Be Effective ($toBeEffectiveCount)'; @@ -48,29 +48,21 @@ class AccessBloc extends Bloc { tabs[3] = 'Expired ($expiredCount)'; } - int selectedIndex = 0; - final List tabs = [ - 'All', - 'To Be Effective (0)', - 'Effective (0)', - 'Expired' - ]; - + final List tabs = ['All', 'To Be Effective (0)', 'Effective (0)', 'Expired']; Future selectFilterTap(TabChangedEvent event, Emitter emit) async { try { emit(AccessLoaded()); - selectedIndex= event.selectedIndex; + selectedIndex = event.selectedIndex; emit(AccessInitial()); emit(TableLoaded(data)); } catch (e) { - emit(FailedState( e.toString())); + emit(FailedState(e.toString())); return; } } - Future selectTime(SelectTime event, Emitter emit) async { emit(AccessLoaded()); final DateTime? picked = await showDatePicker( @@ -80,37 +72,39 @@ class AccessBloc extends Bloc { lastDate: DateTime(2101), ); if (picked != null) { - final selectedDateTime = DateTime( - picked.year, - picked.month, - picked.day, - - ); - final selectedTimestamp = DateTime( - selectedDateTime.year, - selectedDateTime.month, - selectedDateTime.day, - selectedDateTime.hour, - selectedDateTime.minute, - ).millisecondsSinceEpoch ~/ 1000; // Divide by 1000 to remove milliseconds - if (event.isStart) { - if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { - CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); - } else { - startTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds - effectiveTimeTimeStamp = selectedTimestamp; - } + final selectedDateTime = DateTime( + picked.year, + picked.month, + picked.day, + ); + final selectedTimestamp = DateTime( + selectedDateTime.year, + selectedDateTime.month, + selectedDateTime.day, + selectedDateTime.hour, + selectedDateTime.minute, + ).millisecondsSinceEpoch ~/ + 1000; // Divide by 1000 to remove milliseconds + if (event.isStart) { + if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); } else { - if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { - CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); - } else { - endTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds - expirationTimeTimeStamp = selectedTimestamp; - } + startTime = + selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + effectiveTimeTimeStamp = selectedTimestamp; } + } else { + if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); + } else { + endTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + expirationTimeTimeStamp = selectedTimestamp; + } + } } emit(ChangeTimeState()); } + Future _filterData(FilterDataEvent event, Emitter emit) async { emit(AccessLoaded()); try { @@ -118,15 +112,22 @@ class AccessBloc extends Bloc { bool matchesCriteria = true; // Convert timestamp to DateTime and extract date component - DateTime effectiveDate = DateTime.fromMillisecondsSinceEpoch(int.parse(item.effectiveTime.toString()) * 1000).toUtc().toLocal(); - DateTime invalidDate = DateTime.fromMillisecondsSinceEpoch(int.parse(item.invalidTime.toString()) * 1000).toUtc().toLocal(); - DateTime effectiveDateOnly = DateTime(effectiveDate.year, effectiveDate.month, effectiveDate.day); + DateTime effectiveDate = + DateTime.fromMillisecondsSinceEpoch(int.parse(item.effectiveTime.toString()) * 1000) + .toUtc() + .toLocal(); + DateTime invalidDate = + DateTime.fromMillisecondsSinceEpoch(int.parse(item.invalidTime.toString()) * 1000) + .toUtc() + .toLocal(); + DateTime effectiveDateOnly = + DateTime(effectiveDate.year, effectiveDate.month, effectiveDate.day); DateTime invalidDateOnly = DateTime(invalidDate.year, invalidDate.month, invalidDate.day); // Filter by password name if (event.passwordName != null && event.passwordName!.isNotEmpty) { - final bool matchesName = item.passwordName != null && - item.passwordName.contains(event.passwordName); + final bool matchesName = + item.passwordName != null && item.passwordName.contains(event.passwordName); if (!matchesName) { matchesCriteria = false; } @@ -134,7 +135,8 @@ class AccessBloc extends Bloc { // Filter by start date only if (event.startTime != null && event.endTime == null) { - DateTime startDateOnly = DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); + DateTime startDateOnly = + DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); startDateOnly = DateTime(startDateOnly.year, startDateOnly.month, startDateOnly.day); if (effectiveDateOnly.isBefore(startDateOnly)) { matchesCriteria = false; @@ -143,7 +145,8 @@ class AccessBloc extends Bloc { // Filter by end date only if (event.endTime != null && event.startTime == null) { - DateTime endDateOnly = DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); + DateTime endDateOnly = + DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); endDateOnly = DateTime(endDateOnly.year, endDateOnly.month, endDateOnly.day); if (invalidDateOnly.isAfter(endDateOnly)) { matchesCriteria = false; @@ -152,8 +155,10 @@ class AccessBloc extends Bloc { // Filter by both start date and end date if (event.startTime != null && event.endTime != null) { - DateTime startDateOnly = DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); - DateTime endDateOnly = DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); + DateTime startDateOnly = + DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); + DateTime endDateOnly = + DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); startDateOnly = DateTime(startDateOnly.year, startDateOnly.month, startDateOnly.day); endDateOnly = DateTime(endDateOnly.year, endDateOnly.month, endDateOnly.day); if (effectiveDateOnly.isBefore(startDateOnly) || invalidDateOnly.isAfter(endDateOnly)) { @@ -179,16 +184,14 @@ class AccessBloc extends Bloc { } } - - - resetSearch(ResetSearch event, Emitter emit) async{ + resetSearch(ResetSearch event, Emitter emit) async { emit(AccessLoaded()); - startTime = 'Start Time'; - endTime = 'End Time'; - passwordName.clear(); - selectedIndex=0; - effectiveTimeTimeStamp=null; - expirationTimeTimeStamp=null; + startTime = 'Start Time'; + endTime = 'End Time'; + passwordName.clear(); + selectedIndex = 0; + effectiveTimeTimeStamp = null; + expirationTimeTimeStamp = null; add(FetchTableData()); } @@ -206,7 +209,8 @@ class AccessBloc extends Bloc { filteredData = data; break; case 1: // To Be Effective - filteredData = data.where((item) => item.passwordStatus.value == "To Be Effective").toList(); + filteredData = + data.where((item) => item.passwordStatus.value == "To Be Effective").toList(); break; case 2: // Effective filteredData = data.where((item) => item.passwordStatus.value == "Effective").toList(); @@ -221,12 +225,10 @@ class AccessBloc extends Bloc { selectedTabIndex: selectedIndex, passwordName: passwordName.text.toLowerCase(), startTime: effectiveTimeTimeStamp, - endTime: expirationTimeTimeStamp - )); + endTime: expirationTimeTimeStamp)); emit(TableLoaded(filteredData)); } catch (e) { emit(FailedState(e.toString())); } } - } diff --git a/lib/pages/access_management/bloc/access_event.dart b/lib/pages/access_management/bloc/access_event.dart index f2f631b4..1bd7dbd3 100644 --- a/lib/pages/access_management/bloc/access_event.dart +++ b/lib/pages/access_management/bloc/access_event.dart @@ -1,14 +1,15 @@ - import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; -abstract class AccessEvent extends Equatable { +abstract class AccessEvent extends Equatable { const AccessEvent(); @override List get props => []; } + class FetchTableData extends AccessEvent {} + class ResetSearch extends AccessEvent {} class TabChangedEvent extends AccessEvent { @@ -17,16 +18,14 @@ class TabChangedEvent extends AccessEvent { const TabChangedEvent(this.selectedIndex); } - class SelectTime extends AccessEvent { - final BuildContext context; + final BuildContext context; final bool isStart; - const SelectTime({required this.context,required this.isStart}); + const SelectTime({required this.context, required this.isStart}); @override - List get props => [context,isStart]; + List get props => [context, isStart]; } - class FilterDataEvent extends AccessEvent { final String? passwordName; final int? startTime; @@ -34,13 +33,9 @@ class FilterDataEvent extends AccessEvent { final int selectedTabIndex; // Add this field const FilterDataEvent({ - this.passwordName, + this.passwordName, this.startTime, this.endTime, required this.selectedTabIndex, // Initialize this field - }); } - - - diff --git a/lib/pages/access_management/bloc/access_state.dart b/lib/pages/access_management/bloc/access_state.dart index 11253d1f..0790a735 100644 --- a/lib/pages/access_management/bloc/access_state.dart +++ b/lib/pages/access_management/bloc/access_state.dart @@ -11,6 +11,7 @@ abstract class AccessState extends Equatable { class AccessInitial extends AccessState {} class AccessLoaded extends AccessState {} + class FailedState extends AccessState { final String message; @@ -29,7 +30,7 @@ class TableLoaded extends AccessState { List get props => [data]; } -class TabState extends AccessState { +class TabState extends AccessState { final int selectedIndex; const TabState({required this.selectedIndex}); diff --git a/lib/pages/access_management/model/password_model.dart b/lib/pages/access_management/model/password_model.dart index e16cc586..8436ef56 100644 --- a/lib/pages/access_management/model/password_model.dart +++ b/lib/pages/access_management/model/password_model.dart @@ -30,9 +30,9 @@ class PasswordModel { effectiveTime: json['effectiveTime'], passwordCreated: json['passwordCreated'], createdTime: json['createdTime'], - passwordName: json['passwordName']??'No name', // New field - passwordStatus:AccessStatusExtension.fromString(json['passwordStatus']), - passwordType:AccessTypeExtension.fromString(json['passwordType']), + passwordName: json['passwordName'] ?? 'No name', // New field + passwordStatus: AccessStatusExtension.fromString(json['passwordStatus']), + passwordType: AccessTypeExtension.fromString(json['passwordType']), deviceUuid: json['deviceUuid'], ); } @@ -50,5 +50,4 @@ class PasswordModel { 'deviceUuid': deviceUuid, }; } - } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 2e939993..a63b02a3 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -5,11 +5,10 @@ import 'package:go_router/go_router.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/custom_table.dart'; -import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; import 'package:syncrow_web/pages/common/date_time_widget.dart'; -import 'package:syncrow_web/pages/common/default_button.dart'; -import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -22,13 +21,13 @@ class AccessManagementPage extends StatelessWidget { const AccessManagementPage({super.key}); @override Widget build(BuildContext context) { - Size size = MediaQuery.of(context).size; return WebScaffold( enableMenuSideba: false, appBarTitle: Row( children: [ - Text('Access Management', + Text( + 'Access Management', style: Theme.of(context).textTheme.headlineLarge, ) ], @@ -37,9 +36,9 @@ class AccessManagementPage extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Physical Access', - style: Theme.of(context).textTheme.headlineMedium! - .copyWith(color: Colors.white), + Text( + 'Physical Access', + style: Theme.of(context).textTheme.headlineMedium!.copyWith(color: Colors.white), ), Row( children: [ @@ -53,233 +52,236 @@ class AccessManagementPage extends StatelessWidget { Assets.grid, ), ), - const SizedBox(width: 10,) - + const SizedBox( + width: 10, + ) ], ), ], ), ], - scaffoldBody: BlocProvider(create: (BuildContext context) => AccessBloc()..add(FetchTableData()), - child: BlocConsumer(listener: (context, state) {}, + scaffoldBody: BlocProvider( + create: (BuildContext context) => AccessBloc()..add(FetchTableData()), + child: BlocConsumer( + listener: (context, state) {}, builder: (context, state) { final accessBloc = BlocProvider.of(context); final filteredData = accessBloc.filteredData; - return state is AccessLoaded? - const Center(child: CircularProgressIndicator()): - Container( - padding: EdgeInsets.all(30), - height: size.height, - width: size.width, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - decoration: containerDecoration, - height: size.height * 0.05, - child: Flexible( - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: BlocProvider.of(context).tabs.length, - shrinkWrap: true, - itemBuilder: (context, index) { - final isSelected = index == BlocProvider.of(context).selectedIndex; - return InkWell( - onTap: () { - BlocProvider.of(context).add(TabChangedEvent(index)); - }, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.boxColor, - border: Border.all( - color: isSelected ? Colors.blue : Colors.transparent, - width: 2.0, - ), - borderRadius: index == 0 - ? const BorderRadius.only( - topLeft: Radius.circular(10), - bottomLeft: Radius.circular(10)) - : index == 3 - ? const BorderRadius.only( - topRight: Radius.circular(10), - bottomRight: Radius.circular(10)) - : null, + return state is AccessLoaded + ? const Center(child: CircularProgressIndicator()) + : Container( + padding: EdgeInsets.all(30), + height: size.height, + width: size.width, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: containerDecoration, + height: size.height * 0.05, + child: Flexible( + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: BlocProvider.of(context).tabs.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final isSelected = index == + BlocProvider.of(context).selectedIndex; + return InkWell( + onTap: () { + BlocProvider.of(context) + .add(TabChangedEvent(index)); + }, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.boxColor, + border: Border.all( + color: isSelected ? Colors.blue : Colors.transparent, + width: 2.0, + ), + borderRadius: index == 0 + ? const BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10)) + : index == 3 + ? const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10)) + : null, + ), + padding: const EdgeInsets.only(left: 10, right: 10), + child: Center( + child: Text( + BlocProvider.of(context).tabs[index], + style: TextStyle( + color: isSelected ? Colors.blue : Colors.black, + ), + ), + ), + ), + ); + }, + ), + ), + ), + const SizedBox( + height: 20, + ), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + textBaseline: TextBaseline.ideographic, + children: [ + Container( + width: size.width * 0.15, + child: CustomWebTextField( + controller: accessBloc.passwordName, + isRequired: true, + textFieldName: 'Name', + description: '', ), - padding: const EdgeInsets.only(left: 10, right: 10), - child: Center( - child: Text( - BlocProvider.of(context).tabs[index], - style: TextStyle( - color: isSelected ? Colors.blue : Colors.black, + ), + const SizedBox( + width: 15, + ), + DateTimeWebWidget( + icon: Assets.calendarIcon, + isRequired: false, + title: 'Access Time', + size: size, + endTime: () { + accessBloc.add(SelectTime(context: context, isStart: false)); + }, + startTime: () { + accessBloc.add(SelectTime(context: context, isStart: true)); + }, + firstString: BlocProvider.of(context).startTime, + secondString: BlocProvider.of(context).endTime, + ), + const SizedBox( + width: 15, + ), + SizedBox( + width: size.width * 0.06, + child: Container( + decoration: containerDecoration, + child: DefaultButton( + onPressed: () { + accessBloc.add(FilterDataEvent( + selectedTabIndex: BlocProvider.of( + context) + .selectedIndex, // Pass the selected tab index + passwordName: + accessBloc.passwordName.text.toLowerCase(), + startTime: accessBloc.effectiveTimeTimeStamp, + endTime: accessBloc.expirationTimeTimeStamp)); + }, + borderRadius: 9, + child: const Text('Search'))), + ), + const SizedBox( + width: 10, + ), + SizedBox( + width: size.width * 0.06, + child: Container( + decoration: containerDecoration, + child: DefaultButton( + onPressed: () { + accessBloc.add(ResetSearch()); + }, + backgroundColor: ColorsManager.whiteColors, + borderRadius: 9, + child: Text( + 'Reset', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black), ), ), ), ), - ); - }, - ), - ), - ), - const SizedBox( - height: 20, - ), - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - textBaseline: TextBaseline.ideographic, - children: [ - Container( - width: size.width * 0.15, - child: CustomWebTextField( - controller: accessBloc.passwordName, - isRequired: true, - textFieldName: 'Name', - description: '', + ], ), - ), - const SizedBox( - width: 15, - ), - DateTimeWebWidget( - icon: Assets.calendarIcon, - isRequired: false, - title: 'Access Time', - size: size, - endTime: () { - accessBloc.add(SelectTime(context: context, isStart: false)); - }, - startTime: () { - accessBloc.add(SelectTime(context: context, isStart: true)); - }, - firstString:BlocProvider.of(context).startTime , - secondString:BlocProvider.of(context).endTime , - ) , - const SizedBox( - width: 15, - ), - - SizedBox( - - width: size.width * 0.06, - child:Container( - decoration: containerDecoration, - child: DefaultButton( - onPressed: () { - accessBloc.add(FilterDataEvent( - selectedTabIndex: BlocProvider.of(context).selectedIndex, // Pass the selected tab index - passwordName: accessBloc.passwordName.text.toLowerCase(), - startTime: accessBloc.effectiveTimeTimeStamp, - endTime: accessBloc.expirationTimeTimeStamp - )); - }, borderRadius: 9, - child: const Text('Search'))), - ), - const SizedBox( - width: 10, - ), - SizedBox( - width: size.width * 0.06, - child: Container( - decoration: containerDecoration, - child: DefaultButton( - onPressed: () { - accessBloc.add(ResetSearch()); - }, - backgroundColor: ColorsManager.whiteColors, - borderRadius: 9, - child: Text( - 'Reset', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: Colors.black), + const SizedBox( + height: 20, + ), + Wrap( + children: [ + Container( + width: size.width * 0.15, + decoration: containerDecoration, + child: DefaultButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const VisitorPasswordDialog(); + }, + ).then((v) { + if (v != null) { + accessBloc.add(FetchTableData()); + } + }); + }, + borderRadius: 8, + child: const Text('+ Create Visitor Password ')), ), - ), + const SizedBox( + width: 10, + ), + Container( + width: size.width * 0.12, + decoration: containerDecoration, + child: DefaultButton( + borderRadius: 8, + backgroundColor: ColorsManager.whiteColors, + child: Text( + 'Admin Password', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black), + ))) + ], ), - ), - ], - ), - const SizedBox( - height: 20, - ), - Wrap( - children: [ - Container( - width: size.width * 0.15, - decoration: containerDecoration, - child: DefaultButton( - onPressed: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return const VisitorPasswordDialog(); - }, - ).then((v){ - if(v!=null){ - accessBloc.add(FetchTableData()); - } - }); - }, - borderRadius: 8, - child: const Text('+ Create Visitor Password ')), - ), - const SizedBox( - width: 10, - ), - Container( - width: size.width * 0.12, - decoration: containerDecoration, - child: DefaultButton( - borderRadius: 8, - backgroundColor: ColorsManager.whiteColors, - child: Text( - 'Admin Password', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: Colors.black), - ))) - ], - ), - const SizedBox( - height: 20, - ), - Expanded( - child: DynamicTable( - isEmpty: filteredData.isEmpty , - withCheckBox: false, - size: size, - cellDecoration: containerDecoration, - headers: const [ - 'Name', - 'Access Type', - 'Access Period', - 'Accessible Device', - 'Authorizer', - 'Authorization Date & Time', - 'Access Status' - ], - data: filteredData.map((item) { - - return [ - item.passwordName.toString(), - item.passwordType.value, - ('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'), - item.deviceUuid.toString(), - '', - '', - item.passwordStatus.value, - ]; - }).toList(), - ) - // : const Center(child: CircularProgressIndicator()), - ) - ], - ), - ); - })) - ); + const SizedBox( + height: 20, + ), + Expanded( + child: DynamicTable( + isEmpty: filteredData.isEmpty, + withCheckBox: false, + size: size, + cellDecoration: containerDecoration, + headers: const [ + 'Name', + 'Access Type', + 'Access Period', + 'Accessible Device', + 'Authorizer', + 'Authorization Date & Time', + 'Access Status' + ], + data: filteredData.map((item) { + return [ + item.passwordName.toString(), + item.passwordType.value, + ('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'), + item.deviceUuid.toString(), + '', + '', + item.passwordStatus.value, + ]; + }).toList(), + ) + // : const Center(child: CircularProgressIndicator()), + ) + ], + ), + ); + }))); } } - diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 125c2817..ea53fdd3 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -34,7 +34,7 @@ class AuthBloc extends Bloc { TextEditingController(); final TextEditingController forgetOtp = TextEditingController(); final forgetFormKey = GlobalKey(); - late bool checkValidate = false; + late bool checkValidate = false; Timer? _timer; int _remainingTime = 0; @@ -58,8 +58,7 @@ class AuthBloc extends Bloc { add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); } else { add(UpdateTimerEvent( - remainingTime: _remainingTime, - isButtonEnabled: false)); + remainingTime: _remainingTime, isButtonEnabled: false)); } }); } @@ -85,7 +84,7 @@ class AuthBloc extends Bloc { } else if (response == "You entered wrong otp") { forgetValidate = 'Wrong one time password.'; emit(AuthInitialState()); - }else if (response == "OTP expired") { + } else if (response == "OTP expired") { forgetValidate = 'One time password has been expired.'; emit(AuthInitialState()); } @@ -95,6 +94,7 @@ class AuthBloc extends Bloc { // emit(FailureForgetState(error: failure.toString())); } } + //925207 String? validateCode(String? value) { if (value == null || value.isEmpty) { @@ -155,6 +155,7 @@ class AuthBloc extends Bloc { key: UserModel.userUuidKey, value: Token.decodeToken(token.accessToken)['uuid'].toString()); user = UserModel.fromToken(token); + debugPrint(token.accessToken); loginEmailController.clear(); loginPasswordController.clear(); emit(LoginSuccess()); @@ -162,21 +163,27 @@ class AuthBloc extends Bloc { emit(const LoginFailure(error: 'Something went wrong')); } } else { - emit(const LoginFailure(error: 'Accept terms and condition')); } } - checkBoxToggle(CheckBoxEvent event, Emitter emit,) { + checkBoxToggle( + CheckBoxEvent event, + Emitter emit, + ) { emit(AuthLoading()); isChecked = event.newValue!; add(CheckEnableEvent()); emit(LoginInitial()); } - checkOtpCode(ChangePasswordEvent event, Emitter emit,) async { + checkOtpCode( + ChangePasswordEvent event, + Emitter emit, + ) async { emit(LoadingForgetState()); - await AuthenticationAPI.verifyOtp(email: forgetEmailController.text, otpCode: forgetOtp.text); + await AuthenticationAPI.verifyOtp( + email: forgetEmailController.text, otpCode: forgetOtp.text); emit(SuccessForgetState()); } @@ -204,19 +211,17 @@ class AuthBloc extends Bloc { } else if (regionUuid == '') { return 'Please select your region'; } - validate=''; + validate = ''; return null; } String? loginValidateEmail(String? value) { - if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value!)) { + if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value!)) { return ''; } return null; } - - bool _validateInputs(Emitter emit) { emit(LoadingForgetState()); final nameError = validateEmail(forgetEmailController.text); @@ -359,7 +364,9 @@ class AuthBloc extends Bloc { final String formattedTime = [ if (days > 0) '${days}d', // Append 'd' for days if (days > 0 || hours > 0) - hours.toString().padLeft(2, '0'), // Show hours if there are days or hours + hours + .toString() + .padLeft(2, '0'), // Show hours if there are days or hours minutes.toString().padLeft(2, '0'), seconds.toString().padLeft(2, '0'), ].join(':'); @@ -367,9 +374,12 @@ class AuthBloc extends Bloc { return formattedTime; } - bool checkEnable( CheckEnableEvent event, Emitter emit,) { + bool checkEnable( + CheckEnableEvent event, + Emitter emit, + ) { emit(AuthLoading()); - checkValidate = isChecked==true && + checkValidate = isChecked == true && loginPasswordController.text.isNotEmpty && loginEmailController.text.isNotEmpty && regionUuid != ''; @@ -377,14 +387,21 @@ class AuthBloc extends Bloc { return checkValidate; } - changeValidate(ChangeValidateEvent event, Emitter emit,){ + changeValidate( + ChangeValidateEvent event, + Emitter emit, + ) { emit(AuthLoading()); - validate=''; + validate = ''; emit(LoginInitial()); } - changeForgetValidate(ChangeValidateEvent event, Emitter emit,){ + + changeForgetValidate( + ChangeValidateEvent event, + Emitter emit, + ) { emit(AuthLoading()); - forgetValidate=''; + forgetValidate = ''; emit(LoginInitial()); } } diff --git a/lib/pages/auth/bloc/auth_event.dart b/lib/pages/auth/bloc/auth_event.dart index 4f80a2db..a8786cbc 100644 --- a/lib/pages/auth/bloc/auth_event.dart +++ b/lib/pages/auth/bloc/auth_event.dart @@ -13,47 +13,60 @@ class LoginButtonPressed extends AuthEvent { final String password; final String regionUuid; - const LoginButtonPressed({required this.username, required this.password, required this.regionUuid, }); + const LoginButtonPressed({ + required this.username, + required this.password, + required this.regionUuid, + }); @override - List get props => [username, password,regionUuid]; + List get props => [username, password, regionUuid]; } class CheckBoxEvent extends AuthEvent { final bool? newValue; - const CheckBoxEvent({required this.newValue,}); + const CheckBoxEvent({ + required this.newValue, + }); @override - List get props => [newValue!,]; + List get props => [ + newValue!, + ]; } -class GetCodeEvent extends AuthEvent{} +class GetCodeEvent extends AuthEvent {} -class SubmitEvent extends AuthEvent{} +class SubmitEvent extends AuthEvent {} -class StartTimerEvent extends AuthEvent{} +class StartTimerEvent extends AuthEvent {} -class StopTimerEvent extends AuthEvent{} +class StopTimerEvent extends AuthEvent {} class UpdateTimerEvent extends AuthEvent { final int remainingTime; final bool isButtonEnabled; - const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled}); + const UpdateTimerEvent( + {required this.remainingTime, required this.isButtonEnabled}); } -class ChangePasswordEvent extends AuthEvent{} +class ChangePasswordEvent extends AuthEvent {} -class SendOtpEvent extends AuthEvent{} +class SendOtpEvent extends AuthEvent {} -class PasswordVisibleEvent extends AuthEvent{ +class PasswordVisibleEvent extends AuthEvent { final bool? newValue; - const PasswordVisibleEvent({required this.newValue,}); + const PasswordVisibleEvent({ + required this.newValue, + }); } class RegionInitialEvent extends AuthEvent {} + class CheckEnableEvent extends AuthEvent {} + class ChangeValidateEvent extends AuthEvent {} class SelectRegionEvent extends AuthEvent { @@ -62,4 +75,3 @@ class SelectRegionEvent extends AuthEvent { @override List get props => [val]; } - diff --git a/lib/pages/auth/bloc/auth_state.dart b/lib/pages/auth/bloc/auth_state.dart index 973ee400..8f2f6bd5 100644 --- a/lib/pages/auth/bloc/auth_state.dart +++ b/lib/pages/auth/bloc/auth_state.dart @@ -12,6 +12,7 @@ class LoginInitial extends AuthState {} class AuthTokenLoading extends AuthState {} class AuthLoading extends AuthState {} + class AuthInitialState extends AuthState {} class LoginSuccess extends AuthState {} @@ -55,7 +56,8 @@ class TimerState extends AuthState { final bool isButtonEnabled; final int remainingTime; - const TimerState({required this.isButtonEnabled, required this.remainingTime}); + const TimerState( + {required this.isButtonEnabled, required this.remainingTime}); @override List get props => [isButtonEnabled, remainingTime]; @@ -74,6 +76,7 @@ class AuthTokenError extends AuthError { class AuthSuccess extends AuthState {} class AuthTokenSuccess extends AuthSuccess {} + class TimerUpdated extends AuthState { final String formattedTime; final bool isButtonEnabled; diff --git a/lib/pages/auth/model/region_model.dart b/lib/pages/auth/model/region_model.dart index fd6306a2..3a2afcd1 100644 --- a/lib/pages/auth/model/region_model.dart +++ b/lib/pages/auth/model/region_model.dart @@ -1,5 +1,3 @@ - - class RegionModel { final String name; final String id; diff --git a/lib/pages/auth/model/token.dart b/lib/pages/auth/model/token.dart index 3e0728bc..53c6f401 100644 --- a/lib/pages/auth/model/token.dart +++ b/lib/pages/auth/model/token.dart @@ -42,14 +42,11 @@ class Token { //save token to secure storage var storage = const FlutterSecureStorage(); storage.write( - key: loginAccessTokenKey, - value: json[loginAccessTokenKey] ?? ''); + key: loginAccessTokenKey, value: json[loginAccessTokenKey] ?? ''); storage.write( - key: loginRefreshTokenKey, - value: json[loginRefreshTokenKey] ?? ''); + key: loginRefreshTokenKey, value: json[loginRefreshTokenKey] ?? ''); //create token object ? - return Token( - json[loginAccessTokenKey] ?? '', + return Token(json[loginAccessTokenKey] ?? '', json[loginRefreshTokenKey] ?? '', '', 0, 0); } diff --git a/lib/pages/auth/model/user_model.dart b/lib/pages/auth/model/user_model.dart index cbae385a..84d4661f 100644 --- a/lib/pages/auth/model/user_model.dart +++ b/lib/pages/auth/model/user_model.dart @@ -1,5 +1,3 @@ - - import 'package:syncrow_web/pages/auth/model/token.dart'; class UserModel { diff --git a/lib/pages/auth/model/verify_code.dart b/lib/pages/auth/model/verify_code.dart index da29c25b..a4a451ee 100644 --- a/lib/pages/auth/model/verify_code.dart +++ b/lib/pages/auth/model/verify_code.dart @@ -10,7 +10,10 @@ class VerifyPassCode { final String deviceId; VerifyPassCode( - {required this.phone, required this.passCode, required this.agent, required this.deviceId}); + {required this.phone, + required this.passCode, + required this.agent, + required this.deviceId}); factory VerifyPassCode.fromJson(Map json) => VerifyPassCode( phone: json[verificationPhone], diff --git a/lib/pages/auth/view/forget_password_mobile_page.dart b/lib/pages/auth/view/forget_password_mobile_page.dart index 2aab5894..05d472ba 100644 --- a/lib/pages/auth/view/forget_password_mobile_page.dart +++ b/lib/pages/auth/view/forget_password_mobile_page.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; class ForgetPasswordMobilePage extends StatelessWidget { diff --git a/lib/pages/auth/view/forget_password_page.dart b/lib/pages/auth/view/forget_password_page.dart index f90b2ef4..63b4d0f9 100644 --- a/lib/pages/auth/view/forget_password_page.dart +++ b/lib/pages/auth/view/forget_password_page.dart @@ -1,19 +1,15 @@ - import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/auth/view/forget_password_mobile_page.dart'; import 'package:syncrow_web/pages/auth/view/forget_password_web_page.dart'; import 'package:syncrow_web/utils/responsive_layout.dart'; - class ForgetPasswordPage extends StatelessWidget { const ForgetPasswordPage({super.key}); @override Widget build(BuildContext context) { - return const ResponsiveLayout( + return const ResponsiveLayout( desktopBody: ForgetPasswordWebPage(), - mobileBody:ForgetPasswordWebPage() - ); + mobileBody: ForgetPasswordWebPage()); } } - diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index a2a939e3..907569ee 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_event.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_state.dart'; import 'package:syncrow_web/pages/auth/model/region_model.dart'; -import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/first_layer.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -36,7 +36,7 @@ class ForgetPasswordWebPage extends StatelessWidget { } }, builder: (context, state) { - return _buildForm(context, state); + return _buildForm(context, state); }, ), ), @@ -66,7 +66,7 @@ class ForgetPasswordWebPage extends StatelessWidget { child: Stack( children: [ if (state is AuthLoading) - const Center(child: CircularProgressIndicator()), + const Center(child: CircularProgressIndicator()), ListView( shrinkWrap: true, controller: _scrollController, @@ -99,7 +99,8 @@ class ForgetPasswordWebPage extends StatelessWidget { borderRadius: const BorderRadius.all(Radius.circular(30)), border: Border.all( - color: ColorsManager.graysColor.withOpacity(0.2)), + color: + ColorsManager.graysColor.withOpacity(0.2)), ), child: Form( key: forgetBloc.forgetFormKey, @@ -108,7 +109,8 @@ class ForgetPasswordWebPage extends StatelessWidget { horizontal: size.width * 0.02, vertical: size.width * 0.003), child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 10), @@ -122,21 +124,27 @@ class ForgetPasswordWebPage extends StatelessWidget { const SizedBox(height: 10), Text( 'Please fill in your account information to\nretrieve your password', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontSize: 14, - fontWeight: FontWeight.w400), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontSize: 14, + fontWeight: FontWeight.w400), ), const SizedBox(height: 10), Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "Country/Region", - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontSize: 14, - fontWeight: FontWeight.w400), - + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontSize: 14, + fontWeight: FontWeight.w400), ), const SizedBox(height: 10), SizedBox( @@ -145,7 +153,8 @@ class ForgetPasswordWebPage extends StatelessWidget { icon: const Icon( Icons.keyboard_arrow_down_outlined, ), - decoration: textBoxDecoration()!.copyWith( + decoration: + textBoxDecoration()!.copyWith( hintText: null, ), hint: SizedBox( @@ -160,15 +169,14 @@ class ForgetPasswordWebPage extends StatelessWidget { ), ), isDense: true, - style: - const TextStyle(color: Colors.black), + style: const TextStyle( + color: Colors.black), items: forgetBloc.regionList! .map((RegionModel region) { return DropdownMenuItem( value: region.id, child: SizedBox( - width: size.width*0.06, - + width: size.width * 0.06, child: Text(region.name)), ); }).toList(), @@ -183,13 +191,18 @@ class ForgetPasswordWebPage extends StatelessWidget { ), const SizedBox(height: 20), Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "Account", - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), - + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontSize: 14, + fontWeight: FontWeight.w400), ), const SizedBox(height: 10), SizedBox( @@ -197,22 +210,29 @@ class ForgetPasswordWebPage extends StatelessWidget { validator: forgetBloc.validateEmail, controller: forgetBloc.forgetEmailController, - decoration: textBoxDecoration()!.copyWith( - hintText: 'Enter your email'), - style: - const TextStyle(color: Colors.black), + decoration: textBoxDecoration()! + .copyWith( + hintText: 'Enter your email'), + style: const TextStyle( + color: Colors.black), ), ), ], ), const SizedBox(height: 20.0), Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "One Time Password", - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontSize: 14, + fontWeight: FontWeight.w400), ), const SizedBox(height: 10), SizedBox( @@ -221,65 +241,88 @@ class ForgetPasswordWebPage extends StatelessWidget { keyboardType: TextInputType.visiblePassword, controller: forgetBloc.forgetOtp, - decoration: textBoxDecoration()!.copyWith( + decoration: + textBoxDecoration()!.copyWith( hintText: 'Enter Code', suffixIcon: SizedBox( width: 100, child: Center( child: InkWell( - onTap:state is TimerState && !state.isButtonEnabled && state.remainingTime!=1?null: () { - forgetBloc.add(StartTimerEvent()); - }, + onTap: state is TimerState && + !state + .isButtonEnabled && + state.remainingTime != + 1 + ? null + : () { + forgetBloc.add( + StartTimerEvent()); + }, child: Text( - 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime!=1? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', + 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', style: TextStyle( - color: state is TimerState && - !state.isButtonEnabled + color: state + is TimerState && + !state + .isButtonEnabled ? Colors.grey - : ColorsManager.btnColor, + : ColorsManager + .btnColor, ), ), ), ), ), ), - style: - const TextStyle(color: Colors.black), + style: const TextStyle( + color: Colors.black), ), ), - if (forgetBloc.forgetValidate != '') // Check if there is a validation message + if (forgetBloc.forgetValidate != + '') // Check if there is a validation message Padding( - padding: const EdgeInsets.only(top: 8.0), + padding: + const EdgeInsets.only(top: 8.0), child: Text( forgetBloc.forgetValidate, style: const TextStyle( - color: ColorsManager.red, - fontSize: 10, - fontWeight: FontWeight.w700 - ), + color: ColorsManager.red, + fontSize: 10, + fontWeight: FontWeight.w700), ), ), ], ), const SizedBox(height: 20.0), Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "Password", - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontSize: 14, + fontWeight: FontWeight.w400), ), const SizedBox(height: 10), SizedBox( child: TextFormField( - validator: forgetBloc.passwordValidator, - keyboardType: TextInputType.visiblePassword, - controller: forgetBloc.forgetPasswordController, - decoration: textBoxDecoration()!.copyWith( + validator: + forgetBloc.passwordValidator, + keyboardType: + TextInputType.visiblePassword, + controller: forgetBloc + .forgetPasswordController, + decoration: + textBoxDecoration()!.copyWith( hintText: 'At least 8 characters', ), - style: const TextStyle(color: Colors.black), + style: const TextStyle( + color: Colors.black), ), ), ], @@ -289,17 +332,22 @@ class ForgetPasswordWebPage extends StatelessWidget { ), const SizedBox(height: 20.0), Row( - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: size.width * 0.2, child: DefaultButton( - backgroundColor: ColorsManager.btnColor, + backgroundColor: + ColorsManager.btnColor, child: const Text('Submit'), onPressed: () { - if (forgetBloc.forgetFormKey.currentState!.validate()) { - forgetBloc.add(ChangePasswordEvent()); + if (forgetBloc + .forgetFormKey.currentState! + .validate()) { + forgetBloc + .add(ChangePasswordEvent()); } }, ), @@ -321,8 +369,10 @@ class ForgetPasswordWebPage extends StatelessWidget { SizedBox( width: size.width * 0.2, child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, children: [ const Flexible( child: Text( diff --git a/lib/pages/auth/view/login_mobile_page.dart b/lib/pages/auth/view/login_mobile_page.dart index d6544a98..1413db5d 100644 --- a/lib/pages/auth/view/login_mobile_page.dart +++ b/lib/pages/auth/view/login_mobile_page.dart @@ -8,7 +8,7 @@ import 'package:syncrow_web/pages/auth/bloc/auth_event.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_state.dart'; import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/view/forget_password_page.dart'; -import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; @@ -27,7 +27,7 @@ class LoginMobilePage extends StatelessWidget { // Navigate to home screen after successful login Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => HomePage()), + MaterialPageRoute(builder: (context) => HomePage()), ); } else if (state is LoginFailure) { // Show error message @@ -148,14 +148,14 @@ class LoginMobilePage extends StatelessWidget { ), isDense: true, style: const TextStyle(color: Colors.black), - items:loginBloc.regionList!.map((RegionModel region) { + items: loginBloc.regionList! + .map((RegionModel region) { return DropdownMenuItem( value: region.name, child: Text(region.name), ); }).toList(), - onChanged: (String? value) { - }, + onChanged: (String? value) {}, ), ) ], @@ -194,7 +194,8 @@ class LoginMobilePage extends StatelessWidget { validator: loginBloc.validatePassword, obscureText: loginBloc.obscureText, keyboardType: TextInputType.visiblePassword, - controller: loginBloc.loginPasswordController, + controller: + loginBloc.loginPasswordController, decoration: textBoxDecoration()!.copyWith( hintText: 'At least 8 characters', ), @@ -220,7 +221,8 @@ class LoginMobilePage extends StatelessWidget { }, child: Text( "Forgot Password?", - style: Theme.of(context).textTheme.bodySmall, + style: + Theme.of(context).textTheme.bodySmall, ), ), ], @@ -295,12 +297,15 @@ class LoginMobilePage extends StatelessWidget { : ColorsManager.grayColor, child: const Text('Sign in'), onPressed: () { - if (loginBloc.loginFormKey.currentState!.validate()) { + if (loginBloc.loginFormKey.currentState! + .validate()) { loginBloc.add( LoginButtonPressed( - regionUuid:'' , - username: loginBloc.loginEmailController.text, - password: loginBloc.loginPasswordController.text, + regionUuid: '', + username: + loginBloc.loginEmailController.text, + password: loginBloc + .loginPasswordController.text, ), ); } diff --git a/lib/pages/auth/view/login_page.dart b/lib/pages/auth/view/login_page.dart index 92487fe4..31907c68 100644 --- a/lib/pages/auth/view/login_page.dart +++ b/lib/pages/auth/view/login_page.dart @@ -9,8 +9,6 @@ class LoginPage extends StatelessWidget { @override Widget build(BuildContext context) { return const ResponsiveLayout( - desktopBody: LoginWebPage(), - mobileBody:LoginWebPage() - ); + desktopBody: LoginWebPage(), mobileBody: LoginWebPage()); } } diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index 9ce60a4c..7e9123ee 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -1,7 +1,5 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; @@ -10,7 +8,7 @@ import 'package:syncrow_web/pages/auth/bloc/auth_event.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_state.dart'; import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/view/forget_password_page.dart'; -import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/first_layer.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -25,7 +23,6 @@ class LoginWebPage extends StatefulWidget { } class _LoginWebPageState extends State { - @override Widget build(BuildContext context) { return Scaffold( @@ -34,7 +31,7 @@ class _LoginWebPageState extends State { child: BlocConsumer( listener: (context, state) { if (state is LoginSuccess) { - context.pushReplacement(RoutesConst.home); + context.go(RoutesConst.home); } else if (state is LoginFailure) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -44,16 +41,16 @@ class _LoginWebPageState extends State { } }, builder: (context, state) { - return _buildLoginForm(context,state); + return _buildLoginForm(context, state); }, ), ), ); } - Widget _buildLoginForm(BuildContext context,AuthState state) { + Widget _buildLoginForm(BuildContext context, AuthState state) { final loginBloc = BlocProvider.of(context); - Size size = MediaQuery.of(context).size; + Size size = MediaQuery.of(context).size; late ScrollController _scrollController; _scrollController = ScrollController(); void _scrollToCenter() { @@ -64,6 +61,7 @@ class _LoginWebPageState extends State { curve: Curves.easeInOut, ); } + WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToCenter(); }); @@ -76,14 +74,14 @@ class _LoginWebPageState extends State { shrinkWrap: true, children: [ Container( - padding: EdgeInsets.all(size.width*0.02) , - margin: EdgeInsets.all(size.width*0.09), + padding: EdgeInsets.all(size.width * 0.02), + margin: EdgeInsets.all(size.width * 0.09), decoration: BoxDecoration( color: Colors.black.withOpacity(0.3), borderRadius: const BorderRadius.all(Radius.circular(20)), ), child: Center( - child:Row( + child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -97,43 +95,48 @@ class _LoginWebPageState extends State { const Spacer(), Expanded( flex: 3, - child: Container( + child: Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), borderRadius: const BorderRadius.all(Radius.circular(30)), - border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2))), + border: Border.all( + color: ColorsManager.graysColor.withOpacity(0.2))), child: Form( key: loginBloc.loginFormKey, child: Padding( - padding: EdgeInsets.symmetric( - horizontal: size.width*0.02, - vertical: size.width*0.003), + padding: EdgeInsets.symmetric( + horizontal: size.width * 0.02, + vertical: size.width * 0.003), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 40), - Text( - 'Login', - style:Theme.of(context).textTheme.headlineLarge), - SizedBox(height: size.height*0.03), + Text('Login', + style: Theme.of(context).textTheme.headlineLarge), + SizedBox(height: size.height * 0.03), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "Country/Region", - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontSize: 14, fontWeight: FontWeight.w400), + ), + const SizedBox( + height: 10, ), - const SizedBox(height: 10,), - SizedBox( child: DropdownButtonFormField( padding: EdgeInsets.zero, - value: loginBloc.regionList!.any((region) => region.id == loginBloc.regionUuid) + value: loginBloc.regionList!.any((region) => + region.id == loginBloc.regionUuid) ? loginBloc.regionUuid : null, - validator: loginBloc.validateRegion, icon: const Icon( Icons.keyboard_arrow_down_outlined, @@ -144,25 +147,29 @@ class _LoginWebPageState extends State { ), hint: SizedBox( width: size.width * 0.12, - child: Align( + child: Align( alignment: Alignment.centerLeft, child: Text( 'Select your region/country', textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.w400), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400), overflow: TextOverflow.ellipsis, ), ), ), isDense: true, style: const TextStyle(color: Colors.black), - items: loginBloc.regionList!.map((RegionModel region) { + items: + loginBloc.regionList!.map((RegionModel region) { return DropdownMenuItem( value: region.id, child: SizedBox( - width: size.width*0.08, + width: size.width * 0.08, child: Text(region.name)), ); }).toList(), @@ -172,8 +179,6 @@ class _LoginWebPageState extends State { }, ), ) - - ], ), const SizedBox(height: 20.0), @@ -181,8 +186,13 @@ class _LoginWebPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - Text("Email", - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + Text( + "Email", + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontSize: 14, fontWeight: FontWeight.w400), ), const SizedBox( height: 10, @@ -190,18 +200,21 @@ class _LoginWebPageState extends State { SizedBox( child: TextFormField( onChanged: (value) { - loginBloc.add(CheckEnableEvent()); - // print(loginBloc.checkEnable()); + loginBloc.add(CheckEnableEvent()); + // print(loginBloc.checkEnable()); }, - validator:loginBloc.loginValidateEmail , - controller:loginBloc.loginEmailController, + validator: loginBloc.loginValidateEmail, + controller: loginBloc.loginEmailController, decoration: textBoxDecoration()!.copyWith( - errorStyle: const TextStyle(height: 0), // Hide the error text space + errorStyle: const TextStyle( + height: 0), // Hide the error text space hintText: 'Enter your email address', - hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.w400) - ), + hintStyle: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400)), style: const TextStyle(color: Colors.black), ), ), @@ -212,8 +225,13 @@ class _LoginWebPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - Text("Password", - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + Text( + "Password", + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontSize: 14, fontWeight: FontWeight.w400), ), const SizedBox( height: 10, @@ -222,30 +240,36 @@ class _LoginWebPageState extends State { child: TextFormField( onChanged: (value) { loginBloc.add(CheckEnableEvent()); - }, - validator:loginBloc.validatePassword, - obscureText:loginBloc.obscureText, + }, + validator: loginBloc.validatePassword, + obscureText: loginBloc.obscureText, keyboardType: TextInputType.visiblePassword, - controller:loginBloc.loginPasswordController, + controller: loginBloc.loginPasswordController, decoration: textBoxDecoration()!.copyWith( - hintText: 'At least 8 characters', - hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.w400), - suffixIcon: IconButton(onPressed: () { - loginBloc.add(PasswordVisibleEvent(newValue: loginBloc.obscureText)); + hintText: 'At least 8 characters', + hintStyle: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400), + suffixIcon: IconButton( + onPressed: () { + loginBloc.add(PasswordVisibleEvent( + newValue: loginBloc.obscureText)); }, - icon: SizedBox( - child: SvgPicture.asset( - loginBloc.obscureText? - Assets.visiblePassword : - Assets.invisiblePassword, - height: 15, - width: 15, - ), + icon: SizedBox( + child: SvgPicture.asset( + loginBloc.obscureText + ? Assets.visiblePassword + : Assets.invisiblePassword, + height: 15, + width: 15, ), ), - errorStyle: const TextStyle(height: 0), // Hide the error text space + ), + errorStyle: const TextStyle( + height: 0), // Hide the error text space ), style: const TextStyle(color: Colors.black), ), @@ -261,11 +285,20 @@ class _LoginWebPageState extends State { children: [ InkWell( onTap: () { - Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ForgetPasswordPage(),)); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => + const ForgetPasswordPage(), + )); }, child: Text( "Forgot Password?", - style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black,fontSize: 14,fontWeight: FontWeight.w400), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w400), ), ), ], @@ -279,9 +312,10 @@ class _LoginWebPageState extends State { Transform.scale( scale: 1.2, // Adjust the scale as needed child: Checkbox( - fillColor: MaterialStateProperty.all(Colors.white), + fillColor: + MaterialStateProperty.all(Colors.white), activeColor: Colors.white, - value:loginBloc.isChecked, + value: loginBloc.isChecked, checkColor: Colors.black, shape: const CircleBorder(), onChanged: (bool? newValue) { @@ -290,7 +324,7 @@ class _LoginWebPageState extends State { ), ), SizedBox( - width:size.width * 0.14, + width: size.width * 0.14, child: RichText( text: TextSpan( text: 'Agree to ', @@ -299,7 +333,8 @@ class _LoginWebPageState extends State { TextSpan( text: '(Terms of Service)', style: const TextStyle( - color: Colors.black,), + color: Colors.black, + ), recognizer: TapGestureRecognizer() ..onTap = () { loginBloc.launchURL( @@ -317,8 +352,7 @@ class _LoginWebPageState extends State { ), TextSpan( text: ' (Privacy Statement)', - style: const TextStyle( - color: Colors.black), + style: const TextStyle(color: Colors.black), recognizer: TapGestureRecognizer() ..onTap = () { loginBloc.launchURL( @@ -337,28 +371,33 @@ class _LoginWebPageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( - width:size.width * 0.2, + width: size.width * 0.2, child: DefaultButton( enabled: loginBloc.checkValidate, - child:Text('Sign in', - style: Theme.of(context).textTheme.labelLarge !.copyWith( - fontSize: 14, - color: - loginBloc.checkValidate ? - ColorsManager.whiteColors:ColorsManager.whiteColors.withOpacity(0.2), - ) - ), + child: Text('Sign in', + style: Theme.of(context) + .textTheme + .labelLarge! + .copyWith( + fontSize: 14, + color: loginBloc.checkValidate + ? ColorsManager.whiteColors + : ColorsManager.whiteColors + .withOpacity(0.2), + )), onPressed: () { - if(loginBloc.loginFormKey.currentState!.validate() ){ + if (loginBloc.loginFormKey.currentState! + .validate()) { loginBloc.add(LoginButtonPressed( - regionUuid:loginBloc.regionUuid, + regionUuid: loginBloc.regionUuid, username: loginBloc.loginEmailController.text, - password: loginBloc.loginPasswordController.text, + password: + loginBloc.loginPasswordController.text, )); - }else{ + } else { loginBloc.add(ChangeValidateEvent()); } - }, + }, ), ), ], @@ -367,23 +406,31 @@ class _LoginWebPageState extends State { Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, - children: [ SizedBox(child: Text(loginBloc.validate, - style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),)],) + children: [ + SizedBox( + child: Text( + loginBloc.validate, + style: const TextStyle( + fontWeight: FontWeight.w700, + color: ColorsManager.red), + ), + ) + ], + ) ], ), ), - ) - )), + ))), const Spacer(), ], - ),), + ), + ), ), ], ), ), ), - if (state is AuthLoading) - const Center(child: CircularProgressIndicator()) + if (state is AuthLoading) const Center(child: CircularProgressIndicator()) ], ); } diff --git a/lib/pages/common/buttons/default_button.dart b/lib/pages/common/buttons/default_button.dart new file mode 100644 index 00000000..5aa506f8 --- /dev/null +++ b/lib/pages/common/buttons/default_button.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DefaultButton extends StatelessWidget { + const DefaultButton({ + super.key, + this.enabled = true, + this.onPressed, + required this.child, + this.isSecondary = false, + this.isLoading = false, + this.isDone = false, + this.customTextStyle, + this.customButtonStyle, + this.backgroundColor, + this.foregroundColor, + this.borderRadius, + this.height, + this.padding, + }); + final void Function()? onPressed; + final Widget child; + final double? height; + final bool isSecondary; + final double? borderRadius; + final bool enabled; + final double? padding; + final bool isDone; + final bool isLoading; + final TextStyle? customTextStyle; + final ButtonStyle? customButtonStyle; + final Color? backgroundColor; + final Color? foregroundColor; + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: enabled ? onPressed : null, + style: isSecondary + ? null + : customButtonStyle ?? + ButtonStyle( + textStyle: MaterialStateProperty.all( + customTextStyle ?? + Theme.of(context).textTheme.bodySmall!.copyWith( + fontSize: 13, + color: foregroundColor, + fontWeight: FontWeight.normal), + ), + foregroundColor: MaterialStateProperty.all( + isSecondary + ? Colors.black + : enabled + ? foregroundColor ?? Colors.white + : Colors.black, + ), + backgroundColor: MaterialStateProperty.resolveWith( + (Set states) { + return enabled + ? backgroundColor ?? ColorsManager.primaryColor + : Colors.black.withOpacity(0.2); + }), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius ?? 20), + ), + ), + fixedSize: MaterialStateProperty.all( + const Size.fromHeight(50), + ), + padding: MaterialStateProperty.all( + EdgeInsets.all(padding ?? 10), + ), + minimumSize: MaterialStateProperty.all( + const Size.fromHeight(50), + ), + ), + child: SizedBox( + height: height ?? 50, + child: Center( + child: isLoading + ? const SizedBox.square( + dimension: 24, + child: CircularProgressIndicator( + color: Colors.white, + ), + ) + : isDone + ? const Icon( + Icons.check_circle_outline, + color: Colors.white, + ) + : child, + ), + ), + ); + } +} diff --git a/lib/pages/common/buttons/search_reset_buttons.dart b/lib/pages/common/buttons/search_reset_buttons.dart new file mode 100644 index 00000000..a03b889a --- /dev/null +++ b/lib/pages/common/buttons/search_reset_buttons.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/utils/style.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class SearchResetButtons extends StatelessWidget { + const SearchResetButtons({ + super.key, + required this.onSearch, + required this.onReset, + }); + + final VoidCallback onSearch; + final VoidCallback onReset; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 25), + Center( + child: Container( + height: 43, + width: 100, + decoration: containerDecoration, + child: Center( + child: DefaultButton( + onPressed: onSearch, + borderRadius: 9, + child: const Text('Search'), + ), + ), + ), + ), + ], + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 25), + Center( + child: Container( + height: 43, + width: 100, + decoration: containerDecoration, + child: Center( + child: DefaultButton( + backgroundColor: ColorsManager.whiteColors, + borderRadius: 9, + child: Text( + 'Reset', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black), + ), + onPressed: onReset, + ), + ), + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/pages/common/custom_dialog.dart b/lib/pages/common/custom_dialog.dart index 7b3e7192..e75c1e90 100644 --- a/lib/pages/common/custom_dialog.dart +++ b/lib/pages/common/custom_dialog.dart @@ -12,7 +12,8 @@ Future showCustomDialog({ double? iconHeight, double? iconWidth, VoidCallback? onOkPressed, - bool barrierDismissible = false, required actions, + bool barrierDismissible = false, + required actions, }) { return showDialog( context: context, diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index dc3c3afb..fdeff6b6 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -12,7 +12,7 @@ class DynamicTable extends StatefulWidget { final bool withCheckBox; final bool isEmpty; final void Function(bool?)? selectAll; - final void Function(int, bool?)? onRowCheckboxChanged; + final void Function(int, bool, dynamic)? onRowSelected; final List? initialSelectedIds; const DynamicTable({ super.key, @@ -24,7 +24,7 @@ class DynamicTable extends StatefulWidget { this.headerDecoration, this.cellDecoration, this.selectAll, - this.onRowCheckboxChanged, + this.onRowSelected, this.initialSelectedIds, }); @@ -34,7 +34,6 @@ class DynamicTable extends StatefulWidget { class _DynamicTableState extends State { late List _selected; - bool _selectAll = false; @override void initState() { @@ -43,25 +42,14 @@ class _DynamicTableState extends State { return widget.initialSelectedIds != null && widget.initialSelectedIds!.contains(widget.data[index][1]); }); - _selectAll = _selected.every((element) => element == true); } - void _toggleSelectAll(bool? value) { + void _toggleRowSelection(int index) { setState(() { - _selectAll = value ?? false; - _selected = List.filled(widget.data.length, _selectAll); - if (widget.selectAll != null) { - widget.selectAll!(_selectAll); - } - }); - } + _selected[index] = !_selected[index]; - void _toggleRowSelection(int index, bool? value) { - setState(() { - _selected[index] = value ?? false; - _selectAll = _selected.every((element) => element == true); - if (widget.onRowCheckboxChanged != null) { - widget.onRowCheckboxChanged!(index, _selected[index]); + if (widget.onRowSelected != null) { + widget.onRowSelected!(index, _selected[index], widget.data[index]); } }); } @@ -70,76 +58,74 @@ class _DynamicTableState extends State { Widget build(BuildContext context) { return Container( decoration: widget.cellDecoration, - child: Padding( - padding: const EdgeInsets.all(2.0), - child: - ListView( - scrollDirection: Axis.horizontal, - children: [ - SizedBox( - width: widget.size.width, - child: Column( - children: [ - Container( - decoration: widget.headerDecoration ?? - BoxDecoration(color: Colors.grey[200]), - child: Row( - children: [ - if (widget.withCheckBox) - _buildSelectAllCheckbox(), - ...widget.headers - .map((header) => _buildTableHeaderCell(header)) - .toList(), - ], - ), - ), - widget.isEmpty? - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - SvgPicture.asset( - Assets.emptyTable - ), - const SizedBox(height: 15,), - Text('No Passwords',style: Theme.of(context).textTheme.bodySmall!.copyWith(color:ColorsManager.grayColor ),) - ], - ), - ], - ), - ], - ), - ): - Expanded( - child: Container( - color: Colors.white, - child: ListView.builder( - shrinkWrap: true, - itemCount: widget.data.length, - itemBuilder: (context, index) { - final row = widget.data[index]; - return Row( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: widget.size.width, + child: Column( + children: [ + Container( + decoration: widget.headerDecoration ?? BoxDecoration(color: Colors.grey[200]), + child: Row( + children: [ + if (widget.withCheckBox) _buildSelectAllCheckbox(), + ...widget.headers.map((header) => _buildTableHeaderCell(header)).toList(), + ], + ), + ), + widget.isEmpty + ? Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - if (widget.withCheckBox) - _buildRowCheckbox(index,widget.size.height*0.10), - ...row.map((cell) => - _buildTableCell(cell.toString(),widget.size.height*0.10)).toList(), + Column( + children: [ + SvgPicture.asset(Assets.emptyTable), + const SizedBox( + height: 15, + ), + Text( + 'No Devices', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: ColorsManager.grayColor), + ) + ], + ), ], - ); - }, + ), + ], + ), + ) + : Expanded( + child: Container( + color: Colors.white, + child: ListView.builder( + shrinkWrap: true, + itemCount: widget.data.length, + itemBuilder: (context, index) { + final row = widget.data[index]; + return Row( + children: [ + if (widget.withCheckBox) + _buildRowCheckbox(index, widget.size.height * 0.10), + ...row + .map((cell) => + _buildTableCell(cell.toString(), widget.size.height * 0.10)) + .toList(), + ], + ); + }, + ), ), ), - ), - ], - ), - ), - ], + ], + ), ), ), ); @@ -147,57 +133,77 @@ class _DynamicTableState extends State { Widget _buildSelectAllCheckbox() { return Container( + width: 50, padding: const EdgeInsets.all(8.0), - decoration: const BoxDecoration( - border: Border.symmetric( - vertical: BorderSide(color: ColorsManager.boxDivider))), + border: Border.symmetric( + vertical: BorderSide(color: ColorsManager.boxDivider), + ), + ), child: Checkbox( - value: _selectAll, - onChanged: _toggleSelectAll, + value: _selected.every((element) => element == true), + onChanged: null, ), ); } - Widget _buildRowCheckbox(int index,size) { + Widget _buildRowCheckbox(int index, double size) { return Container( - padding: const EdgeInsets.all(8.0), - - height:size , + width: 50, + padding: const EdgeInsets.all(8.0), + height: size, decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: ColorsManager.boxDivider, - width: 1.0, - ), - )), + border: Border( + bottom: BorderSide( + color: ColorsManager.boxDivider, + width: 1.0, + ), + ), + ), alignment: Alignment.centerLeft, - child: Center(child: Checkbox( - value: _selected[index], - onChanged: (bool? value) { - _toggleRowSelection(index, value); - }, - ),) + child: Center( + child: Checkbox( + value: _selected[index], + onChanged: (bool? value) { + _toggleRowSelection(index); + }, + ), + ), ); - } Widget _buildTableHeaderCell(String title) { return Expanded( child: Container( decoration: const BoxDecoration( - border: Border.symmetric( - vertical: BorderSide(color: ColorsManager.boxDivider))), + border: Border.symmetric( + vertical: BorderSide(color: ColorsManager.boxDivider), + ), + ), alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.all(8.0), - child: Text(title, style: const TextStyle(fontWeight: FontWeight.w400,fontSize: 13,color: Color(0xFF999999))), + child: Text( + title, + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 13, + color: Color(0xFF999999), + ), + ), ), ), ); } - Widget _buildTableCell(String content,size) { + Widget _buildTableCell(String content, double size) { + bool isBatteryLevel = content.endsWith('%'); + double? batteryLevel; + + if (isBatteryLevel) { + batteryLevel = double.tryParse(content.replaceAll('%', '').trim()); + } + Color? statusColor; switch (content) { case 'Effective': @@ -209,28 +215,37 @@ class _DynamicTableState extends State { case 'To be effective': statusColor = ColorsManager.yaGreen; break; + case 'Online': + statusColor = ColorsManager.green; + break; + case 'Offline': + statusColor = ColorsManager.red; + break; default: statusColor = Colors.black; // Default color } + return Expanded( child: Container( - height:size , + height: size, padding: const EdgeInsets.all(5.0), decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: ColorsManager.boxDivider, - width: 1.0, - ), - )), + border: Border( + bottom: BorderSide( + color: ColorsManager.boxDivider, + width: 1.0, + ), + ), + ), alignment: Alignment.centerLeft, child: Text( content, - style: TextStyle( - color:statusColor, // Use the passed color or default to black - fontSize: 10, - fontWeight: FontWeight.w400 - ), + style: TextStyle( + color: batteryLevel != null && batteryLevel < 20 + ? ColorsManager.red + : statusColor, // Use the passed color or default to black + fontSize: 10, + fontWeight: FontWeight.w400), ), ), ); diff --git a/lib/pages/common/custom_web_textfield.dart b/lib/pages/common/custom_web_textfield.dart deleted file mode 100644 index a41bc662..00000000 --- a/lib/pages/common/custom_web_textfield.dart +++ /dev/null @@ -1,83 +0,0 @@ - -import 'package:flutter/material.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/style.dart'; -class CustomWebTextField extends StatelessWidget { - const CustomWebTextField({ - super.key, - required this.isRequired, - required this.textFieldName, - required this.controller, - this.description, - this.validator, - }); - - final bool isRequired; - final String textFieldName; - final String? description; - final TextEditingController? controller; - final String? Function(String?)? validator; - - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if(isRequired) - Row( - children: [ - Text('* ', - style: Theme.of(context) - .textTheme.bodyMedium! - .copyWith(color: Colors.red), - ), - Text(textFieldName, style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13),), - ], - ), - const SizedBox(width: 10,), - Expanded( - child: Text( - description??'', - style: Theme.of(context) - .textTheme.bodySmall! - .copyWith(fontSize: 9, - fontWeight: FontWeight.w400, - color: ColorsManager.textGray), - ), - ), - ], - ), - const SizedBox(height: 7,), - Container( - decoration: containerDecoration.copyWith( - color: const Color(0xFFF5F6F7), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius:2, - blurRadius: 3, - offset: const Offset(1, 1), // changes position of shadow - ), - ] - ), - child: TextFormField( - validator: validator, - controller: controller, - style: const TextStyle(color: Colors.black), - decoration: textBoxDecoration()! - .copyWith( - errorStyle: const TextStyle(height: 0), // Hide the error text space - hintStyle: const TextStyle(color: ColorsManager.grayColor), - hintText: 'Please enter'), - ), - ), - ], - ); - } -} diff --git a/lib/pages/common/date_time_widget.dart b/lib/pages/common/date_time_widget.dart index 7945ce33..0b8fa0da 100644 --- a/lib/pages/common/date_time_widget.dart +++ b/lib/pages/common/date_time_widget.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; class DateTimeWebWidget extends StatelessWidget { @@ -23,8 +22,8 @@ class DateTimeWebWidget extends StatelessWidget { final String firstString; final String secondString; final String icon; - final Function()? startTime; - final Function()? endTime; + final Function()? startTime; + final Function()? endTime; @override Widget build(BuildContext context) { @@ -32,60 +31,74 @@ class DateTimeWebWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - Row( - children: [ - if(isRequired) - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - Text(title??'' , - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13),), - ], - ), - const SizedBox(height: 8,), + Row( + children: [ + if (isRequired) + Text( + '* ', + style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Colors.red), + ), + Text( + title, + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black, fontSize: 13), + ), + ], + ), + const SizedBox( + height: 8, + ), Container( - height:50 , - padding: EdgeInsets.only(top: 10,bottom: 10,right: 30,left: 10), + height: size.height * 0.055, + padding: const EdgeInsets.only(top: 10, bottom: 10, right: 30, left: 10), decoration: containerDecoration, child: FittedBox( child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - InkWell( - onTap: startTime, - child: FittedBox( - child: Text(firstString, - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400),), - ) - ), - SizedBox(width: 30,), - const Icon(Icons.arrow_right_alt), - SizedBox(width: 30,), - InkWell( - onTap:endTime, - child: FittedBox( - child: Text(secondString, - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400), - ), - )), - SizedBox(width: 30,), - - SvgPicture.asset( - icon, - ), - ], + InkWell( + onTap: startTime, + child: FittedBox( + child: Text( + firstString, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + fontSize: 12, + fontWeight: FontWeight.w400), + ), + )), + const SizedBox( + width: 30, + ), + const Icon(Icons.arrow_right_alt), + const SizedBox( + width: 30, + ), + InkWell( + onTap: endTime, + child: FittedBox( + child: Text( + secondString, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + fontSize: 12, + fontWeight: FontWeight.w400), + ), + )), + const SizedBox( + width: 30, + ), + SvgPicture.asset( + icon, ), ], - )), + ), + ], + )), ), ], ); diff --git a/lib/pages/common/default_button.dart b/lib/pages/common/default_button.dart deleted file mode 100644 index 181e093a..00000000 --- a/lib/pages/common/default_button.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; - -class DefaultButton extends StatelessWidget { - const DefaultButton({ - super.key, - this.enabled = true, - this.onPressed, - required this.child, - this.isSecondary = false, - this.isLoading = false, - this.isDone = false, - this.customTextStyle, - this.customButtonStyle, - this.backgroundColor, - this.foregroundColor, - this.borderRadius, - this.height, - this.padding, - }); - final void Function()? onPressed; - final Widget child; - final double? height; - final bool isSecondary; - final double? borderRadius; - final bool enabled; - final double? padding; - final bool isDone; - final bool isLoading; - final TextStyle? customTextStyle; - final ButtonStyle? customButtonStyle; - final Color? backgroundColor; - final Color? foregroundColor; - @override - Widget build(BuildContext context) { - return ElevatedButton( - onPressed: enabled ? onPressed : null, - style: isSecondary - ? null - : customButtonStyle ?? - ButtonStyle( - textStyle: MaterialStateProperty.all( - customTextStyle - ?? Theme.of(context).textTheme.bodySmall!.copyWith( - fontSize: 13, - color: foregroundColor, - fontWeight: FontWeight.normal - ), - ), - - foregroundColor: MaterialStateProperty.all( - isSecondary - ? Colors.black - : enabled - ? foregroundColor ?? Colors.white - : Colors.black, - ), - backgroundColor: MaterialStateProperty.resolveWith( - (Set states) { - return enabled - ? backgroundColor ?? ColorsManager.primaryColor - : Colors.black.withOpacity(0.2); - }), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(borderRadius ?? 20), - ), - ), - fixedSize: MaterialStateProperty.all( - const Size.fromHeight(50), - ), - padding: MaterialStateProperty.all( - EdgeInsets.all(padding ?? 10), - ), - minimumSize: MaterialStateProperty.all( - const Size.fromHeight(50), - ), - ), - child: SizedBox( - height: height ?? 50, - child: Center( - child: isLoading - ? const SizedBox.square( - dimension: 24, - child: CircularProgressIndicator( - color: Colors.white, - ), - ) - : isDone - ? const Icon( - Icons.check_circle_outline, - color: Colors.white, - ) - : child, - ), - ), - ); - } -} diff --git a/lib/pages/common/filter/filter_widget.dart b/lib/pages/common/filter/filter_widget.dart new file mode 100644 index 00000000..d6cfcc7e --- /dev/null +++ b/lib/pages/common/filter/filter_widget.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class FilterWidget extends StatelessWidget { + const FilterWidget({ + super.key, + required this.size, + required this.tabs, + required this.selectedIndex, + required this.onTabChanged, + }); + + final Size size; + final List tabs; + final int selectedIndex; + final Function(int) onTabChanged; + + @override + Widget build(BuildContext context) { + return Container( + decoration: containerDecoration, + height: size.height * 0.05, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: tabs.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final isSelected = index == selectedIndex; + return InkWell( + onTap: () { + onTabChanged(index); + }, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.boxColor, + border: Border.all( + color: isSelected + ? ColorsManager.blueColor.withOpacity(0.8) + : Colors.transparent, + width: 2.0, + ), + borderRadius: _getBorderRadius(index), + ), + padding: const EdgeInsets.only(left: 10, right: 10), + child: Center( + child: Text( + tabs[index], + style: TextStyle( + color: isSelected + ? ColorsManager.blueColor.withOpacity(0.8) + : Colors.black, + ), + ), + ), + ), + ); + }, + ), + ); + } + + BorderRadius? _getBorderRadius(int index) { + if (index == 0) { + return const BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10), + ); + } else if (index == tabs.length - 1) { + return const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ); + } + return null; + } +} diff --git a/lib/pages/common/first_layer.dart b/lib/pages/common/first_layer.dart index a566f6d9..8941950e 100644 --- a/lib/pages/common/first_layer.dart +++ b/lib/pages/common/first_layer.dart @@ -4,7 +4,7 @@ import 'package:syncrow_web/utils/constants/assets.dart'; class FirstLayer extends StatelessWidget { final Widget? second; - const FirstLayer({super.key,this.second}); + const FirstLayer({super.key, this.second}); @override Widget build(BuildContext context) { diff --git a/lib/pages/common/hour_picker_dialog.dart b/lib/pages/common/hour_picker_dialog.dart index 718f1ebf..2c89b710 100644 --- a/lib/pages/common/hour_picker_dialog.dart +++ b/lib/pages/common/hour_picker_dialog.dart @@ -1,5 +1,3 @@ - - import 'package:flutter/material.dart'; class HourPickerDialog extends StatefulWidget { @@ -17,7 +15,9 @@ class _HourPickerDialogState extends State { @override void initState() { super.initState(); - _selectedHour = widget.initialTime.hour > 12 ? widget.initialTime.hour - 12 : widget.initialTime.hour; + _selectedHour = widget.initialTime.hour > 12 + ? widget.initialTime.hour - 12 + : widget.initialTime.hour; _isPm = widget.initialTime.period == DayPeriod.pm; } diff --git a/lib/pages/common/info_dialog.dart b/lib/pages/common/info_dialog.dart index cfd2cbd4..97656418 100644 --- a/lib/pages/common/info_dialog.dart +++ b/lib/pages/common/info_dialog.dart @@ -35,7 +35,6 @@ class InfoDialog extends StatelessWidget { width: 35, ), ), - Text( title, style: Theme.of(context).textTheme.headlineLarge!.copyWith( diff --git a/lib/pages/common/text_field/custom_text_field.dart b/lib/pages/common/text_field/custom_text_field.dart new file mode 100644 index 00000000..587a1f4f --- /dev/null +++ b/lib/pages/common/text_field/custom_text_field.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/core/extension/build_context_x.dart'; + +class StatefulTextField extends StatefulWidget { + const StatefulTextField({ + super.key, + required this.title, + this.hintText = 'Please enter', + required this.width, + this.elevation = 0, + required this.controller, // Add the controller + }); + + final String title; + final String hintText; + final double width; + final double elevation; + final TextEditingController controller; + + @override + State createState() => _StatefulTextFieldState(); +} + +class _StatefulTextFieldState extends State { + @override + Widget build(BuildContext context) { + return CustomTextField( + title: widget.title, + controller: widget.controller, + hintText: widget.hintText, + width: widget.width, + elevation: widget.elevation, + ); + } +} + +class CustomTextField extends StatelessWidget { + const CustomTextField({ + super.key, + required this.title, + required this.controller, + this.hintText = 'Please enter', + required this.width, + this.elevation = 0, + }); + + final String title; + final TextEditingController controller; + final String hintText; + final double width; + final double elevation; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + title, + style: context.textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w600, + color: const Color(0xff000000), + ), + ), + const SizedBox(height: 8), + Material( + elevation: elevation, + borderRadius: BorderRadius.circular(8), + child: Container( + width: width, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: TextFormField( + controller: controller, + style: const TextStyle(color: Colors.black), + decoration: InputDecoration( + hintText: hintText, + contentPadding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + border: InputBorder.none, + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/common/text_field/custom_web_textfield.dart b/lib/pages/common/text_field/custom_web_textfield.dart new file mode 100644 index 00000000..6fe0dc49 --- /dev/null +++ b/lib/pages/common/text_field/custom_web_textfield.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class CustomWebTextField extends StatelessWidget { + const CustomWebTextField({ + super.key, + required this.isRequired, + required this.textFieldName, + required this.controller, + this.description, + this.validator, + this.hintText, + }); + + final bool isRequired; + final String textFieldName; + final String? description; + final TextEditingController? controller; + final String? Function(String?)? validator; + final String? hintText; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (isRequired) + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + Text( + textFieldName, + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black, fontSize: 13), + ), + ], + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Text( + description ?? '', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontSize: 9, + fontWeight: FontWeight.w400, + color: ColorsManager.textGray), + ), + ), + ], + ), + const SizedBox( + height: 7, + ), + Container( + decoration: containerDecoration + .copyWith(color: const Color(0xFFF5F6F7), boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 3, + offset: const Offset(1, 1), // changes position of shadow + ), + ]), + child: TextFormField( + validator: validator, + controller: controller, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration()!.copyWith( + errorStyle: + const TextStyle(height: 0), // Hide the error text space + + hintText: hintText ?? 'Please enter'), + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart new file mode 100644 index 00000000..5d147439 --- /dev/null +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -0,0 +1,136 @@ +import 'dart:async'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class AcBloc extends Bloc { + late AcStatusModel deviceStatus; + final String deviceId; + Timer? _timer; + + AcBloc({required this.deviceId}) : super(AcsInitialState()) { + on(_onFetchAcStatus); + on(_onAcControl); + } + + FutureOr _onFetchAcStatus( + AcFetchDeviceStatus event, Emitter emit) async { + emit(AcsLoadingState()); + try { + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); + emit(ACStatusLoaded(deviceStatus)); + } catch (e) { + emit(AcsFailedState(error: e.toString())); + } + } + + FutureOr _onAcControl(AcControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value, emit); + + emit(ACStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + ); + } + + Future _runDebounce({ + required String deviceId, + required String code, + required dynamic value, + required dynamic oldValue, + required Emitter emit, + }) async { + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + try { + final response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + if (!response) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + } catch (e) { + if (e is DioException && e.response != null) { + debugPrint('Error response: ${e.response?.data}'); + } + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + }); + } + + void _revertValueAndEmit( + String deviceId, String code, dynamic oldValue, Emitter emit) { + _updateLocalValue(code, oldValue, emit); + emit(ACStatusLoaded(deviceStatus)); + emit(const AcsFailedState(error: 'Failed to control the device.')); + } + + void _updateLocalValue(String code, dynamic value, Emitter emit) { + switch (code) { + case 'switch': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(acSwitch: value); + } + break; + case 'temp_set': + if (value is int) { + deviceStatus = deviceStatus.copyWith(tempSet: value); + } + break; + case 'mode': + if (value is String) { + deviceStatus = deviceStatus.copyWith( + modeString: value, + ); + } + break; + case 'level': + if (value is String) { + deviceStatus = deviceStatus.copyWith( + fanSpeedsString: value, + ); + } + break; + case 'child_lock': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(childLock: value); + } + break; + default: + break; + } + emit(ACStatusLoaded(deviceStatus)); + } + + dynamic _getValueByCode(String code) { + switch (code) { + case 'switch': + return deviceStatus.acSwitch; + case 'temp_set': + return deviceStatus.tempSet; + case 'mode': + return deviceStatus.modeString; + case 'level': + return deviceStatus.fanSpeedsString; + case 'child_lock': + return deviceStatus.childLock; + default: + return null; + } + } +} diff --git a/lib/pages/device_managment/ac/bloc/ac_event.dart b/lib/pages/device_managment/ac/bloc/ac_event.dart new file mode 100644 index 00000000..400c8136 --- /dev/null +++ b/lib/pages/device_managment/ac/bloc/ac_event.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +sealed class AcsEvent extends Equatable { + const AcsEvent(); + + @override + List get props => []; +} + +class AcFetchDeviceStatus extends AcsEvent { + final String deviceId; + + const AcFetchDeviceStatus(this.deviceId); + + @override + List get props => [deviceId]; +} + +class AcControl extends AcsEvent { + final String deviceId; + final String code; + final dynamic value; + + const AcControl({ + required this.deviceId, + required this.code, + required this.value, + }); + + @override + List get props => [deviceId, code, value]; +} diff --git a/lib/pages/device_managment/ac/bloc/ac_state.dart b/lib/pages/device_managment/ac/bloc/ac_state.dart new file mode 100644 index 00000000..9a3b07f9 --- /dev/null +++ b/lib/pages/device_managment/ac/bloc/ac_state.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; + +abstract class AcsState extends Equatable { + const AcsState(); + + @override + List get props => []; +} + +class AcsInitialState extends AcsState {} + +class AcsLoadingState extends AcsState {} + +class ACStatusLoaded extends AcsState { + final AcStatusModel status; + final DateTime timestamp; + + ACStatusLoaded(this.status) : timestamp = DateTime.now(); + + @override + List get props => [status, timestamp]; +} + +class AcsFailedState extends AcsState { + final String error; + + const AcsFailedState({required this.error}); + + @override + List get props => [error]; +} diff --git a/lib/pages/device_managment/ac/model/ac_model.dart b/lib/pages/device_managment/ac/model/ac_model.dart new file mode 100644 index 00000000..621b9326 --- /dev/null +++ b/lib/pages/device_managment/ac/model/ac_model.dart @@ -0,0 +1,118 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +enum TempModes { hot, cold, wind } + +enum FanSpeeds { auto, low, middle, high } + +class AcStatusModel { + final String uuid; + final bool acSwitch; + final String modeString; + final int tempSet; + final int currentTemp; + final String fanSpeedsString; + final bool childLock; + final TempModes acMode; + final FanSpeeds acFanSpeed; + + AcStatusModel({ + required this.uuid, + required this.acSwitch, + required this.modeString, + required this.tempSet, + required this.currentTemp, + required this.fanSpeedsString, + required this.childLock, + }) : acMode = getACMode(modeString), + acFanSpeed = getFanSpeed(fanSpeedsString); + + factory AcStatusModel.fromJson(String id, List jsonList) { + late bool acSwitch; + late String mode; + late int tempSet; + late int currentTemp; + late String fanSpeeds; + late bool childLock; + + for (var status in jsonList) { + switch (status.code) { + case 'switch': + acSwitch = status.value ?? false; + break; + case 'mode': + mode = status.value ?? 'cold'; // default to 'cold' if null + break; + case 'temp_set': + tempSet = status.value ?? 210; // default value if null + break; + case 'temp_current': + currentTemp = status.value ?? 210; // default value if null + break; + case 'level': + fanSpeeds = status.value ?? 'low'; // default value if null + break; + case 'child_lock': + childLock = status.value ?? false; + break; + } + } + + return AcStatusModel( + uuid: id, + acSwitch: acSwitch, + modeString: mode, + tempSet: tempSet, + currentTemp: currentTemp, + fanSpeedsString: fanSpeeds, + childLock: childLock, + ); + } + + AcStatusModel copyWith({ + String? uuid, + bool? acSwitch, + String? modeString, + int? tempSet, + int? currentTemp, + String? fanSpeedsString, + bool? childLock, + }) { + return AcStatusModel( + uuid: uuid ?? this.uuid, + acSwitch: acSwitch ?? this.acSwitch, + modeString: modeString ?? this.modeString, + tempSet: tempSet ?? this.tempSet, + currentTemp: currentTemp ?? this.currentTemp, + fanSpeedsString: fanSpeedsString ?? this.fanSpeedsString, + childLock: childLock ?? this.childLock, + ); + } + + static TempModes getACMode(String value) { + switch (value) { + case 'cold': + return TempModes.cold; + case 'hot': + return TempModes.hot; + case 'wind': + return TempModes.wind; + default: + return TempModes.cold; + } + } + + static FanSpeeds getFanSpeed(String value) { + switch (value) { + case 'low': + return FanSpeeds.low; + case 'middle': + return FanSpeeds.middle; + case 'high': + return FanSpeeds.high; + case 'auto': + return FanSpeeds.auto; + default: + return FanSpeeds.auto; + } + } +} diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart new file mode 100644 index 00000000..5c49ecfa --- /dev/null +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_toggle.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout { + const AcDeviceControl({super.key, required this.device}); + + final AllDevicesModel device; + + @override + Widget build(BuildContext context) { + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return BlocProvider( + create: (context) => AcBloc(deviceId: device.uuid!) + ..add(AcFetchDeviceStatus(device.uuid!)), + child: BlocBuilder( + builder: (context, state) { + if (state is ACStatusLoaded) { + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + AcToggle( + value: state.status.acSwitch, + code: 'switch', + deviceId: device.uuid!, + ), + CurrentTemp( + currentTemp: state.status.currentTemp, + tempSet: state.status.tempSet, + code: 'temp_set', + deviceId: device.uuid!, + ), + AcMode( + value: state.status.acMode, + code: 'mode', + deviceId: device.uuid!, + ), + FanSpeedControl( + value: state.status.acFanSpeed, + code: 'level', + deviceId: device.uuid!, + ), + AcToggle( + value: state.status.childLock, + code: 'child_lock', + deviceId: device.uuid!, + description: 'Child Lock', + icon: Assets.childLock, + ), + ], + ); + } else if (state is AcsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else { + return const Center(child: Text('Error fetching status')); + } + }, + ), + ); + } +} diff --git a/lib/pages/device_managment/ac/view/control_list/ac_mode.dart b/lib/pages/device_managment/ac/view/control_list/ac_mode.dart new file mode 100644 index 00000000..0e0cd276 --- /dev/null +++ b/lib/pages/device_managment/ac/view/control_list/ac_mode.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; + +class AcMode extends StatelessWidget { + const AcMode({ + super.key, + required this.value, + required this.code, + required this.deviceId, + }); + + final TempModes value; + final String code; + final String deviceId; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: ColorsManager.greyColor.withOpacity(0.2), + border: Border.all(color: ColorsManager.boxDivider), + ), + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildIconContainer(context, TempModes.cold, Assets.freezing, + value == TempModes.cold), + _buildIconContainer( + context, TempModes.hot, Assets.acSun, value == TempModes.hot), + _buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, + value == TempModes.wind), + ], + ), + ); + } + + Widget _buildIconContainer( + BuildContext context, TempModes mode, String assetPath, bool isSelected) { + return Flexible( + child: GestureDetector( + onTap: () { + context.read().add( + AcControl( + deviceId: deviceId, + code: code, + value: mode.name, + ), + ); + }, + child: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + border: Border.all( + color: isSelected ? Colors.blue : Colors.transparent, + width: 2.0, + ), + ), + margin: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.all(4), + child: ClipOval( + child: SvgPicture.asset( + assetPath, + fit: BoxFit.contain, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart b/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart new file mode 100644 index 00000000..53f79761 --- /dev/null +++ b/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart @@ -0,0 +1,85 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class AcToggle extends StatelessWidget { + const AcToggle({ + super.key, + required this.value, + required this.code, + required this.deviceId, + this.icon, + this.description, + }); + + final bool value; + final String code; + final String deviceId; + final String? icon; + final String? description; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: ColorsManager.greyColor.withOpacity(0.2), + border: Border.all(color: ColorsManager.boxDivider), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + icon ?? Assets.acDevice, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + )), + SizedBox( + height: 20, + width: 35, + child: CupertinoSwitch( + activeColor: ColorsManager.dialogBlueTitle, + value: value, + onChanged: (newValue) { + context.read().add( + AcControl( + deviceId: deviceId, + code: code, + value: newValue, + ), + ); + }, + ), + ), + ], + ), + const Spacer(), + Center( + child: Text( + description ?? 'ThermoState', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/ac/view/control_list/current_temp.dart b/lib/pages/device_managment/ac/view/control_list/current_temp.dart new file mode 100644 index 00000000..fc00479b --- /dev/null +++ b/lib/pages/device_managment/ac/view/control_list/current_temp.dart @@ -0,0 +1,134 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/pages/device_managment/shared/celciuse_symbol.dart'; +import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; + +class CurrentTemp extends StatefulWidget { + const CurrentTemp({ + super.key, + required this.code, + required this.deviceId, + required this.currentTemp, + required this.tempSet, + }); + + final String code; + final String deviceId; + final int currentTemp; + final int tempSet; + + @override + State createState() => _CurrentTempState(); +} + +class _CurrentTempState extends State { + late double _adjustedValue; + Timer? _debounce; + + @override + void initState() { + super.initState(); + _adjustedValue = _initialAdjustedValue(widget.tempSet); + } + + double _initialAdjustedValue(dynamic value) { + if (value is int || value is double) { + double doubleValue = value.toDouble(); + return doubleValue > 99 ? doubleValue / 10 : doubleValue; + } else { + throw ArgumentError('Invalid value type: Expected int or double'); + } + } + + void _onValueChanged(double newValue) { + if (_debounce?.isActive ?? false) { + _debounce?.cancel(); + } + _debounce = Timer(const Duration(milliseconds: 500), () { + context.read().add( + AcControl( + deviceId: widget.deviceId, + code: widget.code, + value: (newValue * 10).toInt(), + ), + ); + }); + } + + @override + void dispose() { + _debounce?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: ColorsManager.greyColor.withOpacity(0.2), + border: Border.all(color: ColorsManager.boxDivider), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Current Temperature', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.grey), + ), + const SizedBox( + height: 5, + ), + Row( + children: [ + Text( + (widget.currentTemp > 99 + ? widget.currentTemp / 10 + : widget.currentTemp) + .toString(), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.grey), + ), + const CelsiusSymbol( + color: Colors.grey, + ) + ], + ), + ], + ), + const Spacer(), + IncrementDecrementWidget( + value: _adjustedValue.toString(), + description: '°C', + descriptionColor: ColorsManager.dialogBlueTitle, + onIncrement: () { + setState(() { + _adjustedValue++; + }); + _onValueChanged(_adjustedValue); + }, + onDecrement: () { + setState(() { + _adjustedValue--; + }); + _onValueChanged(_adjustedValue); + }), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/ac/view/control_list/fan_speed.dart b/lib/pages/device_managment/ac/view/control_list/fan_speed.dart new file mode 100644 index 00000000..d8d61d6b --- /dev/null +++ b/lib/pages/device_managment/ac/view/control_list/fan_speed.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; + +class FanSpeedControl extends StatelessWidget { + const FanSpeedControl({ + super.key, + required this.value, + required this.code, + required this.deviceId, + }); + + final FanSpeeds value; + final String code; + final String deviceId; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: ColorsManager.greyColor.withOpacity(0.2), + border: Border.all(color: ColorsManager.boxDivider), + ), + padding: const EdgeInsets.all(8), + child: Column( + children: [ + Wrap( + runSpacing: 8, + spacing: 8, + children: [ + _buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, + value == FanSpeeds.auto), + _buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, + value == FanSpeeds.low), + ], + ), + Wrap( + runSpacing: 8, + spacing: 8, + children: [ + _buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, + value == FanSpeeds.middle), + _buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, + value == FanSpeeds.high), + ], + ) + ], + ), + ); + } + + Widget _buildIconContainer(BuildContext context, FanSpeeds speed, + String assetPath, bool isSelected) { + return GestureDetector( + onTap: () { + context.read().add( + AcControl( + deviceId: deviceId, + code: code, + value: speed.name, + ), + ); + }, + child: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + border: Border.all( + color: isSelected ? Colors.blue : Colors.transparent, + width: 2.0, + ), + ), + padding: const EdgeInsets.all(8), + child: ClipOval( + child: SvgPicture.asset( + assetPath, + fit: BoxFit.contain, + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart new file mode 100644 index 00000000..edb02cb9 --- /dev/null +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -0,0 +1,165 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +part 'device_managment_event.dart'; +part 'device_managment_state.dart'; + +class DeviceManagementBloc + extends Bloc { + int _selectedIndex = 0; + List _devices = []; + int _onlineCount = 0; + int _offlineCount = 0; + int _lowBatteryCount = 0; + List _selectedDevices = []; + + DeviceManagementBloc() : super(DeviceManagementInitial()) { + on(_onFetchDevices); + on(_onFilterDevices); + on(_onSelectedFilterChanged); + on(_onSearchDevices); + on(_onSelectDevice); + } + + Future _onFetchDevices( + FetchDevices event, Emitter emit) async { + emit(DeviceManagementLoading()); + try { + final devices = await DevicesManagementApi().fetchDevices(); + _devices = devices; + _calculateDeviceCounts(); + emit(DeviceManagementLoaded( + devices: devices, + selectedIndex: _selectedIndex, + onlineCount: _onlineCount, + offlineCount: _offlineCount, + lowBatteryCount: _lowBatteryCount, + )); + } catch (e) { + emit(DeviceManagementInitial()); + } + } + + void _onFilterDevices( + FilterDevices event, Emitter emit) { + if (_devices.isNotEmpty) { + final filteredDevices = _devices.where((device) { + switch (event.filter) { + case 'Online': + return device.online == true; + case 'Offline': + return device.online == false; + case 'Low Battery': + return device.batteryLevel != null && device.batteryLevel! < 20; + default: + return true; + } + }).toList(); + emit(DeviceManagementFiltered( + filteredDevices: filteredDevices, + selectedIndex: _selectedIndex, + onlineCount: _onlineCount, + offlineCount: _offlineCount, + lowBatteryCount: _lowBatteryCount, + )); + } + } + + void _onSelectedFilterChanged( + SelectedFilterChanged event, Emitter emit) { + _selectedIndex = event.selectedIndex; + add(FilterDevices(_getFilterFromIndex(_selectedIndex))); + } + + void _onSelectDevice( + SelectDevice event, Emitter emit) { + if (_selectedDevices.contains(event.selectedDevice)) { + _selectedDevices.remove(event.selectedDevice); + } else { + _selectedDevices.add(event.selectedDevice); + } + + bool isControlButtonEnabled = _selectedDevices.length == 1; + + if (state is DeviceManagementLoaded) { + emit(DeviceManagementLoaded( + devices: _devices, + selectedIndex: _selectedIndex, + onlineCount: _onlineCount, + offlineCount: _offlineCount, + lowBatteryCount: _lowBatteryCount, + selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null, + )); + } else if (state is DeviceManagementFiltered) { + emit(DeviceManagementFiltered( + filteredDevices: (state as DeviceManagementFiltered).filteredDevices, + selectedIndex: _selectedIndex, + onlineCount: _onlineCount, + offlineCount: _offlineCount, + lowBatteryCount: _lowBatteryCount, + selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null, + )); + } + } + + void _calculateDeviceCounts() { + _onlineCount = _devices.where((device) => device.online == true).length; + _offlineCount = _devices.where((device) => device.online == false).length; + _lowBatteryCount = _devices + .where((device) => + device.batteryLevel != null && device.batteryLevel! < 20) + .length; + } + + String _getFilterFromIndex(int index) { + switch (index) { + case 1: + return 'Online'; + case 2: + return 'Offline'; + case 3: + return 'Low Battery'; + default: + return 'All'; + } + } + + void _onSearchDevices( + SearchDevices event, Emitter emit) { + if (_devices.isNotEmpty) { + final filteredDevices = _devices.where((device) { + final matchesCommunity = event.community == null || + event.community!.isEmpty || + (device.room?.name + ?.toLowerCase() + .contains(event.community!.toLowerCase()) ?? + false); + final matchesUnit = event.unitName == null || + event.unitName!.isEmpty || + (device.unit?.name + ?.toLowerCase() + .contains(event.unitName!.toLowerCase()) ?? + false); + final matchesProductName = event.productName == null || + event.productName!.isEmpty || + (device.name + ?.toLowerCase() + .contains(event.productName!.toLowerCase()) ?? + false); + return matchesCommunity && matchesUnit && matchesProductName; + }).toList(); + + emit(DeviceManagementFiltered( + filteredDevices: filteredDevices, + selectedIndex: _selectedIndex, + onlineCount: _onlineCount, + offlineCount: _offlineCount, + lowBatteryCount: _lowBatteryCount, + )); + } + } + + List get selectedDevices => _selectedDevices; +} diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart new file mode 100644 index 00000000..444f3406 --- /dev/null +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart @@ -0,0 +1,52 @@ +part of 'device_managment_bloc.dart'; + +abstract class DeviceManagementEvent extends Equatable { + const DeviceManagementEvent(); + + @override + List get props => []; +} + +class FetchDevices extends DeviceManagementEvent {} + +class FilterDevices extends DeviceManagementEvent { + final String filter; + + const FilterDevices(this.filter); + + @override + List get props => [filter]; +} + +class SelectedFilterChanged extends DeviceManagementEvent { + final int selectedIndex; + + const SelectedFilterChanged(this.selectedIndex); + + @override + List get props => [selectedIndex]; +} + +class SearchDevices extends DeviceManagementEvent { + final String? community; + final String? unitName; + final String? productName; + + const SearchDevices({ + this.community, + this.unitName, + this.productName, + }); + + @override + List get props => [community, unitName, productName]; +} + +class SelectDevice extends DeviceManagementEvent { + final AllDevicesModel selectedDevice; + + const SelectDevice(this.selectedDevice); + + @override + List get props => [selectedDevice]; +} diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_state.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_state.dart new file mode 100644 index 00000000..9a6e2f41 --- /dev/null +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_state.dart @@ -0,0 +1,77 @@ +part of 'device_managment_bloc.dart'; + +abstract class DeviceManagementState extends Equatable { + const DeviceManagementState(); + + @override + List get props => []; +} + +class DeviceManagementInitial extends DeviceManagementState {} + +class DeviceManagementLoading extends DeviceManagementState {} + +class DeviceManagementLoaded extends DeviceManagementState { + final List devices; + final int selectedIndex; + final int onlineCount; + final int offlineCount; + final int lowBatteryCount; + final AllDevicesModel? selectedDevice; + + const DeviceManagementLoaded({ + required this.devices, + required this.selectedIndex, + required this.onlineCount, + required this.offlineCount, + required this.lowBatteryCount, + this.selectedDevice, + }); + + @override + List get props => [ + devices, + selectedIndex, + onlineCount, + offlineCount, + lowBatteryCount, + selectedDevice + ]; +} + +class DeviceManagementFiltered extends DeviceManagementState { + final List filteredDevices; + final int selectedIndex; + final int onlineCount; + final int offlineCount; + final int lowBatteryCount; + final AllDevicesModel? selectedDevice; + + const DeviceManagementFiltered({ + required this.filteredDevices, + required this.selectedIndex, + required this.onlineCount, + required this.offlineCount, + required this.lowBatteryCount, + this.selectedDevice, + }); + + @override + List get props => [ + filteredDevices, + selectedIndex, + onlineCount, + offlineCount, + lowBatteryCount, + selectedDevice + ]; +} + +class DeviceSelectionUpdated extends DeviceManagementState { + final List selectedDevices; + + const DeviceSelectionUpdated(this.selectedDevices); + + @override + List get props => [selectedDevices]; +} diff --git a/lib/pages/device_managment/all_devices/enum/ac_values_type.dart b/lib/pages/device_managment/all_devices/enum/ac_values_type.dart new file mode 100644 index 00000000..19611ca5 --- /dev/null +++ b/lib/pages/device_managment/all_devices/enum/ac_values_type.dart @@ -0,0 +1,5 @@ +enum AcValuesEnums { + Cooling, + Heating, + Ventilation, +} diff --git a/lib/pages/device_managment/all_devices/enum/status_type.dart b/lib/pages/device_managment/all_devices/enum/status_type.dart new file mode 100644 index 00000000..b1829b15 --- /dev/null +++ b/lib/pages/device_managment/all_devices/enum/status_type.dart @@ -0,0 +1,9 @@ +enum OperationDialogType { + countdown, + delay, + temperature, + onOff, + integerSteps, + listOfOptions, + none, +} 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 new file mode 100644 index 00000000..fbe1f198 --- /dev/null +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -0,0 +1,36 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_status_view.dart'; +import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; +import 'package:syncrow_web/pages/device_managment/living_room_switch/view/living_room_device_control.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart'; + +mixin RouteControlsBasedCode { + Widget routeControlsWidgets({required AllDevicesModel device}) { + switch (device.productType) { + case '3G': + return LivingRoomDeviceControl( + deviceId: device.uuid!, + ); + case 'GW': + return GateWayControls( + gatewayId: device.uuid!, + ); + case 'DL': + return DoorLockView(device: device); + case 'WPS': + return WallSensorControls(device: device); + case 'CPS': + return CeilingSensorControls( + device: device, + ); + case 'AC': + return AcDeviceControl(device: device); + default: + return const SizedBox(); + } + } +} diff --git a/lib/pages/device_managment/all_devices/models/device_reports.dart b/lib/pages/device_managment/all_devices/models/device_reports.dart new file mode 100644 index 00000000..05604b25 --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/device_reports.dart @@ -0,0 +1,51 @@ +class DeviceReport { + final String? deviceUuid; + final int? startTime; + final int? endTime; + final List? data; + + DeviceReport({ + this.deviceUuid, + this.startTime, + this.endTime, + this.data, + }); + + DeviceReport.fromJson(Map json) + : deviceUuid = json['deviceUuid'] as String?, + startTime = json['startTime'] as int?, + endTime = json['endTime'] as int?, + data = (json['data'] as List?) + ?.map((e) => DeviceEvent.fromJson(e as Map)) + .toList(); + + Map toJson() => { + 'deviceUuid': deviceUuid, + 'startTime': startTime, + 'endTime': endTime, + 'data': data?.map((e) => e.toJson()).toList(), + }; +} + +class DeviceEvent { + final String? code; + final int? eventTime; + final String? value; + + DeviceEvent({ + this.code, + this.eventTime, + this.value, + }); + + DeviceEvent.fromJson(Map json) + : code = json['code'] as String?, + eventTime = json['eventTime'] as int?, + value = json['value'] as String?; + + Map toJson() => { + 'code': code, + 'eventTime': eventTime, + 'value': value, + }; +} diff --git a/lib/pages/device_managment/all_devices/models/device_status.dart b/lib/pages/device_managment/all_devices/models/device_status.dart new file mode 100644 index 00000000..b78f2a30 --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/device_status.dart @@ -0,0 +1,63 @@ +import 'dart:convert'; + +class DeviceStatus { + final String productUuid; + final String productType; + final List status; + + DeviceStatus({ + required this.productUuid, + required this.productType, + required this.status, + }); + + factory DeviceStatus.fromMap(Map map) { + return DeviceStatus( + productUuid: map['productUuid'] ?? '', + productType: map['productType'] ?? '', + status: List.from( + map['status']?.map((x) => Status.fromMap(x)) ?? const []), + ); + } + + Map toMap() { + return { + 'productUuid': productUuid, + 'productType': productType, + 'status': status.map((x) => x.toMap()).toList(), + }; + } + + factory DeviceStatus.fromJson(Map json) => + DeviceStatus.fromMap(json); + + String toJson() => json.encode(toMap()); +} + +class Status { + final String code; + final dynamic value; + + Status({ + required this.code, + required this.value, + }); + + factory Status.fromMap(Map map) { + return Status( + code: map['code'] ?? '', + value: map['value'], + ); + } + + Map toMap() { + return { + 'code': code, + 'value': value, + }; + } + + factory Status.fromJson(String source) => Status.fromMap(json.decode(source)); + + String toJson() => json.encode(toMap()); +} diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart new file mode 100644 index 00000000..48e99a1a --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -0,0 +1,157 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; + +class AllDevicesModel { +/* +{ + "room": { + "uuid": "75ea7d60-5104-4726-b5f8-ea426c0c6a1b", + "name": "Room 1" + }, + "unit": { + "uuid": "04fd1dcf-f24a-40db-970d-d0be884ed30f", + "name": "unit 1" + }, + "productUuid": "894aad5c-ce03-423a-9d61-2fd0c3f67ebf", + "productType": "3G", + "permissionType": "CONTROLLABLE", + "activeTime": 1722173778, + "category": "kg", + "categoryName": "Switch", + "createTime": 1722173778, + "gatewayId": "bf0294123ed2c19067skrk", + "icon": "smart/icon/bay1642572935385vcsA/2b1f5efbaa5bbf81c3164fa312cf2032.png", + "ip": "", + "lat": "31.97", + "localKey": "T/39+:9M", + "lon": "35.89", + "model": "S01ZLSWBSA3", + "name": "3 Gang Button Switch L-L", + "nodeId": "60a423fffed5a7f6", + "online": true, + "ownerId": "199200732", + "sub": true, + "timeZone": "+03:00", + "updateTime": 1723626515, + "uuid": "5b31dae4-ce9c-4c70-b52b-7e15011163bf" +} +*/ + + DevicesModelRoom? room; + DevicesModelUnit? unit; + 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; + + AllDevicesModel({ + this.room, + this.unit, + this.productUuid, + this.productType, + this.permissionType, + this.activeTime, + this.category, + this.categoryName, + this.createTime, + this.gatewayId, + this.icon, + this.ip, + this.lat, + this.localKey, + this.lon, + this.model, + this.name, + this.nodeId, + this.online, + this.ownerId, + this.sub, + this.timeZone, + this.updateTime, + this.uuid, + this.batteryLevel, + }); + AllDevicesModel.fromJson(Map json) { + room = (json['room'] != null && (json['room'] is Map)) + ? DevicesModelRoom.fromJson(json['room']) + : null; + unit = (json['unit'] != null && (json['unit'] is Map)) + ? DevicesModelUnit.fromJson(json['unit']) + : null; + productUuid = json['productUuid']?.toString(); + productType = json['productType']?.toString(); + permissionType = json['permissionType']?.toString(); + activeTime = int.tryParse(json['activeTime']?.toString() ?? ''); + category = json['category']?.toString(); + categoryName = json['categoryName']?.toString(); + createTime = int.tryParse(json['createTime']?.toString() ?? ''); + gatewayId = json['gatewayId']?.toString(); + icon = json['icon']?.toString(); + ip = json['ip']?.toString(); + lat = json['lat']?.toString(); + localKey = json['localKey']?.toString(); + lon = json['lon']?.toString(); + model = json['model']?.toString(); + name = json['name']?.toString(); + nodeId = json['nodeId']?.toString(); + online = json['online']; + ownerId = json['ownerId']?.toString(); + sub = json['sub']; + timeZone = json['timeZone']?.toString(); + updateTime = int.tryParse(json['updateTime']?.toString() ?? ''); + uuid = json['uuid']?.toString(); + batteryLevel = int.tryParse(json['batteryLevel']?.toString() ?? ''); + } + Map toJson() { + final data = {}; + if (room != null) { + data['room'] = room!.toJson(); + } + if (unit != null) { + data['unit'] = unit!.toJson(); + } + data['productUuid'] = productUuid; + data['productType'] = productType; + data['permissionType'] = permissionType; + data['activeTime'] = activeTime; + data['category'] = category; + data['categoryName'] = categoryName; + data['createTime'] = createTime; + data['gatewayId'] = gatewayId; + data['icon'] = icon; + data['ip'] = ip; + data['lat'] = lat; + data['localKey'] = localKey; + data['lon'] = lon; + data['model'] = model; + data['name'] = name; + data['nodeId'] = nodeId; + data['online'] = online; + data['ownerId'] = ownerId; + data['sub'] = sub; + data['timeZone'] = timeZone; + data['updateTime'] = updateTime; + data['uuid'] = uuid; + data['batteryLevel'] = batteryLevel; + return data; + } +} diff --git a/lib/pages/device_managment/all_devices/models/room.dart b/lib/pages/device_managment/all_devices/models/room.dart new file mode 100644 index 00000000..9f0f1268 --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/room.dart @@ -0,0 +1,26 @@ +class DevicesModelRoom { +/* +{ + "uuid": "75ea7d60-5104-4726-b5f8-ea426c0c6a1b", + "name": "Room 1" +} +*/ + + String? uuid; + String? name; + + DevicesModelRoom({ + this.uuid, + this.name, + }); + DevicesModelRoom.fromJson(Map json) { + uuid = json['uuid']?.toString(); + name = json['name']?.toString(); + } + Map toJson() { + final data = {}; + data['uuid'] = uuid; + data['name'] = name; + return data; + } +} diff --git a/lib/pages/device_managment/all_devices/models/unit.dart b/lib/pages/device_managment/all_devices/models/unit.dart new file mode 100644 index 00000000..bea9936f --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/unit.dart @@ -0,0 +1,26 @@ +class DevicesModelUnit { +/* +{ + "uuid": "04fd1dcf-f24a-40db-970d-d0be884ed30f", + "name": "unit 1" +} +*/ + + String? uuid; + String? name; + + DevicesModelUnit({ + this.uuid, + this.name, + }); + DevicesModelUnit.fromJson(Map json) { + uuid = json['uuid']?.toString(); + name = json['name']?.toString(); + } + Map toJson() { + final data = {}; + data['uuid'] = uuid; + data['name'] = name; + return data; + } +} diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart new file mode 100644 index 00000000..c56a661f --- /dev/null +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; +import 'package:syncrow_web/web_layout/web_scaffold.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { + const DeviceManagementPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => DeviceManagementBloc()..add(FetchDevices()), + child: WebScaffold( + appBarTitle: Text( + 'Device Management', + style: Theme.of(context).textTheme.headlineLarge, + ), + enableMenuSideba: isLargeScreenSize(context), + scaffoldBody: BlocBuilder( + builder: (context, state) { + if (state is DeviceManagementLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is DeviceManagementLoaded || + state is DeviceManagementFiltered) { + final devices = state is DeviceManagementLoaded + ? state.devices + : (state as DeviceManagementFiltered).filteredDevices; + + return DeviceManagementBody(devices: devices); + } else { + return const Center(child: Text('No Devices Found')); + } + }, + ), + ), + ); + } +} 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 new file mode 100644 index 00000000..3fb8b300 --- /dev/null +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -0,0 +1,164 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/core/extension/build_context_x.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/common/custom_table.dart'; +import 'package:syncrow_web/pages/common/filter/filter_widget.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/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/shared/device_control_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; +import 'package:syncrow_web/utils/format_date_time.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { + const DeviceManagementBody({super.key, required this.devices}); + + final List devices; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + List devicesToShow = []; + int selectedIndex = 0; + int onlineCount = 0; + int offlineCount = 0; + int lowBatteryCount = 0; + bool isControlButtonEnabled = false; + + if (state is DeviceManagementLoaded) { + devicesToShow = state.devices; + selectedIndex = state.selectedIndex; + onlineCount = state.onlineCount; + offlineCount = state.offlineCount; + lowBatteryCount = state.lowBatteryCount; + isControlButtonEnabled = state.selectedDevice != null; + } else if (state is DeviceManagementFiltered) { + devicesToShow = state.filteredDevices; + selectedIndex = state.selectedIndex; + onlineCount = state.onlineCount; + offlineCount = state.offlineCount; + lowBatteryCount = state.lowBatteryCount; + isControlButtonEnabled = state.selectedDevice != null; + } + + final tabs = [ + 'All (${devices.length})', + 'Online ($onlineCount)', + 'Offline ($offlineCount)', + 'Low Battery ($lowBatteryCount)', + ]; + + return CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Container( + padding: isLargeScreenSize(context) + ? const EdgeInsets.all(30) + : const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FilterWidget( + size: MediaQuery.of(context).size, + tabs: tabs, + selectedIndex: selectedIndex, + onTabChanged: (index) { + context + .read() + .add(SelectedFilterChanged(index)); + }, + ), + const SizedBox(height: 20), + const DeviceSearchFilters(), + const SizedBox(height: 12), + Container( + height: 43, + width: isSmallScreenSize(context) ? double.infinity : 100, + decoration: containerDecoration, + child: Center( + child: DefaultButton( + onPressed: isControlButtonEnabled + ? () { + final selectedDevice = context + .read() + .selectedDevices + .first; + showDialog( + context: context, + builder: (context) => DeviceControlDialog( + device: selectedDevice), + ); + } + : null, + borderRadius: 9, + child: Text( + 'Control', + style: TextStyle( + color: isControlButtonEnabled + ? Colors.white + : Colors.grey, + ), + ), + ), + ), + ), + ], + ), + ), + ), + SliverFillRemaining( + child: Padding( + padding: isLargeScreenSize(context) + ? const EdgeInsets.all(30) + : const EdgeInsets.all(15), + child: DynamicTable( + cellDecoration: containerDecoration, + onRowSelected: (index, isSelected, row) { + final selectedDevice = devicesToShow[index]; + context + .read() + .add(SelectDevice(selectedDevice)); + }, + withCheckBox: true, + size: context.screenSize, + headers: const [ + 'Device Name', + 'Product Name', + 'Device ID', + 'Unit Name', + 'Room', + 'Battery Level', + 'Installation Date and Time', + 'Status', + 'Last Offline Date and Time', + ], + data: devicesToShow.map((device) { + return [ + device.categoryName ?? '', + device.name ?? '', + device.uuid ?? '', + device.unit?.name ?? '', + device.room?.name ?? '', + device.batteryLevel != null + ? '${device.batteryLevel}%' + : '', + formatDateTime(DateTime.fromMillisecondsSinceEpoch( + (device.createTime ?? 0) * 1000)), + device.online == true ? 'Online' : 'Offline', + formatDateTime(DateTime.fromMillisecondsSinceEpoch( + (device.updateTime ?? 0) * 1000)), + ]; + }).toList(), + isEmpty: devicesToShow.isEmpty, + ), + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart new file mode 100644 index 00000000..7af6293e --- /dev/null +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class DeviceSearchFilters extends StatefulWidget { + const DeviceSearchFilters({super.key}); + + @override + State createState() => _DeviceSearchFiltersState(); +} + +class _DeviceSearchFiltersState extends State + with HelperResponsiveLayout { + final TextEditingController communityController = TextEditingController(); + final TextEditingController unitNameController = TextEditingController(); + final TextEditingController productNameController = TextEditingController(); + + @override + void dispose() { + communityController.dispose(); + unitNameController.dispose(); + productNameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return isLargeScreenSize(context) + ? Row( + children: [ + _buildSearchField("Community", communityController, 200), + const SizedBox(width: 20), + _buildSearchField("Unit Name", unitNameController, 200), + const SizedBox(width: 20), + _buildSearchField( + "Device Name / Product Name", productNameController, 300), + const SizedBox(width: 20), + _buildSearchResetButtons(), + ], + ) + : Wrap( + spacing: 20, + runSpacing: 10, + children: [ + _buildSearchField("Community", communityController, 200), + _buildSearchField("Unit Name", unitNameController, 200), + _buildSearchField( + "Device Name / Product Name", productNameController, 300), + _buildSearchResetButtons(), + ], + ); + } + + Widget _buildSearchField( + String title, TextEditingController controller, double width) { + return StatefulTextField( + title: title, + width: width, + elevation: 2, + controller: controller, + ); + } + + Widget _buildSearchResetButtons() { + return SearchResetButtons( + onSearch: () { + context.read().add(SearchDevices( + community: communityController.text, + unitName: unitNameController.text, + productName: productNameController.text, + )); + }, + onReset: () { + communityController.clear(); + unitNameController.clear(); + productNameController.clear(); + context.read().add(FetchDevices()); + }, + ); + } +} diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart b/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart new file mode 100644 index 00000000..fe928c94 --- /dev/null +++ b/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart @@ -0,0 +1,123 @@ +import 'dart:async'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/help_description.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class CeilingSensorBloc extends Bloc { + final String deviceId; + late CeilingSensorModel deviceStatus; + Timer? _timer; + + CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) { + on(_fetchCeilingSensorStatus); + on(_changeValue); + on(_getDeviceReports); + on(_showDescription); + on(_backToGridView); + } + + void _fetchCeilingSensorStatus( + CeilingInitialEvent event, Emitter emit) async { + emit(CeilingLoadingInitialState()); + try { + var response = await DevicesManagementApi().getDeviceStatus(deviceId); + deviceStatus = CeilingSensorModel.fromJson(response.status); + emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); + // _listenToChanges(); + } catch (e) { + emit(CeilingFailedState(error: e.toString())); + return; + } + } + + // _listenToChanges() { + // try { + // DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); + // Stream stream = ref.onValue; + + // stream.listen((DatabaseEvent event) { + // Map usersMap = event.snapshot.value as Map; + // List statusList = []; + + // usersMap['status'].forEach((element) { + // statusList.add(StatusModel(code: element['code'], value: element['value'])); + // }); + + // deviceStatus = WallSensorModel.fromJson(statusList); + // add(WallSensorUpdatedEvent()); + // }); + // } catch (_) {} + // } + + void _changeValue( + CeilingChangeValueEvent event, Emitter emit) async { + emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); + if (event.code == 'sensitivity') { + deviceStatus.sensitivity = event.value; + } else if (event.code == 'none_body_time') { + deviceStatus.noBodyTime = event.value; + } else if (event.code == 'moving_max_dis') { + deviceStatus.maxDistance = event.value; + } + emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); + await _runDeBouncer( + deviceId: deviceId, code: event.code, value: event.value); + } + + _runDeBouncer({ + required String deviceId, + required String code, + required dynamic value, + }) { + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + try { + final response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + + if (!response) { + add(CeilingInitialEvent()); + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(CeilingInitialEvent()); + } + }); + } + + FutureOr _getDeviceReports(GetCeilingDeviceReportsEvent event, + Emitter emit) async { + if (event.code.isEmpty) { + emit(ShowCeilingDescriptionState(description: reportString)); + return; + } else { + emit(CeilingReportsLoadingState()); + + try { + await DevicesManagementApi.getDeviceReports(deviceId, event.code) + .then((value) { + emit(CeilingReportsState(deviceReport: value)); + }); + } catch (e) { + emit(CeilingReportsFailedState(error: e.toString())); + return; + } + } + } + + void _showDescription( + ShowCeilingDescriptionEvent event, Emitter emit) { + emit(ShowCeilingDescriptionState(description: event.description)); + } + + void _backToGridView( + BackToCeilingGridViewEvent event, Emitter emit) { + emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); + } +} diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/event.dart b/lib/pages/device_managment/ceiling_sensor/bloc/event.dart new file mode 100644 index 00000000..31c5ab56 --- /dev/null +++ b/lib/pages/device_managment/ceiling_sensor/bloc/event.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; + +abstract class CeilingSensorEvent extends Equatable { + const CeilingSensorEvent(); + + @override + List get props => []; +} + +class CeilingInitialEvent extends CeilingSensorEvent {} + +class CeilingChangeValueEvent extends CeilingSensorEvent { + final dynamic value; + final String code; + const CeilingChangeValueEvent({required this.value, required this.code}); + + @override + List get props => [value, code]; +} + +class GetCeilingDeviceReportsEvent extends CeilingSensorEvent { + final String code; + final String deviceUuid; + + const GetCeilingDeviceReportsEvent( + {required this.code, required this.deviceUuid}); + + @override + List get props => [code, deviceUuid]; +} + +class ShowCeilingDescriptionEvent extends CeilingSensorEvent { + final String description; + + const ShowCeilingDescriptionEvent({required this.description}); + + @override + List get props => [description]; +} + +class BackToCeilingGridViewEvent extends CeilingSensorEvent {} diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/state.dart b/lib/pages/device_managment/ceiling_sensor/bloc/state.dart new file mode 100644 index 00000000..0bd7e4ed --- /dev/null +++ b/lib/pages/device_managment/ceiling_sensor/bloc/state.dart @@ -0,0 +1,68 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; + +class CeilingSensorState extends Equatable { + const CeilingSensorState(); + + @override + List get props => []; +} + +class CeilingInitialState extends CeilingSensorState {} + +class CeilingLoadingInitialState extends CeilingSensorState {} + +class CeilingUpdateState extends CeilingSensorState { + final CeilingSensorModel ceilingSensorModel; + const CeilingUpdateState({required this.ceilingSensorModel}); + + @override + List get props => [ceilingSensorModel]; +} + +class CeilingLoadingNewSate extends CeilingSensorState { + final CeilingSensorModel ceilingSensorModel; + const CeilingLoadingNewSate({required this.ceilingSensorModel}); + + @override + List get props => [ceilingSensorModel]; +} + +class CeilingFailedState extends CeilingSensorState { + final String error; + + const CeilingFailedState({required this.error}); + + @override + List get props => [error]; +} + +class CeilingReportsState extends CeilingSensorState { + final DeviceReport deviceReport; + + const CeilingReportsState({required this.deviceReport}); + + @override + List get props => [deviceReport]; +} + +class CeilingReportsLoadingState extends CeilingSensorState {} + +class CeilingReportsFailedState extends CeilingSensorState { + final String error; + + const CeilingReportsFailedState({required this.error}); + + @override + List get props => [error]; +} + +class ShowCeilingDescriptionState extends CeilingSensorState { + final String description; + + const ShowCeilingDescriptionState({required this.description}); + + @override + List get props => [description]; +} diff --git a/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart b/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart new file mode 100644 index 00000000..e8118168 --- /dev/null +++ b/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class CeilingSensorModel { + String presenceState; + int sensitivity; + String checkingResult; + int presenceRange; + int sportsPara; + String bodyMovement; + String noBodyTime; + int maxDistance; + + CeilingSensorModel({ + required this.presenceState, + required this.sensitivity, + required this.checkingResult, + required this.presenceRange, + required this.sportsPara, + required this.bodyMovement, + required this.noBodyTime, + required this.maxDistance, + }); + + factory CeilingSensorModel.fromJson(List jsonList) { + late String _presenceState = 'none'; + late int _sensitivity = 1; + late String _checkingResult = ''; + int _presenceRange = 1; + int _sportsPara = 1; + String _bodyMovement = 'none'; + String _noBodyTime = 'none'; + int _maxDis = 0; + + try { + for (var status in jsonList) { + switch (status.code) { + case 'presence_state': + _presenceState = status.value ?? 'none'; + break; + case 'sensitivity': + _sensitivity = status.value is int ? status.value : int.tryParse(status.value ?? '1') ?? 1; + break; + case 'checking_result': + _checkingResult = status.value ?? ''; + break; + case 'presence_range': + _presenceRange = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0; + break; + case 'sports_para': + _sportsPara = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0; + break; + case 'body_movement': + _bodyMovement = status.value ?? ''; + break; + case 'nobody_time': + _noBodyTime = status.value ?? 'none'; + break; + case 'moving_max_dis': + _maxDis = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0; + break; + } + } + } catch (e) { + debugPrint(e.toString()); + } + + return CeilingSensorModel( + presenceState: _presenceState, + sensitivity: _sensitivity, + checkingResult: _checkingResult, + presenceRange: _presenceRange, + sportsPara: _sportsPara, + bodyMovement: _bodyMovement, + noBodyTime: _noBodyTime, + maxDistance: _maxDis, + ); + } +} diff --git a/lib/pages/device_managment/ceiling_sensor/model/help_description.dart b/lib/pages/device_managment/ceiling_sensor/model/help_description.dart new file mode 100644 index 00000000..efcf7ce1 --- /dev/null +++ b/lib/pages/device_managment/ceiling_sensor/model/help_description.dart @@ -0,0 +1,17 @@ +String reportString = ''' +1. Nobody, the report to someone +Instruction: The propagation and processing of electromagnetic waves are complicated. There will be false alarms in the actual use of radar, and there will be some influencing factors in the environment, including: + +A. Physical disturbance: including air conditioning, fan, motor and other facilities vibration, cats, dogs, mice, birds and other animals passing through, may cause radar misjudgement of the environment. + +B. Space electromagnetic wave disturbance, including the possible existence of high-power electrical equipment around the radar, electromagnetic wave intensive places, the simultaneous coexistence of multiple radars and other environmental factors, may also cause radar misjudgement. Such interference items are few in home and office scenes, but more in factories and industrial environments. + +C. Power supply disturbance is mainly caused by power supply radar crosstalk caused by associated facilities and equipment in the mains environment, resulting in unstable power supply of radar and resulting in output misjudgement. + +2. In the case of human misreporting no one: +A. The presence of a human body beyond the radar test range. + +B. The human body is covered by metal, or by extremely thick office desks and chairs. + +C. When sleeping, the body cannot detect breathing micro-movements on the side, and may misjudge as nobody for a short time. +'''; diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart new file mode 100644 index 00000000..2bb7eb76 --- /dev/null +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart @@ -0,0 +1,172 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/description_view.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class CeilingSensorControls extends StatelessWidget + with HelperResponsiveLayout { + const CeilingSensorControls({super.key, required this.device}); + + final AllDevicesModel device; + + @override + Widget build(BuildContext context) { + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return BlocProvider( + create: (context) => CeilingSensorBloc(deviceId: device.uuid ?? '') + ..add(CeilingInitialEvent()), + child: BlocBuilder( + builder: (context, state) { + if (state is CeilingLoadingInitialState || + state is CeilingReportsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is CeilingUpdateState) { + return _buildGridView( + context, state.ceilingSensorModel, isLarge, isMedium); + } else if (state is CeilingReportsState) { + return ReportsTable( + report: state.deviceReport, + onRowTap: (index) {}, + onClose: () { + context + .read() + .add(BackToCeilingGridViewEvent()); + }, + ); + } else if (state is ShowCeilingDescriptionState) { + return DescriptionView( + description: state.description, + onClose: () { + context + .read() + .add(BackToCeilingGridViewEvent()); + }, + ); + } else if (state is CeilingReportsFailedState) { + final model = context.read().deviceStatus; + return _buildGridView(context, model, isLarge, isMedium); + } + return const Center(child: Text('Error fetching status')); + }, + ), + ); + } + + Widget _buildGridView(BuildContext context, CeilingSensorModel model, + bool isLarge, bool isMedium) { + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + PresenceState( + value: model.presenceState, + ), + PresenceDisplayValue( + value: model.sportsPara.toString(), + postfix: '', + description: 'Sports para', + ), + PresenceDisplayValue( + value: model.presenceRange.toString(), + postfix: 'm', + description: 'Detection Range', + ), + const PresenceSpaceType( + listOfIcons: [ + Assets.office, + Assets.parlour, + Assets.dyi, + Assets.bathroom, + Assets.bedroom, + ], + description: 'Space Type', + ), + PresenceUpdateData( + value: model.sensitivity.toDouble(), + title: 'Sensitivity:', + minValue: 1, + maxValue: 5, + steps: 1, + action: (int value) { + context.read().add( + CeilingChangeValueEvent( + code: 'sensitivity', + value: value, + ), + ); + }, + ), + PresenceUpdateData( + value: model.maxDistance.toDouble(), + title: 'Maximum Distance:', + minValue: 0, + maxValue: 500, + steps: 50, + description: 'm', + action: (int value) => context.read().add( + CeilingChangeValueEvent( + code: 'moving_max_dis', + value: value, + ), + ), + ), + PresenceNoBodyTime( + value: model.noBodyTime, + title: 'Nobody Time:', + // description: 'hr', + action: (String value) => context.read().add( + CeilingChangeValueEvent( + code: 'nobody_time', + value: value, + ), + ), + ), + GestureDetector( + onTap: () { + context.read().add(GetCeilingDeviceReportsEvent( + code: 'presence_state', deviceUuid: device.uuid!)); + }, + child: const PresenceStaticWidget( + icon: Assets.illuminanceRecordIcon, + description: 'Presence Record', + ), + ), + GestureDetector( + onTap: () { + context.read().add(GetCeilingDeviceReportsEvent( + code: '', deviceUuid: device.uuid!)); + }, + child: const PresenceStaticWidget( + icon: Assets.helpDescriptionIcon, + description: 'Help Description', + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart b/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart new file mode 100644 index 00000000..8ad2a05c --- /dev/null +++ b/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart @@ -0,0 +1,116 @@ +// ignore_for_file: invalid_use_of_visible_for_testing_member + +import 'dart:async'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_event.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_state.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class DoorLockBloc extends Bloc { + late DoorLockStatusModel deviceStatus; + final String deviceId; + Timer? _timer; + + DoorLockBloc({required this.deviceId}) : super(DoorLockInitial()) { + on(_onFetchDeviceStatus); + //on(_onDoorLockControl); + on(_updateLock); + } + + FutureOr _onFetchDeviceStatus( + DoorLockFetchStatus event, Emitter emit) async { + emit(DoorLockStatusLoading()); + try { + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + DoorLockStatusModel.fromJson(event.deviceId, status.status); + emit(DoorLockStatusLoaded(deviceStatus)); + } catch (e) { + emit(DoorLockControlError(e.toString())); + } + } + + FutureOr _updateLock( + UpdateLockEvent event, Emitter emit) async { + final oldValue = deviceStatus.normalOpenSwitch; + deviceStatus = deviceStatus.copyWith(normalOpenSwitch: !oldValue); + emit(DoorLockStatusLoaded(deviceStatus)); + + try { + final response = await DevicesManagementApi.openDoorLock(deviceId); + + if (!response) { + _revertValueAndEmit(deviceId, 'normal_open_switch', oldValue, emit); + } + } catch (e) { + _revertValueAndEmit(deviceId, 'normal_open_switch', oldValue, emit); + emit(DoorLockControlError('Error controlling the lock: $e')); + } + } + + Future _runDebounce({ + required String deviceId, + required String code, + required dynamic value, + required dynamic oldValue, + required Emitter emit, + }) async { + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + try { + final response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + if (!response) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + } catch (e) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + }); + } + + void _revertValueAndEmit( + String deviceId, + String code, + dynamic oldValue, + Emitter emit, + ) { + _updateLocalValue(code, oldValue); + emit(DoorLockStatusLoaded(deviceStatus)); + emit(const DoorLockControlError('Failed to control the device.')); + } + + void _updateLocalValue(String code, dynamic value) { + switch (code) { + case 'reverse_lock': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(reverseLock: value); + } + break; + case 'normal_open_switch': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(normalOpenSwitch: value); + } + break; + default: + break; + } + emit(DoorLockStatusLoaded(deviceStatus)); + } + + dynamic _getValueByCode(String code) { + switch (code) { + case 'reverse_lock': + return deviceStatus.reverseLock; + case 'normal_open_switch': + return deviceStatus.normalOpenSwitch; + default: + return null; + } + } +} diff --git a/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart b/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart new file mode 100644 index 00000000..8ee2e6aa --- /dev/null +++ b/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart @@ -0,0 +1,39 @@ +import 'package:equatable/equatable.dart'; + +sealed class DoorLockEvent extends Equatable { + const DoorLockEvent(); + + @override + List get props => []; +} + +class DoorLockFetchStatus extends DoorLockEvent { + final String deviceId; + + const DoorLockFetchStatus(this.deviceId); + + @override + List get props => [deviceId]; +} + +class DoorLockControl extends DoorLockEvent { + final String deviceId; + final String code; + final bool value; + + const DoorLockControl({ + required this.deviceId, + required this.code, + required this.value, + }); + + @override + List get props => [deviceId, code, value]; +} + +class UpdateLockEvent extends DoorLockEvent { + final bool value; + const UpdateLockEvent({required this.value}); + @override + List get props => [value]; +} diff --git a/lib/pages/device_managment/door_lock/bloc/door_lock_state.dart b/lib/pages/device_managment/door_lock/bloc/door_lock_state.dart new file mode 100644 index 00000000..39c4ca16 --- /dev/null +++ b/lib/pages/device_managment/door_lock/bloc/door_lock_state.dart @@ -0,0 +1,50 @@ + +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart'; + +sealed class DoorLockState extends Equatable { + const DoorLockState(); + + @override + List get props => []; +} + +class DoorLockInitial extends DoorLockState {} + +class DoorLockStatusLoading extends DoorLockState {} + +class DoorLockStatusLoaded extends DoorLockState { + final DoorLockStatusModel status; + + const DoorLockStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class DoorLockControlLoading extends DoorLockState {} + +class DoorLockControlError extends DoorLockState { + final String message; + + const DoorLockControlError(this.message); + + @override + List get props => [message]; +} + +class LoadingNewSate extends DoorLockState { + final DoorLockStatusModel smartDoorModel; + const LoadingNewSate({required this.smartDoorModel}); + + @override + List get props => [smartDoorModel]; +} + +class UpdateState extends DoorLockState { + final DoorLockStatusModel smartDoorModel; + const UpdateState({required this.smartDoorModel}); + + @override + List get props => [smartDoorModel]; +} diff --git a/lib/pages/device_managment/door_lock/models/door_lock_status_model.dart b/lib/pages/device_managment/door_lock/models/door_lock_status_model.dart new file mode 100644 index 00000000..cda512c5 --- /dev/null +++ b/lib/pages/device_managment/door_lock/models/door_lock_status_model.dart @@ -0,0 +1,184 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class DoorLockStatusModel { + final String uuid; + final int unlockFingerprint; + final int unlockPassword; + final int unlockTemporary; + final int unlockCard; + final String alarmLock; + final int unlockRequest; + final int residualElectricity; + final bool reverseLock; + final int unlockApp; + final bool hijack; + final bool doorbell; + final String unlockOfflinePd; + final String unlockOfflineClear; + final String unlockDoubleKit; + final String remoteNoPdSetkey; + final String remoteNoDpKey; + bool normalOpenSwitch; + + DoorLockStatusModel({ + required this.uuid, + required this.unlockFingerprint, + required this.unlockPassword, + required this.unlockTemporary, + required this.unlockCard, + required this.alarmLock, + required this.unlockRequest, + required this.residualElectricity, + required this.reverseLock, + required this.unlockApp, + required this.hijack, + required this.doorbell, + required this.unlockOfflinePd, + required this.unlockOfflineClear, + required this.unlockDoubleKit, + required this.remoteNoPdSetkey, + required this.remoteNoDpKey, + required this.normalOpenSwitch, + }); + + factory DoorLockStatusModel.fromJson(String id, List jsonList) { + late int unlockFingerprint; + late int unlockPassword; + late int unlockTemporary; + late int unlockCard; + late String alarmLock; + late int unlockRequest; + late int residualElectricity; + late bool reverseLock; + late int unlockApp; + late bool hijack; + late bool doorbell; + late String unlockOfflinePd; + late String unlockOfflineClear; + late String unlockDoubleKit; + late String remoteNoPdSetkey; + late String remoteNoDpKey; + late bool normalOpenSwitch; + + for (var status in jsonList) { + switch (status.code) { + case 'unlock_fingerprint': + unlockFingerprint = status.value ?? 0; + break; + case 'unlock_password': + unlockPassword = status.value ?? 0; + break; + case 'unlock_temporary': + unlockTemporary = status.value ?? 0; + break; + case 'unlock_card': + unlockCard = status.value ?? 0; + break; + case 'alarm_lock': + alarmLock = status.value ?? ''; + break; + case 'unlock_request': + unlockRequest = status.value ?? 0; + break; + case 'residual_electricity': + residualElectricity = status.value ?? 0; + break; + case 'reverse_lock': + reverseLock = status.value ?? false; + break; + case 'unlock_app': + unlockApp = status.value ?? 0; + break; + case 'hijack': + hijack = status.value ?? false; + break; + case 'doorbell': + doorbell = status.value ?? false; + break; + case 'unlock_offline_pd': + unlockOfflinePd = status.value ?? ''; + break; + case 'unlock_offline_clear': + unlockOfflineClear = status.value ?? ''; + break; + case 'unlock_double_kit': + unlockDoubleKit = status.value ?? ''; + break; + case 'remote_no_pd_setkey': + remoteNoPdSetkey = status.value ?? ''; + break; + case 'remote_no_dp_key': + remoteNoDpKey = status.value ?? ''; + break; + case 'normal_open_switch': + normalOpenSwitch = status.value ?? false; + break; + default: + break; + } + } + + return DoorLockStatusModel( + uuid: id, + unlockFingerprint: unlockFingerprint, + unlockPassword: unlockPassword, + unlockTemporary: unlockTemporary, + unlockCard: unlockCard, + alarmLock: alarmLock, + unlockRequest: unlockRequest, + residualElectricity: residualElectricity, + reverseLock: reverseLock, + unlockApp: unlockApp, + hijack: hijack, + doorbell: doorbell, + unlockOfflinePd: unlockOfflinePd, + unlockOfflineClear: unlockOfflineClear, + unlockDoubleKit: unlockDoubleKit, + remoteNoPdSetkey: remoteNoPdSetkey, + remoteNoDpKey: remoteNoDpKey, + normalOpenSwitch: normalOpenSwitch, + ); + } + + DoorLockStatusModel copyWith({ + String? uuid, + int? unlockFingerprint, + int? unlockPassword, + int? unlockTemporary, + int? unlockCard, + String? alarmLock, + int? unlockRequest, + int? residualElectricity, + bool? reverseLock, + int? unlockApp, + bool? hijack, + bool? doorbell, + String? unlockOfflinePd, + String? unlockOfflineClear, + String? unlockDoubleKit, + String? remoteNoPdSetkey, + String? remoteNoDpKey, + bool? normalOpenSwitch, + }) { + return DoorLockStatusModel( + uuid: uuid ?? this.uuid, + unlockFingerprint: unlockFingerprint ?? this.unlockFingerprint, + unlockPassword: unlockPassword ?? this.unlockPassword, + unlockTemporary: unlockTemporary ?? this.unlockTemporary, + unlockCard: unlockCard ?? this.unlockCard, + alarmLock: alarmLock ?? this.alarmLock, + unlockRequest: unlockRequest ?? this.unlockRequest, + residualElectricity: residualElectricity ?? this.residualElectricity, + reverseLock: reverseLock ?? this.reverseLock, + unlockApp: unlockApp ?? this.unlockApp, + hijack: hijack ?? this.hijack, + doorbell: doorbell ?? this.doorbell, + unlockOfflinePd: unlockOfflinePd ?? this.unlockOfflinePd, + unlockOfflineClear: unlockOfflineClear ?? this.unlockOfflineClear, + unlockDoubleKit: unlockDoubleKit ?? this.unlockDoubleKit, + remoteNoPdSetkey: remoteNoPdSetkey ?? this.remoteNoPdSetkey, + remoteNoDpKey: remoteNoDpKey ?? this.remoteNoDpKey, + normalOpenSwitch: normalOpenSwitch ?? this.normalOpenSwitch, + ); + } +} diff --git a/lib/pages/device_managment/door_lock/view/door_lock_status_view.dart b/lib/pages/device_managment/door_lock/view/door_lock_status_view.dart new file mode 100644 index 00000000..12db871f --- /dev/null +++ b/lib/pages/device_managment/door_lock/view/door_lock_status_view.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_event.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_state.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/widget/door_button.dart'; + +class DoorLockView extends StatelessWidget { + final AllDevicesModel device; + + const DoorLockView({super.key, required this.device}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => DoorLockBloc(deviceId: device.uuid!) + ..add(DoorLockFetchStatus(device.uuid!)), + child: BlocListener( + listener: (context, state) { + if (state is DoorLockControlError) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(state.message)), + ); + } + }, + child: BlocBuilder( + builder: (context, state) { + if (state is DoorLockStatusLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is DoorLockStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is UpdateState) { + return _buildStatusControls(context, state.smartDoorModel); + } else if (state is DoorLockControlError) { + return Center(child: Text(state.message)); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ), + ); + } + + Widget _buildStatusControls( + BuildContext context, DoorLockStatusModel status) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + DoorLockButton( + smartDoorModel: status, + doorLock: device, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/door_lock/widget/door_button.dart b/lib/pages/device_managment/door_lock/widget/door_button.dart new file mode 100644 index 00000000..e8e3066e --- /dev/null +++ b/lib/pages/device_managment/door_lock/widget/door_button.dart @@ -0,0 +1,133 @@ +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/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_event.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class DoorLockButton extends StatefulWidget { + const DoorLockButton({ + super.key, + required this.doorLock, + required this.smartDoorModel, + }); + + final AllDevicesModel doorLock; + final DoorLockStatusModel smartDoorModel; + + @override + State createState() => + _DoorLockButtonState(smartDoorModel: smartDoorModel); +} + +class _DoorLockButtonState extends State + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _animation; + DoorLockStatusModel smartDoorModel; + + _DoorLockButtonState({required this.smartDoorModel}); + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + ); + _animation = Tween(begin: 0, end: 1).animate(_animationController) + ..addListener(() { + setState(() {}); + }); + + if (smartDoorModel.unlockRequest > 0) { + _animationController.reverse(from: 1); + } + } + + @override + void didUpdateWidget(covariant DoorLockButton oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.smartDoorModel.normalOpenSwitch != + widget.smartDoorModel.normalOpenSwitch) { + setState(() { + smartDoorModel = widget.smartDoorModel; + }); + + if (smartDoorModel.unlockRequest > 0) { + _animationController.forward(from: 0); + } else { + _animationController.reverse(from: 1); + } + } + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 255, + height: 255, + child: InkWell( + onTap: () { + _animationController.forward(from: 0); + BlocProvider.of(context) + .add(UpdateLockEvent(value: !smartDoorModel.normalOpenSwitch)); + }, + child: Container( + width: 255, + height: 255, + decoration: BoxDecoration( + color: const Color(0xFFEBECED), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + blurRadius: 18, + blurStyle: BlurStyle.outer, + ), + ], + ), + child: Stack( + alignment: Alignment.center, + children: [ + Container( + margin: const EdgeInsets.all(30), + decoration: const BoxDecoration( + color: Color(0xFFF9F9F9), + shape: BoxShape.circle, + ), + child: Center( + child: SvgPicture.asset( + smartDoorModel.normalOpenSwitch + ? Assets.doorUnlock + : Assets.lockIcon, + width: 60, + height: 60, + ), + ), + ), + SizedBox.expand( + child: CircularProgressIndicator( + value: _animation.value, + strokeWidth: 8, + backgroundColor: Colors.transparent, + valueColor: const AlwaysStoppedAnimation( + ColorsManager.primaryColor), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart b/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart new file mode 100644 index 00000000..ca572bf0 --- /dev/null +++ b/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart @@ -0,0 +1,30 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +part 'gate_way_event.dart'; +part 'gate_way_state.dart'; + +class GateWayBloc extends Bloc { + GateWayBloc() : super(GateWayInitial()) { + on((event, emit) {}); + on(_getGatWayById); + } + + FutureOr _getGatWayById( + GatWayById event, Emitter emit) async { + emit(GatewayLoadingState()); + try { + List devicesList = + await DevicesManagementApi.getDevicesByGatewayId(event.getWayId); + + emit(UpdateGatewayState(list: devicesList)); + } catch (e) { + emit(ErrorState(message: e.toString())); + return; + } + } +} diff --git a/lib/pages/device_managment/gateway/bloc/gate_way_event.dart b/lib/pages/device_managment/gateway/bloc/gate_way_event.dart new file mode 100644 index 00000000..22c81a12 --- /dev/null +++ b/lib/pages/device_managment/gateway/bloc/gate_way_event.dart @@ -0,0 +1,20 @@ +part of 'gate_way_bloc.dart'; + +sealed class GateWayEvent extends Equatable { + const GateWayEvent(); + + @override + List get props => []; +} + +class GateWayLoading extends GateWayEvent {} + +class GateWayFetch extends GateWayEvent { + final String deviceId; + const GateWayFetch(this.deviceId); +} + +class GatWayById extends GateWayEvent { + final String getWayId; + const GatWayById(this.getWayId); +} diff --git a/lib/pages/device_managment/gateway/bloc/gate_way_state.dart b/lib/pages/device_managment/gateway/bloc/gate_way_state.dart new file mode 100644 index 00000000..8d6b616e --- /dev/null +++ b/lib/pages/device_managment/gateway/bloc/gate_way_state.dart @@ -0,0 +1,26 @@ +part of 'gate_way_bloc.dart'; + +sealed class GateWayState extends Equatable { + const GateWayState(); + + @override + List get props => []; +} + +final class GateWayInitial extends GateWayState {} + +class GatewayLoadingState extends GateWayState {} + +class UpdateGatewayState extends GateWayState { + final List list; + const UpdateGatewayState({required this.list}); + @override + List get props => [list]; +} + +class ErrorState extends GateWayState { + final String message; + const ErrorState({required this.message}); + @override + List get props => [message]; +} diff --git a/lib/pages/device_managment/gateway/view/gateway_view.dart b/lib/pages/device_managment/gateway/view/gateway_view.dart new file mode 100644 index 00000000..4f14161e --- /dev/null +++ b/lib/pages/device_managment/gateway/view/gateway_view.dart @@ -0,0 +1,98 @@ +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/gateway/bloc/gate_way_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class GateWayControls extends StatelessWidget with HelperResponsiveLayout { + const GateWayControls({super.key, required this.gatewayId}); + + final String gatewayId; + + @override + Widget build(BuildContext context) { + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + + return BlocProvider( + create: (context) => GateWayBloc()..add(GatWayById(gatewayId)), + child: BlocBuilder( + builder: (context, state) { + if (state is GatewayLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is UpdateGatewayState) { + return GridView.builder( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + itemCount: state.list.length, + itemBuilder: (context, index) { + final device = state.list[index]; + return _DeviceItem(device: device); + }, + ); + } else { + return const Center(child: Text('Error fetching devices')); + } + }, + ), + ); + } +} + +class _DeviceItem extends StatelessWidget { + const _DeviceItem({ + required this.device, + }); + + final DeviceModel device; + + @override + Widget build(BuildContext context) { + return DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + ), + margin: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.all(4), + child: ClipOval( + child: SvgPicture.asset( + device.icon, + fit: BoxFit.fill, + ), + ), + ), + const Spacer(), + Text( + device.name ?? 'Unknown Device', + textAlign: TextAlign.center, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart b/lib/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart new file mode 100644 index 00000000..8adc11cb --- /dev/null +++ b/lib/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart @@ -0,0 +1,119 @@ +// ignore_for_file: invalid_use_of_visible_for_testing_member + +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/living_room_switch/models/living_room_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +part 'living_room_event.dart'; +part 'living_room_state.dart'; + +class LivingRoomBloc extends Bloc { + late LivingRoomStatusModel deviceStatus; + final String deviceId; + Timer? _timer; + + LivingRoomBloc({required this.deviceId}) : super(LivingRoomInitial()) { + on(_onFetchDeviceStatus); + on(_livingRoomControl); + } + + FutureOr _onFetchDeviceStatus( + LivingRoomFetchDeviceStatus event, Emitter emit) async { + emit(LivingRoomDeviceStatusLoading()); + try { + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + LivingRoomStatusModel.fromJson(event.deviceId, status.status); + emit(LivingRoomDeviceStatusLoaded(deviceStatus)); + } catch (e) { + emit(LivingRoomDeviceManagementError(e.toString())); + } + } + + FutureOr _livingRoomControl( + LivingRoomControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + + emit(LivingRoomDeviceStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + ); + } + + Future _runDebounce({ + required String deviceId, + required String code, + required dynamic value, + required dynamic oldValue, + required Emitter emit, + }) async { + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + try { + final response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + if (!response) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + } catch (e) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + }); + } + + void _revertValueAndEmit(String deviceId, String code, dynamic oldValue, + Emitter emit) { + _updateLocalValue(code, oldValue); + emit(LivingRoomDeviceStatusLoaded(deviceStatus)); + emit(const LivingRoomControlError('Failed to control the device.')); + } + + void _updateLocalValue(String code, dynamic value) { + switch (code) { + case 'switch_1': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(switch1: value); + } + break; + case 'switch_2': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(switch2: value); + } + break; + case 'switch_3': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(switch3: value); + } + break; + default: + break; + } + emit(LivingRoomDeviceStatusLoaded(deviceStatus)); + } + + dynamic _getValueByCode(String code) { + switch (code) { + case 'switch_1': + return deviceStatus.switch1; + case 'switch_2': + return deviceStatus.switch2; + case 'switch_3': + return deviceStatus.switch3; + default: + return null; + } + } +} diff --git a/lib/pages/device_managment/living_room_switch/bloc/living_room_event.dart b/lib/pages/device_managment/living_room_switch/bloc/living_room_event.dart new file mode 100644 index 00000000..f7b57cde --- /dev/null +++ b/lib/pages/device_managment/living_room_switch/bloc/living_room_event.dart @@ -0,0 +1,29 @@ +part of 'living_room_bloc.dart'; + +sealed class LivingRoomEvent extends Equatable { + const LivingRoomEvent(); + + @override + List get props => []; +} + +class LivingRoomFetchDeviceStatus extends LivingRoomEvent { + final String deviceId; + + const LivingRoomFetchDeviceStatus(this.deviceId); + + @override + List get props => [deviceId]; +} + +class LivingRoomControl extends LivingRoomEvent { + final String deviceId; + final String code; + final bool value; + + const LivingRoomControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} diff --git a/lib/pages/device_managment/living_room_switch/bloc/living_room_state.dart b/lib/pages/device_managment/living_room_switch/bloc/living_room_state.dart new file mode 100644 index 00000000..4b3a5347 --- /dev/null +++ b/lib/pages/device_managment/living_room_switch/bloc/living_room_state.dart @@ -0,0 +1,39 @@ +part of 'living_room_bloc.dart'; + +sealed class LivingRoomState extends Equatable { + const LivingRoomState(); + + @override + List get props => []; +} + +final class LivingRoomInitial extends LivingRoomState {} + +class LivingRoomDeviceStatusLoading extends LivingRoomState {} + +class LivingRoomDeviceStatusLoaded extends LivingRoomState { + final LivingRoomStatusModel status; + + const LivingRoomDeviceStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class LivingRoomDeviceManagementError extends LivingRoomState { + final String message; + + const LivingRoomDeviceManagementError(this.message); + + @override + List get props => [message]; +} + +class LivingRoomControlError extends LivingRoomState { + final String message; + + const LivingRoomControlError(this.message); + + @override + List get props => [message]; +} diff --git a/lib/pages/device_managment/living_room_switch/helper/living_room_helper.dart b/lib/pages/device_managment/living_room_switch/helper/living_room_helper.dart new file mode 100644 index 00000000..8e3f4985 --- /dev/null +++ b/lib/pages/device_managment/living_room_switch/helper/living_room_helper.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/living_room_switch/widgets/cieling_light.dart'; +import 'package:syncrow_web/pages/device_managment/living_room_switch/widgets/spot_light.dart'; +import 'package:syncrow_web/pages/device_managment/living_room_switch/widgets/wall_light.dart'; + +mixin LivingRoomHelper { + Widget livingRoomControlWidgets( + {required bool value, required String code, required String deviceId}) { + switch (code) { + case 'switch_1': + return WallLight(value: value, code: code, deviceId: deviceId); + case 'switch_2': + return CeilingLight(value: value, code: code, deviceId: deviceId); + case 'switch_3': + return SpotLight(value: value, code: code, deviceId: deviceId); + default: + return const SizedBox(); + } + } +} + diff --git a/lib/pages/device_managment/living_room_switch/models/living_room_model.dart b/lib/pages/device_managment/living_room_switch/models/living_room_model.dart new file mode 100644 index 00000000..4bb5eada --- /dev/null +++ b/lib/pages/device_managment/living_room_switch/models/living_room_model.dart @@ -0,0 +1,56 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class LivingRoomStatusModel { + final String uuid; + final bool switch1; + final bool switch2; + final bool switch3; + + LivingRoomStatusModel({ + required this.uuid, + required this.switch1, + required this.switch2, + required this.switch3, + }); + + factory LivingRoomStatusModel.fromJson(String id, List jsonList) { + late bool switch1; + late bool switch2; + late bool switch3; + + for (var status in jsonList) { + switch (status.code) { + case 'switch_1': + switch1 = status.value ?? false; // default to false if null + break; + case 'switch_2': + switch2 = status.value ?? false; // default to false if null + break; + case 'switch_3': + switch3 = status.value ?? false; // default to false if null + break; + } + } + + return LivingRoomStatusModel( + uuid: id, + switch1: switch1, + switch2: switch2, + switch3: switch3, + ); + } + + LivingRoomStatusModel copyWith({ + String? uuid, + bool? switch1, + bool? switch2, + bool? switch3, + }) { + return LivingRoomStatusModel( + uuid: uuid ?? this.uuid, + switch1: switch1 ?? this.switch1, + switch2: switch2 ?? this.switch2, + switch3: switch3 ?? this.switch3, + ); + } +} diff --git a/lib/pages/device_managment/living_room_switch/view/living_room_device_control.dart b/lib/pages/device_managment/living_room_switch/view/living_room_device_control.dart new file mode 100644 index 00000000..60e656ad --- /dev/null +++ b/lib/pages/device_managment/living_room_switch/view/living_room_device_control.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/living_room_switch/models/living_room_model.dart'; +import 'package:syncrow_web/pages/device_managment/living_room_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class LivingRoomDeviceControl extends StatelessWidget + with HelperResponsiveLayout { + final String deviceId; + + const LivingRoomDeviceControl({super.key, required this.deviceId}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => LivingRoomBloc(deviceId: deviceId) + ..add(LivingRoomFetchDeviceStatus(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is LivingRoomDeviceStatusLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is LivingRoomDeviceStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is LivingRoomDeviceManagementError || + state is LivingRoomControlError) { + return Center(child: Text(state.toString())); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls( + BuildContext context, LivingRoomStatusModel status) { + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceId, + label: 'Wall Light', + ), + ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceId, + label: 'Ceiling Light', + ), + ToggleWidget( + value: status.switch3, + code: 'switch_3', + deviceId: deviceId, + label: 'Spotlight', + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/living_room_switch/widgets/cieling_light.dart b/lib/pages/device_managment/living_room_switch/widgets/cieling_light.dart new file mode 100644 index 00000000..b529e6e6 --- /dev/null +++ b/lib/pages/device_managment/living_room_switch/widgets/cieling_light.dart @@ -0,0 +1,71 @@ +import 'package:flutter/cupertino.dart'; +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/living_room_switch/bloc/living_room_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class CeilingLight extends StatelessWidget { + const CeilingLight( + {super.key, + required this.value, + required this.code, + required this.deviceId}); + + final bool value; + final String code; + final String deviceId; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + Assets.lightPulp, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + )), + SizedBox( + height: 20, + width: 35, + child: CupertinoSwitch( + value: value, + activeColor: ColorsManager.dialogBlueTitle, + onChanged: (newValue) { + context.read().add( + LivingRoomControl( + deviceId: deviceId, + code: code, + value: newValue, + ), + ); + }, + ), + ), + ], + ), + const Spacer(), + const Center( + child: Text( + 'Ceiling Light', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/living_room_switch/widgets/living_toggle_widget.dart b/lib/pages/device_managment/living_room_switch/widgets/living_toggle_widget.dart new file mode 100644 index 00000000..701d412b --- /dev/null +++ b/lib/pages/device_managment/living_room_switch/widgets/living_toggle_widget.dart @@ -0,0 +1,80 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class ToggleWidget extends StatelessWidget { + final bool value; + final String code; + final String deviceId; + final String label; + + const ToggleWidget({ + super.key, + required this.value, + required this.code, + required this.deviceId, + required this.label, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: ColorsManager.greyColor.withOpacity(0.2), + border: Border.all(color: ColorsManager.boxDivider), + ), + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + Assets.lightPulp, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + )), + SizedBox( + height: 20, + width: 35, + child: CupertinoSwitch( + value: value, + activeColor: ColorsManager.dialogBlueTitle, + onChanged: (newValue) { + context.read().add( + LivingRoomControl( + deviceId: deviceId, + code: code, + value: newValue, + ), + ); + }, + ), + ), + ], + ), + const Spacer(), + Text( + label, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/living_room_switch/widgets/spot_light.dart b/lib/pages/device_managment/living_room_switch/widgets/spot_light.dart new file mode 100644 index 00000000..14c4fe5e --- /dev/null +++ b/lib/pages/device_managment/living_room_switch/widgets/spot_light.dart @@ -0,0 +1,71 @@ +import 'package:flutter/cupertino.dart'; +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/living_room_switch/bloc/living_room_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class SpotLight extends StatelessWidget { + const SpotLight( + {super.key, + required this.value, + required this.code, + required this.deviceId}); + + final bool value; + final String code; + final String deviceId; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + Assets.lightPulp, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + )), + SizedBox( + height: 20, + width: 35, + child: CupertinoSwitch( + value: value, + activeColor: ColorsManager.dialogBlueTitle, + onChanged: (newValue) { + context.read().add( + LivingRoomControl( + deviceId: deviceId, + code: code, + value: newValue, + ), + ); + }, + ), + ), + ], + ), + const Spacer(), + const Center( + child: Text( + 'Spotlight', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/living_room_switch/widgets/wall_light.dart b/lib/pages/device_managment/living_room_switch/widgets/wall_light.dart new file mode 100644 index 00000000..8e168ec0 --- /dev/null +++ b/lib/pages/device_managment/living_room_switch/widgets/wall_light.dart @@ -0,0 +1,71 @@ +import 'package:flutter/cupertino.dart'; +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/living_room_switch/bloc/living_room_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class WallLight extends StatelessWidget { + const WallLight( + {super.key, + required this.value, + required this.code, + required this.deviceId}); + + final bool value; + final String code; + final String deviceId; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + Assets.lightPulp, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + )), + SizedBox( + height: 20, + width: 35, + child: CupertinoSwitch( + value: value, + activeColor: ColorsManager.dialogBlueTitle, + onChanged: (newValue) { + context.read().add( + LivingRoomControl( + deviceId: deviceId, + code: code, + value: newValue, + ), + ); + }, + ), + ), + ], + ), + const Spacer(), + const Center( + child: Text( + 'Wall Light', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/shared/celciuse_symbol.dart b/lib/pages/device_managment/shared/celciuse_symbol.dart new file mode 100644 index 00000000..b8db1439 --- /dev/null +++ b/lib/pages/device_managment/shared/celciuse_symbol.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class CelsiusSymbol extends StatelessWidget { + const CelsiusSymbol({this.color, super.key}); + + final Color? color; + + @override + Widget build(BuildContext context) { + return Text( + '°C', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: color, + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/device_control_dialog.dart b/lib/pages/device_managment/shared/device_control_dialog.dart new file mode 100644 index 00000000..d115f57e --- /dev/null +++ b/lib/pages/device_managment/shared/device_control_dialog.dart @@ -0,0 +1,148 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'package:syncrow_web/core/extension/build_context_x.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart'; + +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { + final AllDevicesModel device; + + const DeviceControlDialog({super.key, required this.device}); + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.white, + insetPadding: const EdgeInsets.all(20), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: SizedBox( + width: 798, + height: context.screenHeight * 0.7, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(), + Text( + device.categoryName ?? 'Device Control', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 22, + color: ColorsManager.dialogBlueTitle, + ), + ), + Container( + width: 25, + decoration: BoxDecoration( + color: Colors.transparent, + shape: BoxShape.circle, + border: Border.all( + color: Colors.grey, + width: 1.0, + ), + ), + child: IconButton( + padding: EdgeInsets.all(1), + icon: const Icon( + Icons.close, + color: Colors.grey, + size: 18, + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ), + const SizedBox(height: 20), + _buildDeviceInfoSection(), + const SizedBox(height: 20), + //// BUILD DEVICE CONTROLS + /// + //// ROUTE TO SPECIFIC CONTROL VIEW BASED ON DEVICE CATEGORY + routeControlsWidgets(device: device), + ], + ), + ), + ), + ), + ); + } + + Widget _buildDeviceInfoSection() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 50), + child: Table( + children: [ + TableRow( + children: [ + _buildInfoRow('Product Name:', device.categoryName ?? 'N/A'), + _buildInfoRow('Device ID:', device.uuid ?? ''), + ], + ), + TableRow(children: [ + _buildInfoRow('Virtual Address:', + 'Area - Street 1 - Building 1 - First Floor'), + const SizedBox.shrink(), + ]), + TableRow( + children: [ + _buildInfoRow('Unit Name:', device.unit?.name ?? 'N/A'), + _buildInfoRow('Room:', device.room?.name ?? 'N/A'), + ], + ), + TableRow( + children: [ + _buildInfoRow('Installation Date and Time:', '09/08/2024 13:30'), + const SizedBox.shrink(), + ], + ), + TableRow( + children: [ + _buildInfoRow('Status:', 'Online', statusColor: Colors.green), + _buildInfoRow('Last Offline Date and Time:', '-'), + ], + ), + ], + ), + ); + } + + Widget _buildInfoRow(String title, String value, {Color? statusColor}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 12, + color: ColorsManager.lightGreyColor, + ), + ), + const SizedBox(width: 10), + Text( + value, + style: TextStyle( + fontSize: 16, + color: statusColor ?? Colors.black, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/device_controls_container.dart b/lib/pages/device_managment/shared/device_controls_container.dart new file mode 100644 index 00000000..b60a958f --- /dev/null +++ b/lib/pages/device_managment/shared/device_controls_container.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DeviceControlsContainer extends StatelessWidget { + const DeviceControlsContainer({required this.child, super.key}); + final Widget child; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: ColorsManager.greyColor.withOpacity(0.2), + border: Border.all(color: ColorsManager.boxDivider), + ), + padding: const EdgeInsets.all(12), + child: child, + ); + } +} diff --git a/lib/pages/device_managment/shared/increament_decreament.dart b/lib/pages/device_managment/shared/increament_decreament.dart new file mode 100644 index 00000000..71459c42 --- /dev/null +++ b/lib/pages/device_managment/shared/increament_decreament.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class IncrementDecrementWidget extends StatelessWidget { + final String value; + final String description; + final VoidCallback onIncrement; + final VoidCallback onDecrement; + final Color? descriptionColor; + + const IncrementDecrementWidget({ + super.key, + required this.value, + required this.description, + required this.onIncrement, + required this.onDecrement, + this.descriptionColor, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Material( + type: MaterialType.transparency, + child: ClipRRect( + borderRadius: BorderRadius.circular(100), + child: InkWell( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onTap: onDecrement, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon( + Icons.remove, + color: ColorsManager.greyColor, + size: 28, + ), + ), + ), + ), + ), + Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + child: RichText( + text: TextSpan( + text: '', + children: [ + TextSpan( + text: value, + style: TextStyle( + fontSize: 40, + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + TextSpan( + text: description, + style: TextStyle( + fontSize: 16, + color: descriptionColor ?? ColorsManager.blackColor, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ), + Material( + type: MaterialType.transparency, + child: ClipRRect( + borderRadius: BorderRadius.circular(100), + child: InkWell( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onTap: onIncrement, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon( + Icons.add, + color: ColorsManager.greyColor, + size: 28, + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/shared/sensors_widgets/presence_display_data.dart b/lib/pages/device_managment/shared/sensors_widgets/presence_display_data.dart new file mode 100644 index 00000000..aac34b77 --- /dev/null +++ b/lib/pages/device_managment/shared/sensors_widgets/presence_display_data.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class PresenceDisplayValue extends StatelessWidget { + const PresenceDisplayValue( + {super.key, required this.value, required this.postfix, required this.description}); + + final String value; + final String postfix; + final String description; + + @override + Widget build(BuildContext context) { + return DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + value, + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontSize: 40, + fontWeight: FontWeight.w700), + ), + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Text( + postfix, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.blackColor, fontSize: 16, fontWeight: FontWeight.w700), + ), + ), + ], + ), + Text( + description, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 16), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/sensors_widgets/presence_space_type.dart b/lib/pages/device_managment/shared/sensors_widgets/presence_space_type.dart new file mode 100644 index 00000000..185144e1 --- /dev/null +++ b/lib/pages/device_managment/shared/sensors_widgets/presence_space_type.dart @@ -0,0 +1,50 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/core/extension/build_context_x.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class PresenceSpaceType extends StatelessWidget { + const PresenceSpaceType({ + super.key, + required this.listOfIcons, + required this.description, + }); + + final List listOfIcons; + final String description; + + @override + Widget build(BuildContext context) { + return DeviceControlsContainer( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + description, + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 10, + ), + ), + const SizedBox( + height: 8, + ), + Wrap( + runSpacing: 8, + spacing: 16, + children: [ + ...listOfIcons.map((icon) => SvgPicture.asset( + icon, + width: 40, + height: 40, + )), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart b/lib/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart new file mode 100644 index 00000000..d2d48b78 --- /dev/null +++ b/lib/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class PresenceStaticWidget extends StatelessWidget { + const PresenceStaticWidget({required this.icon, required this.description, super.key}); + final String icon; + final String description; + + @override + Widget build(BuildContext context) { + return DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset( + icon, + width: 60, + height: 60, + ), + Text( + description, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 16), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/sensors_widgets/presence_status.dart b/lib/pages/device_managment/shared/sensors_widgets/presence_status.dart new file mode 100644 index 00000000..26e47c8b --- /dev/null +++ b/lib/pages/device_managment/shared/sensors_widgets/presence_status.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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 PresenceState extends StatelessWidget { + const PresenceState({ + super.key, + required this.value, + }); + + final String value; + + @override + Widget build(BuildContext context) { + return DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Status:', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 10), + ), + ], + ), + SvgPicture.asset( + value.toLowerCase() == 'motion' + ? Assets.sensorMotionIcon + : value.toLowerCase() == 'presence' + ? Assets.sensorPresenceIcon + : Assets.sensorVacantIcon, + width: 60, + height: 60, + ), + Text( + value, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 16), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart b/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart new file mode 100644 index 00000000..16596a1f --- /dev/null +++ b/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class PresenceUpdateData extends StatefulWidget { + const PresenceUpdateData({ + super.key, + required this.title, + required this.value, + required this.action, + required this.minValue, + required this.maxValue, + required this.steps, + this.description, + }); + + final String title; + final double value; + final double minValue; + final double maxValue; + final double steps; + final Function action; + final String? description; + + @override + State createState() => _CurrentTempState(); +} + +class _CurrentTempState extends State { + late double _adjustedValue; + + @override + void initState() { + super.initState(); + _adjustedValue = _initialAdjustedValue(widget.value); + } + + double _initialAdjustedValue(dynamic value) { + if (value is int || value is double) { + return value; + } else { + throw ArgumentError('Invalid value type: Expected int or double'); + } + } + + void _onValueChanged(double newValue) { + widget.action(newValue.toInt()); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DeviceControlsContainer( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.title, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 10), + ), + IncrementDecrementWidget( + value: widget.value.toString(), + description: widget.description ?? '', + descriptionColor: ColorsManager.blackColor, + onIncrement: () { + if (_adjustedValue < widget.maxValue) { + setState(() { + _adjustedValue = _adjustedValue + widget.steps; + }); + _onValueChanged(_adjustedValue); + } + }, + onDecrement: () { + if (_adjustedValue > widget.minValue) { + setState(() { + _adjustedValue = _adjustedValue - widget.steps; + }); + _onValueChanged(_adjustedValue); + } + }), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart b/lib/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart new file mode 100644 index 00000000..6d37ec3a --- /dev/null +++ b/lib/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class PresenceNoBodyTime extends StatefulWidget { + const PresenceNoBodyTime({ + super.key, + required this.title, + required this.value, + required this.action, + this.description, + }); + + final String title; + final String value; + final Function(String) action; + final String? description; + + @override + State createState() => _PresenceUpdateDataState(); +} + +class _PresenceUpdateDataState extends State { + late String _currentValue; + + final List nobodyTimeRange = [ + 'none', + '10s', + '30s', + '1min', + '2min', + '5min', + '10min', + '30min', + '1hour' + ]; + + @override + void initState() { + super.initState(); + _currentValue = widget.value; + } + + void _onValueChanged(String newValue) { + widget.action(newValue); + } + + void _incrementValue() { + int currentIndex = nobodyTimeRange.indexOf(_currentValue); + if (currentIndex < nobodyTimeRange.length - 1) { + setState(() { + _currentValue = nobodyTimeRange[currentIndex + 1]; + }); + _onValueChanged(_currentValue); + } + } + + void _decrementValue() { + int currentIndex = nobodyTimeRange.indexOf(_currentValue); + if (currentIndex > 0) { + setState(() { + _currentValue = nobodyTimeRange[currentIndex - 1]; + }); + _onValueChanged(_currentValue); + } + } + + @override + Widget build(BuildContext context) { + return DeviceControlsContainer( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.title, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 10), + ), + IncrementDecrementWidget( + value: _currentValue, + description: widget.description ?? '', + descriptionColor: ColorsManager.blackColor, + onIncrement: _incrementValue, + onDecrement: _decrementValue), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/table/description_view.dart b/lib/pages/device_managment/shared/table/description_view.dart new file mode 100644 index 00000000..8c68e841 --- /dev/null +++ b/lib/pages/device_managment/shared/table/description_view.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +class DescriptionView extends StatelessWidget { + final String description; + final VoidCallback onClose; + + const DescriptionView({ + super.key, + required this.description, + required this.onClose, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 50), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Help Description', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.grey), + ), + GestureDetector( + onTap: onClose, + child: Text( + 'Close', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.grey), + ), + ), + ], + ), + const SizedBox(height: 10), + Text(description), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/table/report_table.dart b/lib/pages/device_managment/shared/table/report_table.dart new file mode 100644 index 00000000..619df168 --- /dev/null +++ b/lib/pages/device_managment/shared/table/report_table.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/table_cell_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/table_header.dart'; + +class ReportsTable extends StatelessWidget { + final DeviceReport report; + final Function(int index) onRowTap; + final VoidCallback onClose; + + const ReportsTable({ + super.key, + required this.report, + required this.onRowTap, + required this.onClose, + }); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Padding( + padding: const EdgeInsets.all(20.0), + child: Table( + border: TableBorder.all(color: Colors.grey.shade300, width: 1), + columnWidths: const { + 0: FlexColumnWidth(), + 1: FlexColumnWidth(), + 2: FlexColumnWidth(), + }, + children: [ + TableRow( + decoration: BoxDecoration(color: Colors.grey.shade200), + children: const [ + TableHeader(title: 'Date'), + TableHeader(title: 'Time'), + TableHeader(title: 'Status'), + ], + ), + ...report.data!.asMap().entries.map((entry) { + int index = entry.key; + DeviceEvent data = entry.value; + + // Parse eventTime into Date and Time + DateTime eventDateTime = + DateTime.fromMillisecondsSinceEpoch(data.eventTime!); + String date = DateFormat('dd/MM/yyyy').format(eventDateTime); + String time = DateFormat('HH:mm').format(eventDateTime); + + return TableRow( + children: [ + TableCellWidget(value: date), + TableCellWidget(value: time), + TableCellWidget( + value: data.value!, + onTap: () => onRowTap(index), + ), + ], + ); + }).toList(), + ], + ), + ), + Positioned( + top: 0, + right: 0, + child: IconButton( + icon: const Icon( + Icons.close, + color: Colors.red, + size: 18, + ), + onPressed: onClose, + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/shared/table/table_cell_widget.dart b/lib/pages/device_managment/shared/table/table_cell_widget.dart new file mode 100644 index 00000000..171e1462 --- /dev/null +++ b/lib/pages/device_managment/shared/table/table_cell_widget.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class TableCellWidget extends StatelessWidget { + final String value; + final Function()? onTap; + + const TableCellWidget({ + super.key, + required this.value, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(value), + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/table/table_header.dart b/lib/pages/device_managment/shared/table/table_header.dart new file mode 100644 index 00000000..39bc1c73 --- /dev/null +++ b/lib/pages/device_managment/shared/table/table_header.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class TableHeader extends StatelessWidget { + final String title; + + const TableHeader({ + super.key, + required this.title, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + title, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ); + } +} diff --git a/lib/pages/device_managment/wall_sensor/bloc/bloc.dart b/lib/pages/device_managment/wall_sensor/bloc/bloc.dart new file mode 100644 index 00000000..a2697cd3 --- /dev/null +++ b/lib/pages/device_managment/wall_sensor/bloc/bloc.dart @@ -0,0 +1,119 @@ +import 'dart:async'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/event.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class WallSensorBloc extends Bloc { + final String deviceId; + late WallSensorModel deviceStatus; + Timer? _timer; + + WallSensorBloc({required this.deviceId}) : super(WallSensorInitialState()) { + on(_fetchWallSensorStatus); + on(_changeValue); + on(_getDeviceReports); + on(_showDescription); + on(_backToGridView); + } + + void _fetchWallSensorStatus( + WallSensorInitialEvent event, Emitter emit) async { + emit(WallSensorLoadingInitialState()); + try { + var response = await DevicesManagementApi().getDeviceStatus(deviceId); + deviceStatus = WallSensorModel.fromJson(response.status); + emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); + // _listenToChanges(); + } catch (e) { + emit(WallSensorFailedState(error: e.toString())); + return; + } + } + + // _listenToChanges() { + // try { + // DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); + // Stream stream = ref.onValue; + + // stream.listen((DatabaseEvent event) { + // Map usersMap = event.snapshot.value as Map; + // List statusList = []; + + // usersMap['status'].forEach((element) { + // statusList.add(StatusModel(code: element['code'], value: element['value'])); + // }); + + // deviceStatus = WallSensorModel.fromJson(statusList); + // add(WallSensorUpdatedEvent()); + // }); + // } catch (_) {} + // } + + void _changeValue( + WallSensorChangeValueEvent event, Emitter emit) async { + emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); + if (event.code == 'far_detection') { + deviceStatus.farDetection = event.value; + } else if (event.code == 'motionless_sensitivity') { + deviceStatus.motionlessSensitivity = event.value; + } else if (event.code == 'motion_sensitivity_value') { + deviceStatus.motionSensitivity = event.value; + } else if (event.code == 'no_one_time') { + deviceStatus.noBodyTime = event.value; + } + emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); + await _runDeBouncer( + deviceId: deviceId, code: event.code, value: event.value); + } + + _runDeBouncer({ + required String deviceId, + required String code, + required dynamic value, + }) { + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + try { + final response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + + if (!response) { + add(WallSensorInitialEvent()); + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(WallSensorInitialEvent()); + } + }); + } + + FutureOr _getDeviceReports( + GetDeviceReportsEvent event, Emitter emit) async { + emit(DeviceReportsLoadingState()); + + try { + await DevicesManagementApi.getDeviceReports(deviceId, event.code) + .then((value) { + emit(DeviceReportsState(deviceReport: value)); + }); + } catch (e) { + emit(DeviceReportsFailedState(error: e.toString())); + return; + } + } + + void _showDescription( + ShowDescriptionEvent event, Emitter emit) { + emit(WallSensorShowDescriptionState(description: event.description)); + } + + void _backToGridView( + BackToGridViewEvent event, Emitter emit) { + emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); + } +} diff --git a/lib/pages/device_managment/wall_sensor/bloc/event.dart b/lib/pages/device_managment/wall_sensor/bloc/event.dart new file mode 100644 index 00000000..d3c20ba7 --- /dev/null +++ b/lib/pages/device_managment/wall_sensor/bloc/event.dart @@ -0,0 +1,38 @@ +import 'package:equatable/equatable.dart'; + +abstract class WallSensorEvent extends Equatable { + const WallSensorEvent(); + + @override + List get props => []; +} + +class WallSensorInitialEvent extends WallSensorEvent {} + +class WallSensorChangeValueEvent extends WallSensorEvent { + final int value; + final String code; + const WallSensorChangeValueEvent({required this.value, required this.code}); + + @override + List get props => [value, code]; +} + +class GetDeviceReportsEvent extends WallSensorEvent { + final String deviceUuid; + final String code; + const GetDeviceReportsEvent({ + required this.deviceUuid, + required this.code, + }); + + @override + List get props => [deviceUuid, code]; +} + +class ShowDescriptionEvent extends WallSensorEvent { + final String description; + const ShowDescriptionEvent({required this.description}); +} + +class BackToGridViewEvent extends WallSensorEvent {} diff --git a/lib/pages/device_managment/wall_sensor/bloc/state.dart b/lib/pages/device_managment/wall_sensor/bloc/state.dart new file mode 100644 index 00000000..67ce0d19 --- /dev/null +++ b/lib/pages/device_managment/wall_sensor/bloc/state.dart @@ -0,0 +1,56 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; + +class WallSensorState extends Equatable { + const WallSensorState(); + + @override + List get props => []; +} + +class WallSensorInitialState extends WallSensorState {} + +class WallSensorLoadingInitialState extends WallSensorState {} + +class WallSensorUpdateState extends WallSensorState { + final WallSensorModel wallSensorModel; + const WallSensorUpdateState({required this.wallSensorModel}); + + @override + List get props => [wallSensorModel]; +} + +class WallSensorLoadingNewSate extends WallSensorState { + final WallSensorModel wallSensorModel; + const WallSensorLoadingNewSate({required this.wallSensorModel}); + + @override + List get props => [wallSensorModel]; +} + +class WallSensorFailedState extends WallSensorState { + final String error; + + const WallSensorFailedState({required this.error}); + + @override + List get props => [error]; +} + +class DeviceReportsLoadingState extends WallSensorState {} + +class DeviceReportsState extends WallSensorState { + final DeviceReport deviceReport; + const DeviceReportsState({required this.deviceReport}); +} + +class DeviceReportsFailedState extends WallSensorState { + final String error; + const DeviceReportsFailedState({required this.error}); +} + +class WallSensorShowDescriptionState extends WallSensorState { + final String description; + const WallSensorShowDescriptionState({required this.description}); +} diff --git a/lib/pages/device_managment/wall_sensor/model/wall_sensor_model.dart b/lib/pages/device_managment/wall_sensor/model/wall_sensor_model.dart new file mode 100644 index 00000000..92b1842e --- /dev/null +++ b/lib/pages/device_managment/wall_sensor/model/wall_sensor_model.dart @@ -0,0 +1,68 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class WallSensorModel { + String presenceState; + int farDetection; + int presenceTime; + int motionSensitivity; + int motionlessSensitivity; + int currentDistance; + int illuminance; + bool indicator; + int noBodyTime; + + WallSensorModel( + {required this.presenceState, + required this.farDetection, + required this.presenceTime, + required this.motionSensitivity, + required this.motionlessSensitivity, + required this.currentDistance, + required this.illuminance, + required this.indicator, + required this.noBodyTime}); + + factory WallSensorModel.fromJson(List jsonList) { + late String _presenceState; + late int _farDetection; + late int _presenceTime; + late int _motionSensitivity; + late int _motionlessSensitivity; + late int _currentDistance; + late int _illuminance; + late bool _indicator; + late int _noBodyTime; + + for (int i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'presence_state') { + _presenceState = jsonList[i].value ?? 'none'; + } else if (jsonList[i].code == 'far_detection') { + _farDetection = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'presence_time') { + _presenceTime = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'motion_sensitivity_value') { + _motionSensitivity = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'motionless_sensitivity') { + _motionlessSensitivity = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'dis_current') { + _currentDistance = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'illuminance_value') { + _illuminance = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'indicator') { + _indicator = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'no_one_time') { + _noBodyTime = jsonList[i].value ?? 0; + } + } + return WallSensorModel( + presenceState: _presenceState, + farDetection: _farDetection, + presenceTime: _presenceTime, + motionSensitivity: _motionSensitivity, + motionlessSensitivity: _motionlessSensitivity, + currentDistance: _currentDistance, + illuminance: _illuminance, + indicator: _indicator, + noBodyTime: _noBodyTime); + } +} diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart new file mode 100644 index 00000000..068cb27d --- /dev/null +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/description_view.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/bloc.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/event.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class WallSensorControls extends StatelessWidget with HelperResponsiveLayout { + const WallSensorControls({super.key, required this.device}); + + final AllDevicesModel device; + + @override + Widget build(BuildContext context) { + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return BlocProvider( + create: (context) => + WallSensorBloc(deviceId: device.uuid!)..add(WallSensorInitialEvent()), + child: BlocBuilder( + builder: (context, state) { + if (state is WallSensorLoadingInitialState || + state is DeviceReportsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is WallSensorUpdateState) { + return _buildGridView( + context, state.wallSensorModel, isLarge, isMedium); + } else if (state is DeviceReportsState) { + return ReportsTable( + report: state.deviceReport, + onRowTap: (index) {}, + onClose: () { + context.read().add(BackToGridViewEvent()); + }, + ); + } else if (state is WallSensorShowDescriptionState) { + return DescriptionView( + description: state.description, + onClose: () { + context.read().add(BackToGridViewEvent()); + }, + ); + } else if (state is DeviceReportsFailedState) { + final model = context.read().deviceStatus; + return _buildGridView(context, model, isLarge, isMedium); + } + return const Center(child: Text('Error fetching status')); + }, + ), + ); + } + + Widget _buildGridView(BuildContext context, WallSensorModel model, + bool isLarge, bool isMedium) { + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + PresenceState( + value: model.presenceState, + ), + PresenceDisplayValue( + value: model.presenceTime.toString(), + postfix: 'min', + description: 'Presence Time', + ), + PresenceDisplayValue( + value: model.currentDistance.toString(), + postfix: 'cm', + description: 'Current Distance', + ), + PresenceDisplayValue( + value: model.illuminance.toString(), + postfix: 'Lux', + description: 'Illuminance Value', + ), + PresenceUpdateData( + value: model.motionSensitivity.toDouble(), + title: 'Motion Detection Sensitivity:', + minValue: 1, + maxValue: 5, + steps: 1, + action: (int value) { + context.read().add( + WallSensorChangeValueEvent( + code: 'motion_sensitivity_value', + value: value, + ), + ); + }, + ), + PresenceUpdateData( + value: model.motionlessSensitivity.toDouble(), + title: 'Motionless Detection Sensitivity:', + minValue: 1, + maxValue: 5, + steps: 1, + action: (int value) => context.read().add( + WallSensorChangeValueEvent( + code: 'motionless_sensitivity', + value: value, + ), + ), + ), + PresenceUpdateData( + value: model.noBodyTime.toDouble(), + title: 'Nobody Time:', + minValue: 10, + maxValue: 10000, + steps: 1, + description: 'hr', + action: (int value) => + context.read().add(WallSensorChangeValueEvent( + code: 'no_one_time', + value: value, + ))), + PresenceUpdateData( + value: model.farDetection.toDouble(), + title: 'Far Detection:', + minValue: 75, + maxValue: 600, + steps: 75, + description: 'cm', + action: (int value) => context.read().add( + WallSensorChangeValueEvent( + code: 'far_detection', + value: value, + ), + ), + ), + GestureDetector( + onTap: () { + context.read().add(GetDeviceReportsEvent( + code: 'illuminance_value', deviceUuid: device.uuid!)); + }, + child: const PresenceStaticWidget( + icon: Assets.illuminanceRecordIcon, + description: 'Illuminance Record', + ), + ), + GestureDetector( + onTap: () { + context.read().add(GetDeviceReportsEvent( + code: 'presence_state', deviceUuid: device.uuid!)); + }, + child: const PresenceStaticWidget( + icon: Assets.presenceRecordIcon, + description: 'Presence Record', + ), + ), + ], + ); + } +} diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 57913c1f..42a63592 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -1,15 +1,11 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:go_router/go_router.dart'; import 'package:graphview/GraphView.dart'; -import 'package:syncrow_web/pages/access_management/view/access_management.dart'; import 'package:syncrow_web/pages/auth/model/user_model.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/home_model/home_item_model.dart'; -import 'package:syncrow_web/pages/spaseManagementIcon.dart'; import 'package:syncrow_web/services/home_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -25,7 +21,6 @@ class HomeBloc extends Bloc { HomeBloc() : super((HomeInitial())) { on(_createNode); fetchUserInfo(); - } void _createNode(CreateNewNode event, Emitter emit) async { @@ -44,12 +39,9 @@ class HomeBloc extends Bloc { emit(HomeUpdateTree(graph: graph, builder: builder)); } - - Future fetchUserInfo() async { try { - var uuid = - await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); emit(HomeUserInfoLoaded(user!)); // Emit state after fetching user info } catch (e) { @@ -71,9 +63,7 @@ class HomeBloc extends Bloc { title: 'Space Management', icon: Assets.spaseManagementIcon, active: true, - onPress: (context) { - - }, + onPress: (context) {}, color: ColorsManager.primaryColor, ), HomeItemModel( @@ -81,6 +71,7 @@ class HomeBloc extends Bloc { icon: Assets.devicesIcon, active: true, onPress: (context) { + context.go(RoutesConst.deviceManagementPage); }, color: ColorsManager.primaryColor, ), @@ -88,40 +79,35 @@ class HomeBloc extends Bloc { title: 'Move in', icon: Assets.moveinIcon, active: false, - onPress: (context) { - }, + onPress: (context) {}, color: ColorsManager.primaryColor, ), HomeItemModel( title: 'Construction', icon: Assets.constructionIcon, active: false, - onPress: (context) { - }, + onPress: (context) {}, color: ColorsManager.primaryColor, ), HomeItemModel( title: 'Energy', icon: Assets.energyIcon, active: false, - onPress: (context) { - }, + onPress: (context) {}, color: ColorsManager.slidingBlueColor.withOpacity(0.2), ), HomeItemModel( title: 'Integrations', icon: Assets.integrationsIcon, active: false, - onPress: (context) { - }, + onPress: (context) {}, color: ColorsManager.slidingBlueColor.withOpacity(0.2), ), HomeItemModel( title: 'Asset', icon: Assets.assetIcon, active: false, - onPress: (context) { - }, + onPress: (context) {}, color: ColorsManager.slidingBlueColor.withOpacity(0.2), ), ]; diff --git a/lib/pages/home/bloc/home_state.dart b/lib/pages/home/bloc/home_state.dart index 7a1be0be..dda3fa50 100644 --- a/lib/pages/home/bloc/home_state.dart +++ b/lib/pages/home/bloc/home_state.dart @@ -25,6 +25,7 @@ class HomeUpdateTree extends HomeState { @override List get props => [graph, builder]; } + class HomeUserInfoLoaded extends HomeState { final UserModel user; diff --git a/lib/pages/home/home_model/home_item_model.dart b/lib/pages/home/home_model/home_item_model.dart index 5626cd3f..dee44346 100644 --- a/lib/pages/home/home_model/home_item_model.dart +++ b/lib/pages/home/home_model/home_item_model.dart @@ -1,7 +1,3 @@ - - - - import 'package:flutter/cupertino.dart'; class HomeItemModel { @@ -11,12 +7,11 @@ class HomeItemModel { final bool? active; final void Function(BuildContext context) onPress; - HomeItemModel({ - this.title, - this.icon, - this.color, - this.active, + this.title, + this.icon, + this.color, + this.active, required this.onPress, }); -} \ No newline at end of file +} diff --git a/lib/pages/home/view/home_card.dart b/lib/pages/home/view/home_card.dart index 747ecd59..d2e71608 100644 --- a/lib/pages/home/view/home_card.dart +++ b/lib/pages/home/view/home_card.dart @@ -22,19 +22,19 @@ class HomeCard extends StatelessWidget { return InkWell( onTap: active ? onTap : null, child: Container( - padding: const EdgeInsets.only(left: 10,right: 10,bottom: 10), + padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10), decoration: BoxDecoration( - color: index==0&&active? - ColorsManager.blue1.withOpacity(0.9): - index==1&&active? - ColorsManager.blue2.withOpacity(0.9) : - index==2&&active? - ColorsManager.blue3: - index==4&&active==false? - ColorsManager.blue4.withOpacity(0.2): - index==7&&active==false? - ColorsManager.blue4.withOpacity(0.2): - ColorsManager.blueColor.withOpacity(0.2), + color: index == 0 && active + ? ColorsManager.blue1.withOpacity(0.9) + : index == 1 && active + ? ColorsManager.blue2.withOpacity(0.9) + : index == 2 && active + ? ColorsManager.blue3 + : index == 4 && active == false + ? ColorsManager.blue4.withOpacity(0.2) + : index == 7 && active == false + ? ColorsManager.blue4.withOpacity(0.2) + : ColorsManager.blueColor.withOpacity(0.2), // (active ?ColorsManager.blueColor // : ColorsManager.blueColor.withOpacity(0.2)), borderRadius: BorderRadius.circular(30), diff --git a/lib/pages/home/view/home_page.dart b/lib/pages/home/view/home_page.dart index 3d23317c..c1e36729 100644 --- a/lib/pages/home/view/home_page.dart +++ b/lib/pages/home/view/home_page.dart @@ -8,11 +8,6 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { - return - - ResponsiveLayout( - desktopBody: HomeWebPage(), - mobileBody:HomeMobilePage() - ); + return ResponsiveLayout(desktopBody: HomeWebPage(), mobileBody: HomeMobilePage()); } } diff --git a/lib/pages/home/view/home_page_mobile.dart b/lib/pages/home/view/home_page_mobile.dart index 1b868906..dcf35a41 100644 --- a/lib/pages/home/view/home_page_mobile.dart +++ b/lib/pages/home/view/home_page_mobile.dart @@ -44,7 +44,8 @@ class HomeMobilePage extends StatelessWidget { width: size.width * 0.68, child: GridView.builder( itemCount: 8, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index f542f6d3..2a578fc9 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -43,7 +43,8 @@ class HomeWebPage extends StatelessWidget { Text( 'ACCESS YOUR APPS', style: Theme.of(context) - .textTheme.headlineLarge! + .textTheme + .headlineLarge! .copyWith(color: Colors.black, fontSize: 40), ), const SizedBox(height: 30), @@ -67,7 +68,8 @@ class HomeWebPage extends StatelessWidget { active: homeBloc.homeItems[index].active!, name: homeBloc.homeItems[index].title!, img: homeBloc.homeItems[index].icon!, - onTap: () => homeBloc.homeItems[index].onPress(context), + onTap: () => + homeBloc.homeItems[index].onPress(context), ); }, ), diff --git a/lib/pages/home/view/tree_page.dart b/lib/pages/home/view/tree_page.dart index 6038dadf..2166467f 100644 --- a/lib/pages/home/view/tree_page.dart +++ b/lib/pages/home/view/tree_page.dart @@ -32,8 +32,8 @@ class TreeWidget extends StatelessWidget { SizedBox( width: 100, child: TextFormField( - decoration: - const InputDecoration(labelText: "Subtree separation"), + decoration: const InputDecoration( + labelText: "Subtree separation"), onChanged: (text) { firstNodeName = text; }, @@ -73,9 +73,7 @@ class TreeWidget extends StatelessWidget { child: GraphView( graph: state.graph, algorithm: BuchheimWalkerAlgorithm( - state.builder, - TreeEdgeRenderer(state.builder) - ), + state.builder, TreeEdgeRenderer(state.builder)), paint: Paint() ..color = Colors.green ..strokeWidth = 1 @@ -138,7 +136,7 @@ Widget rectangleWidget(String text, Node node, BuildContext blocContext) { ); }, child: Container( - width: MediaQuery.of(blocContext).size.width*0.2, + width: MediaQuery.of(blocContext).size.width * 0.2, margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), padding: EdgeInsets.all(20.0), decoration: BoxDecoration( @@ -163,7 +161,7 @@ Widget rectangleWidget(String text, Node node, BuildContext blocContext) { ), ), const SizedBox(width: 10.0), - SizedBox( + SizedBox( child: Text( text, style: const TextStyle( diff --git a/lib/pages/spaseManagementIcon.dart b/lib/pages/space_management/spaseManagementIcon.dart similarity index 100% rename from lib/pages/spaseManagementIcon.dart rename to lib/pages/space_management/spaseManagementIcon.dart diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index e1ee0edd..2b64d606 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -15,8 +15,7 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; -class VisitorPasswordBloc - extends Bloc { +class VisitorPasswordBloc extends Bloc { VisitorPasswordBloc() : super(VisitorPasswordInitial()) { on(selectUsageFrequency); on(_onFetchDevice); @@ -39,8 +38,7 @@ class VisitorPasswordBloc final TextEditingController deviceNameController = TextEditingController(); final TextEditingController deviceIdController = TextEditingController(); final TextEditingController unitNameController = TextEditingController(); - final TextEditingController virtualAddressController = - TextEditingController(); + final TextEditingController virtualAddressController = TextEditingController(); List selectedDevices = []; List data = []; @@ -65,14 +63,12 @@ class VisitorPasswordBloc String startTimeAccess = 'Start Time'; String endTimeAccess = 'End Time'; PasswordStatus? passwordStatus; - selectAccessType( - SelectPasswordType event, Emitter emit) { + selectAccessType(SelectPasswordType event, Emitter emit) { accessTypeSelected = event.type; emit(PasswordTypeSelected(event.type)); } - selectUsageFrequency( - SelectUsageFrequency event, Emitter emit) { + selectUsageFrequency(SelectUsageFrequency event, Emitter emit) { usageFrequencySelected = event.usageType; emit(UsageFrequencySelected(event.usageType)); } @@ -119,12 +115,10 @@ class VisitorPasswordBloc timePicked.minute, ); - final selectedTimestamp = - selectedDateTime.millisecondsSinceEpoch ~/ 1000; + final selectedTimestamp = selectedDateTime.millisecondsSinceEpoch ~/ 1000; if (event.isStart) { - if (expirationTimeTimeStamp != null && - selectedTimestamp > expirationTimeTimeStamp!) { + if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { CustomSnackBar.displaySnackBar( 'Effective Time cannot be later than Expiration Time.', ); @@ -133,8 +127,7 @@ class VisitorPasswordBloc effectiveTimeTimeStamp = selectedTimestamp; startTimeAccess = selectedDateTime.toString().split('.').first; } else { - if (effectiveTimeTimeStamp != null && - selectedTimestamp < effectiveTimeTimeStamp!) { + if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { CustomSnackBar.displaySnackBar( 'Expiration Time cannot be earlier than Effective Time.', ); @@ -149,8 +142,7 @@ class VisitorPasswordBloc } } - bool toggleRepeat( - ToggleRepeatEvent event, Emitter emit) { + bool toggleRepeat(ToggleRepeatEvent event, Emitter emit) { emit(LoadingInitialState()); repeat = !repeat; emit(IsRepeatState(repeat: repeat)); @@ -182,8 +174,7 @@ class VisitorPasswordBloc emit(ChangeTimeState()); } - Future _onFetchDevice( - FetchDevice event, Emitter emit) async { + Future _onFetchDevice(FetchDevice event, Emitter emit) async { try { emit(DeviceLoaded()); data = await AccessMangApi().fetchDevices(); @@ -194,8 +185,8 @@ class VisitorPasswordBloc } //online password - Future postOnlineOneTimePassword(OnlineOneTimePasswordEvent event, - Emitter emit) async { + Future postOnlineOneTimePassword( + OnlineOneTimePasswordEvent event, Emitter emit) async { try { emit(LoadingInitialState()); generate7DigitNumber(); @@ -206,23 +197,21 @@ class VisitorPasswordBloc passwordName: event.passwordName, effectiveTime: effectiveTimeTimeStamp.toString(), invalidTime: expirationTimeTimeStamp.toString()); - if(res['statusCode']==201){ - passwordStatus= PasswordStatus.fromJson(res['data']); + if (res['statusCode'] == 201) { + passwordStatus = PasswordStatus.fromJson(res['data']); emit(SuccessState()); } emit(TableLoaded(data)); - } on DioException catch (e){ - final errorData = e.response!.data; + } on DioException catch (e) { + final errorData = e.response!.data; String errorMessage = errorData['message']; print('errorMessage==$errorData'); emit(FailedState(errorMessage.toString())); } } - Future postOnlineMultipleTimePassword( - OnlineMultipleTimePasswordEvent event, - Emitter emit) async { + OnlineMultipleTimePasswordEvent event, Emitter emit) async { try { emit(LoadingInitialState()); @@ -242,13 +231,12 @@ class VisitorPasswordBloc email: event.email, devicesUuid: selectedDevices, passwordName: event.passwordName); - if(res['statusCode']==201){ - passwordStatus= PasswordStatus.fromJson(res['data']); + if (res['statusCode'] == 201) { + passwordStatus = PasswordStatus.fromJson(res['data']); emit(SuccessState()); } emit(TableLoaded(data)); - - } on DioException catch (e){ + } on DioException catch (e) { final errorData = e.response!.data; String errorMessage = errorData['message']; print('errorMessage==$errorData'); @@ -258,22 +246,18 @@ class VisitorPasswordBloc //offline password Future postOfflineOneTimePassword( - OfflineOneTimePasswordEvent event, - Emitter emit) async { + OfflineOneTimePasswordEvent event, Emitter emit) async { try { emit(LoadingInitialState()); await generate7DigitNumber(); - var res = await AccessMangApi().postOffLineOneTime( - email: event.email, - devicesUuid: selectedDevices, - passwordName: event.passwordName); - if(res['statusCode']==201){ - passwordStatus= PasswordStatus.fromJson(res['data']); + var res = await AccessMangApi().postOffLineOneTime( + email: event.email, devicesUuid: selectedDevices, passwordName: event.passwordName); + if (res['statusCode'] == 201) { + passwordStatus = PasswordStatus.fromJson(res['data']); emit(SuccessState()); } emit(TableLoaded(data)); - - } on DioException catch (e){ + } on DioException catch (e) { final errorData = e.response!.data; String errorMessage = errorData['message']; print('errorMessage==$errorData'); @@ -282,24 +266,23 @@ class VisitorPasswordBloc } Future postOfflineMultipleTimePassword( - OfflineMultipleTimePasswordEvent event, - Emitter emit) async { + OfflineMultipleTimePasswordEvent event, Emitter emit) async { try { emit(LoadingInitialState()); await generate7DigitNumber(); - var res = await AccessMangApi().postOffLineMultipleTime( + var res = await AccessMangApi().postOffLineMultipleTime( email: event.email, devicesUuid: selectedDevices, passwordName: event.passwordName, invalidTime: expirationTimeTimeStamp.toString(), effectiveTime: effectiveTimeTimeStamp.toString(), ); - if(res['statusCode']==201){ - passwordStatus= PasswordStatus.fromJson(res['data']); + if (res['statusCode'] == 201) { + passwordStatus = PasswordStatus.fromJson(res['data']); emit(SuccessState()); } emit(TableLoaded(data)); - } on DioException catch (e){ + } on DioException catch (e) { final errorData = e.response!.data; String errorMessage = errorData['message']; print('errorMessage==$errorData'); @@ -307,8 +290,7 @@ class VisitorPasswordBloc } } - void selectDevice( - SelectDeviceEvent event, Emitter emit) { + void selectDevice(SelectDeviceEvent event, Emitter emit) { if (selectedDeviceIds.contains(event.deviceId)) { selectedDeviceIds.remove(event.deviceId); } else { @@ -351,8 +333,7 @@ class VisitorPasswordBloc } @override - Stream mapEventToState( - VisitorPasswordEvent event) async* { + Stream mapEventToState(VisitorPasswordEvent event) async* { if (event is FetchDevice) { } else if (event is UpdateFilteredDevicesEvent) { yield TableLoaded(event.filteredData); @@ -400,27 +381,20 @@ class VisitorPasswordBloc ).millisecondsSinceEpoch ~/ 1000; // Divide by 1000 to remove milliseconds if (event.isEffective) { - if (expirationTimeTimeStamp != null && - selectedTimestamp > expirationTimeTimeStamp!) { - CustomSnackBar.displaySnackBar( - 'Effective Time cannot be later than Expiration Time.'); + if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); } else { - effectiveTime = selectedDateTime - .toString() - .split('.') - .first; // Remove seconds and milliseconds + effectiveTime = + selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds effectiveTimeTimeStamp = selectedTimestamp; } } else { - if (effectiveTimeTimeStamp != null && - selectedTimestamp < effectiveTimeTimeStamp!) { + if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { CustomSnackBar.displaySnackBar( 'Expiration Time cannot be earlier than Effective Time.'); } else { - expirationTime = selectedDateTime - .toString() - .split('.') - .first; // Remove seconds and milliseconds + expirationTime = + selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds expirationTimeTimeStamp = selectedTimestamp; } } @@ -475,7 +449,7 @@ class VisitorPasswordBloc iconPath: Assets.deviceNoteIcon, title: title, widget: widgeta, - dialogHeight: MediaQuery.of(context).size.height*0.3, + dialogHeight: MediaQuery.of(context).size.height * 0.3, actions: actions ?? [ TextButton( @@ -487,6 +461,4 @@ class VisitorPasswordBloc ], ); } - } - diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index 9526bf54..6ecae200 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -26,33 +26,32 @@ class SelectUsageFrequency extends VisitorPasswordEvent { @override List get props => [usageType]; } + class SelectTimeVisitorPassword extends VisitorPasswordEvent { final BuildContext context; final bool isStart; final bool isRepeat; - const SelectTimeVisitorPassword({ required this.context,required this.isStart,required this.isRepeat}); + const SelectTimeVisitorPassword( + {required this.context, required this.isStart, required this.isRepeat}); @override - List get props => [context,isStart,isRepeat]; + List get props => [context, isStart, isRepeat]; } - - class ToggleDaySelectionEvent extends VisitorPasswordEvent { - final String key; + final String key; const ToggleDaySelectionEvent({required this.key}); @override List get props => [key]; } - class ToggleRepeatEvent extends VisitorPasswordEvent {} + class GeneratePasswordEvent extends VisitorPasswordEvent {} -class FetchDevice extends VisitorPasswordEvent { -} +class FetchDevice extends VisitorPasswordEvent {} //online password class OnlineOneTimePasswordEvent extends VisitorPasswordEvent { @@ -60,20 +59,31 @@ class OnlineOneTimePasswordEvent extends VisitorPasswordEvent { final String? passwordName; final BuildContext? context; - const OnlineOneTimePasswordEvent({this.email,this.passwordName,this.context}); + const OnlineOneTimePasswordEvent( + {this.email, this.passwordName, this.context}); @override - List get props => [email!,passwordName!,]; + List get props => [ + email!, + passwordName!, + ]; } + class OnlineMultipleTimePasswordEvent extends VisitorPasswordEvent { final String? email; final String? passwordName; final String? invalidTime; final String? effectiveTime; final BuildContext? context; - const OnlineMultipleTimePasswordEvent({this.email,this.passwordName,this.invalidTime,this.effectiveTime,this.context}); + const OnlineMultipleTimePasswordEvent( + {this.email, + this.passwordName, + this.invalidTime, + this.effectiveTime, + this.context}); @override - List get props => [email!,passwordName!,invalidTime!,effectiveTime!,context!]; + List get props => + [email!, passwordName!, invalidTime!, effectiveTime!, context!]; } //offline password @@ -81,9 +91,14 @@ class OfflineOneTimePasswordEvent extends VisitorPasswordEvent { final BuildContext? context; final String? email; final String? passwordName; - const OfflineOneTimePasswordEvent({this.email,this.passwordName,this.context}); + const OfflineOneTimePasswordEvent( + {this.email, this.passwordName, this.context}); @override - List get props => [email!,passwordName!,context!,]; + List get props => [ + email!, + passwordName!, + context!, + ]; } class OfflineMultipleTimePasswordEvent extends VisitorPasswordEvent { @@ -93,13 +108,18 @@ class OfflineMultipleTimePasswordEvent extends VisitorPasswordEvent { final String? effectiveTime; final BuildContext? context; - const OfflineMultipleTimePasswordEvent({this.context,this.email,this.passwordName,this.invalidTime,this.effectiveTime}); + const OfflineMultipleTimePasswordEvent( + {this.context, + this.email, + this.passwordName, + this.invalidTime, + this.effectiveTime}); @override - List get props => [email!,passwordName!,invalidTime!,effectiveTime!,context!]; + List get props => + [email!, passwordName!, invalidTime!, effectiveTime!, context!]; } - class SelectDeviceEvent extends VisitorPasswordEvent { final String deviceId; const SelectDeviceEvent(this.deviceId); @@ -116,22 +136,26 @@ class FilterDataEvent extends VisitorPasswordEvent { this.endTime, }); } + class UpdateFilteredDevicesEvent extends VisitorPasswordEvent { final List filteredData; UpdateFilteredDevicesEvent(this.filteredData); -}class SelectTimeEvent extends VisitorPasswordEvent { - final BuildContext context; - final bool isEffective; - const SelectTimeEvent({required this.context,required this.isEffective}); - @override - List get props => [context,isEffective]; } + +class SelectTimeEvent extends VisitorPasswordEvent { + final BuildContext context; + final bool isEffective; + const SelectTimeEvent({required this.context, required this.isEffective}); + @override + List get props => [context, isEffective]; +} + class ChangeTimeEvent extends VisitorPasswordEvent { final dynamic val; final bool isStartEndTime; - const ChangeTimeEvent({required this.val,required this.isStartEndTime}); + const ChangeTimeEvent({required this.val, required this.isStartEndTime}); @override - List get props => [val,isStartEndTime]; -} \ No newline at end of file + List get props => [val, isStartEndTime]; +} diff --git a/lib/pages/visitor_password/bloc/visitor_password_state.dart b/lib/pages/visitor_password/bloc/visitor_password_state.dart index 279c9809..273220b8 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_state.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_state.dart @@ -1,5 +1,3 @@ - - import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; @@ -12,8 +10,6 @@ abstract class VisitorPasswordState extends Equatable { class VisitorPasswordInitial extends VisitorPasswordState {} - - class PasswordTypeSelected extends VisitorPasswordState { final String selectedType; const PasswordTypeSelected(this.selectedType); @@ -36,13 +32,16 @@ class IsRepeatState extends VisitorPasswordState { @override List get props => [repeat]; - } class LoadingInitialState extends VisitorPasswordState {} + class ChangeTimeState extends VisitorPasswordState {} + class TimeSelectedState extends VisitorPasswordState {} + class DeviceLoaded extends VisitorPasswordState {} + class SuccessState extends VisitorPasswordState {} class FailedState extends VisitorPasswordState { @@ -62,4 +61,4 @@ class TableLoaded extends VisitorPasswordState { class DeviceSelectionUpdated extends VisitorPasswordState { final List selectedDeviceIds; const DeviceSelectionUpdated(this.selectedDeviceIds); -} \ No newline at end of file +} diff --git a/lib/pages/visitor_password/model/device_model.dart b/lib/pages/visitor_password/model/device_model.dart index 38ea0396..6d1c714d 100644 --- a/lib/pages/visitor_password/model/device_model.dart +++ b/lib/pages/visitor_password/model/device_model.dart @@ -1,28 +1,28 @@ - - +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/enum/device_types.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; class DeviceModel { - dynamic productUuid; - dynamic productType; - dynamic activeTime; - dynamic category; - dynamic categoryName; - dynamic createTime; - dynamic gatewayId; - dynamic icon; - dynamic ip; - dynamic lat; - dynamic localKey; - dynamic lon; - dynamic model; - dynamic name; - DeviseStatus online; - dynamic ownerId; - dynamic sub; - dynamic timeZone; - dynamic updateTime; - dynamic uuid; + dynamic productUuid; + dynamic productType; + dynamic activeTime; + dynamic category; + dynamic categoryName; + dynamic createTime; + dynamic gatewayId; + dynamic icon; + dynamic ip; + dynamic lat; + dynamic localKey; + dynamic lon; + dynamic model; + dynamic name; + DeviseStatus online; + dynamic ownerId; + dynamic sub; + dynamic timeZone; + dynamic updateTime; + dynamic uuid; DeviceModel({ required this.productUuid, @@ -49,26 +49,46 @@ class DeviceModel { // Deserialize from JSON factory DeviceModel.fromJson(Map json) { + String tempIcon = ''; + DeviceType type = devicesTypesMap[json['productType']] ?? DeviceType.Other; + + if (type == DeviceType.LightBulb) { + tempIcon = Assets.lightBulb; + } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) { + tempIcon = Assets.sensors; + } else if (type == DeviceType.AC) { + tempIcon = Assets.ac; + } else if (type == DeviceType.DoorLock) { + tempIcon = Assets.doorLock; + } else if (type == DeviceType.Curtain) { + tempIcon = Assets.curtain; + } else if (type == DeviceType.ThreeGang) { + tempIcon = Assets.gangSwitch; + } else if (type == DeviceType.Gateway) { + tempIcon = Assets.gateway; + } else { + tempIcon = Assets.logo; + } return DeviceModel( - productUuid: json['productUuid'] , + productUuid: json['productUuid'], productType: json['productType'], activeTime: json['activeTime'], - category: json['category'] , - categoryName: json['categoryName'] , - createTime: json['createTime'] , + category: json['category'], + categoryName: json['categoryName'], + createTime: json['createTime'], gatewayId: json['gatewayId'], - icon: json['icon'], - ip: json['ip'] , - lat: json['lat'] , - localKey: json['localKey'] , - lon: json['lon'] , - model: json['model'] , + icon: tempIcon, + ip: json['ip'], + lat: json['lat'], + localKey: json['localKey'], + lon: json['lon'], + model: json['model'], name: json['name'], online: OnlineTypeExtension.fromString(json['online']), - ownerId: json['ownerId'] , + ownerId: json['ownerId'], sub: json['sub'], timeZone: json['timeZone'], - updateTime: json['updateTime'] , + updateTime: json['updateTime'], uuid: json['uuid'], ); } diff --git a/lib/pages/visitor_password/model/schedule_model.dart b/lib/pages/visitor_password/model/schedule_model.dart index efeeff35..d34dadbc 100644 --- a/lib/pages/visitor_password/model/schedule_model.dart +++ b/lib/pages/visitor_password/model/schedule_model.dart @@ -24,4 +24,4 @@ class Schedule { 'workingDay': workingDay, }; } -} \ No newline at end of file +} diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index 25bf56c4..85262ff1 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/custom_table.dart'; -import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; -import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.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'; @@ -14,7 +14,7 @@ import 'package:syncrow_web/utils/style.dart'; class AddDeviceDialog extends StatelessWidget { final List? selectedDeviceIds; - const AddDeviceDialog({super.key,this.selectedDeviceIds }); + const AddDeviceDialog({super.key, this.selectedDeviceIds}); @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; @@ -33,14 +33,16 @@ class AddDeviceDialog extends StatelessWidget { return AlertDialog( backgroundColor: Colors.white, - title: Text('Add Accessible Device', + title: Text( + 'Add Accessible Device', style: Theme.of(context).textTheme.headlineLarge!.copyWith( - fontWeight: FontWeight.w400, - fontSize: 24, - color: Colors.black),), + fontWeight: FontWeight.w400, + fontSize: 24, + color: Colors.black), + ), content: Container( - height: MediaQuery.of(context).size.height/1.7, - width: MediaQuery.of(context).size.width/2, + height: MediaQuery.of(context).size.height / 1.7, + width: MediaQuery.of(context).size.width / 2, child: Padding( padding: const EdgeInsets.all(10.0), child: Column( @@ -48,11 +50,10 @@ class AddDeviceDialog extends StatelessWidget { Container( width: size.width, padding: EdgeInsets.all(15), - decoration:containerDecoration.copyWith( + decoration: containerDecoration.copyWith( color: ColorsManager.worningColor, border: Border.all(color: Color(0xffFFD22F)), - boxShadow: [] - ), + boxShadow: []), child: Row( children: [ SizedBox( @@ -62,21 +63,31 @@ class AddDeviceDialog extends StatelessWidget { width: 15, ), ), - const SizedBox(width: 10,), - Text('Only online accessible devices can be added', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.grayColor),), + const SizedBox( + width: 10, + ), + Text( + 'Only online accessible devices can be added', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.grayColor), + ), ], - ) + )), + SizedBox( + height: 20, + ), + const SizedBox( + height: 20, ), - const SizedBox(height: 20,), Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, textBaseline: TextBaseline.alphabetic, - children: [ Expanded( flex: 4, @@ -133,14 +144,19 @@ class AddDeviceDialog extends StatelessWidget { child: DefaultButton( backgroundColor: ColorsManager.whiteColors, borderRadius: 9, - child: Text('Reset', - style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black), + child: Text( + 'Reset', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black), ), onPressed: () { visitorBloc.deviceNameController.clear(); visitorBloc.deviceIdController.clear(); visitorBloc.unitNameController.clear(); - visitorBloc.add(FetchDevice()); // Reset to original list + visitorBloc.add( + FetchDevice()); // Reset to original list }, ), ), @@ -149,35 +165,42 @@ class AddDeviceDialog extends StatelessWidget { ), const SizedBox(height: 20), Expanded( - flex: 3, - child: state is TableLoaded - ? DynamicTable( - initialSelectedIds:selectedDeviceIds , - cellDecoration: containerDecoration, - isEmpty:visitorBloc.data.isEmpty, - selectAll: (p0) { - visitorBloc.selectedDeviceIds.clear(); - for (var item in state.data) { - visitorBloc.add(SelectDeviceEvent(item.uuid)); - } - }, - onRowCheckboxChanged: (index, isSelected) { - final deviceId = state.data[index].uuid; - visitorBloc.add(SelectDeviceEvent(deviceId)); - }, - withCheckBox: true, - size: size*0.5, - headers: const [ 'Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status'], - data: state.data.map((item) { - return [ - item.name.toString(), - item.uuid.toString(), - item.productType.toString(), - '', - item.online.value.toString(), - ]; - }).toList(), - ) + flex: 3, + child: state is TableLoaded + ? DynamicTable( + initialSelectedIds: selectedDeviceIds, + cellDecoration: containerDecoration, + isEmpty: visitorBloc.data.isEmpty, + selectAll: (p0) { + visitorBloc.selectedDeviceIds.clear(); + for (var item in state.data) { + visitorBloc + .add(SelectDeviceEvent(item.uuid)); + } + }, + onRowSelected: (index, isSelected, row) { + final deviceId = state.data[index].uuid; + visitorBloc.add(SelectDeviceEvent(deviceId)); + }, + withCheckBox: true, + size: size * 0.5, + headers: const [ + 'Device Name', + 'Device ID', + 'Access Type', + 'Unit Name', + 'Status' + ], + data: state.data.map((item) { + return [ + item.name.toString(), + item.uuid.toString(), + item.productType.toString(), + '', + item.online.value.toString(), + ]; + }).toList(), + ) : const Center(child: CircularProgressIndicator())) ], ), @@ -203,7 +226,7 @@ class AddDeviceDialog extends StatelessWidget { Container( decoration: containerDecoration, width: size.width * 0.2, - child: DefaultButton( + child: DefaultButton( onPressed: () { visitorBloc.addDeviceToList(context); }, diff --git a/lib/pages/visitor_password/view/repeat_widget.dart b/lib/pages/visitor_password/view/repeat_widget.dart index ae37b1e3..8b40b580 100644 --- a/lib/pages/visitor_password/view/repeat_widget.dart +++ b/lib/pages/visitor_password/view/repeat_widget.dart @@ -17,70 +17,72 @@ class RepeatWidget extends StatelessWidget { Size size = MediaQuery.of(context).size; return BlocBuilder( builder: (context, state) { - final visitorBloc = BlocProvider.of(context); - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Wrap the Row in a SingleChildScrollView to handle overflow - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: visitorBloc.days.map((day) { - return Container( - width: 70, // Adjust width as needed - margin: EdgeInsets.all(5), - child: CheckboxListTile( - contentPadding: EdgeInsets.zero, - title: Text( - day['day']!, - style: TextStyle( - fontSize: 10, - color: visitorBloc.selectedDays.contains(day['key']) - ? Colors.black - : ColorsManager.blackColor, - ), - ), - value: visitorBloc.selectedDays.contains(day['key']), - onChanged: (bool? value) { - if (value != null) { - visitorBloc.add(ToggleDaySelectionEvent(key: day['key']!)); - } - }, + final visitorBloc = BlocProvider.of(context); + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Wrap the Row in a SingleChildScrollView to handle overflow + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: visitorBloc.days.map((day) { + return Container( + width: 70, // Adjust width as needed + margin: EdgeInsets.all(5), + child: CheckboxListTile( + contentPadding: EdgeInsets.zero, + title: Text( + day['day']!, + style: TextStyle( + fontSize: 10, + color: visitorBloc.selectedDays.contains(day['key']) + ? Colors.black + : ColorsManager.blackColor, ), - ); - }).toList(), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: DateTimeWebWidget( - icon: Assets.timeIcon, - isRequired: false, - title: '', - size: size, - endTime: () { - visitorBloc.add(SelectTimeEvent( - context: context, - isEffective: false)); - Future.delayed(const Duration(milliseconds: 500), () { - visitorBloc.add(ChangeTimeEvent(val: visitorBloc.endTime, isStartEndTime: true)); - }); - }, - startTime: () { - Future.delayed(const Duration(milliseconds: 500), () { - visitorBloc.add(ChangeTimeEvent(val: visitorBloc.endTime, isStartEndTime: true)); - }); - visitorBloc.add(SelectTimeEvent(context: context, isEffective: true)); - }, - firstString: visitorBloc.effectiveTime, - secondString: visitorBloc.expirationTime, - ), - ), - const SizedBox(height: 20), - ], - ); - } - ); + ), + value: visitorBloc.selectedDays.contains(day['key']), + onChanged: (bool? value) { + if (value != null) { + visitorBloc + .add(ToggleDaySelectionEvent(key: day['key']!)); + } + }, + ), + ); + }).toList(), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: DateTimeWebWidget( + icon: Assets.timeIcon, + isRequired: false, + title: '', + size: size, + endTime: () { + visitorBloc + .add(SelectTimeEvent(context: context, isEffective: false)); + Future.delayed(const Duration(milliseconds: 500), () { + visitorBloc.add(ChangeTimeEvent( + val: visitorBloc.endTime, isStartEndTime: true)); + }); + }, + startTime: () { + Future.delayed(const Duration(milliseconds: 500), () { + visitorBloc.add(ChangeTimeEvent( + val: visitorBloc.endTime, isStartEndTime: true)); + }); + visitorBloc + .add(SelectTimeEvent(context: context, isEffective: true)); + }, + firstString: visitorBloc.effectiveTime, + secondString: visitorBloc.expirationTime, + ), + ), + const SizedBox(height: 20), + ], + ); + }); } } diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 1200ce0e..20238252 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -2,9 +2,9 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/date_time_widget.dart'; -import 'package:syncrow_web/pages/common/default_button.dart'; +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'; @@ -20,69 +20,73 @@ 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( listener: (context, state) { final visitorBloc = BlocProvider.of(context); if (state is SuccessState) { - visitorBloc.stateDialog( - context: context, - message: 'Password Created Successfully', - title: 'Send Success', - widgeta:Column( - children: [ - if(visitorBloc.passwordStatus!.failedOperations.isNotEmpty) - Column( - children: [ - const Text('Failed Devises'), - SizedBox( - width: 200, - height: 50, - child: ListView.builder( - scrollDirection: Axis.horizontal, - - shrinkWrap: true, - itemCount:visitorBloc.passwordStatus!.failedOperations.length , - itemBuilder: (context, index) { - return Container( - margin: EdgeInsets.all(5), - - decoration: containerDecoration, - height: 45, - child: Center(child: Text(visitorBloc.passwordStatus!.failedOperations[index].deviceUuid)), - ); - },), - ), - ], - ), - if(visitorBloc.passwordStatus!.successOperations.isNotEmpty) - Column( + visitorBloc + .stateDialog( + context: context, + message: 'Password Created Successfully', + title: 'Send Success', + widgeta: Column( children: [ - const Text('Success Devises'), - SizedBox( - width: 200, - height: 50, - child: ListView.builder( - scrollDirection: Axis.horizontal, - shrinkWrap: true, - 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)), - ); - },), - ), + if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty) + Column( + children: [ + const Text('Failed Devises'), + SizedBox( + width: 200, + height: 50, + child: ListView.builder( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: visitorBloc.passwordStatus!.failedOperations.length, + itemBuilder: (context, index) { + return Container( + margin: EdgeInsets.all(5), + decoration: containerDecoration, + height: 45, + child: Center( + child: Text(visitorBloc + .passwordStatus!.failedOperations[index].deviceUuid)), + ); + }, + ), + ), + ], + ), + if (visitorBloc.passwordStatus!.successOperations.isNotEmpty) + Column( + children: [ + const Text('Success Devises'), + SizedBox( + width: 200, + height: 50, + child: ListView.builder( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + 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)), + ); + }, + ), + ), + ], + ), ], - ), - ], - ) - ).then((v){ + )) + .then((v) { Navigator.of(context).pop(); }); } else if (state is FailedState) { @@ -101,365 +105,399 @@ class VisitorPasswordDialog extends StatelessWidget { 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()): - SingleChildScrollView( - child: Form( - key: visitorBloc.forgetFormKey, - child: Padding( - 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(), - ], - ), - ), - const SizedBox( - height: 15, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text('* ', - style: Theme.of(context).textTheme - .bodyMedium!.copyWith(color: Colors.red), - ), - Text('Access Type', - style:text ), - ], - - ), - 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') - 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), - ), - 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),), - - if(visitorBloc.accessTypeSelected == 'Dynamic Password') - Text( - 'Quick and short-acting password, only valid within 5 minutes after creation, the system randomly generates a digital password.', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.grayColor,fontSize: 9),), - - const SizedBox( - height: 20, - ) - ], - ), - visitorBloc.accessTypeSelected == 'Dynamic Password' - ? const SizedBox() - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text('* ', - style: Theme.of(context).textTheme.bodyMedium! - .copyWith(color: Colors.red), - ), - Text('Usage Frequency',style:text ,), - ], - ), - 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)); - } - }, - ), - ), - ], - ), - - //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, 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), - ), - - // 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') && - (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)); - }else{ - 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)); - }else{ - visitorBloc.add(SelectTimeVisitorPassword( - context: context, - isStart: true, - isRepeat: false)); - } - }, - - firstString: - (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Offline Password')? - visitorBloc.effectiveTime:visitorBloc.startTimeAccess.toString(), - secondString: (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Offline Password')? - visitorBloc.expirationTime:visitorBloc.endTimeAccess.toString(), - icon: Assets.calendarIcon - ), - const SizedBox( - height: 20, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text('* ', - style: Theme.of(context).textTheme.bodyMedium! - .copyWith(color: Colors.red), - ), - Text('Access Devices', - style:text ,), - ], - ), - 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),), - const SizedBox( - height: 20, - ), - if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Online Password') - SizedBox( - width: 100, - child: Column( + content: state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + child: Form( + key: visitorBloc.forgetFormKey, + child: Padding( + padding: const EdgeInsets.all(5.0), + child: ListBody( + children: [ + Container( + child: Row( children: [ - Text('Repeat', - style:text), - Transform.scale( - scale: .8, - child: CupertinoSwitch( - value: visitorBloc.repeat, - onChanged: (value) { - visitorBloc.add(ToggleRepeatEvent()); - }, - applyTheme: true, + 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(), ], ), ), - if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Online Password') - isRepeat ? const RepeatWidget() : const SizedBox(), - Container( - decoration: containerDecoration, - width: size.width / 9, - child: DefaultButton( - onPressed: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return AddDeviceDialog(selectedDeviceIds: visitorBloc.selectedDevices,); - }, - ).then((listDevice) { - if(listDevice!=null){ - visitorBloc.selectedDevices = listDevice; - } - }); - }, - borderRadius: 8, - child: Text('+ Add Device',style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.whiteColors,fontSize: 12),), + const SizedBox( + height: 15, ), - ), - ], + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + Text('Access Type', style: text), + ], + ), + 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') + 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), + ), + 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), + ), + if (visitorBloc.accessTypeSelected == 'Dynamic Password') + Text( + 'Quick and short-acting password, only valid within 5 minutes after creation, the system randomly generates a digital password.', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.grayColor, + fontSize: 9), + ), + const SizedBox( + height: 20, + ) + ], + ), + visitorBloc.accessTypeSelected == 'Dynamic Password' + ? const SizedBox() + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + Text( + 'Usage Frequency', + style: text, + ), + ], + ), + 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)); + } + }, + ), + ), + ], + ), + + //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, 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), + ), + + // 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') && + (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)); + } else { + 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)); + } else { + visitorBloc.add(SelectTimeVisitorPassword( + context: context, isStart: true, isRepeat: false)); + } + }, + firstString: (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == 'Offline Password') + ? visitorBloc.effectiveTime + : visitorBloc.startTimeAccess.toString(), + secondString: (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == 'Offline Password') + ? visitorBloc.expirationTime + : visitorBloc.endTimeAccess.toString(), + icon: Assets.calendarIcon), + const SizedBox( + height: 20, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + Text( + 'Access Devices', + style: text, + ), + ], + ), + 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), + ), + const SizedBox( + height: 20, + ), + if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Online Password') + SizedBox( + width: 100, + child: Column( + children: [ + Text('Repeat', style: text), + Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: visitorBloc.repeat, + onChanged: (value) { + visitorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + ), + ), + ], + ), + ), + if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Online Password') + isRepeat ? const RepeatWidget() : const SizedBox(), + Container( + decoration: containerDecoration, + width: size.width / 9, + child: DefaultButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AddDeviceDialog( + selectedDeviceIds: visitorBloc.selectedDevices, + ); + }, + ).then((listDevice) { + if (listDevice != null) { + visitorBloc.selectedDevices = listDevice; + } + }); + }, + borderRadius: 8, + child: Text( + '+ Add Device', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors, + fontSize: 12), + ), + ), + ), + ], + ), + ], + ), ), - ], + ), ), - ), - ), - ), actionsAlignment: MainAxisAlignment.center, actions: [ Container( @@ -475,7 +513,8 @@ class VisitorPasswordDialog extends StatelessWidget { 'Cancel', style: Theme.of(context).textTheme.bodySmall!.copyWith( fontWeight: FontWeight.w400, - color: ColorsManager.blackColor,fontSize: 16), + color: ColorsManager.blackColor, + fontSize: 16), ), ), ), @@ -485,37 +524,43 @@ class VisitorPasswordDialog extends StatelessWidget { child: DefaultButton( onPressed: () { if (visitorBloc.forgetFormKey.currentState!.validate()) { - if(visitorBloc.selectedDevices.isNotEmpty){ - - if(visitorBloc.usageFrequencySelected == 'One-Time' && visitorBloc.accessTypeSelected == 'Offline Password'){ + if (visitorBloc.selectedDevices.isNotEmpty) { + if (visitorBloc.usageFrequencySelected == 'One-Time' && + visitorBloc.accessTypeSelected == 'Offline Password') { setPasswordFunction(context, size, visitorBloc); - } - else if(visitorBloc.accessTypeSelected == 'Dynamic Password'){ + } else if (visitorBloc.accessTypeSelected == 'Dynamic Password') { print('objectobjectobjectobject'); setPasswordFunction(context, size, visitorBloc); - - }else{ - if(visitorBloc.effectiveTimeTimeStamp!=null&&visitorBloc.expirationTimeTimeStamp!=null) { - if(isRepeat==true){ - if(visitorBloc.expirationTime!='End Time'&&visitorBloc.effectiveTime!='Start Time'&&visitorBloc.selectedDays.isNotEmpty){ + } else { + 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); + } else { + visitorBloc.stateDialog( + context: context, + message: + 'Please select days and fill start time and end time to continue', + title: 'Access Period'); } - else{ - visitorBloc.stateDialog(context: - context,message: 'Please select days and fill start time and end time to continue',title: 'Access Period'); - } - }else{ + } else { setPasswordFunction(context, size, visitorBloc); } - } - 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(context: - context,message: 'Please select devices to continue',title: 'Select Devices'); + } else { + visitorBloc.stateDialog( + context: context, + message: 'Please select devices to continue', + title: 'Select Devices'); } } }, @@ -571,24 +616,28 @@ class VisitorPasswordDialog extends StatelessWidget { // } // }, borderRadius: 8, - child: Text('Ok', style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.whiteColors,fontSize: 16),), + child: Text( + 'Ok', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors, + fontSize: 16), + ), ), ), ], - ); }, + ); + }, ), ), - ); } Future setPasswordFunction( - BuildContext context, - Size size, - VisitorPasswordBloc visitorBloc, - ) { + BuildContext context, + Size size, + VisitorPasswordBloc visitorBloc, + ) { return showDialog( context: context, barrierDismissible: false, @@ -606,7 +655,7 @@ class VisitorPasswordDialog extends StatelessWidget { ), ), ); - }else{ + } else { return AlertDialog( alignment: Alignment.center, content: SizedBox( @@ -627,10 +676,10 @@ class VisitorPasswordDialog extends StatelessWidget { Text( 'Set Password', style: Theme.of(context).textTheme.headlineLarge!.copyWith( - fontSize: 30, - fontWeight: FontWeight.w400, - color: Colors.black, - ), + fontSize: 30, + fontWeight: FontWeight.w400, + color: Colors.black, + ), ), ], ), @@ -639,10 +688,10 @@ class VisitorPasswordDialog extends StatelessWidget { '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, - ), + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400, + fontSize: 18, + ), ), ], ), @@ -661,10 +710,10 @@ class VisitorPasswordDialog extends StatelessWidget { child: Text( 'Cancel', style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, - fontSize: 16, - ), + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + fontSize: 16, + ), ), ), ), @@ -675,9 +724,8 @@ class VisitorPasswordDialog extends StatelessWidget { borderRadius: 8, onPressed: () { Navigator.pop(context); - if(visitorBloc.accessTypeSelected == 'Dynamic Password'){ - - }else{ + if (visitorBloc.accessTypeSelected == 'Dynamic Password') { + } else { if (visitorBloc.usageFrequencySelected == 'One-Time' && visitorBloc.accessTypeSelected == 'Online Password') { visitorBloc.add(OnlineOneTimePasswordEvent( @@ -685,8 +733,7 @@ class VisitorPasswordDialog extends StatelessWidget { passwordName: visitorBloc.userNameController.text, email: visitorBloc.emailController.text, )); - } - else if (visitorBloc.usageFrequencySelected == 'Periodic' && + } else if (visitorBloc.usageFrequencySelected == 'Periodic' && visitorBloc.accessTypeSelected == 'Online Password') { visitorBloc.add(OnlineMultipleTimePasswordEvent( passwordName: visitorBloc.userNameController.text, @@ -694,16 +741,14 @@ class VisitorPasswordDialog extends StatelessWidget { effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), invalidTime: visitorBloc.expirationTimeTimeStamp.toString(), )); - } - else if (visitorBloc.usageFrequencySelected == 'One-Time' && + } 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' && + } else if (visitorBloc.usageFrequencySelected == 'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password') { visitorBloc.add(OfflineMultipleTimePasswordEvent( passwordName: visitorBloc.userNameController.text, @@ -713,15 +758,14 @@ class VisitorPasswordDialog extends StatelessWidget { )); } } - }, child: Text( 'Ok', style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.whiteColors, - fontSize: 16, - ), + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors, + fontSize: 16, + ), ), ), ), diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart index 703ecd25..c93466d4 100644 --- a/lib/services/access_mang_api.dart +++ b/lib/services/access_mang_api.dart @@ -1,15 +1,12 @@ import 'dart:convert'; -import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:syncrow_web/pages/access_management/model/password_model.dart'; import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; -import 'package:syncrow_web/pages/visitor_password/model/failed_operation.dart'; import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -class AccessMangApi{ - +class AccessMangApi { Future> fetchVisitorPassword() async { try { final response = await HTTPService().get( @@ -24,7 +21,7 @@ class AccessMangApi{ }, ); return response; - } catch (e) { + } catch (e) { debugPrint('Error fetching visitor passwords: $e'); return []; } @@ -44,95 +41,88 @@ class AccessMangApi{ }, ); return response; - } catch (e) { + } catch (e) { debugPrint('Error fetching $e'); return []; } } - Future postOnlineOneTime({ - String? email, - String? passwordName, - String? password, - String? effectiveTime, - String? invalidTime, - List? devicesUuid}) async { - final response = await HTTPService().post( - path: ApiEndpoints.sendOnlineOneTime, - body: jsonEncode({ - "email": email, - "passwordName": passwordName, - "password": password, - "devicesUuid": devicesUuid, - "effectiveTime":effectiveTime , - "invalidTime": invalidTime - }), - showServerMessage: true, - expectedResponseModel: (json) { - return json; - }, - ); - return response; - - } - - Future postOnlineMultipleTime({ - String? effectiveTime, - String? invalidTime, - String? email, - String? password, - String? passwordName, - List? scheduleList, - List? devicesUuid}) async { - Map body = { + Future postOnlineOneTime( + {String? email, + String? passwordName, + String? password, + String? effectiveTime, + String? invalidTime, + List? devicesUuid}) async { + final response = await HTTPService().post( + path: ApiEndpoints.sendOnlineOneTime, + body: jsonEncode({ "email": email, - "devicesUuid": devicesUuid, "passwordName": passwordName, "password": password, + "devicesUuid": devicesUuid, "effectiveTime": effectiveTime, - "invalidTime": invalidTime, - }; - if (scheduleList != null) { - body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList(); - } - final response = await HTTPService().post( - path: ApiEndpoints.sendOnlineMultipleTime, - body: jsonEncode(body), - showServerMessage: true, - expectedResponseModel: (json) { - return json; - }, - ); - return response; + "invalidTime": invalidTime + }), + showServerMessage: true, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + Future postOnlineMultipleTime( + {String? effectiveTime, + String? invalidTime, + String? email, + String? password, + String? passwordName, + List? scheduleList, + List? devicesUuid}) async { + Map body = { + "email": email, + "devicesUuid": devicesUuid, + "passwordName": passwordName, + "password": password, + "effectiveTime": effectiveTime, + "invalidTime": invalidTime, + }; + if (scheduleList != null) { + body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList(); + } + final response = await HTTPService().post( + path: ApiEndpoints.sendOnlineMultipleTime, + body: jsonEncode(body), + showServerMessage: true, + expectedResponseModel: (json) { + return json; + }, + ); + return response; } // OffLine One Time Password - Future postOffLineOneTime({String? email,String? passwordName,List? devicesUuid}) async { - final response = await HTTPService().post( - path: ApiEndpoints.sendOffLineOneTime, - body: jsonEncode({ - "email": email, - "passwordName": passwordName, - "devicesUuid": devicesUuid - }), - showServerMessage: true, - expectedResponseModel: (json) { - return json; - }, - ); - return response; - + Future postOffLineOneTime( + {String? email, String? passwordName, List? devicesUuid}) async { + final response = await HTTPService().post( + path: ApiEndpoints.sendOffLineOneTime, + body: jsonEncode({"email": email, "passwordName": passwordName, "devicesUuid": devicesUuid}), + showServerMessage: true, + expectedResponseModel: (json) { + return json; + }, + ); + return response; } - Future postOffLineMultipleTime({ - String? email, - String? passwordName, - String? effectiveTime, - String? invalidTime, - List? devicesUuid - }) async { + Future postOffLineMultipleTime( + {String? email, + String? passwordName, + String? effectiveTime, + String? invalidTime, + List? devicesUuid}) async { print(jsonEncode({ "email": email, "devicesUuid": devicesUuid, @@ -140,23 +130,20 @@ class AccessMangApi{ "effectiveTime": effectiveTime, "invalidTime": invalidTime, })); - final response = await HTTPService().post( - path: ApiEndpoints.sendOffLineMultipleTime, - body: jsonEncode({ - "email": email, - "devicesUuid": devicesUuid, - "passwordName": passwordName, - "effectiveTime": effectiveTime, - "invalidTime": invalidTime, - }), - showServerMessage: true, - expectedResponseModel: (json) { - return json; - }, - ); - return response; - + final response = await HTTPService().post( + path: ApiEndpoints.sendOffLineMultipleTime, + body: jsonEncode({ + "email": email, + "devicesUuid": devicesUuid, + "passwordName": passwordName, + "effectiveTime": effectiveTime, + "invalidTime": invalidTime, + }), + showServerMessage: true, + expectedResponseModel: (json) { + return json; + }, + ); + return response; } } - - diff --git a/lib/services/api/http_interceptor.dart b/lib/services/api/http_interceptor.dart index cece39a7..bef8d804 100644 --- a/lib/services/api/http_interceptor.dart +++ b/lib/services/api/http_interceptor.dart @@ -22,16 +22,19 @@ class HTTPInterceptor extends InterceptorsWrapper { if (await validateResponse(response)) { super.onResponse(response, handler); } else { - handler.reject(DioException(requestOptions: response.requestOptions, response: response)); + handler.reject(DioException( + requestOptions: response.requestOptions, response: response)); } } @override - void onRequest(RequestOptions options, RequestInterceptorHandler handler) async { + void onRequest( + RequestOptions options, RequestInterceptorHandler handler) async { var storage = const FlutterSecureStorage(); var token = await storage.read(key: Token.loginAccessTokenKey); if (checkHeaderExclusionListOfAddedParameters(options.path)) { - options.headers.putIfAbsent(HttpHeaders.authorizationHeader, () => "Bearer $token"); + options.headers + .putIfAbsent(HttpHeaders.authorizationHeader, () => "Bearer $token"); } // options.headers['Authorization'] = 'Bearer ${'${token!}123'}'; super.onRequest(options, handler); diff --git a/lib/services/api/network_exception.dart b/lib/services/api/network_exception.dart index 81f12b3e..d85ef27c 100644 --- a/lib/services/api/network_exception.dart +++ b/lib/services/api/network_exception.dart @@ -31,8 +31,7 @@ class ServerFailure extends Failure { { // var document = parser.parse(dioError.response!.data.toString()); // var message = document.body!.text; - return ServerFailure.fromResponse( - dioError.response!.statusCode!, + return ServerFailure.fromResponse(dioError.response!.statusCode!, dioError.response?.data['message'] ?? "Error"); } case DioExceptionType.cancel: diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index dc1064aa..6573f3ad 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -1,4 +1,3 @@ - import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:syncrow_web/pages/auth/model/region_model.dart'; @@ -18,34 +17,27 @@ class AuthenticationAPI { return response; } - static Future forgetPassword( - {required var email, required var password,}) async { + static Future forgetPassword({ + required var email, + required var password, + }) async { final response = await HTTPService().post( path: ApiEndpoints.forgetPassword, - body: { - "email": email, - "password": password - }, + body: {"email": email, "password": password}, showServerMessage: true, expectedResponseModel: (json) {}); return response; } - static Future sendOtp({required String email, required String regionUuid}) async { try { final response = await HTTPService().post( path: ApiEndpoints.sendOtp, - body: { - "email": email, - "type": "PASSWORD", - "regionUuid": regionUuid - }, + body: {"email": email, "type": "PASSWORD", "regionUuid": regionUuid}, showServerMessage: true, expectedResponseModel: (json) { return json['data']['cooldown']; - } - ); + }); return response; } on DioException catch (e) { @@ -53,9 +45,9 @@ class AuthenticationAPI { if (e.response!.statusCode == 400) { final errorData = e.response!.data; String errorMessage = errorData['message']; - if(errorMessage=='User not found'){ + if (errorMessage == 'User not found') { return 1; - }else{ + } else { int cooldown = errorData['data']['cooldown'] ?? 1; return cooldown; } @@ -63,8 +55,7 @@ class AuthenticationAPI { debugPrint('Error: ${e.response!.statusCode} - ${e.response!.statusMessage}'); return 1; } - } - else { + } else { debugPrint('Error: ${e.message}'); return 1; } @@ -74,9 +65,8 @@ class AuthenticationAPI { } } - static Future verifyOtp( - {required String email, required String otpCode}) async { - try{ + static Future verifyOtp({required String email, required String otpCode}) async { + try { final response = await HTTPService().post( path: ApiEndpoints.verifyOtp, body: {"email": email, "type": "PASSWORD", "otpCode": otpCode}, @@ -89,12 +79,12 @@ class AuthenticationAPI { } }); return response; - }on DioException catch (e){ + } on DioException catch (e) { if (e.response != null) { if (e.response!.statusCode == 400) { final errorData = e.response!.data; String errorMessage = errorData['message']; - return errorMessage; + return errorMessage; } } else { debugPrint('Error: ${e.message}'); @@ -108,9 +98,7 @@ class AuthenticationAPI { showServerMessage: true, expectedResponseModel: (json) { return (json as List).map((zone) => RegionModel.fromJson(zone)).toList(); - } - ); - return response as List; + }); + return response; } - } diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart new file mode 100644 index 00000000..7e2dc4f8 --- /dev/null +++ b/lib/services/devices_mang_api.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +class DevicesManagementApi { + Future> fetchDevices() async { + try { + final response = await HTTPService().get( + path: ApiEndpoints.getAllDevices, + showServerMessage: true, + expectedResponseModel: (json) { + List jsonData = json; + List devicesList = jsonData.map((jsonItem) { + return AllDevicesModel.fromJson(jsonItem); + }).toList(); + return devicesList; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return []; + } + } + + Future getDeviceStatus(String uuid) async { + try { + final response = await HTTPService().get( + path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', uuid), + showServerMessage: true, + expectedResponseModel: (json) { + return DeviceStatus.fromJson(json); + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return DeviceStatus( + productUuid: '', + productType: '', + status: [], + ); + } + } + + //deviceControl + Future deviceControl(String uuid, Status status) async { + try { + final response = await HTTPService().post( + path: ApiEndpoints.deviceControl.replaceAll('{uuid}', uuid), + body: status.toMap(), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return false; + } + } + + static Future> getDevicesByGatewayId( + String gatewayId) async { + final response = await HTTPService().get( + path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), + showServerMessage: false, + expectedResponseModel: (json) { + List devices = []; + if (json == null || json.isEmpty || json == []) { + return devices; + } + for (var device in json['devices']) { + devices.add(DeviceModel.fromJson(device)); + } + return devices; + }, + ); + return response; + } + + static Future openDoorLock(String deviceId) async { + final response = await HTTPService().post( + path: ApiEndpoints.openDoorLock.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } + + static Future getDeviceReports(String uuid, String code) async { + final response = await HTTPService().get( + path: ApiEndpoints.getDeviceLogs + .replaceAll('{uuid}', uuid) + .replaceAll('{code}', code), + showServerMessage: false, + expectedResponseModel: (json) { + return DeviceReport.fromJson(json); + }, + ); + return response; + } +} diff --git a/lib/services/home_api.dart b/lib/services/home_api.dart index 42e732b4..dfbaf4bf 100644 --- a/lib/services/home_api.dart +++ b/lib/services/home_api.dart @@ -1,16 +1,15 @@ - import 'package:syncrow_web/pages/auth/model/user_model.dart'; +import 'package:syncrow_web/pages/auth/model/user_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -class HomeApi{ - Future fetchUserInfo(userId) async { - final response = await HTTPService().get( - path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!), - showServerMessage: true, - expectedResponseModel: (json) { - return UserModel.fromJson(json); - } - ); - return response; - } - } \ No newline at end of file +class HomeApi { + Future fetchUserInfo(userId) async { + final response = await HTTPService().get( + path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!), + showServerMessage: true, + expectedResponseModel: (json) { + return UserModel.fromJson(json); + }); + return response; + } +} diff --git a/lib/services/locator.dart b/lib/services/locator.dart index 4a9263a0..055deb05 100644 --- a/lib/services/locator.dart +++ b/lib/services/locator.dart @@ -4,7 +4,7 @@ import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/api/http_service.dart'; final GetIt serviceLocator = GetIt.instance; - //setupLocator() // to search for dependency injection in flutter +//setupLocator() // to search for dependency injection in flutter initialSetup() { serviceLocator.registerSingleton(HTTPInterceptor()); //Base classes diff --git a/lib/utils/app_routes.dart b/lib/utils/app_routes.dart index 7f979d74..a730341b 100644 --- a/lib/utils/app_routes.dart +++ b/lib/utils/app_routes.dart @@ -1,6 +1,7 @@ import 'package:go_router/go_router.dart'; import 'package:syncrow_web/pages/access_management/view/access_management.dart'; import 'package:syncrow_web/pages/auth/view/login_page.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; @@ -9,25 +10,25 @@ class AppRoutes { static List getRoutes() { return [ GoRoute( - path:RoutesConst.main, + path: RoutesConst.auth, builder: (context, state) => const LoginPage(), ), - GoRoute( path: RoutesConst.home, builder: (context, state) => const HomePage(), ), - GoRoute( path: RoutesConst.visitorPassword, builder: (context, state) => const VisitorPasswordDialog(), ), - GoRoute( - path: RoutesConst.accessManagementPage , + path: RoutesConst.accessManagementPage, builder: (context, state) => const AccessManagementPage(), ), - + GoRoute( + path: RoutesConst.deviceManagementPage, + builder: (context, state) => const DeviceManagementPage(), + ), ]; } } diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 8118ac97..b62889d0 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -3,17 +3,20 @@ import 'package:flutter/material.dart'; abstract class ColorsManager { static const Color textPrimaryColor = Color(0xFF5D5D5D); static const Color switchOffColor = Color(0x7F8D99AE); - static const Color primaryColor = Color(0xFF0030CB);//023DFE + static const Color primaryColor = Color(0xFF0030CB); //023DFE static const Color secondaryTextColor = Color(0xFF848484); - static Color primaryColorWithOpacity = const Color(0xFF023DFE).withOpacity(0.6); + static Color primaryColorWithOpacity = + const Color(0xFF023DFE).withOpacity(0.6); static const Color whiteColors = Colors.white; static const Color secondaryColor = Color(0xFF023DFE); static const Color onSecondaryColor = Color(0xFF023DFE); + static Color dialogBlueTitle = Color(0xFF023DFE).withOpacity(0.6); + static const Color primaryTextColor = Colors.black; static const Color greyColor = Color(0xFFd5d5d5); - + static const Color lightGreyColor = Color(0xFF999999); static const Color backgroundColor = Color(0xFFececec); static const Color dozeColor = Color(0xFFFEC258); static const Color relaxColor = Color(0xFFFBD288); @@ -24,6 +27,7 @@ abstract class ColorsManager { static const Color slidingBlueColor = Color(0x99023DFE); static const Color blackColor = Color(0xFF000000); static const Color lightGreen = Color(0xFF00FF0A); + static const Color green = Color(0xFF008905); static const Color grayColor = Color(0xFF999999); static const Color red = Color(0xFFFF0000); static const Color graysColor = Color(0xffEBEBEB); diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index f9581cb6..2d85a7f5 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -5,21 +5,37 @@ abstract class ApiEndpoints { ////////////////////////////////////// Authentication /////////////////////////////// static const String signUp = '$baseUrl/authentication/user/signup'; static const String login = '$baseUrl/authentication/user/login'; - static const String forgetPassword = '$baseUrl/authentication/user/forget-password'; + static const String forgetPassword = + '$baseUrl/authentication/user/forget-password'; static const String sendOtp = '$baseUrl/authentication/user/send-otp'; static const String verifyOtp = '$baseUrl/authentication/user/verify-otp'; static const String getRegion = '$baseUrl/region'; static const String visitorPassword = '$baseUrl/visitor-password'; static const String getDevices = '$baseUrl/visitor-password/devices'; - - static const String sendOnlineOneTime = '$baseUrl/visitor-password/temporary-password/online/one-time'; - static const String sendOnlineMultipleTime = '$baseUrl/visitor-password/temporary-password/online/multiple-time'; + static const String sendOnlineOneTime = + '$baseUrl/visitor-password/temporary-password/online/one-time'; + static const String sendOnlineMultipleTime = + '$baseUrl/visitor-password/temporary-password/online/multiple-time'; //offline Password - static const String sendOffLineOneTime = '$baseUrl/visitor-password/temporary-password/offline/one-time'; - static const String sendOffLineMultipleTime = '$baseUrl/visitor-password/temporary-password/offline/multiple-time'; - + static const String sendOffLineOneTime = + '$baseUrl/visitor-password/temporary-password/offline/one-time'; + static const String sendOffLineMultipleTime = + '$baseUrl/visitor-password/temporary-password/offline/multiple-time'; static const String getUser = '$baseUrl/user/{userUuid}'; + + ////// Devices Management //////////////// + + static const String getAllDevices = '$baseUrl/device'; + static const String getDeviceStatus = + '$baseUrl/device/{uuid}/functions/status'; + + static const String deviceControl = '$baseUrl/device/{uuid}/control'; + static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices'; + static const String openDoorLock = '/door-lock/open/{doorLockUuid}'; + + static const String getDeviceLogs = + '$baseUrl/device/report-logs/{uuid}?code={code}'; } diff --git a/lib/utils/constants/app_enum.dart b/lib/utils/constants/app_enum.dart index 77beff69..4ca37d9b 100644 --- a/lib/utils/constants/app_enum.dart +++ b/lib/utils/constants/app_enum.dart @@ -1,4 +1,3 @@ - enum AccessType { onlineOnetime, onlineMultiple, @@ -36,11 +35,6 @@ extension AccessTypeExtension on AccessType { } } - - - - - enum DeviseStatus { online, offline, @@ -53,7 +47,6 @@ extension OnlineTypeExtension on DeviseStatus { return "Online"; case DeviseStatus.offline: return "Offline"; - } } @@ -69,10 +62,9 @@ extension OnlineTypeExtension on DeviseStatus { } } - enum AccessStatus { - expired , - effective , + expired, + effective, toBeEffective, } @@ -82,28 +74,26 @@ extension AccessStatusExtension on AccessStatus { case AccessStatus.expired: return "Expired"; case AccessStatus.effective: - return "Effective" ; - case AccessStatus.toBeEffective: + return "Effective"; + case AccessStatus.toBeEffective: return "To be effective"; - } } static AccessStatus fromString(String value) { switch (value) { - case "EXPIRED" : + case "EXPIRED": return AccessStatus.expired; - case "EFFECTIVE" : + case "EFFECTIVE": return AccessStatus.effective; - case "TO_BE_EFFECTIVE": + case "TO_BE_EFFECTIVE": return AccessStatus.toBeEffective; default: - throw ArgumentError("Invalid access type: $value"); + throw ArgumentError("Invalid access type: $value"); } } } +enum TempModes { hot, cold, wind } - - - +enum FanSpeeds { auto, low, middle, high } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 18c3bb7b..49cefd2d 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -27,5 +27,101 @@ class Assets { static const String deviceNoteIcon = "assets/images/device_note.svg"; static const String timeIcon = "assets/images/time_icon.svg"; static const String emptyTable = "assets/images/empty_table.svg"; + + // General assets + static const String motionlessDetection = "assets/icons/motionless_detection.svg"; + static const String acHeating = "assets/icons/ac_heating.svg"; + static const String acPowerOff = "assets/icons/ac_power_off.svg"; + static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; + static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; + static const String resetOff = "assets/icons/reset_off.svg"; + static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; + static const String motionDetection = "assets/icons/motion_detection.svg"; + static const String freezing = "assets/icons/freezing.svg"; + static const String indicator = "assets/icons/indicator.svg"; + static const String sceneRefresh = "assets/icons/scene_refresh.svg"; + static const String temperature = "assets/icons/tempreture.svg"; + static const String acFanHigh = "assets/icons/ac_fan_high.svg"; + static const String fanSpeed = "assets/icons/fan_speed.svg"; + static const String acFanLow = "assets/icons/ac_fan_low.svg"; + static const String sensitivity = "assets/icons/sensitivity.svg"; + static const String lightCountdown = "assets/icons/light_countdown.svg"; + static const String farDetection = "assets/icons/far_detection.svg"; + static const String sceneChildUnlock = "assets/icons/scene_child_unlock.svg"; + static const String acFanAuto = "assets/icons/ac_fan_auto.svg"; + static const String childLock = "assets/icons/child_lock.svg"; + static const String factoryReset = "assets/icons/factory_reset.svg"; + static const String acCooling = "assets/icons/ac_cooling.svg"; + static const String sceneChildLock = "assets/icons/scene_child_lock.svg"; + static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; + static const String masterState = "assets/icons/master_state.svg"; + static const String acPower = "assets/icons/ac_power.svg"; + static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; + static const String nobodyTime = "assets/icons/nobody_time.svg"; + + // Automation functions + static const String tempPasswordUnlock = + "assets/icons/automation_functions/temp_password_unlock.svg"; + static const String doorlockNormalOpen = + "assets/icons/automation_functions/doorlock_normal_open.svg"; + static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; + static const String remoteUnlockViaApp = + "assets/icons/automation_functions/remote_unlock_via_app.svg"; + static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; + static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; + static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; + static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; + static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; + static const String presence = "assets/icons/automation_functions/presence.svg"; + static const String residualElectricity = + "assets/icons/automation_functions/residual_electricity.svg"; + static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; + static const String remoteUnlockRequest = + "assets/icons/automation_functions/remote_unlock_req.svg"; + static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; + static const String motion = "assets/icons/automation_functions/motion.svg"; + static const String fingerprintUnlock = + "assets/icons/automation_functions/fingerprint_unlock.svg"; + + // Presence Sensor Assets + static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; + static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; + static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; + static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; + static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; + + static const String lightPulp = "assets/icons/light_pulb.svg"; + static const String acDevice = "assets/icons/ac_device.svg"; + static const String acAirConditioner = "assets/icons/ac_air.svg"; + static const String acSun = "assets/icons/ac_sun.svg"; + + //assets/icons/3GangSwitch.svg + static const String gangSwitch = "assets/icons/3GangSwitch.svg"; + //assets/icons/AC.svg + static const String ac = "assets/icons/AC.svg"; + //assets/icons/Curtain.svg + static const String curtain = "assets/icons/Curtain.svg"; + //assets/icons/doorLock.svg + static const String doorLock = "assets/icons/doorLock.svg"; + //assets/icons/Gateway.svg + static const String gateway = "assets/icons/Gateway.svg"; + //assets/icons/Light.svg + static const String lightBulb = "assets/icons/Light.svg"; + //assets/icons/sensors.svg + static const String sensors = "assets/icons/sensors.svg"; + + //assets/icons/door_un_look_ic.svg + static const String doorUnlock = 'assets/icons/door_un_look_ic.svg'; + + //assets/icons/lockIcon.svg + static const String lockIcon = 'assets/icons/lockIcon.svg'; + + static const String bathroom = 'assets/icons/bathroom.svg'; + static const String bedroom = 'assets/icons/bedroom.svg'; + static const String dyi = 'assets/icons/dyi.svg'; + static const String office = 'assets/icons/office.svg'; + static const String parlour = 'assets/icons/parlour.svg'; static const String grid = "assets/images/grid.svg"; } diff --git a/lib/utils/constants/routes_const.dart b/lib/utils/constants/routes_const.dart index b107b0fe..093a61dc 100644 --- a/lib/utils/constants/routes_const.dart +++ b/lib/utils/constants/routes_const.dart @@ -1,6 +1,7 @@ class RoutesConst { - static const String main = '/'; + static const String auth = '/'; static const String home = '/home'; static const String visitorPassword = '/visitor-password'; static const String accessManagementPage = '/access-management-page'; + static const String deviceManagementPage = '/device-management-page'; } diff --git a/lib/utils/enum/device_types.dart b/lib/utils/enum/device_types.dart new file mode 100644 index 00000000..94821ef6 --- /dev/null +++ b/lib/utils/enum/device_types.dart @@ -0,0 +1,21 @@ +enum DeviceType { + AC, + LightBulb, + DoorLock, + Curtain, + Blind, + ThreeGang, + Gateway, + CeilingSensor, + WallSensor, + Other, +} + +Map devicesTypesMap = { + "AC": DeviceType.AC, + "GW": DeviceType.Gateway, + "CPS": DeviceType.CeilingSensor, + "DL": DeviceType.DoorLock, + "WPS": DeviceType.WallSensor, + "3G": DeviceType.ThreeGang, +}; diff --git a/lib/utils/format_date_time.dart b/lib/utils/format_date_time.dart new file mode 100644 index 00000000..5c089a2c --- /dev/null +++ b/lib/utils/format_date_time.dart @@ -0,0 +1,11 @@ +import 'package:intl/intl.dart'; + +String formatDateTime(DateTime? dateTime) { + if (dateTime == null) { + return '-'; + } + final DateFormat dateFormatter = DateFormat('dd/MM/yyyy'); + final DateFormat timeFormatter = DateFormat('HH:mm'); + + return '${dateFormatter.format(dateTime)} ${timeFormatter.format(dateTime)}'; +} diff --git a/lib/utils/helpers/decodeBase64.dart b/lib/utils/helpers/decodeBase64.dart index 9931a8e3..e0473992 100644 --- a/lib/utils/helpers/decodeBase64.dart +++ b/lib/utils/helpers/decodeBase64.dart @@ -4,7 +4,7 @@ String decodeBase64(String str) { //'-', '+' 62nd char of encoding, '_', '/' 63rd char of encoding String output = str.replaceAll('-', '+').replaceAll('_', '/'); switch (output.length % 4) { - // Pad with trailing '=' + // Pad with trailing '=' case 0: // No pad chars in this case break; case 2: // Two pad chars diff --git a/lib/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart b/lib/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart new file mode 100644 index 00000000..d64a5144 --- /dev/null +++ b/lib/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +mixin HelperResponsiveLayout { + bool isSmallScreenSize(BuildContext context) { + return MediaQuery.of(context).size.width < 700; + } + + bool isMediumScreenSize(BuildContext context) { + return MediaQuery.of(context).size.width >= 700 && + MediaQuery.of(context).size.width < 1400; + } + + bool isLargeScreenSize(BuildContext context) { + return MediaQuery.of(context).size.width >= 1400; + } +} diff --git a/lib/utils/responsive_layout.dart b/lib/utils/responsive_layout.dart index 804b5ddb..efc1600b 100644 --- a/lib/utils/responsive_layout.dart +++ b/lib/utils/responsive_layout.dart @@ -1,19 +1,20 @@ import 'package:flutter/material.dart'; - class ResponsiveLayout extends StatelessWidget { - final Widget desktopBody; + final Widget desktopBody; final Widget mobileBody; - const ResponsiveLayout({super.key,required this.desktopBody,required this.mobileBody}); + const ResponsiveLayout( + {super.key, required this.desktopBody, required this.mobileBody}); @override Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - if(constraints.maxWidth<600){ - return mobileBody; - }else{ - return desktopBody; - } - }, + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth < 600) { + return mobileBody; + } else { + return desktopBody; + } + }, ); } } diff --git a/lib/utils/snack_bar.dart b/lib/utils/snack_bar.dart index 11e46828..d50a4250 100644 --- a/lib/utils/snack_bar.dart +++ b/lib/utils/snack_bar.dart @@ -16,7 +16,6 @@ class CustomSnackBar { BuildContext? currentContext = key?.currentContext; if (key != null && currentContext != null) { final snackBar = SnackBar( - padding: const EdgeInsets.all(16), backgroundColor: Colors.green, content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 24747880..3f31f9d7 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -1,46 +1,43 @@ import 'package:flutter/material.dart'; import 'color_manager.dart'; -InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration( - focusColor: ColorsManager.grayColor, - suffixIcon:suffixIcon? const Icon(Icons.search):null, - hintText: 'Search', - filled: true, // Enable background filling - fillColor: const Color(0xffF5F6F7), // Set the background color - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius - borderSide: BorderSide.none, // Remove the underline - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius - borderSide: BorderSide.none, // Remove the underline - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius - borderSide: BorderSide.none, // Remove the underline - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(8), - ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(8), - ), -); +InputDecoration? textBoxDecoration({bool suffixIcon = false}) => + InputDecoration( + focusColor: ColorsManager.grayColor, + suffixIcon: suffixIcon ? const Icon(Icons.search) : null, + hintText: 'Search', + filled: true, // Enable background filling + fillColor: const Color(0xffF5F6F7), // Set the background color + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), // Add border radius + borderSide: BorderSide.none, // Remove the underline + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), // Add border radius + borderSide: BorderSide.none, // Remove the underline + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), // Add border radius + borderSide: BorderSide.none, // Remove the underline + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red, width: 2), + borderRadius: BorderRadius.circular(8), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red, width: 2), + borderRadius: BorderRadius.circular(8), + ), + ); - -BoxDecoration containerDecoration = BoxDecoration( +BoxDecoration containerDecoration = BoxDecoration( boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 8, - offset: const Offset(0, - 3), // changes position of shadow + offset: const Offset(0, 3), // changes position of shadow ), ], color: ColorsManager.boxColor, borderRadius: const BorderRadius.all(Radius.circular(10))); - - diff --git a/lib/web_layout/menu_sidebar.dart b/lib/web_layout/menu_sidebar.dart index 5cc882a8..8bd95d6c 100644 --- a/lib/web_layout/menu_sidebar.dart +++ b/lib/web_layout/menu_sidebar.dart @@ -1,54 +1,75 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/style.dart'; +import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class MenuSidebar extends StatelessWidget { +class MenuSidebar extends StatelessWidget with HelperResponsiveLayout { const MenuSidebar({super.key}); @override Widget build(BuildContext context) { - return Expanded( - child: Container( - decoration: const BoxDecoration( - shape: BoxShape.rectangle, - boxShadow: [ - BoxShadow( - color: Colors.black26, - offset: Offset(4, 0), - blurRadius: 10, - ) - ], - color: ColorsManager.whiteColors, - ), - width: 200, - child: Padding( - padding: const EdgeInsets.all(15.0), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('Community',style: TextStyle(fontSize: 20),), - CircleAvatar( - backgroundColor: Colors.grey.shade200, - child: IconButton( - color: ColorsManager.onSecondaryColor, - onPressed: () {}, - icon: const Icon(Icons.add) - ), - ) - ], - ), - const SizedBox(height: 20,), - TextFormField( - controller: TextEditingController(), - decoration:textBoxDecoration(suffixIcon: true) - ), - Container(height: 100,) + return LayoutBuilder( + builder: (context, constraints) { + double sidebarWidth; + + if (isLargeScreenSize(context)) { + sidebarWidth = 250; + } else if (isMediumScreenSize(context)) { + sidebarWidth = 230; + } else { + sidebarWidth = 200; + } + + return Container( + width: sidebarWidth, + decoration: const BoxDecoration( + shape: BoxShape.rectangle, + boxShadow: [ + BoxShadow( + color: Colors.black26, + offset: Offset(4, 0), + blurRadius: 10, + ) ], + color: Colors.white, ), - ), - ), + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Community', + style: TextStyle(fontSize: 20), + ), + CircleAvatar( + backgroundColor: Colors.grey.shade200, + child: IconButton( + color: Colors.grey, + onPressed: () {}, + icon: const Icon(Icons.add), + ), + ), + ], + ), + const SizedBox(height: 20), + SizedBox( + height: 100, + child: CustomWebTextField( + isRequired: false, + textFieldName: '', + controller: TextEditingController(), + hintText: 'Search...', + ), + ), + const Spacer(), + ], + ), + ), + ); + }, ); } } diff --git a/lib/web_layout/web_app_bar.dart b/lib/web_layout/web_app_bar.dart index 7b1ee1a7..6ad54d0b 100644 --- a/lib/web_layout/web_app_bar.dart +++ b/lib/web_layout/web_app_bar.dart @@ -12,11 +12,10 @@ class WebAppBar extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { - return Container( - height: 100, - decoration: const BoxDecoration(color: ColorsManager.secondaryColor), - padding: const EdgeInsets.all(10), - child: Expanded( + return Container( + height: 100, + decoration: const BoxDecoration(color: ColorsManager.secondaryColor), + padding: const EdgeInsets.all(10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -52,17 +51,16 @@ class WebAppBar extends StatelessWidget { const SizedBox( width: 10, ), - if(HomeBloc.user!=null) - Text( - '${HomeBloc.user!.firstName.toString() ?? ''} ${HomeBloc.user!.lastName.toString() ?? ''} ', - style: Theme.of(context).textTheme.bodyLarge, - ), + if (HomeBloc.user != null) + Text( + '${HomeBloc.user!.firstName.toString()} ${HomeBloc.user!.lastName.toString()} ', + style: Theme.of(context).textTheme.bodyLarge, + ), ], ) ], ), - ), - ); - }); + ); + }); } } diff --git a/lib/web_layout/web_scaffold.dart b/lib/web_layout/web_scaffold.dart index 31b8d958..72bcb777 100644 --- a/lib/web_layout/web_scaffold.dart +++ b/lib/web_layout/web_scaffold.dart @@ -1,53 +1,58 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/web_layout/web_app_bar.dart'; import 'menu_sidebar.dart'; -class WebScaffold extends StatelessWidget { + +class WebScaffold extends StatelessWidget with HelperResponsiveLayout { final bool enableMenuSideba; final Widget? appBarTitle; final List? appBarBody; final Widget? scaffoldBody; - const WebScaffold({super.key,this.appBarTitle,this.appBarBody,this.scaffoldBody,this.enableMenuSideba=true}); + const WebScaffold( + {super.key, + this.appBarTitle, + this.appBarBody, + this.scaffoldBody, + this.enableMenuSideba = true}); @override Widget build(BuildContext context) { + final isSmall = isSmallScreenSize(context); return Scaffold( body: Stack( + children: [ + SizedBox( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + child: SvgPicture.asset( + Assets.webBackground, + fit: BoxFit.cover, + ), + ), + Container( + color: Colors.white.withOpacity(0.7), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SizedBox( - width: MediaQuery.sizeOf(context).width, - height: MediaQuery.sizeOf(context).height, - child: SvgPicture.asset( - Assets.webBackground, - fit: BoxFit.cover, + Opacity( + opacity: 0.7, + child: WebAppBar( + title: appBarTitle, + body: appBarBody, + )), + Expanded( + child: Row( + children: [ + if (enableMenuSideba && !isSmall) const MenuSidebar(), + Expanded(flex: 5, child: scaffoldBody!) + ], ), - ), - Container(color: Colors.white.withOpacity(0.7),), - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Opacity( - opacity: 0.7, - child: WebAppBar( - title: appBarTitle, - body: appBarBody, - ) - ), - Expanded( - child: Row( - children: [ - if(enableMenuSideba) - const MenuSidebar(), - Expanded( - flex: 5, - child: scaffoldBody! - ) - ], - ), - ) - ], - ), + ) ], - )); + ), + ], + )); } } diff --git a/pubspec.yaml b/pubspec.yaml index 20118a0a..a8e96a9b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,6 +71,8 @@ flutter: # To add assets to your application, add an assets section, like this: assets: + - assets/icons/automation_functions/ + - assets/icons/ - assets/images/ - assets/ @@ -86,17 +88,13 @@ flutter: # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # + fonts: + - family: Aftika + fonts: + - asset: assets/fonts/fonnts.com-AftikaBlack.ttf + - asset: assets/fonts/fonnts.com-AftikaBold.ttf + - asset: assets/fonts/fonnts.com-AftikaRegular.ttf + + # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages