From 57508fe17e1111586498c68876147693e5eed689 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 15 Jun 2025 13:29:32 +0300 Subject: [PATCH] Refactor routine creation logic and add new dropdown events --- .../create_routine_bloc.dart | 15 -- .../create_routine_event.dart | 6 - .../create_new_routines/commu_dropdown.dart | 251 ++++++++++-------- .../create_new_routines.dart | 3 +- .../dropdown_menu_content.dart | 53 ++-- .../space_tree_dropdown_bloc.dart | 151 ++++++++++- .../space_tree_dropdown_event.dart | 18 +- .../space_tree_dropdown_state.dart | 46 +++- linux/flutter/generated_plugin_registrant.h | 15 ++ 9 files changed, 392 insertions(+), 166 deletions(-) create mode 100644 linux/flutter/generated_plugin_registrant.h diff --git a/lib/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart b/lib/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart index b472d034..84610b56 100644 --- a/lib/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart +++ b/lib/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart @@ -11,7 +11,6 @@ class CreateRoutineBloc extends Bloc { on(_fetchSpaceOnlyWithDevices); on(saveSpaceIdCommunityId); on(resetSelected); - on(_fetchCommunity); } String selectedSpaceId = ''; @@ -50,18 +49,4 @@ class CreateRoutineBloc extends Bloc { selectedCommunityId = ''; emit(const ResetSelectedState()); } - - Future _fetchCommunity( - FetchCommunityEvent event, Emitter emit) async { - emit(const CommunitiesLoadingState()); - - try { - final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - communities = - await CommunitySpaceManagementApi().fetchCommunities(projectUuid); - emit(const CommunityLoadedState()); - } catch (e) { - emit(SpaceTreeErrorState('Error loading communities $e')); - } - } } diff --git a/lib/pages/routines/bloc/create_routine_bloc/create_routine_event.dart b/lib/pages/routines/bloc/create_routine_bloc/create_routine_event.dart index ba901497..14c63344 100644 --- a/lib/pages/routines/bloc/create_routine_bloc/create_routine_event.dart +++ b/lib/pages/routines/bloc/create_routine_bloc/create_routine_event.dart @@ -43,9 +43,3 @@ class ResetSelectedEvent extends CreateRoutineEvent { } -class FetchCommunityEvent extends CreateRoutineEvent { - const FetchCommunityEvent(); - - @override - List get props => []; -} \ No newline at end of file diff --git a/lib/pages/routines/create_new_routines/commu_dropdown.dart b/lib/pages/routines/create_new_routines/commu_dropdown.dart index 6fd562b0..9f5cc33a 100644 --- a/lib/pages/routines/create_new_routines/commu_dropdown.dart +++ b/lib/pages/routines/create_new_routines/commu_dropdown.dart @@ -1,13 +1,11 @@ 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 SpaceTreeDropdown extends StatefulWidget { +class SpaceTreeDropdown extends StatelessWidget { final String? selectedSpaceId; final Function(String?)? onChanged; @@ -18,23 +16,33 @@ class SpaceTreeDropdown extends StatefulWidget { }); @override - State createState() => _SpaceTreeDropdownState(); + Widget build(BuildContext context) { + return BlocProvider( + create: (context) { + final bloc = SpaceTreeDropdownBloc(selectedSpaceId); + bloc.add(FetchSpacesEvent()); + return bloc; + }, + child: _DropdownContent(onChanged: onChanged), + ); + } } -class _SpaceTreeDropdownState extends State { - late SpaceTreeDropdownBloc _dropdownBloc; +class _DropdownContent extends StatefulWidget { + final Function(String?)? onChanged; + + const _DropdownContent({this.onChanged}); + + @override + State<_DropdownContent> createState() => _DropdownContentState(); +} + +class _DropdownContentState extends State<_DropdownContent> { final LayerLink _layerLink = LayerLink(); OverlayEntry? _overlayEntry; - @override - void initState() { - super.initState(); - _dropdownBloc = SpaceTreeDropdownBloc(widget.selectedSpaceId); - } - @override void dispose() { - _dropdownBloc.close(); _removeOverlay(); super.dispose(); } @@ -46,100 +54,120 @@ class _SpaceTreeDropdownState extends State { @override Widget build(BuildContext context) { - 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(context), + child: BlocBuilder( + builder: (context, state) { + return _buildDropdownTrigger(state); + }, + ), + ), + ), + ], + ); + } - return BlocBuilder( - builder: (context, dropdownState) { - final selectedCommunity = _findCommunity( - communities, - dropdownState.selectedSpaceId, - ); + Widget _buildDropdownTrigger(SpaceTreeDropdownState state) { + if (state.status == SpaceTreeDropdownStatus.loading) { + return Container( + height: 46, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.symmetric(horizontal: 10), + child: const Center(child: CircularProgressIndicator()), + ); + } - 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(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, - ), - ), - ], - ), - ), - ), - ), - ], - ); - }, - ); - }, + if (state.status == SpaceTreeDropdownStatus.failure) { + return Container( + height: 46, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.symmetric(horizontal: 10), + child: Center( + child: Text( + 'Error: ${state.errorMessage}', + style: const TextStyle(color: Colors.red), + ), + ), + ); + } + + final selectedCommunity = _findCommunity(state, state.selectedSpaceId); + + return 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(BuildContext context, List communities) { + void _toggleDropdown(BuildContext context) { if (_overlayEntry != null) { _removeOverlay(); return; } + final bloc = context.read(); + _overlayEntry = OverlayEntry( builder: (context) => Positioned( width: 300, @@ -148,18 +176,22 @@ class _SpaceTreeDropdownState extends State { showWhenUnlinked: false, offset: const Offset(0, 48), child: Material( + color: ColorsManager.whiteColors, 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, + child: BlocProvider.value( + value: bloc, + child: DropdownMenuContent( + selectedSpaceId: bloc.state.selectedSpaceId, + onChanged: (id) { + if (id != null && mounted) { + bloc.add(SpaceTreeDropdownSelectEvent(id)); + widget.onChanged?.call(id); + _removeOverlay(); + } + }, + onClose: _removeOverlay, + ), ), ), ), @@ -170,10 +202,13 @@ class _SpaceTreeDropdownState extends State { } CommunityModel? _findCommunity( - List communities, String? communityId) { + SpaceTreeDropdownState state, String? communityId) { if (communityId == null) return null; try { - return communities.firstWhere((c) => c.uuid == communityId); + return state.filteredCommunities.firstWhere((c) => c.uuid == communityId); + } catch (_) {} + try { + return state.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 fe207910..7bc38e09 100644 --- a/lib/pages/routines/create_new_routines/create_new_routines.dart +++ b/lib/pages/routines/create_new_routines/create_new_routines.dart @@ -23,8 +23,7 @@ class _CreateNewRoutinesDialogState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => - CreateRoutineBloc()..add(const FetchCommunityEvent()), + create: (BuildContext context) => CreateRoutineBloc(), child: BlocBuilder( builder: (context, state) { final _bloc = BlocProvider.of(context); diff --git a/lib/pages/routines/create_new_routines/dropdown_menu_content.dart b/lib/pages/routines/create_new_routines/dropdown_menu_content.dart index 70c88087..65243f53 100644 --- a/lib/pages/routines/create_new_routines/dropdown_menu_content.dart +++ b/lib/pages/routines/create_new_routines/dropdown_menu_content.dart @@ -1,12 +1,7 @@ - - - - +import 'dart:async'; 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'; +import 'space_tree_dropdown_bloc.dart'; class DropdownMenuContent extends StatefulWidget { final String? selectedSpaceId; @@ -14,6 +9,7 @@ class DropdownMenuContent extends StatefulWidget { final VoidCallback onClose; const DropdownMenuContent({ + super.key, required this.selectedSpaceId, required this.onChanged, required this.onClose, @@ -26,6 +22,7 @@ class DropdownMenuContent extends StatefulWidget { class _DropdownMenuContentState extends State { final ScrollController _scrollController = ScrollController(); final TextEditingController _searchController = TextEditingController(); + Timer? _debounceTimer; @override void initState() { @@ -35,43 +32,49 @@ class _DropdownMenuContentState extends State { @override void dispose() { + _debounceTimer?.cancel(); _scrollController.dispose(); _searchController.dispose(); super.dispose(); } void _onScroll() { - final bloc = context.read(); + 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)); + if (state.paginationModel?.hasNext == true && + !state.paginationIsLoading) { + bloc.add(PaginationEvent()); } } } + void _handleSearch(String query) { + _debounceTimer?.cancel(); + _debounceTimer = Timer(const Duration(milliseconds: 500), () { + context.read().add(SearchQueryEvent(query)); + }); + } + @override Widget build(BuildContext context) { return ConstrainedBox( constraints: const BoxConstraints(maxHeight: 300), - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { final communities = state.searchQuery.isNotEmpty - ? state.filteredCommunity - : state.communityList; + ? state.filteredCommunities + : state.communities; 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)); - }, + onChanged: _handleSearch, style: const TextStyle(fontSize: 14, color: Colors.black), decoration: InputDecoration( hintText: 'Search for space...', @@ -85,7 +88,6 @@ class _DropdownMenuContentState extends State { ), ), ), - // Community list Expanded( child: ListView.builder( controller: _scrollController, @@ -121,19 +123,12 @@ class _DropdownMenuContentState extends State { ), ), onTap: () { - setState(() { - _searchController.clear(); - _searchController.text.isEmpty - ? context - .read() - .add(SearchQueryEvent('')) - : context.read().add( - SearchQueryEvent(_searchController.text)); - }); - // Future.delayed(const Duration(seconds: 1), () { + context + .read() + .add(SearchQueryEvent('')); + widget.onChanged(community.uuid); widget.onClose(); - // }); }, ); }, 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 index be2a7e9b..384f2729 100644 --- a/lib/pages/routines/create_new_routines/space_tree_dropdown_bloc.dart +++ b/lib/pages/routines/create_new_routines/space_tree_dropdown_bloc.dart @@ -1,5 +1,9 @@ +import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; - +import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/space_tree/model/pagination_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/services/space_mana_api.dart'; part 'space_tree_dropdown_event.dart'; part 'space_tree_dropdown_state.dart'; @@ -9,19 +13,158 @@ class SpaceTreeDropdownBloc : super(SpaceTreeDropdownState(selectedSpaceId: initialId)) { on(_onSelect); on(_onReset); + on(_fetchSpaces); + on(_onSearch); + on(_onPagination); + on(_onDebouncedSearch); } + Timer? _debounceTimer; void _onSelect( SpaceTreeDropdownSelectEvent event, Emitter emit, ) { - emit(SpaceTreeDropdownState(selectedSpaceId: event.spaceId)); + final exists = state.communities.any((c) => c.uuid == event.spaceId); + + if (!exists) { + final community = state.filteredCommunities.firstWhere( + (c) => c.uuid == event.spaceId, + orElse: () => CommunityModel( + uuid: event.spaceId!, + name: 'Loading...', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + spaces: [], + description: ''), + ); + + emit(state.copyWith( + selectedSpaceId: event.spaceId, + communities: [...state.communities, community], + )); + } else { + emit(state.copyWith(selectedSpaceId: event.spaceId)); + } } void _onReset( SpaceTreeDropdownResetEvent event, Emitter emit, ) { - emit(SpaceTreeDropdownState(selectedSpaceId: event.initialId)); + emit(state.copyWith(selectedSpaceId: event.initialId)); } -} \ No newline at end of file + + Future _fetchSpaces( + FetchSpacesEvent event, + Emitter emit, + ) async { + if (state.status != SpaceTreeDropdownStatus.initial) return; + emit(state.copyWith(status: SpaceTreeDropdownStatus.loading)); + try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + final paginationModel = await CommunitySpaceManagementApi() + .fetchCommunitiesAndSpaces(projectId: projectUuid, page: 1); + + emit(state.copyWith( + status: SpaceTreeDropdownStatus.success, + communities: paginationModel.communities, + filteredCommunities: paginationModel.communities, + paginationModel: paginationModel, + )); + } catch (e) { + emit(state.copyWith( + status: SpaceTreeDropdownStatus.failure, + errorMessage: 'Error loading communities: $e', + )); + } + } + + void _onSearch( + SearchQueryEvent event, + Emitter emit, + ) { + _debounceTimer?.cancel(); + _debounceTimer = Timer(const Duration(seconds: 1), () { + add(DebouncedSearchEvent(event.searchQuery)); + }); + } + + Future _onDebouncedSearch( + DebouncedSearchEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isSearching: true)); + + try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + final paginationModel = + await CommunitySpaceManagementApi().fetchCommunitiesAndSpaces( + projectId: projectUuid, + page: 1, + search: event.searchQuery, + ); + + emit(state.copyWith( + filteredCommunities: paginationModel.communities, + isSearching: false, + searchQuery: event.searchQuery, + paginationModel: paginationModel, + )); + } catch (e) { + emit(state.copyWith( + isSearching: false, + errorMessage: 'Error searching communities: $e', + )); + } + } + + @override + Future close() { + _debounceTimer?.cancel(); + return super.close(); + } + + Future _onPagination( + PaginationEvent event, + Emitter emit, + ) async { + if (state.paginationIsLoading || state.paginationModel?.hasNext != true) { + return; + } + + emit(state.copyWith(paginationIsLoading: true)); + + try { + final nextPage = state.paginationModel!.pageNum; + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + final newPagination = await CommunitySpaceManagementApi() + .fetchCommunitiesAndSpaces(projectId: projectUuid, page: nextPage); + + final combinedCommunities = [ + ...state.communities, + ...newPagination.communities + ]; + List filteredCommunities; + if (state.searchQuery.isNotEmpty) { + final query = state.searchQuery.toLowerCase(); + filteredCommunities = combinedCommunities.where((community) { + return community.name.toLowerCase().contains(query); + }).toList(); + } else { + filteredCommunities = combinedCommunities; + } + + emit(state.copyWith( + communities: combinedCommunities, + filteredCommunities: filteredCommunities, + paginationModel: newPagination, + paginationIsLoading: false, + )); + } catch (e) { + emit(state.copyWith( + paginationIsLoading: false, + errorMessage: 'Error loading more communities: $e', + )); + } + } +} 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 index dec701dc..24047f7a 100644 --- a/lib/pages/routines/create_new_routines/space_tree_dropdown_event.dart +++ b/lib/pages/routines/create_new_routines/space_tree_dropdown_event.dart @@ -12,4 +12,20 @@ class SpaceTreeDropdownResetEvent extends SpaceTreeDropdownEvent { final String? initialId; SpaceTreeDropdownResetEvent(this.initialId); -} \ No newline at end of file +} + +class FetchSpacesEvent extends SpaceTreeDropdownEvent {} + +class SearchQueryEvent extends SpaceTreeDropdownEvent { + final String searchQuery; + + SearchQueryEvent(this.searchQuery); +} + +class DebouncedSearchEvent extends SpaceTreeDropdownEvent { + final String searchQuery; + + DebouncedSearchEvent(this.searchQuery); +} + +class PaginationEvent extends SpaceTreeDropdownEvent {} \ 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 index dd22d095..428aa41d 100644 --- a/lib/pages/routines/create_new_routines/space_tree_dropdown_state.dart +++ b/lib/pages/routines/create_new_routines/space_tree_dropdown_state.dart @@ -1,7 +1,51 @@ part of 'space_tree_dropdown_bloc.dart'; +enum SpaceTreeDropdownStatus { initial, loading, success, failure } + class SpaceTreeDropdownState { final String? selectedSpaceId; + final List communities; + final List filteredCommunities; + final SpaceTreeDropdownStatus status; + final String? errorMessage; + final String searchQuery; + final bool paginationIsLoading; + final PaginationModel? paginationModel; + final bool isSearching; - SpaceTreeDropdownState({this.selectedSpaceId}); + SpaceTreeDropdownState({ + this.selectedSpaceId, + this.communities = const [], + this.filteredCommunities = const [], + this.status = SpaceTreeDropdownStatus.initial, + this.errorMessage, + this.searchQuery = '', + this.paginationIsLoading = false, + this.paginationModel, + this.isSearching = false, + }); + + SpaceTreeDropdownState copyWith({ + String? selectedSpaceId, + List? communities, + List? filteredCommunities, + SpaceTreeDropdownStatus? status, + String? errorMessage, + String? searchQuery, + bool? paginationIsLoading, + PaginationModel? paginationModel, + bool? isSearching, + }) { + return SpaceTreeDropdownState( + selectedSpaceId: selectedSpaceId ?? this.selectedSpaceId, + communities: communities ?? this.communities, + filteredCommunities: filteredCommunities ?? this.filteredCommunities, + status: status ?? this.status, + errorMessage: errorMessage ?? this.errorMessage, + searchQuery: searchQuery ?? this.searchQuery, + paginationIsLoading: paginationIsLoading ?? this.paginationIsLoading, + paginationModel: paginationModel ?? this.paginationModel, + isSearching: isSearching ?? this.isSearching, + ); + } } \ No newline at end of file diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_