implemented space management side bar.

This commit is contained in:
Faris Armoush
2025-06-22 11:04:39 +03:00
parent 20d044f2e5
commit 2f233db332
16 changed files with 808 additions and 21 deletions

View File

@ -1,5 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_management_body.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/services/communities_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
@ -8,20 +16,67 @@ class SpaceManagementPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WebScaffold( return MultiBlocProvider(
appBarTitle: Text( providers: [
'Space Management', BlocProvider(
style: ResponsiveTextTheme.of(context).deviceManagementTitle, create: (context) => CommunitiesBloc(
communitiesService: _FakeCommunitiesService(),
)..add(const LoadCommunities(LoadCommunitiesParam())),
),
BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()),
],
child: WebScaffold(
appBarTitle: Text(
'Space Management',
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
enableMenuSidebar: false,
centerBody: Text(
'Community Structure',
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontWeight: FontWeight.bold,
),
),
rightBody: const NavigateHomeGridView(),
scaffoldBody: const SpaceManagementBody(),
), ),
enableMenuSidebar: false, );
centerBody: Text( }
'Community Structure', }
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontWeight: FontWeight.bold, class _FakeCommunitiesService extends CommunitiesService {
), @override
), Future<List<CommunityModel>> getCommunity(LoadCommunitiesParam param) async {
rightBody: const NavigateHomeGridView(), return Future.delayed(
scaffoldBody: const Center(child: Text('Space Management')), const Duration(seconds: 1),
() => [
const CommunityModel(
uuid: '1',
name: 'Community 1',
spaces: [
SpaceModel(
uuid: '3',
spaceName: 'Space 1',
icon: 'assets/icons/space.png',
children: [
SpaceModel(
uuid: '4',
spaceName: 'Space 2',
icon: 'assets/icons/space.png',
children: [],
status: SpaceStatus.active,
),
],
status: SpaceStatus.active,
),
],
),
const CommunityModel(
uuid: '2',
name: 'Community 1',
spaces: [],
),
],
); );
} }
} }

View File

@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree.dart';
class SpaceManagementBody extends StatelessWidget {
const SpaceManagementBody({super.key});
@override
Widget build(BuildContext context) {
return const Row(
children: [
SpaceManagementCommunitiesTree(),
],
);
}
}

View File

@ -1,9 +1,11 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/services/communities_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/services/communities_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
class RemoteCommunitiesService implements CommunitiesService { class RemoteCommunitiesService implements CommunitiesService {
const RemoteCommunitiesService(this._httpService); const RemoteCommunitiesService(this._httpService);
@ -14,13 +16,27 @@ class RemoteCommunitiesService implements CommunitiesService {
@override @override
Future<List<CommunityModel>> getCommunity(LoadCommunitiesParam param) async { Future<List<CommunityModel>> getCommunity(LoadCommunitiesParam param) async {
final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) throw APIException('Project UUID is not set');
try { try {
return _httpService.get( final allCommunities = <CommunityModel>[];
path: '/api/communities/', await _httpService.get(
expectedResponseModel: (json) => (json as List<dynamic>) path: await _makeUrl(),
.map((e) => CommunityModel.fromJson(e as Map<String, dynamic>)) expectedResponseModel: (json) {
.toList(), final response = json as Map<String, dynamic>;
final jsonData = response['data'] as List<dynamic>? ?? [];
return jsonData
.map(
(jsonItem) => CommunityModel.fromJson(
jsonItem as Map<String, dynamic>,
),
)
.toList();
},
); );
return allCommunities;
} on DioException catch (e) { } on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
@ -31,4 +47,10 @@ class RemoteCommunitiesService implements CommunitiesService {
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} }
} }
Future<String> _makeUrl() async {
final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) throw APIException('Project UUID is required');
return ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectUuid);
}
} }

View File

@ -1,16 +1,31 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
enum SpaceStatus {
active,
deleted,
parentDeleted;
static SpaceStatus getValueFromString(String value) => switch (value) {
'active' => active,
'deleted' => deleted,
'parentDeleted' => parentDeleted,
_ => active,
};
}
class SpaceModel extends Equatable { class SpaceModel extends Equatable {
final String uuid; final String uuid;
final String spaceName; final String spaceName;
final String icon; final String icon;
final List<SpaceModel> children; final List<SpaceModel> children;
final SpaceStatus status;
const SpaceModel({ const SpaceModel({
required this.uuid, required this.uuid,
required this.spaceName, required this.spaceName,
required this.icon, required this.icon,
required this.children, required this.children,
required this.status,
}); });
factory SpaceModel.fromJson(Map<String, dynamic> json) { factory SpaceModel.fromJson(Map<String, dynamic> json) {
@ -22,6 +37,7 @@ class SpaceModel extends Equatable {
?.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>)) ?.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
.toList() ?? .toList() ??
[], [],
status: SpaceStatus.getValueFromString(json['status'] as String),
); );
} }

View File

@ -0,0 +1,47 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
part 'communities_tree_selection_event.dart';
part 'communities_tree_selection_state.dart';
class CommunitiesTreeSelectionBloc
extends Bloc<CommunitiesTreeSelectionEvent, CommunitiesTreeSelectionState> {
CommunitiesTreeSelectionBloc() : super(const CommunitiesTreeSelectionState()) {
on<SelectCommunityEvent>(_onSelectCommunity);
on<SelectSpaceEvent>(_onSelectSpace);
on<ClearCommunitiesTreeSelectionEvent>(_onClearSelection);
}
void _onSelectCommunity(
SelectCommunityEvent event,
Emitter<CommunitiesTreeSelectionState> emit,
) {
emit(
CommunitiesTreeSelectionState(
selectedCommunity: event.community,
selectedSpace: null,
),
);
}
void _onSelectSpace(
SelectSpaceEvent event,
Emitter<CommunitiesTreeSelectionState> emit,
) {
emit(
CommunitiesTreeSelectionState(
selectedCommunity: null,
selectedSpace: event.space,
),
);
}
void _onClearSelection(
ClearCommunitiesTreeSelectionEvent event,
Emitter<CommunitiesTreeSelectionState> emit,
) {
emit(const CommunitiesTreeSelectionState());
}
}

View File

@ -0,0 +1,30 @@
part of 'communities_tree_selection_bloc.dart';
sealed class CommunitiesTreeSelectionEvent extends Equatable {
const CommunitiesTreeSelectionEvent();
@override
List<Object?> get props => [];
}
final class SelectCommunityEvent extends CommunitiesTreeSelectionEvent {
final CommunityModel? community;
const SelectCommunityEvent({required this.community});
@override
List<Object?> get props => [community];
}
final class SelectSpaceEvent extends CommunitiesTreeSelectionEvent {
final SpaceModel? space;
const SelectSpaceEvent({required this.space});
@override
List<Object?> get props => [space];
}
final class ClearCommunitiesTreeSelectionEvent
extends CommunitiesTreeSelectionEvent {
const ClearCommunitiesTreeSelectionEvent();
}

View File

@ -0,0 +1,29 @@
part of 'communities_tree_selection_bloc.dart';
final class CommunitiesTreeSelectionState extends Equatable {
const CommunitiesTreeSelectionState({
this.selectedCommunity,
this.selectedSpace,
});
final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace;
CommunitiesTreeSelectionState copyWith({
CommunityModel? selectedCommunity,
SpaceModel? selectedSpace,
List<CommunityModel>? expandedCommunities,
List<SpaceModel>? expandedSpaces,
}) {
return CommunitiesTreeSelectionState(
selectedCommunity: selectedCommunity ?? this.selectedCommunity,
selectedSpace: selectedSpace ?? this.selectedSpace,
);
}
@override
List<Object?> get props => [
selectedCommunity,
selectedSpace,
];
}

View File

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart';
class CommunityTile extends StatelessWidget {
final String title;
final List<Widget>? children;
final bool isExpanded;
final bool isSelected;
final void Function(String, bool isExpanded) onExpansionChanged;
final void Function() onItemSelected;
const CommunityTile({
super.key,
required this.title,
required this.isExpanded,
required this.onExpansionChanged,
required this.onItemSelected,
required this.isSelected,
this.children,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: CustomExpansionTile(
title: title,
initiallyExpanded: isExpanded,
isSelected: isSelected,
onExpansionChanged: (bool expanded) {
onExpansionChanged(title, expanded);
},
onItemSelected: onItemSelected,
children: children ?? [],
));
}
}

View File

@ -0,0 +1,181 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/create_community/bloc/community_dialog_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_community/bloc/community_dialog_event.dart';
import 'package:syncrow_web/pages/spaces_management/create_community/bloc/community_dialog_state.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateCommunityDialog extends StatefulWidget {
final void Function(String name) onCreateCommunity;
final String? initialName;
final Widget title;
const CreateCommunityDialog({
super.key,
required this.onCreateCommunity,
required this.title,
this.initialName,
});
@override
State<CreateCommunityDialog> createState() => _CreateCommunityDialogState();
}
class _CreateCommunityDialogState extends State<CreateCommunityDialog> {
late final TextEditingController _nameController;
@override
void initState() {
_nameController = TextEditingController(text: widget.initialName ?? '');
super.initState();
}
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CommunityDialogBloc([]),
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
backgroundColor: ColorsManager.transparentColor,
child: Stack(
children: [
Container(
width: MediaQuery.of(context).size.width * 0.3,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.25),
blurRadius: 20,
spreadRadius: 5,
offset: const Offset(0, 5),
),
],
),
child: SingleChildScrollView(
child: BlocBuilder<CommunityDialogBloc, CommunityDialogState>(
builder: (context, state) {
var isNameValid = true;
var isNameEmpty = false;
if (state is CommunityNameValidationState) {
isNameValid = state.isNameValid;
isNameEmpty = state.isNameEmpty;
}
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DefaultTextStyle(
style: Theme.of(context).textTheme.headlineMedium!,
child: widget.title,
),
const SizedBox(height: 18),
TextField(
controller: _nameController,
onChanged: (value) {
context
.read<CommunityDialogBloc>()
.add(ValidateCommunityNameEvent(value));
},
style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration(
hintText: 'Please enter the community name',
filled: true,
fillColor: ColorsManager.boxColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: isNameValid && !isNameEmpty
? ColorsManager.boxColor
: ColorsManager.red,
width: 1,
),
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(
color: ColorsManager.boxColor,
width: 1.5,
),
),
),
),
if (!isNameValid)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Name already exists.',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
if (isNameEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Name should not be empty.',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () => Navigator.of(context).pop(),
),
),
const SizedBox(width: 16),
Expanded(
child: DefaultButton(
onPressed: () {
if (isNameValid && !isNameEmpty) {
widget.onCreateCommunity(
_nameController.text.trim(),
);
Navigator.of(context).pop();
}
},
backgroundColor: isNameValid && !isNameEmpty
? ColorsManager.secondaryColor
: ColorsManager.lightGrayColor,
borderRadius: 10,
foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'),
),
),
],
),
],
);
},
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,160 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/community_tile.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/create_community_dialog.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_communities_list.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_header.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_tile.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceManagementCommunitiesTree extends StatelessWidget {
const SpaceManagementCommunitiesTree({super.key});
bool _isSpaceOrChildSelected(BuildContext context, SpaceModel space) {
final selectedSpace =
context.read<CommunitiesTreeSelectionBloc>().state.selectedSpace;
final isSpaceSelected = selectedSpace?.uuid == space.uuid;
final anySubSpaceIsSelected = space.children.any(
(child) => _isSpaceOrChildSelected(context, child),
);
return isSpaceSelected || anySubSpaceIsSelected;
}
static const _width = 300.0;
@override
Widget build(BuildContext context) {
return BlocBuilder<CommunitiesBloc, CommunitiesState>(
builder: (context, state) {
return Container(
width: _width,
decoration: subSectionContainerDecoration,
child: Column(
children: [
SpaceManagementSidebarHeader(
onAddCommunity: () => _onAddCommunity(context),
),
CustomSearchBar(onSearchChanged: (value) {}),
const SizedBox(height: 16),
switch (state.status) {
CommunitiesStatus.initial =>
const Center(child: CircularProgressIndicator()),
CommunitiesStatus.loading =>
const Center(child: CircularProgressIndicator()),
CommunitiesStatus.success =>
_buildCommunitiesTree(context, state.communities),
CommunitiesStatus.failure => Center(
child: Text(state.errorMessage ?? 'Something went wrong'),
),
},
],
),
);
},
);
}
Widget _buildCommunitiesTree(
BuildContext context,
List<CommunityModel> communities,
) {
return Expanded(
child: SpaceManagementSidebarCommunitiesList(
communities: communities,
itemBuilder: (context, index) {
return _buildCommunityTile(context, communities[index]);
},
),
);
}
Widget _buildCommunityTile(BuildContext context, CommunityModel community) {
final spaces = community.spaces
.where((space) => space.status == SpaceStatus.active)
.map((space) => _buildSpaceTile(
space: space,
community: community,
context: context,
))
.toList();
return CommunityTile(
title: community.name,
key: ValueKey(community.uuid),
isSelected: context
.watch<CommunitiesTreeSelectionBloc>()
.state
.selectedCommunity
?.uuid ==
community.uuid,
isExpanded: false,
onItemSelected: () {
context.read<CommunitiesTreeSelectionBloc>().add(
SelectCommunityEvent(community: community),
);
},
onExpansionChanged: (title, expanded) {},
children: spaces,
);
}
Widget _buildSpaceTile({
required SpaceModel space,
required CommunityModel community,
required BuildContext context,
}) {
final spaceIsExpanded = _isSpaceOrChildSelected(context, space);
final isSelected =
context.watch<CommunitiesTreeSelectionBloc>().state.selectedSpace?.uuid ==
space.uuid;
return Padding(
padding: const EdgeInsetsDirectional.only(start: 16.0),
child: SpaceTile(
title: space.spaceName,
key: ValueKey(space.uuid),
isSelected: isSelected,
initiallyExpanded: spaceIsExpanded,
onExpansionChanged: (expanded) {},
onItemSelected: () => context.read<CommunitiesTreeSelectionBloc>().add(
SelectSpaceEvent(space: space),
),
children: space.children
.map(
(childSpace) => _buildSpaceTile(
space: childSpace,
community: community,
context: context,
),
)
.toList(),
),
);
}
void _onAddCommunity(BuildContext context) => context
.read<CommunitiesTreeSelectionBloc>()
.state
.selectedCommunity
?.uuid
.isNotEmpty ??
true
? _clearSelection(context)
: _showCreateCommunityDialog(context);
void _clearSelection(BuildContext context) =>
context.read<CommunitiesTreeSelectionBloc>().add(
const ClearCommunitiesTreeSelectionEvent(),
);
void _showCreateCommunityDialog(BuildContext context) => showDialog<void>(
context: context,
builder: (context) => CreateCommunityDialog(
title: const Text('Community Name'),
onCreateCommunity: (name) {},
),
);
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class SpaceManagementSidebarAddCommunityButton extends StatelessWidget {
const SpaceManagementSidebarAddCommunityButton({
required this.onTap,
super.key,
});
final void Function() onTap;
@override
Widget build(BuildContext context) {
return SizedBox.square(
dimension: 30,
child: IconButton(
style: IconButton.styleFrom(
iconSize: 20,
backgroundColor: ColorsManager.circleImageBackground,
shape: const CircleBorder(
side: BorderSide(
color: ColorsManager.lightGrayBorderColor,
width: 3,
),
),
),
onPressed: onTap,
icon: SvgPicture.asset(Assets.addIcon),
),
);
}
}

View File

@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SpaceManagementSidebarCommunitiesList extends StatefulWidget {
const SpaceManagementSidebarCommunitiesList({
required this.communities,
required this.itemBuilder,
super.key,
});
final List<CommunityModel> communities;
final Widget Function(BuildContext context, int index) itemBuilder;
@override
State<SpaceManagementSidebarCommunitiesList> createState() =>
_SpaceManagementSidebarCommunitiesListState();
}
class _SpaceManagementSidebarCommunitiesListState
extends State<SpaceManagementSidebarCommunitiesList> {
late final ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController();
}
bool _onNotification(ScrollEndNotification notification) {
final hasReachedEnd = notification.metrics.extentAfter == 0;
if (hasReachedEnd) {
// Call data from API.
return true;
}
return false;
}
@override
void dispose() {
_scrollController
..removeListener(() {})
..dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: context.screenWidth * 0.5,
child: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: NotificationListener<ScrollEndNotification>(
onNotification: _onNotification,
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsetsDirectional.only(start: 16),
itemCount: widget.communities.length,
controller: _scrollController,
itemBuilder: widget.itemBuilder,
),
),
),
),
);
}
}

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_add_community_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceManagementSidebarHeader extends StatelessWidget {
const SpaceManagementSidebarHeader({
required this.onAddCommunity,
super.key,
});
final void Function() onAddCommunity;
@override
Widget build(BuildContext context) {
return Container(
decoration: subSectionContainerDecoration,
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Communities',
style: context.textTheme.titleMedium?.copyWith(
color: ColorsManager.blackColor,
),
),
SpaceManagementSidebarAddCommunityButton(
onTap: onAddCommunity,
),
],
),
);
}
}

View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart';
class SpaceTile extends StatefulWidget {
final String title;
final bool isSelected;
final bool initiallyExpanded;
final ValueChanged<bool> onExpansionChanged;
final List<Widget>? children;
final void Function() onItemSelected;
const SpaceTile({
super.key,
required this.title,
required this.initiallyExpanded,
required this.onExpansionChanged,
required this.onItemSelected,
required this.isSelected,
this.children,
});
@override
State<SpaceTile> createState() => _SpaceTileState();
}
class _SpaceTileState extends State<SpaceTile> {
late bool _isExpanded;
@override
void initState() {
super.initState();
_isExpanded = widget.initiallyExpanded;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0),
child: CustomExpansionTile(
isSelected: widget.isSelected,
title: widget.title,
initiallyExpanded: _isExpanded,
onItemSelected: widget.onItemSelected,
onExpansionChanged: (bool expanded) {
setState(() {
_isExpanded = expanded;
});
widget.onExpansionChanged(expanded);
},
children: widget.children ?? [],
),
);
}
}

View File

@ -4,11 +4,10 @@ import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart';
class SpaceTile extends StatefulWidget { class SpaceTile extends StatefulWidget {
final String title; final String title;
final bool isSelected; final bool isSelected;
final bool initiallyExpanded; final bool initiallyExpanded;
final ValueChanged<bool> onExpansionChanged; final ValueChanged<bool> onExpansionChanged;
final List<Widget>? children; final List<Widget>? children;
final Function() onItemSelected; final void Function() onItemSelected;
const SpaceTile({ const SpaceTile({
super.key, super.key,

View File

@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/auth/view/login_page.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart';
import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_page.dart'; import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_page.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/view/spaces_management_page.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/views/space_management_page.dart';
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart';