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/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()); }, ); } 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 304beea1..ad6ed4d8 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -36,7 +36,7 @@ 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 d29bd1a1..53a994f6 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -20,12 +20,7 @@ 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) { diff --git a/lib/pages/routines/create_new_routines/commu_dropdown.dart b/lib/pages/routines/create_new_routines/commu_dropdown.dart index 5b96e977..6fd562b0 100644 --- a/lib/pages/routines/create_new_routines/commu_dropdown.dart +++ b/lib/pages/routines/create_new_routines/commu_dropdown.dart @@ -1,156 +1,181 @@ -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'; +import 'space_tree_dropdown_bloc.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 SpaceTreeDropdownBloc _dropdownBloc; + final LayerLink _layerLink = LayerLink(); + OverlayEntry? _overlayEntry; + + @override + void initState() { + super.initState(); + _dropdownBloc = SpaceTreeDropdownBloc(widget.selectedSpaceId); + } + + @override + void dispose() { + _dropdownBloc.close(); + _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, - ), - ), - ), - 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, - ), - overflow: TextOverflow.ellipsis, - ), - ), - Expanded( - child: Container( - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: const BorderRadius.only( - topRight: Radius.circular(10), - bottomRight: Radius.circular(10), + return BlocProvider.value( + value: _dropdownBloc, + child: BlocBuilder( + builder: (context, spaceTreeState) { + final communities = spaceTreeState.searchQuery.isNotEmpty + ? spaceTreeState.filteredCommunity + : spaceTreeState.communityList; + + 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, ), - ), - 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), + ), + 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, + ), + ), + ], + ), ), ), ), - ), - 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, - ), - ), - )) - ], + ], + ); + }, + ); + }, ), ); } + + void _toggleDropdown(BuildContext context, List communities) { + 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: _dropdownBloc.state.selectedSpaceId, + onChanged: (id) { + if (id != null && mounted) { + _dropdownBloc.add(SpaceTreeDropdownSelectEvent(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 null; + } + } } 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..fe207910 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) { @@ -30,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...'; @@ -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, @@ -51,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( @@ -60,40 +66,44 @@ class _CreateNewRoutinesDialogState extends State { mainAxisSize: MainAxisSize.min, 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(), + 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; + }); + }, ), - selectedValue: _selectedCommunity, - onChanged: (String? newValue) { - setState(() { - _selectedCommunity = newValue; - _selectedSpace = null; - }); - if (newValue != null) { - _bloc.add(SpaceOnlyWithDevicesEvent(newValue)); - } - }, - ), - ), - 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), const Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, 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(); + // }); + }, + ); + }, + ), + ), + ], + ); + }, + ), + ); + } +} 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( 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