Sp 1716 fe implement edit community service and b lo c and ensure data consistency (#340)

<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1716](https://syncrow.atlassian.net/browse/SP-1716)

## Description

Implemented Edit Community Feature, and ensured data integrity by
reflecting changes anywhere needed immediately when changing a community
name.
Made subspaces unique and removed duplication when calling data from
API.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1716]:
https://syncrow.atlassian.net/browse/SP-1716?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
This commit is contained in:
Faris Armoush
2025-07-07 10:20:45 +03:00
committed by GitHub
20 changed files with 294 additions and 144 deletions

View File

@ -1,24 +1,39 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/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/create_community/presentation/create_community_dialog.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart';
abstract final class SpaceManagementCommunityDialogHelper { abstract final class SpaceManagementCommunityDialogHelper {
static void showCreateDialog(BuildContext context) { static void showCreateDialog(BuildContext context) => showDialog<void>(
context: context,
builder: (_) => const CreateCommunityDialog(),
);
static void showEditDialog(
BuildContext context,
CommunityModel community,
) {
showDialog<void>( showDialog<void>(
context: context, context: context,
builder: (_) => CreateCommunityDialog( builder: (_) => EditCommunityDialog(
title: const SelectableText('Community Name'), community: community,
onCreateCommunity: (community) { parentContext: context,
context.read<CommunitiesBloc>().add(
InsertCommunity(community),
);
context.read<CommunitiesTreeSelectionBloc>().add(
SelectCommunityEvent(community: community),
);
},
), ),
); );
} }
static void showLoadingDialog(BuildContext context) => showDialog<void>(
context: context,
barrierDismissible: false,
builder: (context) => const Center(
child: CircularProgressIndicator(),
),
);
static void showSuccessSnackBar(BuildContext context, String message) =>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
),
);
} }

View File

@ -1,28 +1,29 @@
import 'package:flutter/material.dart'; 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/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/param/create_community_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/bloc/create_community_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_name_text_field.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_name_text_field.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class CreateCommunityDialogWidget extends StatefulWidget { class CommunityDialog extends StatefulWidget {
final String? initialName; final String? initialName;
final Widget title; final Widget title;
final void Function(String name) onSubmit;
final String? errorMessage;
const CreateCommunityDialogWidget({ const CommunityDialog({
super.key,
required this.title, required this.title,
required this.onSubmit,
this.initialName, this.initialName,
this.errorMessage,
super.key,
}); });
@override @override
State<CreateCommunityDialogWidget> createState() => State<CommunityDialog> createState() => _CommunityDialogState();
_CreateCommunityDialogWidgetState();
} }
class _CreateCommunityDialogWidgetState extends State<CreateCommunityDialogWidget> { class _CommunityDialogState extends State<CommunityDialog> {
late final TextEditingController _nameController; late final TextEditingController _nameController;
@override @override
@ -63,9 +64,7 @@ class _CreateCommunityDialogWidgetState extends State<CreateCommunityDialogWidge
child: Form( child: Form(
key: _formKey, key: _formKey,
child: SingleChildScrollView( child: SingleChildScrollView(
child: BlocBuilder<CreateCommunityBloc, CreateCommunityState>( child: Column(
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -74,24 +73,11 @@ class _CreateCommunityDialogWidgetState extends State<CreateCommunityDialogWidge
child: widget.title, child: widget.title,
), ),
const SizedBox(height: 18), const SizedBox(height: 18),
CreateCommunityNameTextField( CreateCommunityNameTextField(nameController: _nameController),
nameController: _nameController, _buildErrorMessage(),
),
if (state case CreateCommunityFailure(:final message))
Padding(
padding: const EdgeInsets.only(top: 18),
child: SelectableText(
'* $message',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
),
const SizedBox(height: 24), const SizedBox(height: 24),
_buildActionButtons(context), _buildActionButtons(context),
], ],
);
},
), ),
), ),
), ),
@ -132,13 +118,22 @@ class _CreateCommunityDialogWidgetState extends State<CreateCommunityDialogWidge
void _onSubmit(BuildContext context) { void _onSubmit(BuildContext context) {
if (_formKey.currentState?.validate() ?? false) { if (_formKey.currentState?.validate() ?? false) {
context.read<CreateCommunityBloc>().add( widget.onSubmit.call(_nameController.text.trim());
CreateCommunity( }
CreateCommunityParam( }
name: _nameController.text.trim(),
Widget _buildErrorMessage() {
return Visibility(
visible: widget.errorMessage != null,
child: Padding(
padding: const EdgeInsetsDirectional.symmetric(vertical: 18),
child: SelectableText(
'* ${widget.errorMessage}',
style: context.textTheme.bodyMedium?.copyWith(
color: context.theme.colorScheme.error,
),
), ),
), ),
); );
} }
}
} }

View File

@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
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/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/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
@ -30,9 +31,11 @@ class SpaceManagementPage extends StatelessWidget {
BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()), BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()),
BlocProvider( BlocProvider(
create: (context) => SpaceDetailsBloc( create: (context) => SpaceDetailsBloc(
UniqueSubspacesDecorator(
RemoteSpaceDetailsService(httpService: HTTPService()), RemoteSpaceDetailsService(httpService: HTTPService()),
), ),
), ),
),
], ],
child: WebScaffold( child: WebScaffold(
appBarTitle: Text( appBarTitle: Text(

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; 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/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.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/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@ -44,18 +44,6 @@ class CommunityStructureHeader extends StatelessWidget {
); );
} }
void _showCreateCommunityDialog(BuildContext context) {
showDialog<void>(
context: context,
builder: (context) => CreateCommunityDialog(
title: const Text('Edit Community'),
onCreateCommunity: (community) {
// TODO(FarisArmoush): Implement
},
),
);
}
Widget _buildCommunityInfo( Widget _buildCommunityInfo(
BuildContext context, ThemeData theme, double screenWidth) { BuildContext context, ThemeData theme, double screenWidth) {
final selectedCommunity = final selectedCommunity =
@ -86,7 +74,12 @@ class CommunityStructureHeader extends StatelessWidget {
), ),
const SizedBox(width: 2), const SizedBox(width: 2),
GestureDetector( GestureDetector(
onTap: () => _showCreateCommunityDialog(context), onTap: () {
SpaceManagementCommunityDialogHelper.showEditDialog(
context,
selectedCommunity,
);
},
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.iconEdit, Assets.iconEdit,
width: 16, width: 16,

View File

@ -39,6 +39,26 @@ class CommunityModel extends Equatable {
.toList(); .toList();
} }
CommunityModel copyWith({
String? uuid,
String? name,
DateTime? createdAt,
DateTime? updatedAt,
String? description,
String? externalId,
List<SpaceModel>? spaces,
}) {
return CommunityModel(
uuid: uuid ?? this.uuid,
name: name ?? this.name,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
description: description ?? this.description,
externalId: externalId ?? this.externalId,
spaces: spaces ?? this.spaces,
);
}
@override @override
List<Object?> get props => [uuid, name, spaces]; List<Object?> get props => [uuid, name, spaces];
} }

View File

@ -16,6 +16,7 @@ class CommunitiesBloc extends Bloc<CommunitiesEvent, CommunitiesState> {
on<LoadCommunities>(_onLoadCommunities); on<LoadCommunities>(_onLoadCommunities);
on<LoadMoreCommunities>(_onLoadMoreCommunities); on<LoadMoreCommunities>(_onLoadMoreCommunities);
on<InsertCommunity>(_onInsertCommunity); on<InsertCommunity>(_onInsertCommunity);
on<CommunitiesUpdateCommunity>(_onCommunitiesUpdateCommunity);
} }
final CommunitiesService _communitiesService; final CommunitiesService _communitiesService;
@ -114,4 +115,18 @@ class CommunitiesBloc extends Bloc<CommunitiesEvent, CommunitiesState> {
) { ) {
emit(state.copyWith(communities: [event.community, ...state.communities])); emit(state.copyWith(communities: [event.community, ...state.communities]));
} }
void _onCommunitiesUpdateCommunity(
CommunitiesUpdateCommunity event,
Emitter<CommunitiesState> emit,
) {
final updatedCommunities = state.communities
.map((e) => e.uuid == event.community.uuid ? event.community : e)
.toList();
emit(
state.copyWith(
communities: updatedCommunities,
),
);
}
} }

View File

@ -31,3 +31,12 @@ final class InsertCommunity extends CommunitiesEvent {
@override @override
List<Object?> get props => [community]; List<Object?> get props => [community];
} }
final class CommunitiesUpdateCommunity extends CommunitiesEvent {
const CommunitiesUpdateCommunity(this.community);
final CommunityModel community;
@override
List<Object?> get props => [community];
}

View File

@ -1,57 +1,58 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/widgets/community_dialog.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/create_community/data/services/remote_create_community_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/create_community/data/services/remote_create_community_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/param/create_community_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/bloc/create_community_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/bloc/create_community_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog_widget.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
class CreateCommunityDialog extends StatelessWidget { class CreateCommunityDialog extends StatelessWidget {
final void Function(CommunityModel community) onCreateCommunity; const CreateCommunityDialog({super.key});
final String? initialName;
final Widget title;
const CreateCommunityDialog({
super.key,
required this.onCreateCommunity,
required this.title,
this.initialName,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => CreateCommunityBloc(RemoteCreateCommunityService(HTTPService())), create: (_) => CreateCommunityBloc(
child: BlocListener<CreateCommunityBloc, CreateCommunityState>( RemoteCreateCommunityService(HTTPService()),
),
child: BlocConsumer<CreateCommunityBloc, CreateCommunityState>(
listener: (context, state) { listener: (context, state) {
switch (state) { switch (state) {
case CreateCommunityLoading(): case CreateCommunityLoading() || CreateCommunityInitial():
showDialog<void>( SpaceManagementCommunityDialogHelper.showLoadingDialog(context);
context: context,
builder: (context) => const Center(
child: CircularProgressIndicator(),
),
);
break; break;
case CreateCommunitySuccess(:final community): case CreateCommunitySuccess(:final community):
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pop(); Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar( SpaceManagementCommunityDialogHelper.showSuccessSnackBar(
const SnackBar(content: Text('Community created successfully')), context,
'${community.name} community created successfully',
);
context.read<CommunitiesBloc>().add(
InsertCommunity(community),
);
context.read<CommunitiesTreeSelectionBloc>().add(
SelectCommunityEvent(community: community),
); );
onCreateCommunity.call(community);
break; break;
case CreateCommunityFailure(): case CreateCommunityFailure():
Navigator.of(context).pop(); Navigator.of(context).pop();
break; break;
default:
break;
} }
}, },
child: CreateCommunityDialogWidget( builder: (BuildContext context, CreateCommunityState state) {
title: title, return CommunityDialog(
initialName: initialName, title: const Text('Create Community'),
initialName: null,
onSubmit: (name) => context.read<CreateCommunityBloc>().add(
CreateCommunity(CreateCommunityParam(name: name)),
), ),
errorMessage: state is CreateCommunityFailure ? state.message : null,
);
},
), ),
); );
} }

View File

@ -0,0 +1,27 @@
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
class UniqueSubspacesDecorator implements SpaceDetailsService {
final SpaceDetailsService _decoratee;
const UniqueSubspacesDecorator(this._decoratee);
@override
Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param) async {
final response = await _decoratee.getSpaceDetails(param);
final uniqueSubspaces = <String, Subspace>{};
for (final subspace in response.subspaces) {
final normalizedName = subspace.name.trim().toLowerCase();
if (!uniqueSubspaces.containsKey(normalizedName)) {
uniqueSubspaces[normalizedName] = subspace;
}
}
return response.copyWith(
subspaces: uniqueSubspaces.values.toList(),
);
}
}

View File

@ -16,7 +16,7 @@ abstract final class SpaceDetailsDialogHelper {
), ),
child: SpaceDetailsDialog( child: SpaceDetailsDialog(
context: context, context: context,
title: const Text('Create Space'), title: const SelectableText('Create Space'),
spaceModel: SpaceModel.empty(), spaceModel: SpaceModel.empty(),
onSave: print, onSave: print,
), ),
@ -36,7 +36,7 @@ abstract final class SpaceDetailsDialogHelper {
), ),
child: SpaceDetailsDialog( child: SpaceDetailsDialog(
context: context, context: context,
title: const Text('Edit Space'), title: const SelectableText('Edit Space'),
spaceModel: spaceModel, spaceModel: spaceModel,
onSave: (space) {}, onSave: (space) {},
), ),

View File

@ -78,7 +78,11 @@ class _SpaceDetailsDialogState extends State<SpaceDetailsDialog> {
return AlertDialog( return AlertDialog(
title: widget.title, title: widget.title,
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
content: const Center(child: CircularProgressIndicator()), content: SizedBox(
height: context.screenHeight * 0.3,
width: context.screenWidth * 0.5,
child: const Center(child: CircularProgressIndicator()),
),
); );
} }

View File

@ -28,7 +28,7 @@ class SpaceDetailsForm extends StatelessWidget {
create: (context) => SpaceDetailsModelBloc(initialState: space), create: (context) => SpaceDetailsModelBloc(initialState: space),
child: BlocBuilder<SpaceDetailsModelBloc, SpaceDetailsModel>( child: BlocBuilder<SpaceDetailsModelBloc, SpaceDetailsModel>(
buildWhen: (previous, current) => previous != current, buildWhen: (previous, current) => previous != current,
builder: (context, state) { builder: (context, space) {
return AlertDialog( return AlertDialog(
title: title, title: title,
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
@ -39,7 +39,7 @@ class SpaceDetailsForm extends StatelessWidget {
spacing: 20, spacing: 20,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded(child: SpaceIconPicker(iconPath: state.icon)), Expanded(child: SpaceIconPicker(iconPath: space.icon)),
Expanded( Expanded(
flex: 2, flex: 2,
child: Column( child: Column(
@ -47,17 +47,17 @@ class SpaceDetailsForm extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SpaceNameTextField( SpaceNameTextField(
initialValue: state.spaceName, initialValue: space.spaceName,
isNameFieldExist: (value) => state.subspaces.any( isNameFieldExist: (value) => space.subspaces.any(
(subspace) => subspace.name == value, (subspace) => subspace.name == value,
), ),
), ),
const Spacer(), const Spacer(),
SpaceSubSpacesBox( SpaceSubSpacesBox(
subspaces: state.subspaces, subspaces: space.subspaces,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
SpaceDetailsDevicesBox(space: state), SpaceDetailsDevicesBox(space: space),
], ],
), ),
), ),
@ -66,7 +66,7 @@ class SpaceDetailsForm extends StatelessWidget {
), ),
actions: [ actions: [
SpaceDetailsActionButtons( SpaceDetailsActionButtons(
onSave: () => onSave(state), onSave: () => onSave(space),
onCancel: Navigator.of(context).pop, onCancel: Navigator.of(context).pop,
), ),
], ],

View File

@ -56,8 +56,9 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('Create Sub Spaces'), title: const SelectableText('Create Sub Spaces'),
content: Column( content: Column(
spacing: 12,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
SubSpacesInput( SubSpacesInput(
@ -70,7 +71,7 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
child: Visibility( child: Visibility(
key: ValueKey(_hasDuplicateNames), key: ValueKey(_hasDuplicateNames),
visible: _hasDuplicateNames, visible: _hasDuplicateNames,
child: const Text( child: const SelectableText(
'Error: Duplicate subspace names are not allowed.', 'Error: Duplicate subspace names are not allowed.',
style: TextStyle(color: Colors.red), style: TextStyle(color: Colors.red),
), ),

View File

@ -1,6 +1,6 @@
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/update_community/domain/params/update_community_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/update_community/domain/services/update_community_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_community/domain/services/update_community_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';
@ -13,15 +13,15 @@ class RemoteUpdateCommunityService implements UpdateCommunityService {
static const _defaultErrorMessage = 'Failed to update community'; static const _defaultErrorMessage = 'Failed to update community';
@override @override
Future<CommunityModel> updateCommunity(UpdateCommunityParam param) async { Future<CommunityModel> updateCommunity(CommunityModel param) async {
final endpoint = await _makeUrl(param.uuid);
try { try {
final response = await _httpService.put( await _httpService.put(
path: 'endpoint', path: endpoint,
expectedResponseModel: (data) => CommunityModel.fromJson( body: {'name': param.name},
data as Map<String, dynamic>, expectedResponseModel: (data) => null,
),
); );
return response; return param;
} 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>?;
@ -36,4 +36,12 @@ class RemoteUpdateCommunityService implements UpdateCommunityService {
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} }
} }
Future<String> _makeUrl(String communityUuid) async {
final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) {
throw APIException('Project UUID is not set');
}
return '/projects/$projectUuid/communities/$communityUuid';
}
} }

View File

@ -1,10 +0,0 @@
import 'package:equatable/equatable.dart';
class UpdateCommunityParam extends Equatable {
const UpdateCommunityParam({required this.name});
final String name;
@override
List<Object> get props => [name];
}

View File

@ -1,6 +1,5 @@
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/update_community/domain/params/update_community_param.dart';
abstract class UpdateCommunityService { abstract class UpdateCommunityService {
Future<CommunityModel> updateCommunity(UpdateCommunityParam param); Future<CommunityModel> updateCommunity(CommunityModel community);
} }

View File

@ -1,7 +1,6 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.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/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/update_community/domain/params/update_community_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/update_community/domain/services/update_community_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_community/domain/services/update_community_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/api_exception.dart';
@ -24,7 +23,7 @@ class UpdateCommunityBloc extends Bloc<UpdateCommunityEvent, UpdateCommunityStat
emit(UpdateCommunityLoading()); emit(UpdateCommunityLoading());
try { try {
final updatedCommunity = await _updateCommunityService.updateCommunity( final updatedCommunity = await _updateCommunityService.updateCommunity(
event.param, event.communityModel,
); );
emit(UpdateCommunitySuccess(updatedCommunity)); emit(UpdateCommunitySuccess(updatedCommunity));
} on APIException catch (e) { } on APIException catch (e) {

View File

@ -8,10 +8,10 @@ sealed class UpdateCommunityEvent extends Equatable {
} }
final class UpdateCommunity extends UpdateCommunityEvent { final class UpdateCommunity extends UpdateCommunityEvent {
const UpdateCommunity(this.param); const UpdateCommunity(this.communityModel);
final UpdateCommunityParam param; final CommunityModel communityModel ;
@override @override
List<Object> get props => [param]; List<Object> get props => [communityModel];
} }

View File

@ -21,10 +21,10 @@ final class UpdateCommunitySuccess extends UpdateCommunityState {
} }
final class UpdateCommunityFailure extends UpdateCommunityState { final class UpdateCommunityFailure extends UpdateCommunityState {
final String message; final String errorMessage;
const UpdateCommunityFailure(this.message); const UpdateCommunityFailure(this.errorMessage);
@override @override
List<Object> get props => [message]; List<Object> get props => [errorMessage];
} }

View File

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/widgets/community_dialog.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/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/update_community/data/services/remote_update_community_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_bloc.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class EditCommunityDialog extends StatelessWidget {
const EditCommunityDialog({
required this.community,
required this.parentContext,
super.key,
});
final CommunityModel community;
final BuildContext parentContext;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => UpdateCommunityBloc(
RemoteUpdateCommunityService(HTTPService()),
),
child: BlocConsumer<UpdateCommunityBloc, UpdateCommunityState>(
listener: (context, state) {
switch (state) {
case UpdateCommunityInitial() || UpdateCommunityLoading():
SpaceManagementCommunityDialogHelper.showLoadingDialog(context);
break;
case UpdateCommunitySuccess(:final community):
_onUpdateCommunitySuccess(context, community);
break;
case UpdateCommunityFailure():
Navigator.of(context).pop();
break;
}
},
builder: (context, state) => CommunityDialog(
title: const Text('Edit Community'),
initialName: community.name,
errorMessage: state is UpdateCommunityFailure ? state.errorMessage : null,
onSubmit: (name) => context.read<UpdateCommunityBloc>().add(
UpdateCommunity(community.copyWith(name: name)),
),
),
),
);
}
void _onUpdateCommunitySuccess(
BuildContext context,
CommunityModel community,
) {
Navigator.of(context).pop();
Navigator.of(context).pop();
SpaceManagementCommunityDialogHelper.showSuccessSnackBar(
context,
'${community.name} community updated successfully',
);
parentContext.read<CommunitiesBloc>().add(
CommunitiesUpdateCommunity(community),
);
parentContext.read<CommunitiesTreeSelectionBloc>().add(
SelectCommunityEvent(community: community),
);
}
}