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(); + // }); + }, + ); + }, + ), + ), + ], + ); + }, + ), + ); + } +}