mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 07:07:19 +00:00
implemented space management side bar.
This commit is contained in:
@ -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: [],
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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,
|
||||||
|
];
|
||||||
|
}
|
@ -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 ?? [],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -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'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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) {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
@ -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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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 ?? [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user