Refactor SidebarCommunitiesList to be a StatelessWidget and update its usage across SpaceTreeView and SidebarWidget for improved performance and maintainability.

This commit is contained in:
Faris Armoush
2025-04-16 14:09:36 +03:00
parent afdd44e098
commit 8b441aaf46
3 changed files with 167 additions and 166 deletions

View File

@ -2,34 +2,19 @@ import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SidebarCommunitiesList extends StatefulWidget { class SidebarCommunitiesList extends StatelessWidget {
const SidebarCommunitiesList({ const SidebarCommunitiesList({
required this.communities, required this.communities,
required this.itemBuilder, required this.itemBuilder,
required this.scrollController,
required this.onScrollToEnd,
super.key, super.key,
}); });
final List<CommunityModel> communities; final List<CommunityModel> communities;
final Widget Function(BuildContext context, int index) itemBuilder; final Widget Function(BuildContext context, int index) itemBuilder;
final ScrollController scrollController;
@override final void Function() onScrollToEnd;
State<SidebarCommunitiesList> createState() => _SidebarCommunitiesListState();
}
class _SidebarCommunitiesListState extends State<SidebarCommunitiesList> {
late final ScrollController _scrollController;
@override
void initState() {
_scrollController = ScrollController();
super.initState();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -40,13 +25,22 @@ class _SidebarCommunitiesListState extends State<SidebarCommunitiesList> {
child: Scrollbar( child: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left, scrollbarOrientation: ScrollbarOrientation.left,
thumbVisibility: true, thumbVisibility: true,
controller: _scrollController, controller: scrollController,
child: ListView.builder( child: NotificationListener(
padding: const EdgeInsetsDirectional.only(start: 16), onNotification: (notification) {
shrinkWrap: true, if (notification is ScrollEndNotification &&
itemCount: widget.communities.length, notification.metrics.extentAfter == 0) {
controller: _scrollController, onScrollToEnd.call();
itemBuilder: widget.itemBuilder, }
return false;
},
child: ListView.builder(
padding: const EdgeInsetsDirectional.only(start: 16),
shrinkWrap: true,
itemCount: communities.length,
controller: scrollController,
itemBuilder: itemBuilder,
),
), ),
), ),
), ),

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_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_event.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
@ -34,160 +35,158 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) { return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
List<CommunityModel> list = final communities = state.searchQuery.isNotEmpty
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList; ? state.filteredCommunity
: state.communityList;
return Container( return Container(
height: MediaQuery.sizeOf(context).height, height: MediaQuery.sizeOf(context).height,
decoration: widget.isSide == true decoration: widget.isSide == true
? subSectionContainerDecoration.copyWith(color: ColorsManager.whiteColors) ? subSectionContainerDecoration.copyWith(
color: ColorsManager.whiteColors)
: const BoxDecoration(color: ColorsManager.whiteColors), : const BoxDecoration(color: ColorsManager.whiteColors),
child: state is SpaceTreeLoadingState child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Column( : Column(
children: [ children: [
widget.isSide == true if (widget.isSide == true)
? Container( Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: ColorsManager.circleRolesBackground, color: ColorsManager.circleRolesBackground,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topRight: Radius.circular(20), topLeft: Radius.circular(20)), topRight: Radius.circular(20),
), topLeft: Radius.circular(20),
child: Padding( ),
padding: const EdgeInsets.all(8.0), ),
child: Row( child: Padding(
children: [ padding: const EdgeInsets.all(8.0),
Expanded( child: Row(
child: Container( children: [
decoration: BoxDecoration( Expanded(
borderRadius: const BorderRadius.all(Radius.circular(20)), child: Container(
border: Border.all(color: ColorsManager.grayBorder)), decoration: BoxDecoration(
child: TextFormField( borderRadius: const BorderRadius.all(
style: context.textTheme.bodyMedium Radius.circular(20),
?.copyWith(color: ColorsManager.blackColor), ),
onChanged: (value) { border: Border.all(
context.read<SpaceTreeBloc>().add(SearchQueryEvent(value)); color: ColorsManager.grayBorder,
}, ),
decoration: textBoxDecoration(radios: 20)!.copyWith( ),
fillColor: Colors.white, child: TextFormField(
suffixIcon: Padding( style: context.textTheme.bodyMedium?.copyWith(
padding: const EdgeInsets.only(right: 16), color: ColorsManager.blackColor,
child: SvgPicture.asset( ),
Assets.textFieldSearch, onChanged: (value) =>
width: 24, context.read<SpaceTreeBloc>().add(
height: 24, SearchQueryEvent(value),
), ),
), decoration:
hintStyle: context.textTheme.bodyMedium?.copyWith( textBoxDecoration(radios: 20)?.copyWith(
fontWeight: FontWeight.w400, fillColor: Colors.white,
fontSize: 12, suffixIcon: Padding(
color: ColorsManager.textGray), padding: const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
Assets.textFieldSearch,
width: 24,
height: 24,
), ),
), ),
hintStyle:
context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray,
),
), ),
), ),
], ),
), ),
), ],
)
: CustomSearchBar(
onSearchChanged: (query) {
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
), ),
),
)
else
CustomSearchBar(
onSearchChanged: (query) => context.read<SpaceTreeBloc>().add(
SearchQueryEvent(query),
),
),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
child: state.isSearching child: state.isSearching
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: ListView( : SidebarCommunitiesList(
shrinkWrap: true, onScrollToEnd: () => context.read<SpaceTreeBloc>().add(
scrollDirection: Axis.horizontal, PaginationEvent(
children: [ state.paginationModel,
Container( state.communityList,
width: MediaQuery.sizeOf(context).width * 0.5, ),
padding: const EdgeInsets.all(8.0), ),
child: list.isEmpty scrollController: _scrollController,
? Center( communities: communities,
child: Text( itemBuilder: (context, index) {
'No results found', return CustomExpansionTileSpaceTree(
style: Theme.of(context).textTheme.bodySmall!.copyWith( title: communities[index].name,
color: ColorsManager.lightGrayColor, isSelected: state.selectedCommunities
fontWeight: FontWeight.w400, .contains(communities[index].uuid),
), isSoldCheck: state.selectedCommunities
), .contains(communities[index].uuid),
) onExpansionChanged: () =>
: Scrollbar( context.read<SpaceTreeBloc>().add(
scrollbarOrientation: ScrollbarOrientation.left, OnCommunityExpanded(
thumbVisibility: true, communities[index].uuid,
controller: _scrollController,
child: NotificationListener(
onNotification: (notification) {
if (notification is ScrollEndNotification &&
notification.metrics.extentAfter == 0) {
// If the user has reached the end of the list Load more data
context.read<SpaceTreeBloc>().add(PaginationEvent(
state.paginationModel, state.communityList));
}
return false;
},
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
controller: _scrollController,
itemBuilder: (context, index) {
return CustomExpansionTileSpaceTree(
title: list[index].name,
isSelected: state.selectedCommunities
.contains(list[index].uuid),
isSoldCheck: state.selectedCommunities
.contains(list[index].uuid),
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(
OnCommunityExpanded(list[index].uuid));
},
isExpanded: state.expandedCommunities
.contains(list[index].uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(list[index].uuid,
list[index].spaces));
widget.onSelect();
},
children: list[index].spaces.map((space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded: state.expandedSpaces
.contains(space.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
list[index],
space.uuid ?? '',
space.children));
widget.onSelect();
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(list[index].uuid,
space.uuid ?? ''));
},
isSelected: state.selectedSpaces
.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck:
state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces(
context, state, space, list[index]),
);
}).toList(),
);
}),
), ),
), ),
isExpanded: state.expandedCommunities.contains(
communities[index].uuid,
),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
communities[index].uuid,
communities[index].spaces,
),
);
widget.onSelect();
},
children: communities[index].spaces.map(
(space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded:
state.expandedSpaces.contains(space.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
communities[index],
space.uuid ?? '',
space.children,
),
);
widget.onSelect();
},
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
communities[index].uuid,
space.uuid ?? '',
),
),
isSelected: state.selectedSpaces
.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck:
state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces(
context,
state,
space,
communities[index],
), ),
), );
], },
).toList(),
);
},
), ),
), ),
if (state.paginationIsLoading) const CircularProgressIndicator(), if (state.paginationIsLoading) const CircularProgressIndicator(),
@ -198,22 +197,28 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
} }
List<Widget> _buildNestedSpaces( List<Widget> _buildNestedSpaces(
BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) { BuildContext context,
SpaceTreeState state,
SpaceModel space,
CommunityModel community,
) {
return space.children.map((child) { return space.children.map((child) {
return CustomExpansionTileSpaceTree( return CustomExpansionTileSpaceTree(
isSelected: isSelected: state.selectedSpaces.contains(child.uuid) ||
state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid), state.soldCheck.contains(child.uuid),
isSoldCheck: state.soldCheck.contains(child.uuid), isSoldCheck: state.soldCheck.contains(child.uuid),
title: child.name, title: child.name,
isExpanded: state.expandedSpaces.contains(child.uuid), isExpanded: state.expandedSpaces.contains(child.uuid),
onItemSelected: () { onItemSelected: () {
context context.read<SpaceTreeBloc>().add(
.read<SpaceTreeBloc>() OnSpaceSelected(community, child.uuid ?? '', child.children),
.add(OnSpaceSelected(community, child.uuid ?? '', child.children)); );
widget.onSelect(); widget.onSelect();
}, },
onExpansionChanged: () { onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(community.uuid, child.uuid ?? '')); context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(community.uuid, child.uuid ?? ''),
);
}, },
children: _buildNestedSpaces(context, state, child, community), children: _buildNestedSpaces(context, state, child, community),
); );

View File

@ -120,6 +120,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
visible: filteredCommunities.isNotEmpty, visible: filteredCommunities.isNotEmpty,
replacement: const EmptySearchResultWidget(), replacement: const EmptySearchResultWidget(),
child: SidebarCommunitiesList( child: SidebarCommunitiesList(
scrollController: _scrollController,
onScrollToEnd: () {},
communities: filteredCommunities, communities: filteredCommunities,
itemBuilder: (context, index) => _buildCommunityTile( itemBuilder: (context, index) => _buildCommunityTile(
context, context,