Compare commits

..

23 Commits

Author SHA1 Message Date
ae3eb6fca8 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1722-FE-Implement-Duplicate-Space-Feature 2025-07-24 09:52:06 +03:00
f341dcd482 Remove unnecessary event dispatch in CommunityStructureHeaderActionButtonsComposer to streamline community update logic. This change enhances code clarity by eliminating the selection event for the community, focusing solely on the update action. 2025-07-24 09:47:39 +03:00
e98b091253 Refactor SpaceManagementBody to use a Stack layout for improved UI structure, allowing better positioning of the SpaceManagementCommunitiesTree and the main content. Enhance SpaceManagementCommunitiesTree with a shadow effect for better visual separation. This change promotes a more organized and visually appealing interface. 2025-07-24 09:39:38 +03:00
77d6d822cb Refactor SpaceSubSpacesDialog and SubSpacesInput to integrate a shared TextEditingController for improved state management of subspace names. This change enhances the input handling and ensures proper disposal of the controller, promoting better resource management. 2025-07-24 09:39:33 +03:00
f1aab13263 refactor booking page to use PageController (#366)
<!--
  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:
-->

## Description

<!--- Describe your changes in detail -->
refactor booking page to use PageController 

## Type of Change

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

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ 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
2025-07-23 14:37:43 +03:00
97530dd351 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1722-FE-Implement-Duplicate-Space-Feature 2025-07-23 14:19:55 +03:00
a57b6e0853 Enhance CommunityStructureCanvas by adding a _centerOnTree method to improve the centering logic of the community structure. This method calculates the optimal view based on the positions of spaces and adjusts the transformation controller accordingly, ensuring a smoother user experience during updates and animations. 2025-07-23 14:19:25 +03:00
845397e819 Update CommunityStructureCanvas to improve widget update logic by animating to the selected space based on community UUID changes. This enhances the responsiveness of the UI when the community context changes. 2025-07-23 13:21:49 +03:00
2077ef053f Refactor DuplicateSpaceService to return a list of SpaceModel objects instead of a single instance. Update related components including DuplicateSpaceSuccess state and DuplicateSpaceDialog to handle multiple spaces. Enhance CommunityStructureHeaderActionButtonsComposer to reflect these changes in the success callback. 2025-07-23 12:42:23 +03:00
d21850edc8 Enhance DuplicateSpaceDialog to use Bloc for state management and streamline the duplication process with success and error handling. Update CommunityStructureHeaderActionButtonsComposer to integrate the new dialog for duplicating spaces. 2025-07-23 12:37:27 +03:00
85f53ed1f2 Remove DuplicateSpaceBloc and its associated service from SpaceManagementPage to streamline dependencies and improve code clarity. 2025-07-23 12:37:17 +03:00
5fde74fc7d Add DuplicateSpaceFailureDialog widget to display error messages when duplicating spaces fails. 2025-07-23 12:37:07 +03:00
994efc302b Add AppSnackBarsBuildContextExtension for displaying success and failure snack bars in the app. 2025-07-23 12:36:45 +03:00
04b7a506be Remove newSpaceIcon parameter from DuplicateSpaceParam class since it isnt needed. 2025-07-23 10:52:46 +03:00
19ddf443a9 Refactor RemoteDuplicateSpaceService to improve code readability by aligning method chaining for URL replacements. 2025-07-23 10:52:29 +03:00
3779176978 Add DuplicateSpaceDialog widget for user interaction in duplicate space management. 2025-07-23 10:52:18 +03:00
7c5bca35fc Add DuplicateSpaceTextField widget for user input in duplicate space management. 2025-07-23 10:52:07 +03:00
aed3004a31 Added DuplicateSpaceBloc to SpaceManagementPage for managing duplicate space functionality. 2025-07-23 10:51:51 +03:00
4241d11cb6 Implemented duplicate_space data layer. 2025-07-23 09:59:13 +03:00
ef8c9efff0 Added duplicate space endpoint to ApiEndpoints. 2025-07-23 09:58:46 +03:00
c59d2b7fd6 Implemented toJson method in DuplicateSpaceParam. 2025-07-23 09:58:15 +03:00
71f0da9299 Created duplicate_space presentation layer. 2025-07-23 09:51:09 +03:00
e6d9000ee2 Implemented duplicate space domain layer. 2025-07-23 09:48:23 +03:00
18 changed files with 572 additions and 22 deletions

View File

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart';
@ -51,13 +53,23 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
duration: const Duration(milliseconds: 150),
);
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_centerOnTree();
}
});
}
@override
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedSpace == null) return;
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
if (oldWidget.community.uuid != widget.community.uuid) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_centerOnTree(animate: true);
}
});
} else if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_animateToSpace(widget.selectedSpace);
@ -151,6 +163,60 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
_runAnimation(matrix);
}
void _centerOnTree({bool animate = false}) {
if (_positions.isEmpty) {
if (animate) {
_runAnimation(Matrix4.identity());
} else {
_transformationController.value = Matrix4.identity();
}
return;
}
var minX = double.infinity;
var maxX = double.negativeInfinity;
var minY = double.infinity;
var maxY = double.negativeInfinity;
_positions.forEach((uuid, offset) {
final cardWidth = _cardWidths[uuid] ?? _minCardWidth;
minX = min(minX, offset.dx);
maxX = max(maxX, offset.dx + cardWidth);
minY = min(minY, offset.dy);
maxY = max(maxY, offset.dy + _cardHeight);
});
if (!minX.isFinite || !maxX.isFinite || !minY.isFinite || !maxY.isFinite) {
return;
}
final treeWidth = maxX - minX;
final treeHeight = maxY - minY;
final viewSize = context.size;
if (viewSize == null) return;
final scaleX = viewSize.width / treeWidth;
final scaleY = viewSize.height / treeHeight;
final scale = min(scaleX, scaleY).clamp(0.5, 1.0) * 0.9;
final treeCenterX = minX + treeWidth / 2;
final treeCenterY = minY + treeHeight / 2;
final x = -treeCenterX * scale + viewSize.width / 2;
final y = -treeCenterY * scale + viewSize.height / 2;
final matrix = Matrix4.identity()
..translate(x, y)
..scale(scale);
if (animate) {
_runAnimation(matrix);
} else {
_transformationController.value = matrix;
}
}
void _onReorder(SpaceReorderDataModel data, int newIndex) {
final newCommunity = widget.community.copyWith();
final children = data.parent?.children ?? newCommunity.spaces;

View File

@ -7,6 +7,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/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
@ -44,7 +45,22 @@ class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
},
),
),
onDuplicate: (space) {},
onDuplicate: (space) => showDialog<void>(
context: context,
builder: (_) => DuplicateSpaceDialog(
initialName: space.spaceName,
selectedSpaceUuid: space.uuid,
selectedCommunityUuid: selectedCommunity.uuid,
onSuccess: (spaces) {
final updatedCommunity = selectedCommunity.copyWith(
spaces: spaces,
);
context.read<CommunitiesBloc>().add(
CommunitiesUpdateCommunity(updatedCommunity),
);
},
),
),
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
context,
spaceModel: selectedSpace!,

View File

@ -10,21 +10,26 @@ class SpaceManagementBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
return Stack(
children: [
const SpaceManagementCommunitiesTree(),
Expanded(
child: BlocBuilder<CommunitiesTreeSelectionBloc,
CommunitiesTreeSelectionState>(
buildWhen: (previous, current) =>
previous.selectedCommunity != current.selectedCommunity,
builder: (context, state) => Visibility(
visible: state.selectedCommunity == null,
replacement: const SpaceManagementCommunityStructure(),
child: const SpaceManagementTemplatesView(),
Row(
children: [
const SizedBox(width: 320),
Expanded(
child: BlocBuilder<CommunitiesTreeSelectionBloc,
CommunitiesTreeSelectionState>(
buildWhen: (previous, current) =>
previous.selectedCommunity != current.selectedCommunity,
builder: (context, state) => Visibility(
visible: state.selectedCommunity == null,
replacement: const SpaceManagementCommunityStructure(),
child: const SpaceManagementTemplatesView(),
),
),
),
),
],
),
const SpaceManagementCommunitiesTree(),
],
);
}

View File

@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree_community_tile.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/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceManagementCommunitiesTree extends StatefulWidget {
@ -44,7 +45,15 @@ class _SpaceManagementCommunitiesTreeState
return BlocBuilder<CommunitiesBloc, CommunitiesState>(
builder: (context, state) => Container(
width: 320,
decoration: subSectionContainerDecoration,
decoration: subSectionContainerDecoration.copyWith(
boxShadow: [
BoxShadow(
color: ColorsManager.shadowBlackColor.withValues(alpha: 0.1),
blurRadius: 10,
offset: const Offset(10, 0),
),
],
),
child: Column(
children: [
const SpaceManagementSidebarHeader(),

View File

@ -0,0 +1,60 @@
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/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
final class RemoteDuplicateSpaceService implements DuplicateSpaceService {
RemoteDuplicateSpaceService(this._httpService);
final HTTPService _httpService;
@override
Future<List<SpaceModel>> duplicateSpace(DuplicateSpaceParam param) async {
try {
final response = await _httpService.post(
path: await _makeUrl(param),
body: param.toJson(),
expectedResponseModel: (json) {
final response = json as Map<String, dynamic>;
final data = response['data'] as List<dynamic>;
return data
.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
.toList();
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
throw APIException(errorMessage);
} catch (e) {
throw APIException(e.toString());
}
}
Future<String> _makeUrl(DuplicateSpaceParam param) async {
final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) {
throw APIException('Project UUID is not set');
}
if (param.communityUuid.isEmpty) {
throw APIException('Community UUID is not set');
}
if (param.spaceUuid.isEmpty) {
throw APIException('Space UUID is not set');
}
return ApiEndpoints.duplicateSpace
.replaceAll('{projectUuid}', projectUuid)
.replaceAll('{communityUuid}', param.communityUuid)
.replaceAll('{spaceUuid}', param.spaceUuid);
}
}

View File

@ -0,0 +1,15 @@
class DuplicateSpaceParam {
final String communityUuid;
final String spaceUuid;
final String newSpaceName;
DuplicateSpaceParam({
required this.communityUuid,
required this.spaceUuid,
required this.newSpaceName,
});
Map<String, dynamic> toJson() => {
'spaceName': newSpaceName,
};
}

View File

@ -0,0 +1,6 @@
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
abstract interface class DuplicateSpaceService {
Future<List<SpaceModel>> duplicateSpace(DuplicateSpaceParam param);
}

View File

@ -0,0 +1,36 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.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/duplicate_space/domain/params/duplicate_space_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'duplicate_space_event.dart';
part 'duplicate_space_state.dart';
class DuplicateSpaceBloc extends Bloc<DuplicateSpaceEvent, DuplicateSpaceState> {
DuplicateSpaceBloc(
this._duplicateSpaceService,
) : super(const DuplicateSpaceInitial()) {
on<DuplicateSpaceEvent>(_onDuplicateSpaceEvent);
}
final DuplicateSpaceService _duplicateSpaceService;
Future<void> _onDuplicateSpaceEvent(
DuplicateSpaceEvent event,
Emitter<DuplicateSpaceState> emit,
) async {
try {
emit(const DuplicateSpaceLoading());
final result = await _duplicateSpaceService.duplicateSpace(event.param);
emit(DuplicateSpaceSuccess(result));
} on APIException catch (e) {
emit(DuplicateSpaceFailure(e.message));
} catch (e) {
emit(DuplicateSpaceFailure(e.toString()));
} finally {
emit(const DuplicateSpaceInitial());
}
}
}

View File

@ -0,0 +1,10 @@
part of 'duplicate_space_bloc.dart';
final class DuplicateSpaceEvent extends Equatable {
const DuplicateSpaceEvent({required this.param});
final DuplicateSpaceParam param;
@override
List<Object> get props => [param];
}

View File

@ -0,0 +1,34 @@
part of 'duplicate_space_bloc.dart';
sealed class DuplicateSpaceState extends Equatable {
const DuplicateSpaceState();
@override
List<Object> get props => [];
}
final class DuplicateSpaceInitial extends DuplicateSpaceState {
const DuplicateSpaceInitial();
}
final class DuplicateSpaceLoading extends DuplicateSpaceState {
const DuplicateSpaceLoading();
}
final class DuplicateSpaceSuccess extends DuplicateSpaceState {
const DuplicateSpaceSuccess(this.spaces);
final List<SpaceModel> spaces;
@override
List<Object> get props => [spaces];
}
final class DuplicateSpaceFailure extends DuplicateSpaceState {
const DuplicateSpaceFailure(this.errorMessage);
final String errorMessage;
@override
List<Object> get props => [errorMessage];
}

View File

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/widgets/app_loading_indicator.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/duplicate_space/data/services/remote_duplicate_space_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/extension/app_snack_bar.dart';
class DuplicateSpaceDialog extends StatelessWidget {
const DuplicateSpaceDialog({
required this.initialName,
required this.onSuccess,
required this.selectedSpaceUuid,
required this.selectedCommunityUuid,
super.key,
});
final String initialName;
final void Function(List<SpaceModel> spaces) onSuccess;
final String selectedSpaceUuid;
final String selectedCommunityUuid;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DuplicateSpaceBloc(
RemoteDuplicateSpaceService(HTTPService()),
),
child: BlocListener<DuplicateSpaceBloc, DuplicateSpaceState>(
listener: _listener,
child: DuplicateSpaceDialogForm(
initialName: initialName,
selectedSpaceUuid: selectedSpaceUuid,
selectedCommunityUuid: selectedCommunityUuid,
),
),
);
}
void _listener(BuildContext context, DuplicateSpaceState state) {
switch (state) {
case DuplicateSpaceLoading():
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (context) => const AppLoadingIndicator(),
);
break;
case DuplicateSpaceFailure(:final errorMessage):
Navigator.pop(context);
Navigator.pop(context);
context.showFailureSnackbar(errorMessage);
break;
case DuplicateSpaceSuccess(:final spaces):
onSuccess.call(spaces);
Navigator.of(context).pop();
Navigator.of(context).pop();
context.showSuccessSnackbar('Space duplicated successfully');
break;
default:
break;
}
}
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart';
class DuplicateSpaceDialogForm extends StatefulWidget {
const DuplicateSpaceDialogForm({
required this.initialName,
required this.selectedSpaceUuid,
required this.selectedCommunityUuid,
super.key,
});
final String initialName;
final String selectedSpaceUuid;
final String selectedCommunityUuid;
@override
State<DuplicateSpaceDialogForm> createState() => _DuplicateSpaceDialogFormState();
}
class _DuplicateSpaceDialogFormState extends State<DuplicateSpaceDialogForm> {
late final TextEditingController _nameController;
bool _isNameValid = true;
@override
void initState() {
super.initState();
_nameController = TextEditingController(text: '${widget.initialName}(1)');
_nameController.addListener(_validateName);
}
void _validateName() => setState(
() => _isNameValid = _nameController.text.trim() != widget.initialName,
);
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const SelectableText('Duplicate Space'),
content: Column(
mainAxisSize: MainAxisSize.min,
spacing: 16,
children: [
const SelectableText('Enter a new name for the duplicated space:'),
DuplicateSpaceTextField(
nameController: _nameController,
isNameValid: _isNameValid,
initialName: widget.initialName,
),
],
),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: const Text('Cancel'),
),
TextButton(
onPressed: _isNameValid ? () => _submit(context) : null,
child: const Text('Duplicate'),
),
],
);
}
void _submit(BuildContext context) {
context.read<DuplicateSpaceBloc>().add(
DuplicateSpaceEvent(
param: DuplicateSpaceParam(
newSpaceName: _nameController.text,
spaceUuid: widget.selectedSpaceUuid,
communityUuid: widget.selectedCommunityUuid,
),
),
);
}
}

View File

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class DuplicateSpaceFailureDialog extends StatelessWidget {
const DuplicateSpaceFailureDialog(this.errorMessage, {super.key});
final String errorMessage;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Failed to duplicate space'),
content: Text(errorMessage),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: const Text('Close'),
),
],
);
}
}

View File

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class DuplicateSpaceTextField extends StatelessWidget {
const DuplicateSpaceTextField({
required this.nameController,
required this.isNameValid,
required this.initialName,
super.key,
});
final TextEditingController nameController;
final bool isNameValid;
final String initialName;
String get _errorText => 'Name must be different from "$initialName"';
@override
Widget build(BuildContext context) {
return TextField(
controller: nameController,
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.blackColor,
),
decoration: InputDecoration(
label: const Text('Space Name'),
border: _border(),
enabledBorder: _border(),
focusedBorder: _border(ColorsManager.primaryColor),
errorBorder: _border(context.theme.colorScheme.error),
focusedErrorBorder: _border(context.theme.colorScheme.error),
errorStyle: context.textTheme.bodyMedium!.copyWith(
color: context.theme.colorScheme.error,
fontSize: 8,
),
errorText: isNameValid ? null : _errorText,
),
);
}
OutlineInputBorder _border([Color? color]) {
return OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
color: color ?? ColorsManager.blackColor,
width: 0.5,
),
);
}
}

View File

@ -19,6 +19,7 @@ class SpaceSubSpacesDialog extends StatefulWidget {
}
class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
late final TextEditingController _subspaceNameController;
late List<Subspace> _subspaces;
bool get _hasDuplicateNames =>
@ -29,6 +30,13 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
void initState() {
super.initState();
_subspaces = List.from(widget.subspaces);
_subspaceNameController = TextEditingController();
}
@override
void dispose() {
_subspaceNameController.dispose();
super.dispose();
}
void _handleSubspaceAdded(String name) {
@ -49,6 +57,10 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
);
void _handleSave() {
final name = _subspaceNameController.text.trim();
if (name.isNotEmpty) {
_handleSubspaceAdded(name);
}
widget.onSave(_subspaces);
Navigator.of(context).pop();
}
@ -65,6 +77,7 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
subSpaces: _subspaces,
onSubspaceAdded: _handleSubspaceAdded,
onSubspaceDeleted: _handleSubspaceDeleted,
controller: _subspaceNameController,
),
AnimatedSwitcher(
duration: const Duration(milliseconds: 100),

View File

@ -10,29 +10,28 @@ class SubSpacesInput extends StatefulWidget {
required this.subSpaces,
required this.onSubspaceAdded,
required this.onSubspaceDeleted,
required this.controller,
});
final List<Subspace> subSpaces;
final void Function(String name) onSubspaceAdded;
final void Function(String uuid) onSubspaceDeleted;
final TextEditingController controller;
@override
State<SubSpacesInput> createState() => _SubSpacesInputState();
}
class _SubSpacesInputState extends State<SubSpacesInput> {
late final TextEditingController _subspaceNameController;
late final FocusNode _focusNode;
@override
void initState() {
super.initState();
_subspaceNameController = TextEditingController();
_focusNode = FocusNode();
}
@override
void dispose() {
_subspaceNameController.dispose();
_focusNode.dispose();
super.dispose();
}
@ -81,7 +80,7 @@ class _SubSpacesInputState extends State<SubSpacesInput> {
width: 200,
child: TextField(
focusNode: _focusNode,
controller: _subspaceNameController,
controller: widget.controller,
decoration: InputDecoration(
border: InputBorder.none,
hintText: widget.subSpaces.isEmpty ? 'Please enter the name' : null,
@ -93,7 +92,7 @@ class _SubSpacesInputState extends State<SubSpacesInput> {
final trimmedValue = value.trim();
if (trimmedValue.isNotEmpty) {
widget.onSubspaceAdded(trimmedValue);
_subspaceNameController.clear();
widget.controller.clear();
_focusNode.requestFocus();
}
},

View File

@ -141,7 +141,7 @@ abstract class ApiEndpoints {
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
static const String saveSchedule = '/schedule/{deviceUuid}';
static const String duplicateSpace = '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/duplicate';
////booking System
static const String bookableSpaces = '/bookable-spaces';

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
extension AppSnackBarsBuildContextExtension on BuildContext {
void showSuccessSnackbar(String message) {
ScaffoldMessenger.of(this).showSnackBar(
_makeSnackbar(
message: message,
icon: Icons.check_circle,
backgroundColor: Colors.green,
),
);
}
void showFailureSnackbar(String message) {
ScaffoldMessenger.of(this).showSnackBar(
_makeSnackbar(
message: message,
icon: Icons.error,
backgroundColor: Colors.red,
),
);
}
SnackBar _makeSnackbar({
required String message,
required Color backgroundColor,
required IconData icon,
}) {
return SnackBar(
content: Row(
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [
Icon(icon, color: Colors.white),
Text(
message,
style: textTheme.bodyMedium?.copyWith(
color: ColorsManager.whiteColors,
),
),
],
),
backgroundColor: backgroundColor,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
margin: const EdgeInsetsDirectional.symmetric(
horizontal: 92,
vertical: 32,
),
);
}
}