From 662fe211ebe0767cf9ada366b88727d9468631b4 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 9 Jun 2025 22:55:00 +0300 Subject: [PATCH 1/4] Refactor HomeBloc and GarageDoorBloc event handling; update CreateNewRoutinesDialog to use SpaceTreeDropdown; add settings button SVG. --- ...ettings_button.svg => sittings_button.svg} | 0 .../garage_door/bloc/garage_door_bloc.dart | 4 +- lib/pages/home/bloc/home_bloc.dart | 1 - lib/pages/home/view/home_page_web.dart | 18 +- .../create_new_routines/commu_dropdown.dart | 277 +++++++++--------- .../create_new_routines.dart | 36 +-- .../dropdown_menu_content.dart | 148 ++++++++++ 7 files changed, 313 insertions(+), 171 deletions(-) rename assets/icons/{settings_button.svg => sittings_button.svg} (100%) create mode 100644 lib/pages/routines/create_new_routines/dropdown_menu_content.dart diff --git a/assets/icons/settings_button.svg b/assets/icons/sittings_button.svg similarity index 100% rename from assets/icons/settings_button.svg rename to assets/icons/sittings_button.svg diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index 593fdeab..28a7e33b 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -379,7 +379,7 @@ class GarageDoorBloc extends Bloc { } emit(GarageDoorLoadedState(status: deviceStatus)); add(GarageDoorControlEvent( - deviceId: event.deviceId, + deviceId: deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); } catch (e) { @@ -396,7 +396,7 @@ class GarageDoorBloc extends Bloc { _updateLocalValue(event.code, event.value); emit(GarageDoorLoadedState(status: deviceStatus)); final success = await _runDeBouncer( - deviceId: event.deviceId, + deviceId: deviceId, code: event.code, value: event.value, oldValue: oldValue, diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index f6aab9eb..dc6a1280 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -35,7 +35,6 @@ class HomeBloc extends Bloc { if (user != null && user!.project != null) { await ProjectManager.setProjectUUID(user!.project!.uuid); - NavigationService.navigatorKey.currentContext!.read().add(InitialEvent()); } add(FetchTermEvent()); add(FetchPolicyEvent()); diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index fb35fa04..e6251c86 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -20,12 +20,6 @@ class _HomeWebPageState extends State { // Flag to track whether the dialog is already shown. bool _dialogShown = false; - @override - void initState() { - super.initState(); - final homeBloc = BlocProvider.of(context); - homeBloc.add(const FetchUserInfo()); - } @override Widget build(BuildContext context) { @@ -38,8 +32,10 @@ class _HomeWebPageState extends State { child: BlocConsumer( listener: (BuildContext context, state) { if (state is HomeInitial) { - if (homeBloc.user!.hasAcceptedWebAgreement == false && !_dialogShown) { - _dialogShown = true; // Set the flag to true to indicate the dialog is showing. + if (homeBloc.user!.hasAcceptedWebAgreement == false && + !_dialogShown) { + _dialogShown = + true; // Set the flag to true to indicate the dialog is showing. Future.delayed(const Duration(seconds: 1), () { showDialog( context: context, @@ -98,7 +94,8 @@ class _HomeWebPageState extends State { width: size.width * 0.68, child: GridView.builder( itemCount: homeBloc.homeItems.length, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, // Adjust as needed. crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, @@ -111,7 +108,8 @@ class _HomeWebPageState extends State { 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/routines/create_new_routines/commu_dropdown.dart b/lib/pages/routines/create_new_routines/commu_dropdown.dart index 5b96e977..b6cece30 100644 --- a/lib/pages/routines/create_new_routines/commu_dropdown.dart +++ b/lib/pages/routines/create_new_routines/commu_dropdown.dart @@ -1,156 +1,157 @@ -import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/create_new_routines/dropdown_menu_content.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class CommunityDropdown extends StatelessWidget { - final String? selectedValue; - final List communities; - final Function(String?) onChanged; - final TextEditingController _searchController = TextEditingController(); +class SpaceTreeDropdown extends StatefulWidget { + final String? selectedSpaceId; + final Function(String?)? onChanged; - CommunityDropdown({ - Key? key, - required this.selectedValue, - required this.onChanged, - required this.communities, - }) : super(key: key); + const SpaceTreeDropdown({ + super.key, + this.selectedSpaceId, + this.onChanged, + }); + + @override + State createState() => _SpaceTreeDropdownState(); +} + +class _SpaceTreeDropdownState extends State { + late String? _selectedSpaceId; + final LayerLink _layerLink = LayerLink(); + OverlayEntry? _overlayEntry; + + @override + void initState() { + super.initState(); + _selectedSpaceId = widget.selectedSpaceId; + } + + @override + void dispose() { + _removeOverlay(); + super.dispose(); + } + + void _removeOverlay() { + _overlayEntry?.remove(); + _overlayEntry = null; + } @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Community", - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.w400, - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - const SizedBox(height: 8), - SizedBox( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - ), - child: DropdownButton2( - underline: const SizedBox(), - value: selectedValue, - items: communities.map((community) { - return DropdownMenuItem( - value: community.uuid, - child: Text( - ' ${community.name}', - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ); - }).toList(), - onChanged: onChanged, - style: const TextStyle(color: Colors.black), - hint: Padding( - padding: const EdgeInsets.only(left: 10), - child: Text( - " Please Select", - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.textGray, - ), - ), + return BlocBuilder( + builder: (context, state) { + final communities = state.searchQuery.isNotEmpty + ? state.filteredCommunity + : state.communityList; + final selectedCommunity = _findCommunity(communities, _selectedSpaceId); + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: _toggleDropdown, + child: Container( + height: 46, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), ), - customButton: Container( - height: 45, - decoration: BoxDecoration( - border: Border.all(color: ColorsManager.textGray, width: 1.0), - borderRadius: BorderRadius.circular(10), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - flex: 5, - child: Text( - selectedValue != null - ? " ${communities.firstWhere((element) => element.uuid == selectedValue).name}" - : ' Please Select', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: selectedValue != null - ? Colors.black - : ColorsManager.textGray, - ), + margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + selectedCommunity?.name ?? 'Select a space', + style: TextStyle( + color: selectedCommunity != null + ? Colors.black + : Colors.grey, overflow: TextOverflow.ellipsis, - ), - ), - Expanded( - child: Container( - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: const BorderRadius.only( - topRight: Radius.circular(10), - bottomRight: Radius.circular(10), - ), - ), - height: 45, - child: const Icon( - Icons.keyboard_arrow_down, - color: ColorsManager.textGray, - ), - ), - ), - ], - ), - ), - dropdownStyleData: DropdownStyleData( - maxHeight: MediaQuery.of(context).size.height * 0.4, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - ), - ), - dropdownSearchData: DropdownSearchData( - searchController: _searchController, - searchInnerWidgetHeight: 50, - searchInnerWidget: Container( - height: 50, - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: TextFormField( - style: const TextStyle(color: Colors.black), - controller: _searchController, - decoration: InputDecoration( - isDense: true, - contentPadding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 12, - ), - hintText: 'Search for community...', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + fontWeight: FontWeight.w400, + fontSize: 15, ), ), ), - ), - searchMatchFn: (item, searchValue) { - final communityName = - (item.child as Text).data?.toLowerCase() ?? ''; - return communityName - .contains(searchValue.toLowerCase().trim()); - }, - ), - onMenuStateChange: (isOpen) { - if (!isOpen) { - _searchController.clear(); - } - }, - menuItemStyleData: const MenuItemStyleData( - height: 40, + Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + height: 45, + width: 35, + child: const Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), + ], ), ), - )) - ], - ), + ), + ); + }, ); } + + void _toggleDropdown() { + if (_overlayEntry != null) { + _removeOverlay(); + return; + } + + _overlayEntry = OverlayEntry( + builder: (context) => Positioned( + width: 300, + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: const Offset(0, 48), + child: Material( + elevation: 8, + borderRadius: BorderRadius.circular(12), + child: DropdownMenuContent( + selectedSpaceId: _selectedSpaceId, + onChanged: (id) { + if (id != null && mounted) { + setState(() => _selectedSpaceId = id); + widget.onChanged?.call(id); + _removeOverlay(); + } + }, + onClose: _removeOverlay, + ), + ), + ), + ), + ); + + Overlay.of(context).insert(_overlayEntry!); + } + + CommunityModel? _findCommunity( + List communities, String? communityId) { + if (communityId == null) return null; + + try { + return communities.firstWhere((c) => c.uuid == communityId); + } catch (e) { + return CommunityModel( + uuid: '', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + name: '', + description: '', + spaces: []); + } + } } diff --git a/lib/pages/routines/create_new_routines/create_new_routines.dart b/lib/pages/routines/create_new_routines/create_new_routines.dart index 8f28208f..a763683d 100644 --- a/lib/pages/routines/create_new_routines/create_new_routines.dart +++ b/lib/pages/routines/create_new_routines/create_new_routines.dart @@ -18,6 +18,7 @@ class CreateNewRoutinesDialog extends StatefulWidget { class _CreateNewRoutinesDialogState extends State { String? _selectedCommunity; String? _selectedSpace; + String? _selectedId; @override Widget build(BuildContext context) { @@ -40,7 +41,10 @@ class _CreateNewRoutinesDialogState extends State { spaceHint = 'Select Space'; } } - + if (_selectedId != null && _selectedCommunity != _selectedId) { + _selectedSpace = null; + _selectedCommunity = _selectedId; + } return AlertDialog( backgroundColor: Colors.white, insetPadding: EdgeInsets.zero, @@ -61,25 +65,17 @@ class _CreateNewRoutinesDialogState extends State { children: [ const Divider(), Padding( - padding: const EdgeInsets.only(left: 15, right: 15), - child: CommunityDropdown( - communities: _bloc.communities..sort( - (a, b) => a.name.toLowerCase().compareTo( - b.name.toLowerCase(), - ), - ), - selectedValue: _selectedCommunity, - onChanged: (String? newValue) { - setState(() { - _selectedCommunity = newValue; - _selectedSpace = null; - }); - if (newValue != null) { - _bloc.add(SpaceOnlyWithDevicesEvent(newValue)); - } - }, - ), - ), + padding: const EdgeInsets.only(left: 15, right: 15), + child: SpaceTreeDropdown( + selectedSpaceId: _selectedId, + onChanged: (String? newValue) { + setState(() => _selectedId = newValue!); + if (_selectedId != null) { + _bloc.add( + SpaceOnlyWithDevicesEvent(_selectedId!)); + } + }, + )), const SizedBox(height: 5), Padding( padding: const EdgeInsets.only(left: 15, right: 15), diff --git a/lib/pages/routines/create_new_routines/dropdown_menu_content.dart b/lib/pages/routines/create_new_routines/dropdown_menu_content.dart new file mode 100644 index 00000000..70c88087 --- /dev/null +++ b/lib/pages/routines/create_new_routines/dropdown_menu_content.dart @@ -0,0 +1,148 @@ + + + + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart'; + +class DropdownMenuContent extends StatefulWidget { + final String? selectedSpaceId; + final ValueChanged onChanged; + final VoidCallback onClose; + + const DropdownMenuContent({ + required this.selectedSpaceId, + required this.onChanged, + required this.onClose, + }); + + @override + State createState() => _DropdownMenuContentState(); +} + +class _DropdownMenuContentState extends State { + final ScrollController _scrollController = ScrollController(); + final TextEditingController _searchController = TextEditingController(); + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + _searchController.dispose(); + super.dispose(); + } + + void _onScroll() { + final bloc = context.read(); + final state = bloc.state; + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent - 30) { + if (state is SpaceTreeState && !state.paginationIsLoading) { + bloc.add(PaginationEvent(state.paginationModel, state.communityList)); + } + } + } + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 300), + child: BlocBuilder( + builder: (context, state) { + final communities = state.searchQuery.isNotEmpty + ? state.filteredCommunity + : state.communityList; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Search bar + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _searchController, + onChanged: (query) { + context.read().add(SearchQueryEvent(query)); + }, + style: const TextStyle(fontSize: 14, color: Colors.black), + decoration: InputDecoration( + hintText: 'Search for space...', + prefixIcon: const Icon(Icons.search, size: 20), + contentPadding: + const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + isDense: true, + ), + ), + ), + // Community list + Expanded( + child: ListView.builder( + controller: _scrollController, + itemCount: + communities.length + (state.paginationIsLoading ? 1 : 0), + itemBuilder: (context, index) { + if (index >= communities.length) { + return state.paginationIsLoading + ? const Padding( + padding: EdgeInsets.all(8.0), + child: Center( + child: SizedBox( + width: 20, + height: 20, + child: + CircularProgressIndicator(strokeWidth: 2), + ), + ), + ) + : const SizedBox.shrink(); + } + + final community = communities[index]; + final isSelected = community.uuid == widget.selectedSpaceId; + + return ListTile( + title: Text( + community.name, + style: TextStyle( + color: isSelected ? Colors.blue : Colors.black, + fontWeight: + isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + onTap: () { + setState(() { + _searchController.clear(); + _searchController.text.isEmpty + ? context + .read() + .add(SearchQueryEvent('')) + : context.read().add( + SearchQueryEvent(_searchController.text)); + }); + // Future.delayed(const Duration(seconds: 1), () { + widget.onChanged(community.uuid); + widget.onClose(); + // }); + }, + ); + }, + ), + ), + ], + ); + }, + ), + ); + } +} From ed06a760d2b018b86d74bbc47770dc2177e314b7 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 10 Jun 2025 11:41:42 +0300 Subject: [PATCH 2/4] change spaceHint and size --- .../create_new_routines/commu_dropdown.dart | 107 +++++++++++------- .../create_new_routines.dart | 68 ++++++----- .../create_new_routines/space_dropdown.dart | 6 +- 3 files changed, 107 insertions(+), 74 deletions(-) diff --git a/lib/pages/routines/create_new_routines/commu_dropdown.dart b/lib/pages/routines/create_new_routines/commu_dropdown.dart index b6cece30..431e633a 100644 --- a/lib/pages/routines/create_new_routines/commu_dropdown.dart +++ b/lib/pages/routines/create_new_routines/commu_dropdown.dart @@ -51,53 +51,74 @@ class _SpaceTreeDropdownState extends State { : state.communityList; final selectedCommunity = _findCommunity(communities, _selectedSpaceId); - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: _toggleDropdown, - child: Container( - height: 46, - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(12), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, ), - margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Text( - selectedCommunity?.name ?? 'Select a space', - style: TextStyle( - color: selectedCommunity != null - ? Colors.black - : Colors.grey, - overflow: TextOverflow.ellipsis, - fontWeight: FontWeight.w400, - fontSize: 15, - ), + child: Text( + "Community", + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.blackColor, ), - ), - Container( - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: const BorderRadius.only( - topRight: Radius.circular(10), - bottomRight: Radius.circular(10), - ), - ), - height: 45, - width: 35, - child: const Icon( - Icons.keyboard_arrow_down, - color: ColorsManager.textGray, - ), - ), - ], ), ), - ), + CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: _toggleDropdown, + child: Container( + height: 46, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.symmetric( + horizontal: 10, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Text( + selectedCommunity?.name ?? 'Please Select', + style: TextStyle( + color: selectedCommunity != null + ? ColorsManager.blackColor + : ColorsManager.textGray, + overflow: TextOverflow.ellipsis, + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ), + Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + height: 45, + width: 33, + child: const Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), + ], + ), + ), + ), + ), + ], ); }, ); diff --git a/lib/pages/routines/create_new_routines/create_new_routines.dart b/lib/pages/routines/create_new_routines/create_new_routines.dart index a763683d..fe207910 100644 --- a/lib/pages/routines/create_new_routines/create_new_routines.dart +++ b/lib/pages/routines/create_new_routines/create_new_routines.dart @@ -31,7 +31,7 @@ class _CreateNewRoutinesDialogState extends State { final spaces = _bloc.spacesOnlyWithDevices; final isLoadingCommunities = state is CommunitiesLoadingState; final isLoadingSpaces = state is SpaceWithDeviceLoadingState; - String spaceHint = 'Select a community first'; + String spaceHint = 'Please Select'; if (_selectedCommunity != null) { if (isLoadingSpaces) { spaceHint = 'Loading spaces...'; @@ -55,7 +55,9 @@ class _CreateNewRoutinesDialogState extends State { 'Create New Routines', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColor, + color: ColorsManager.spaceColor, + fontSize: 20, + fontWeight: FontWeight.w700, ), ), content: Stack( @@ -64,32 +66,44 @@ class _CreateNewRoutinesDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ const Divider(), - Padding( - padding: const EdgeInsets.only(left: 15, right: 15), - child: SpaceTreeDropdown( - selectedSpaceId: _selectedId, - onChanged: (String? newValue) { - setState(() => _selectedId = newValue!); - if (_selectedId != null) { - _bloc.add( - SpaceOnlyWithDevicesEvent(_selectedId!)); - } - }, - )), - const SizedBox(height: 5), - Padding( - padding: const EdgeInsets.only(left: 15, right: 15), - child: SpaceDropdown( - hintMessage: spaceHint, - spaces: spaces, - selectedValue: _selectedSpace, - onChanged: (String? newValue) { - setState(() { - _selectedSpace = newValue; - }); - }, - ), + const SizedBox(height: 20), + Column( + children: [ + Padding( + padding: + const EdgeInsets.only(left: 13, right: 8), + child: Column( + children: [ + SpaceTreeDropdown( + selectedSpaceId: _selectedId, + onChanged: (String? newValue) { + setState(() => _selectedId = newValue!); + if (_selectedId != null) { + _bloc.add(SpaceOnlyWithDevicesEvent( + _selectedId!)); + } + }, + ), + ], + )), + const SizedBox(height: 5), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.only(left: 15, right: 15), + child: SpaceDropdown( + hintMessage: spaceHint, + spaces: spaces, + selectedValue: _selectedSpace, + onChanged: (String? newValue) { + setState(() { + _selectedSpace = newValue; + }); + }, + ), + ), + ], ), + const SizedBox(height: 20), const Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, diff --git a/lib/pages/routines/create_new_routines/space_dropdown.dart b/lib/pages/routines/create_new_routines/space_dropdown.dart index a26ff9f4..1d11b02d 100644 --- a/lib/pages/routines/create_new_routines/space_dropdown.dart +++ b/lib/pages/routines/create_new_routines/space_dropdown.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; - class SpaceDropdown extends StatelessWidget { final List spaces; final String? selectedValue; @@ -21,7 +20,7 @@ class SpaceDropdown extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.only(left: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -33,7 +32,6 @@ class SpaceDropdown extends StatelessWidget { color: ColorsManager.blackColor, ), ), - const SizedBox(height: 8), SizedBox( child: Container( decoration: BoxDecoration( @@ -90,7 +88,7 @@ class SpaceDropdown extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - flex: 5, + flex: 6, child: Padding( padding: const EdgeInsets.only(left: 10), child: Text( From a1b20078a3f97f8ac060f37e5c162f6fd7eedc94 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 10 Jun 2025 12:10:47 +0300 Subject: [PATCH 3/4] Refactor AccessBloc to emit filtered data instead of fetching; clean up device search filters for improved readability. --- lib/pages/access_management/bloc/access_bloc.dart | 3 ++- .../all_devices/widgets/device_search_filters.dart | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index 562bd5b5..dd82d739 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -267,7 +267,8 @@ class AccessBloc extends Bloc { selectedIndex = 0; effectiveTimeTimeStamp = null; expirationTimeTimeStamp = null; - add(FetchTableData()); + filteredData = List.from(data); + emit(TableLoaded(filteredData)); } String timestampToDate(dynamic timestamp) { diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 18d72fc9..6440d18f 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -34,7 +34,8 @@ class _DeviceSearchFiltersState extends State runSpacing: 10, children: [ _buildSearchField("Space Name", _unitNameController, 200), - _buildSearchField("Device Name / Product Name", _productNameController, 300), + _buildSearchField( + "Device Name / Product Name", _productNameController, 300), _buildSearchResetButtons(), ], ); @@ -74,9 +75,7 @@ class _DeviceSearchFiltersState extends State onReset: () { _unitNameController.clear(); _productNameController.clear(); - context.read() - ..add(ResetFilters()) - ..add(FetchDevices(context)); + context.read().add(ResetFilters()); }, ); } From fc86042af7dd399d7ce1b17c9e6590ef1c293eea Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 11 Jun 2025 14:14:21 +0300 Subject: [PATCH 4/4] Implement SpaceTreeDropdownBloc for improved state management in SpaceTreeDropdown; refactor dropdown logic and event handling. --- .../create_new_routines/commu_dropdown.dart | 173 +++++++++--------- .../space_tree_dropdown_bloc.dart | 27 +++ .../space_tree_dropdown_event.dart | 15 ++ .../space_tree_dropdown_state.dart | 7 + 4 files changed, 137 insertions(+), 85 deletions(-) create mode 100644 lib/pages/routines/create_new_routines/space_tree_dropdown_bloc.dart create mode 100644 lib/pages/routines/create_new_routines/space_tree_dropdown_event.dart create mode 100644 lib/pages/routines/create_new_routines/space_tree_dropdown_state.dart diff --git a/lib/pages/routines/create_new_routines/commu_dropdown.dart b/lib/pages/routines/create_new_routines/commu_dropdown.dart index 431e633a..6fd562b0 100644 --- a/lib/pages/routines/create_new_routines/commu_dropdown.dart +++ b/lib/pages/routines/create_new_routines/commu_dropdown.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'space_tree_dropdown_bloc.dart'; class SpaceTreeDropdown extends StatefulWidget { final String? selectedSpaceId; @@ -21,18 +22,19 @@ class SpaceTreeDropdown extends StatefulWidget { } class _SpaceTreeDropdownState extends State { - late String? _selectedSpaceId; + late SpaceTreeDropdownBloc _dropdownBloc; final LayerLink _layerLink = LayerLink(); OverlayEntry? _overlayEntry; @override void initState() { super.initState(); - _selectedSpaceId = widget.selectedSpaceId; + _dropdownBloc = SpaceTreeDropdownBloc(widget.selectedSpaceId); } @override void dispose() { + _dropdownBloc.close(); _removeOverlay(); super.dispose(); } @@ -44,87 +46,95 @@ class _SpaceTreeDropdownState extends State { @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - final communities = state.searchQuery.isNotEmpty - ? state.filteredCommunity - : state.communityList; - final selectedCommunity = _findCommunity(communities, _selectedSpaceId); + return BlocProvider.value( + value: _dropdownBloc, + child: BlocBuilder( + builder: (context, spaceTreeState) { + final communities = spaceTreeState.searchQuery.isNotEmpty + ? spaceTreeState.filteredCommunity + : spaceTreeState.communityList; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - ), - child: Text( - "Community", - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.w400, - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - ), - CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: _toggleDropdown, - child: Container( - height: 46, - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(12), - ), - margin: const EdgeInsets.symmetric( - horizontal: 10, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Text( - selectedCommunity?.name ?? 'Please Select', - style: TextStyle( - color: selectedCommunity != null - ? ColorsManager.blackColor - : ColorsManager.textGray, - overflow: TextOverflow.ellipsis, + return BlocBuilder( + builder: (context, dropdownState) { + final selectedCommunity = _findCommunity( + communities, + dropdownState.selectedSpaceId, + ); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Text( + "Community", + style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontWeight: FontWeight.w400, fontSize: 13, + color: ColorsManager.blackColor, ), - ), - ), - Container( - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: const BorderRadius.only( - topRight: Radius.circular(10), - bottomRight: Radius.circular(10), - ), - ), - height: 45, - width: 33, - child: const Icon( - Icons.keyboard_arrow_down, - color: ColorsManager.textGray, - ), - ), - ], + ), ), - ), - ), - ), - ], - ); - }, + CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () => _toggleDropdown(context, communities), + child: Container( + height: 46, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10), + child: Text( + selectedCommunity?.name ?? 'Please Select', + style: TextStyle( + color: selectedCommunity != null + ? ColorsManager.blackColor + : ColorsManager.textGray, + overflow: TextOverflow.ellipsis, + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ), + Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + height: 45, + width: 33, + child: const Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), + ], + ), + ), + ), + ), + ], + ); + }, + ); + }, + ), ); } - void _toggleDropdown() { + void _toggleDropdown(BuildContext context, List communities) { if (_overlayEntry != null) { _removeOverlay(); return; @@ -141,10 +151,10 @@ class _SpaceTreeDropdownState extends State { elevation: 8, borderRadius: BorderRadius.circular(12), child: DropdownMenuContent( - selectedSpaceId: _selectedSpaceId, + selectedSpaceId: _dropdownBloc.state.selectedSpaceId, onChanged: (id) { if (id != null && mounted) { - setState(() => _selectedSpaceId = id); + _dropdownBloc.add(SpaceTreeDropdownSelectEvent(id)); widget.onChanged?.call(id); _removeOverlay(); } @@ -162,17 +172,10 @@ class _SpaceTreeDropdownState extends State { CommunityModel? _findCommunity( List communities, String? communityId) { if (communityId == null) return null; - try { return communities.firstWhere((c) => c.uuid == communityId); } catch (e) { - return CommunityModel( - uuid: '', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - name: '', - description: '', - spaces: []); + return null; } } } diff --git a/lib/pages/routines/create_new_routines/space_tree_dropdown_bloc.dart b/lib/pages/routines/create_new_routines/space_tree_dropdown_bloc.dart new file mode 100644 index 00000000..be2a7e9b --- /dev/null +++ b/lib/pages/routines/create_new_routines/space_tree_dropdown_bloc.dart @@ -0,0 +1,27 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'space_tree_dropdown_event.dart'; +part 'space_tree_dropdown_state.dart'; + +class SpaceTreeDropdownBloc + extends Bloc { + SpaceTreeDropdownBloc(String? initialId) + : super(SpaceTreeDropdownState(selectedSpaceId: initialId)) { + on(_onSelect); + on(_onReset); + } + + void _onSelect( + SpaceTreeDropdownSelectEvent event, + Emitter emit, + ) { + emit(SpaceTreeDropdownState(selectedSpaceId: event.spaceId)); + } + + void _onReset( + SpaceTreeDropdownResetEvent event, + Emitter emit, + ) { + emit(SpaceTreeDropdownState(selectedSpaceId: event.initialId)); + } +} \ No newline at end of file diff --git a/lib/pages/routines/create_new_routines/space_tree_dropdown_event.dart b/lib/pages/routines/create_new_routines/space_tree_dropdown_event.dart new file mode 100644 index 00000000..dec701dc --- /dev/null +++ b/lib/pages/routines/create_new_routines/space_tree_dropdown_event.dart @@ -0,0 +1,15 @@ +part of 'space_tree_dropdown_bloc.dart'; + +abstract class SpaceTreeDropdownEvent {} + +class SpaceTreeDropdownSelectEvent extends SpaceTreeDropdownEvent { + final String? spaceId; + + SpaceTreeDropdownSelectEvent(this.spaceId); +} + +class SpaceTreeDropdownResetEvent extends SpaceTreeDropdownEvent { + final String? initialId; + + SpaceTreeDropdownResetEvent(this.initialId); +} \ No newline at end of file diff --git a/lib/pages/routines/create_new_routines/space_tree_dropdown_state.dart b/lib/pages/routines/create_new_routines/space_tree_dropdown_state.dart new file mode 100644 index 00000000..dd22d095 --- /dev/null +++ b/lib/pages/routines/create_new_routines/space_tree_dropdown_state.dart @@ -0,0 +1,7 @@ +part of 'space_tree_dropdown_bloc.dart'; + +class SpaceTreeDropdownState { + final String? selectedSpaceId; + + SpaceTreeDropdownState({this.selectedSpaceId}); +} \ No newline at end of file