diff --git a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index 66b2d6da..5ef9c79b 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; @@ -107,6 +109,11 @@ class _LoadedSpaceViewState extends State { selectedSpaceUuid: widget.selectedSpace?.uuid ?? widget.selectedCommunity?.uuid ?? '', + onCreateCommunity: (name, description) { + context.read().add( + CreateCommunityEvent(name, description, context), + ); + }, ), CommunityStructureArea( selectedCommunity: widget.selectedCommunity, diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index b30a8c96..3eb1c001 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -16,9 +16,11 @@ import 'package:syncrow_web/utils/style.dart'; class SidebarWidget extends StatefulWidget { final List communities; final String? selectedSpaceUuid; + final void Function(String name, String description) onCreateCommunity; const SidebarWidget({ required this.communities, + required this.onCreateCommunity, this.selectedSpaceUuid, super.key, }); @@ -195,11 +197,7 @@ class _SidebarWidgetState extends State { builder: (context) => CreateCommunityDialog( isEditMode: false, existingCommunityNames: widget.communities.map((e) => e.name).toList(), - onCreateCommunity: (name, description) { - context.read().add( - CreateCommunityEvent(name, description, context), - ); - }, + onCreateCommunity: widget.onCreateCommunity, ), ); } diff --git a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart index 7a39891b..66acdf3d 100644 --- a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart @@ -1,12 +1,13 @@ 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_subspace_model/bloc/subspace_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_footer_buttons.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class CreateSubSpaceModelDialog extends StatelessWidget { final bool isEdit; @@ -14,211 +15,67 @@ class CreateSubSpaceModelDialog extends StatelessWidget { final List? existingSubSpaces; final void Function(List newSubspaces)? onUpdate; - const CreateSubSpaceModelDialog( - {Key? key, - required this.isEdit, - required this.dialogTitle, - this.existingSubSpaces, - this.onUpdate}) - : super(key: key); + const CreateSubSpaceModelDialog({ + required this.isEdit, + required this.dialogTitle, + this.existingSubSpaces, + this.onUpdate, + super.key, + }); @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - final textController = TextEditingController(); return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: BlocProvider( - create: (_) { + create: (context) { final bloc = SubSpaceModelBloc(); if (existingSubSpaces != null) { - for (var subSpace in existingSubSpaces!) { + for (final subSpace in existingSubSpaces ?? []) { bloc.add(AddSubSpaceModel(subSpace)); } } return bloc; }, child: BlocBuilder( - builder: (context, state) { - return Container( - color: ColorsManager.whiteColors, - child: SizedBox( - width: screenWidth * 0.3, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - dialogTitle, - style: Theme.of(context) - .textTheme - .headlineLarge - ?.copyWith(color: ColorsManager.blackColor), - ), - const SizedBox(height: 16), - Container( - width: screenWidth * 0.35, - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 16.0), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(10), - ), - child: Wrap( - spacing: 8.0, - runSpacing: 8.0, - children: [ - ...state.subSpaces.asMap().entries.map( - (entry) { - final index = entry.key; - final subSpace = entry.value; - - final lowerName = - subSpace.subspaceName.toLowerCase(); - - final duplicateIndices = state.subSpaces - .asMap() - .entries - .where((e) => - e.value.subspaceName.toLowerCase() == - lowerName) - .map((e) => e.key) - .toList(); - final isDuplicate = - duplicateIndices.length > 1 && - duplicateIndices.indexOf(index) != 0; - - return Chip( - label: Text(subSpace.subspaceName, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith( - color: ColorsManager.spaceColor, - )), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - side: BorderSide( - color: isDuplicate - ? ColorsManager.red - : ColorsManager.transparentColor, - width: 0, - ), - ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, - ), - ), - child: const Icon( - Icons.close, - size: 16, - color: ColorsManager.lightGrayColor, - ), - ), - onDeleted: () => context - .read() - .add(RemoveSubSpaceModel(subSpace)), - ); - }, - ), - SizedBox( - width: 200, - child: TextField( - controller: textController, - decoration: InputDecoration( - border: InputBorder.none, - hintText: state.subSpaces.isEmpty - ? 'Please enter the name' - : null, - hintStyle: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( - color: ColorsManager - .lightGrayColor)), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - context.read().add( - AddSubSpaceModel( - SubspaceTemplateModel( - subspaceName: value.trim(), - disabled: false))); - textController.clear(); - } - }, - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith( - color: ColorsManager.blackColor)), - ), - ], - ), - ), - if (state.errorMessage.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: Text(state.errorMessage, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith( - color: ColorsManager.red, - )), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - onPressed: (state.errorMessage.isNotEmpty) - ? null - : () async { - final subSpaces = context - .read() - .state - .subSpaces; - Navigator.of(context).pop(); - if (onUpdate != null) { - onUpdate!(subSpaces); - } - }, - backgroundColor: ColorsManager.secondaryColor, - borderRadius: 10, - foregroundColor: state.errorMessage.isNotEmpty - ? ColorsManager.whiteColorsWithOpacity - : ColorsManager.whiteColors, - child: const Text('OK'), - ), - ), - ], - ), - ], + builder: (context, state) => Container( + color: ColorsManager.whiteColors, + width: screenWidth * 0.3, + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + dialogTitle, + style: context.textTheme.headlineLarge?.copyWith( + color: ColorsManager.blackColor, + ), + ), + const SizedBox(height: 16), + CreateSubspaceModelChipsBox(subSpaces: state.subSpaces), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text( + state.errorMessage, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.red, + ), ), ), - )); - }, + const SizedBox(height: 16), + CreateSubspaceModelFooterButtons( + onUpdate: onUpdate, + errorMessage: state.errorMessage, + ), + ], + ), + ), ), ), ); diff --git a/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart b/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart new file mode 100644 index 00000000..a18fc8d8 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CreateSubspaceModelChipsBox extends StatelessWidget { + const CreateSubspaceModelChipsBox({ + required this.subSpaces, + super.key, + }); + + final List subSpaces; + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return Container( + width: screenWidth * 0.35, + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 16, + ), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(10), + ), + child: Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + ...subSpaces.asMap().entries.map( + (entry) { + final index = entry.key; + final subSpace = entry.value; + + final lowerName = subSpace.subspaceName.toLowerCase(); + + final duplicateIndices = subSpaces + .asMap() + .entries + .where((e) => e.value.subspaceName.toLowerCase() == lowerName) + .map((e) => e.key) + .toList(); + final isDuplicate = duplicateIndices.length > 1 && + duplicateIndices.indexOf(index) != 0; + + return SubspaceChip( + subSpace: subSpace, + isDuplicate: isDuplicate, + ); + }, + ), + SubspacesTextfield( + hintText: subSpaces.isEmpty ? 'Please enter the name' : null, + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_footer_buttons.dart b/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_footer_buttons.dart new file mode 100644 index 00000000..a8dcf89c --- /dev/null +++ b/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_footer_buttons.dart @@ -0,0 +1,53 @@ +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_subspace_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CreateSubspaceModelFooterButtons extends StatelessWidget { + const CreateSubspaceModelFooterButtons({ + required this.onUpdate, + required this.errorMessage, + super.key, + }); + + final void Function(List newSubspaces)? onUpdate; + final String errorMessage; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: errorMessage.isEmpty + ? () { + Navigator.of(context).pop(); + if (onUpdate != null) { + final subSpaces = + context.read().state.subSpaces; + onUpdate!(subSpaces); + } + } + : null, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: errorMessage.isNotEmpty + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ); + } +} diff --git a/lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart b/lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart new file mode 100644 index 00000000..b54e0712 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class SubspaceChip extends StatelessWidget { + const SubspaceChip({ + required this.subSpace, + required this.isDuplicate, + super.key, + }); + + final SubspaceTemplateModel subSpace; + final bool isDuplicate; + + @override + Widget build(BuildContext context) { + return Chip( + label: Text( + subSpace.subspaceName, + style: context.textTheme.bodySmall?.copyWith( + color: isDuplicate ? ColorsManager.red : ColorsManager.spaceColor, + ), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + color: isDuplicate ? ColorsManager.red : ColorsManager.transparentColor, + width: 0, + ), + ), + deleteIcon: Container( + padding: const EdgeInsetsDirectional.all(1), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.lightGrayColor, + width: 1.5, + ), + ), + child: const FittedBox( + fit: BoxFit.scaleDown, + child: Icon( + Icons.close, + color: ColorsManager.lightGrayColor, + ), + ), + ), + onDeleted: () => context.read().add( + RemoveSubSpaceModel(subSpace), + ), + ); + } +} diff --git a/lib/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart b/lib/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart new file mode 100644 index 00000000..d654b960 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class SubspacesTextfield extends StatefulWidget { + const SubspacesTextfield({ + required this.hintText, + super.key, + }); + + final String? hintText; + + @override + State createState() => _SubspacesTextfieldState(); +} + +class _SubspacesTextfieldState extends State { + late final TextEditingController _controller; + @override + void initState() { + _controller = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 100, + child: TextField( + controller: _controller, + decoration: InputDecoration( + border: InputBorder.none, + hintText: widget.hintText, + hintStyle: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.lightGrayColor, + ), + ), + onSubmitted: (value) { + final trimmedValue = value.trim(); + if (trimmedValue.isNotEmpty) { + context.read().add( + AddSubSpaceModel( + SubspaceTemplateModel( + subspaceName: trimmedValue, + disabled: false, + ), + ), + ); + _controller.clear(); + } + }, + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.blackColor, + ), + ), + ); + } +}