diff --git a/lib/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart b/lib/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart new file mode 100644 index 0000000..033e57b --- /dev/null +++ b/lib/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart @@ -0,0 +1,158 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; + +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_event.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_state.dart'; +import 'package:syncrow_app/features/devices/model/device_category_model.dart'; + +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/services/api/home_management_api.dart'; + +class DeviceManagerBloc extends Bloc { + DeviceManagerBloc() : super(DeviceManagerInitial()) { + on(_onFetchAllDevices); + on(_onFetchDevicesByRoomId); + on(_onSelectCategory); + on(_onUnselectAllCategories); + on(_onSelectDevice); + on(_onChangeCategorySwitchValue); + on(_onTurnOnOffDevice); + on(_onClearCategoriesSelection); + // on(_onDeviceControl); + } + + static List? allCategories; + + Future _onFetchAllDevices( + FetchAllDevices event, Emitter emit) async { + emit(GetDevicesLoading()); + try { + final allDevices = await HomeManagementAPI.fetchDevicesByUserId(); + emit(GetDevicesSuccess(allDevices)); + } catch (e) { + emit(GetDevicesError(e.toString())); + } + } + + Future _onFetchDevicesByRoomId( + FetchDevicesByRoomId event, Emitter emit) async { + emit(GetDevicesLoading()); + try { + final devices = await DevicesAPI.getDevicesByRoomId(event.roomId); + emit(GetDevicesSuccess(devices)); + } catch (e) { + emit(GetDevicesError(e.toString())); + } + } + + void _onSelectCategory( + SelectCategory event, Emitter emit) { + for (var i = 0; i < allCategories!.length; i++) { + allCategories![i].isSelected = i == event.index; + } + emit(DevicesCategoryChanged()); + } + + void _onUnselectAllCategories( + UnselectAllCategories event, Emitter emit) { + for (var category in allCategories!) { + category.isSelected = false; + } + emit(DevicesCategoryChanged()); + } + + void _onSelectDevice(SelectDevice event, Emitter emit) { + for (var category in allCategories!) { + if (category.devices != null) { + for (var device in category.devices!) { + if (device.isSelected) { + category.isSelected = false; + emit(DeviceSelected()); + return; + } + } + } + } + event.device.isSelected = !event.device.isSelected; + emit(DeviceSelected()); + } + + void _onChangeCategorySwitchValue( + ChangeCategorySwitchValue event, Emitter emit) { + var category = event.category; + if (category.devicesStatus != null) { + category.devicesStatus = !category.devicesStatus!; + if (category.devices != null) { + for (var device in category.devices!) { + device.isOnline = category.devicesStatus; + } + } + } else { + category.devicesStatus = true; + if (category.devices != null) { + for (var device in category.devices!) { + device.isOnline = true; + } + } + } + _updateDevicesStatus(category, emit); + } + + void _onTurnOnOffDevice( + TurnOnOffDevice event, Emitter emit) { + var device = event.device; + device.isOnline = !device.isOnline!; + DevicesCategoryModel category = allCategories!.firstWhere((category) { + return category.devices != null && category.devices!.contains(device); + }); + _updateDevicesStatus(category, emit); + } + + void _onClearCategoriesSelection( + ClearCategoriesSelection event, Emitter emit) { + for (var category in allCategories!) { + category.isSelected = false; + if (category.devices != null) { + for (var device in category.devices!) { + device.isSelected = false; + } + } + } + Navigator.popUntil(event.context, (route) => route.isFirst); + emit(DevicesCategoryChanged()); + } + + // Future _onDeviceControl( + // DeviceControl event, Emitter emit) async { + // emit(DeviceControlLoading(code: event.control.code)); + // try { + // var response = + // await DevicesAPI.controlDevice(event.control, event.deviceId); + // if (response['success'] ?? false) { + // emit(DeviceControlSuccess(code: event.control.code)); + // await Future.delayed(const Duration(milliseconds: 400), () { + // add(FetchDevicesByRoomId(event.deviceId)); + // }); + // } else { + // emit(DeviceControlError('Failed to control the device')); + // } + // } catch (e) { + // emit(DeviceControlError(e.toString())); + // } + // } + + void _updateDevicesStatus( + DevicesCategoryModel category, Emitter emit) { + if (category.devices != null && category.devices!.isNotEmpty) { + bool? tempStatus = category.devices![0].isOnline; + for (var device in category.devices!) { + if (device.isOnline != tempStatus) { + category.devicesStatus = null; + break; + } + category.devicesStatus = tempStatus; + } + emit(CategorySwitchChanged()); + } + } +} diff --git a/lib/features/devices/bloc/device_manager_bloc/device_manager_event.dart b/lib/features/devices/bloc/device_manager_bloc/device_manager_event.dart new file mode 100644 index 0000000..44de0a2 --- /dev/null +++ b/lib/features/devices/bloc/device_manager_bloc/device_manager_event.dart @@ -0,0 +1,80 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/model/device_category_model.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; + +abstract class DeviceManagerEvent extends Equatable { + const DeviceManagerEvent(); + + @override + List get props => []; +} + +class FetchAllDevices extends DeviceManagerEvent {} + +class FetchDevicesByRoomId extends DeviceManagerEvent { + final String roomId; + + const FetchDevicesByRoomId(this.roomId); + + @override + List get props => [roomId]; +} + +class SelectCategory extends DeviceManagerEvent { + final int index; + + const SelectCategory(this.index); + + @override + List get props => [index]; +} + +class UnselectAllCategories extends DeviceManagerEvent {} + +class SelectDevice extends DeviceManagerEvent { + final DeviceModel device; + + const SelectDevice(this.device); + + @override + List get props => [device]; +} + +class ChangeCategorySwitchValue extends DeviceManagerEvent { + final DevicesCategoryModel category; + + const ChangeCategorySwitchValue(this.category); + + @override + List get props => [category]; +} + +class TurnOnOffDevice extends DeviceManagerEvent { + final DeviceModel device; + + const TurnOnOffDevice(this.device); + + @override + List get props => [device]; +} + +class ClearCategoriesSelection extends DeviceManagerEvent { + final BuildContext context; + + const ClearCategoriesSelection(this.context); + + @override + List get props => [context]; +} + +class DeviceControl extends DeviceManagerEvent { + final DeviceControlModel control; + final String deviceId; + + const DeviceControl(this.control, this.deviceId); + + @override + List get props => [control, deviceId]; +} diff --git a/lib/features/devices/bloc/device_manager_bloc/device_manager_state.dart b/lib/features/devices/bloc/device_manager_bloc/device_manager_state.dart new file mode 100644 index 0000000..1dc8099 --- /dev/null +++ b/lib/features/devices/bloc/device_manager_bloc/device_manager_state.dart @@ -0,0 +1,112 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; + +abstract class DeviceManagerState extends Equatable { + const DeviceManagerState(); + + @override + List get props => []; +} + +class DeviceManagerInitial extends DeviceManagerState {} + +class DevicesCategoryChanged extends DeviceManagerState {} + +class DeviceSelected extends DeviceManagerState {} + +class DeviceControlLoading extends DeviceManagerState { + final String code; + + const DeviceControlLoading({required this.code}); + + @override + List get props => [code]; +} + +class DeviceControlSuccess extends DeviceManagerState { + final String code; + + const DeviceControlSuccess({required this.code}); + + @override + List get props => [code]; +} + +class DeviceControlError extends DeviceManagerState { + final String message; + + const DeviceControlError(this.message); + + @override + List get props => [message]; +} + +class DevicesCategoriesLoading extends DeviceManagerState {} + +class DevicesCategoriesSuccess extends DeviceManagerState {} + +class DevicesCategoriesError extends DeviceManagerState { + final String error; + + const DevicesCategoriesError(this.error); + + @override + List get props => [error]; +} + +class GetDevicesLoading extends DeviceManagerState {} + +class GetDevicesSuccess extends DeviceManagerState { + final List? devices; + + const GetDevicesSuccess(this.devices); + + @override + List get props => [devices ?? []]; +} + +class GetDevicesError extends DeviceManagerState { + final String error; + + const GetDevicesError(this.error); + + @override + List get props => [error]; +} + +class GetDeviceStatusLoading extends DeviceManagerState { + final String? code; + + const GetDeviceStatusLoading({this.code}); + + @override + List get props => [code ?? '']; +} + +class GetDeviceStatusSuccess extends DeviceManagerState { + final String? code; + + const GetDeviceStatusSuccess({this.code}); + + @override + List get props => [code ?? '']; +} + +class GetDeviceStatusError extends DeviceManagerState { + final String error; + + const GetDeviceStatusError(this.error); + + @override + List get props => [error]; +} + +class CategorySwitchChanged extends DeviceManagerState {} + +class DeviceSwitchChanged extends DeviceManagerState {} + +class CurtainsIsOpening extends DeviceManagerState {} + +class CurtainsIsClosing extends DeviceManagerState {} + +class CurtainsStopped extends DeviceManagerState {} diff --git a/lib/features/devices/bloc/devices_cubit.dart b/lib/features/devices/bloc/devices_cubit.dart index a6c1469..79bf03b 100644 --- a/lib/features/devices/bloc/devices_cubit.dart +++ b/lib/features/devices/bloc/devices_cubit.dart @@ -99,6 +99,20 @@ class DevicesCubit extends Cubit { return null; } + // Getter to retrieve all devices from HomeCubit + List get allDevices { + List devices = []; + if (HomeCubit.getInstance().selectedSpace != null && + HomeCubit.getInstance().selectedSpace!.rooms != null) { + for (var room in HomeCubit.getInstance().selectedSpace!.rooms!) { + if (room.devices != null) { + devices.addAll(room.devices!); + } + } + } + return devices; + } + // DevicesCategoryModel? get chosenCategory { // for (var category in allCategories!) { // if (category.isSelected) { @@ -290,9 +304,6 @@ class DevicesCubit extends Cubit { fetchDevicesByRoomId(String? roomId) async { if (roomId == null) return; - if (roomId == '-1') { - return fetchAllDevices(roomId); - } emitSafe(GetDevicesLoading()); int roomIndex = HomeCubit.getInstance() @@ -320,7 +331,7 @@ class DevicesCubit extends Cubit { // } } - void fetchAllDevices(String roomId) async { + void fetchAllDevices() async { emitSafe(GetDevicesLoading()); try { final allDevices = await HomeManagementAPI.fetchDevicesByUserId(); diff --git a/lib/features/scene/bloc/tab_change/tab_change_bloc.dart b/lib/features/scene/bloc/tab_change/tab_change_bloc.dart index bb822f2..a7c6481 100644 --- a/lib/features/scene/bloc/tab_change/tab_change_bloc.dart +++ b/lib/features/scene/bloc/tab_change/tab_change_bloc.dart @@ -1,12 +1,25 @@ +import 'dart:async'; + import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_event.dart'; import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_event.dart'; import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_state.dart'; class TabBarBloc extends Bloc { - TabBarBloc() : super(const Initial()) { - on((event, emit) { - emit(TabSelected( - selectedTabIndex: event.selectedIndex, roomId: event.roomId)); - }); + final DeviceManagerBloc deviceManagerBloc; + TabBarBloc(this.deviceManagerBloc) : super(const Initial()) { + on(_handleTabChanged); + } + + FutureOr _handleTabChanged( + TabChanged event, Emitter emit) { + if (event.roomId == "-1") { + deviceManagerBloc.add(FetchAllDevices()); + } else { + deviceManagerBloc.add(FetchDevicesByRoomId(event.roomId)); + } + emit(TabSelected( + roomId: event.roomId, selectedTabIndex: event.selectedIndex)); } } diff --git a/lib/features/scene/view/scene_control_devices.dart b/lib/features/scene/view/scene_control_devices.dart index e4cce41..67754d8 100644 --- a/lib/features/scene/view/scene_control_devices.dart +++ b/lib/features/scene/view/scene_control_devices.dart @@ -34,7 +34,7 @@ class _SceneControlDevicesViewState extends State 0, RoomModel( name: 'All Devices', - devices: [], + devices: DevicesCubit.getInstance().allDevices, id: '-1', type: SpaceType.Room, ), @@ -57,7 +57,7 @@ class _SceneControlDevicesViewState extends State TabChanged(selectedIndex: value, roomId: rooms?[value].id ?? '')); /// fetch devices - context.read().fetchDevicesByRoomId(rooms![value].id!); + // context.read().fetchDevicesByRoomId(rooms![value].id!); return; } } diff --git a/lib/features/scene/view/widgets/scene_devices/scene_devices_body.dart b/lib/features/scene/view/widgets/scene_devices/scene_devices_body.dart index de05499..0c2576e 100644 --- a/lib/features/scene/view/widgets/scene_devices/scene_devices_body.dart +++ b/lib/features/scene/view/widgets/scene_devices/scene_devices_body.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_state.dart'; import 'package:syncrow_app/features/devices/model/room_model.dart'; import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_state.dart'; @@ -8,18 +9,16 @@ import 'package:syncrow_app/features/scene/view/widgets/scene_list_tile.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; - import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; - class SceneDevicesBody extends StatelessWidget { const SceneDevicesBody({ super.key, required TabController tabController, required this.rooms, - }) : _tabController = tabController; + }) : _tabController = tabController; final TabController _tabController; final List? rooms; @@ -27,11 +26,10 @@ class SceneDevicesBody extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - builder: (context, state) { + builder: (context, tabState) { return Column( mainAxisSize: MainAxisSize.min, children: [ - /// Tab bar TabBar( controller: _tabController, dividerColor: Colors.transparent, @@ -42,7 +40,8 @@ class SceneDevicesBody extends StatelessWidget { text: e.name ?? '', textAlign: TextAlign.start, style: context.bodyLarge.copyWith( - color: (state is TabSelected) && state.roomId == e.id + color: (tabState is TabSelected) && + tabState.roomId == e.id ? ColorsManager.textPrimaryColor : ColorsManager.textPrimaryColor.withOpacity(0.2), ), @@ -52,59 +51,10 @@ class SceneDevicesBody extends StatelessWidget { isScrollable: true, tabAlignment: TabAlignment.start, ), - - /// Tab bar view Expanded( child: TabBarView( controller: _tabController, - children: rooms! - .map((e) => BlocBuilder( - builder: (context, state) { - if (state is GetDevicesLoading) { - return const Center( - child: CircularProgressIndicator()); - } else if (state is GetDevicesSuccess) { - return ListView.builder( - itemCount: state.devices!.length, - itemBuilder: (context, index) { - final device = state.devices![index]; - return DefaultContainer( - child: SceneListTile( - minLeadingWidth: 40, - leadingWidget: Image.network( - device.icon ?? '', - errorBuilder: - (context, error, stackTrace) => - Image.asset( - Assets.assetsIconsLogo, - width: 20, - ), - ), - titleWidget: BodyMedium( - text: device.name ?? '', - style: context.titleSmall.copyWith( - color: - ColorsManager.secondaryTextColor, - fontWeight: FontWeight.w400, - fontSize: 20, - ), - ), - trailingWidget: const Icon( - Icons.arrow_forward_ios_rounded, - size: 16, - weight: 0.2, - ), - ), - ); - }, - ); - } else if (state is GetDevicesError) { - return Center(child: Text(state.errorMsg)); - } - return const SizedBox(); - }, - )) - .toList(), + children: rooms!.map((e) => _buildRoomTab(e, context)).toList(), ), ), ], @@ -112,4 +62,49 @@ class SceneDevicesBody extends StatelessWidget { }, ); } + + Widget _buildRoomTab(RoomModel room, BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is GetDevicesLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is GetDevicesSuccess) { + return ListView.builder( + itemCount: state.devices?.length ?? 0, + itemBuilder: (context, index) { + final device = state.devices![index]; + return DefaultContainer( + child: SceneListTile( + minLeadingWidth: 40, + leadingWidget: Image.network( + device.icon ?? '', + errorBuilder: (context, error, stackTrace) => Image.asset( + Assets.assetsIconsLogo, + width: 20, + ), + ), + titleWidget: BodyMedium( + text: device.name ?? '', + style: context.titleSmall.copyWith( + color: ColorsManager.secondaryTextColor, + fontWeight: FontWeight.w400, + fontSize: 20, + ), + ), + trailingWidget: const Icon( + Icons.arrow_forward_ios_rounded, + size: 16, + weight: 0.2, + ), + ), + ); + }, + ); + } else if (state is GetDevicesError) { + return Center(child: Text(state.error)); + } + return const SizedBox(); + }, + ); + } } diff --git a/lib/navigation/router.dart b/lib/navigation/router.dart index 220e1f1..3b022a4 100644 --- a/lib/navigation/router.dart +++ b/lib/navigation/router.dart @@ -5,6 +5,8 @@ import 'package:syncrow_app/features/auth/view/otp_view.dart'; import 'package:syncrow_app/features/auth/view/login_view.dart'; import 'package:syncrow_app/features/auth/view/sign_up_view.dart'; import 'package:syncrow_app/features/dashboard/view/dashboard_view.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_event.dart'; import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; import 'package:syncrow_app/features/layout/view/layout_view.dart'; import 'package:syncrow_app/features/menu/view/menu_view.dart'; @@ -75,12 +77,15 @@ class Router { return MaterialPageRoute( builder: (_) => MultiBlocProvider( providers: [ - BlocProvider( - create: (BuildContext context) => TabBarBloc() + BlocProvider( + create: (BuildContext context) => + DeviceManagerBloc()..add(FetchAllDevices()), + ), + BlocProvider( + create: (BuildContext context) => TabBarBloc( + context.read()) ..add(const TabChanged(selectedIndex: 0, roomId: '-1')), ), - BlocProvider( - create: (context) => DevicesCubit.getInstance()), ], child: const SceneControlDevicesView(), ),