diff --git a/assets/icons/delete_space_link_icon.svg b/assets/icons/delete_space_link_icon.svg new file mode 100644 index 00000000..a55d2e04 --- /dev/null +++ b/assets/icons/delete_space_link_icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/space_link_icon.svg b/assets/icons/space_link_icon.svg new file mode 100644 index 00000000..f10c57ad --- /dev/null +++ b/assets/icons/space_link_icon.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/success_icon.svg b/assets/icons/success_icon.svg new file mode 100644 index 00000000..6f5dbf9e --- /dev/null +++ b/assets/icons/success_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart index 2863a862..cbe15ecd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart @@ -20,7 +20,6 @@ class TreeView extends StatelessWidget { @override Widget build(BuildContext context) { final _blocRole = BlocProvider.of(context); - debugPrint('TreeView constructed with userId = $userId'); return BlocProvider( create: (_) => UsersBloc(), // ..add(const LoadCommunityAndSpacesEvent()), diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 5d52a4d3..9e5f9725 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; @@ -8,11 +9,14 @@ import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SpaceTreeView extends StatefulWidget { + final bool? isSide; final Function onSelect; - const SpaceTreeView({required this.onSelect, super.key}); + const SpaceTreeView({required this.onSelect, this.isSide, super.key}); @override State createState() => _SpaceTreeViewState(); @@ -29,21 +33,77 @@ class _SpaceTreeViewState extends State { @override Widget build(BuildContext context) { - return BlocBuilder(builder: (context, state) { - List list = state.isSearching ? state.filteredCommunity : state.communityList; + return BlocBuilder( + builder: (context, state) { + List list = + state.isSearching ? state.filteredCommunity : state.communityList; return Container( height: MediaQuery.sizeOf(context).height, - decoration: subSectionContainerDecoration, + decoration: + widget.isSide == true ? subSectionContainerDecoration : null, child: state is SpaceTreeLoadingState ? const Center(child: CircularProgressIndicator()) : Column( children: [ - CustomSearchBar( - searchQuery: state.searchQuery, - onSearchChanged: (query) { - context.read().add(SearchQueryEvent(query)); - }, - ), + widget.isSide == true + ? Container( + decoration: const BoxDecoration( + color: ColorsManager.circleRolesBackground, + borderRadius: BorderRadius.only( + topRight: Radius.circular(20), + topLeft: Radius.circular(20)), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(20)), + border: Border.all( + color: ColorsManager.grayBorder)), + child: TextFormField( + style: + const TextStyle(color: Colors.black), + onChanged: (value) { + context + .read() + .add(SearchQueryEvent(value)); + }, + decoration: textBoxDecoration(radios: 20)! + .copyWith( + fillColor: Colors.white, + suffixIcon: Padding( + padding: + const EdgeInsets.only(right: 16), + child: SvgPicture.asset( + Assets.textFieldSearch, + width: 24, + height: 24, + ), + ), + hintStyle: context.textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + ), + ), + ), + ], + ), + ), + ) + : CustomSearchBar( + onSearchChanged: (query) { + context + .read() + .add(SearchQueryEvent(query)); + }, + ), const SizedBox(height: 16), Expanded( child: ListView( @@ -57,14 +117,18 @@ class _SpaceTreeViewState extends State { ? Center( child: Text( 'No results found', - style: Theme.of(context).textTheme.bodySmall!.copyWith( + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( color: ColorsManager.lightGrayColor, fontWeight: FontWeight.w400, ), ), ) : Scrollbar( - scrollbarOrientation: ScrollbarOrientation.left, + scrollbarOrientation: + ScrollbarOrientation.left, thumbVisibility: true, controller: _scrollController, child: Padding( @@ -74,30 +138,39 @@ class _SpaceTreeViewState extends State { shrinkWrap: true, children: list .map( - (community) => CustomExpansionTileSpaceTree( + (community) => + CustomExpansionTileSpaceTree( title: community.name, - isSelected: state.selectedCommunities + isSelected: state + .selectedCommunities .contains(community.uuid), - isSoldCheck: state.selectedCommunities + isSoldCheck: state + .selectedCommunities .contains(community.uuid), onExpansionChanged: () { context .read() - .add(OnCommunityExpanded(community.uuid)); + .add(OnCommunityExpanded( + community.uuid)); }, - isExpanded: state.expandedCommunities + isExpanded: state + .expandedCommunities .contains(community.uuid), onItemSelected: () { - context.read().add( - OnCommunitySelected( - community.uuid, community.spaces)); + context + .read() + .add(OnCommunitySelected( + community.uuid, + community.spaces)); widget.onSelect(); }, - children: community.spaces.map((space) { + children: + community.spaces.map((space) { return CustomExpansionTileSpaceTree( title: space.name, - isExpanded: - state.expandedSpaces.contains(space.uuid), + isExpanded: state + .expandedSpaces + .contains(space.uuid), onItemSelected: () { context.read().add( OnSpaceSelected(community, space.uuid ?? '', @@ -105,14 +178,20 @@ class _SpaceTreeViewState extends State { widget.onSelect(); }, onExpansionChanged: () { - context.read().add( - OnSpaceExpanded( - community.uuid, space.uuid ?? '')); + context + .read() + .add(OnSpaceExpanded( + community.uuid, + space.uuid ?? '')); }, - isSelected: - state.selectedSpaces.contains(space.uuid) || - state.soldCheck.contains(space.uuid), - isSoldCheck: state.soldCheck.contains(space.uuid), + isSelected: state + .selectedSpaces + .contains( + space.uuid) || + state.soldCheck + .contains(space.uuid), + isSoldCheck: state.soldCheck + .contains(space.uuid), children: _buildNestedSpaces( context, state, space, community), ); @@ -200,8 +279,8 @@ class _SpaceTreeViewState extends State { BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) { return space.children.map((child) { return CustomExpansionTileSpaceTree( - isSelected: - state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid), + isSelected: state.selectedSpaces.contains(child.uuid) || + state.soldCheck.contains(child.uuid), isSoldCheck: state.soldCheck.contains(child.uuid), title: child.name, isExpanded: state.expandedSpaces.contains(child.uuid), diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart deleted file mode 100644 index aa9a446d..00000000 --- a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart'; - - -class SpaceModelBloc extends Bloc { - SpaceModelBloc() : super(SpaceModelInitial()) { - on((event, emit) { - emit(SpaceModelSelectedState(event.selectedIndex)); - }); - } -} \ No newline at end of file diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart deleted file mode 100644 index 8bff0202..00000000 --- a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart +++ /dev/null @@ -1,7 +0,0 @@ -abstract class SpaceModelEvent {} - -class SpaceModelSelectedEvent extends SpaceModelEvent { - final int selectedIndex; - - SpaceModelSelectedEvent(this.selectedIndex); -} diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart deleted file mode 100644 index cc745e4d..00000000 --- a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart +++ /dev/null @@ -1,9 +0,0 @@ -abstract class SpaceModelState {} - -class SpaceModelInitial extends SpaceModelState {} - -class SpaceModelSelectedState extends SpaceModelState { - final int selectedIndex; - - SpaceModelSelectedState(this.selectedIndex); -} diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart new file mode 100644 index 00000000..c789c2a9 --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart @@ -0,0 +1,104 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; +import 'package:syncrow_web/utils/navigation_service.dart'; + +class LinkSpaceToModelBloc + extends Bloc { + LinkSpaceToModelBloc() : super(SpaceModelInitial()) { + on(_getSpaceIds); + on(_handleLinkSpaceModel); + on(_validateLinkSpaceModel); + on((event, emit) { + emit(SpaceModelSelectedState(event.selectedIndex)); + }); + } + + List spacesListIds = []; + bool hasSelectedSpaces = false; + String validate = ''; + + Future _getSpaceIds(LinkSpaceModelSelectedIdsEvent event, + Emitter emit) async { + try { + BuildContext context = NavigationService.navigatorKey.currentContext!; + var spaceBloc = context.read(); + for (var communityId in spaceBloc.state.selectedCommunities) { + List spacesList = + spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + spacesListIds = spacesList; + } + hasSelectedSpaces = + spaceBloc.state.selectedCommunities.any((communityId) { + List spacesList = + spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + return spacesList.isNotEmpty; + }); + if (hasSelectedSpaces) { + debugPrint("At least one space is selected."); + } else { + debugPrint("No spaces selected."); + } + } catch (e) { + debugPrint("Error in _getSpaceIds: $e"); + } + } + + Future _handleLinkSpaceModel( + LinkSpaceModelEvent event, + Emitter emit, + ) async { + emit(SpaceModelLoading()); + try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + await SpaceModelManagementApi().linkSpaceModel( + spaceModelUuid: event.selectedSpaceMode!, + projectId: projectUuid, + spaceUuids: spacesListIds, + isOverWrite: event.isOverWrite); + emit(SpaceModelLinkSuccess()); + } on DioException catch (e) { + final errorMessage = _parseDioError(e); + emit(SpaceModelOperationFailure(errorMessage)); + } catch (e) { + emit(SpaceModelOperationFailure('Unexpected error: $e')); + } + } + + Future _validateLinkSpaceModel( + ValidateSpaceModelEvent event, + Emitter emit, + ) async { + emit(SpaceModelLoading()); + try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + await SpaceModelManagementApi().validateSpaceModel( + projectUuid, + spacesListIds, + ); + emit(SpaceValidationSuccess()); + } on DioException catch (e) { + final errorMessage = _parseDioError(e); + if (errorMessage == + 'Selected spaces already have linked space model / sub-spaces and devices') { + emit(const AlreadyHaveLinkedState()); + } else { + emit(SpaceModelOperationFailure(errorMessage)); + } + } catch (e) { + emit(SpaceModelOperationFailure('Unexpected error: $e')); + } + } + + String _parseDioError(DioException e) { + if (e.response?.data is Map) { + return e.response!.data['error']['message'] ?? 'Unknown error occurred'; + } + return e.message ?? 'Network request failed'; + } +} diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart new file mode 100644 index 00000000..694358db --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +abstract class LinkSpaceToModelEvent {} + +class LinkSpaceModelSelectedEvent extends LinkSpaceToModelEvent { + final int selectedIndex; + + LinkSpaceModelSelectedEvent(this.selectedIndex); +} + +class LinkSpaceModelSelectedIdsEvent extends LinkSpaceToModelEvent {} + +class LinkSpaceModelEvent extends LinkSpaceToModelEvent { + final String? selectedSpaceMode; + final bool isOverWrite; + LinkSpaceModelEvent({this.selectedSpaceMode, this.isOverWrite = false}); +} + +class ValidateSpaceModelEvent extends LinkSpaceToModelEvent { + BuildContext? context; + ValidateSpaceModelEvent({this.context}); +} diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart new file mode 100644 index 00000000..047567a9 --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart @@ -0,0 +1,35 @@ +abstract class LinkSpaceToModelState { + const LinkSpaceToModelState(); +} + +class SpaceModelInitial extends LinkSpaceToModelState {} + +class SpaceModelLoading extends LinkSpaceToModelState {} + +class SpaceModelSelectedState extends LinkSpaceToModelState { + final int selectedIndex; + const SpaceModelSelectedState(this.selectedIndex); +} + +class SpaceModelSelectionUpdated extends LinkSpaceToModelState { + final bool hasSelectedSpaces; + const SpaceModelSelectionUpdated(this.hasSelectedSpaces); +} + +class SpaceValidationSuccess extends LinkSpaceToModelState {} + +class SpaceModelLinkSuccess extends LinkSpaceToModelState {} + +class ValidationError extends LinkSpaceToModelState { + final String message; + const ValidationError(this.message); +} + +class SpaceModelOperationFailure extends LinkSpaceToModelState { + final String message; + const SpaceModelOperationFailure(this.message); +} + +class AlreadyHaveLinkedState extends LinkSpaceToModelState { + const AlreadyHaveLinkedState(); +} diff --git a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart index 69023857..bbd0de36 100644 --- a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart +++ b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart @@ -2,9 +2,9 @@ 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/link_space_model/bloc/link_space_model_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -24,13 +24,13 @@ class LinkSpaceModelDialog extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => SpaceModelBloc() + create: (context) => LinkSpaceToModelBloc() ..add( - SpaceModelSelectedEvent(initialSelectedIndex ?? -1), + LinkSpaceModelSelectedEvent(initialSelectedIndex ?? -1), ), child: Builder( builder: (context) { - final bloc = context.read(); + final bloc = context.read(); return AlertDialog( backgroundColor: ColorsManager.whiteColors, title: const Text('Link a space model'), @@ -39,7 +39,7 @@ class LinkSpaceModelDialog extends StatelessWidget { color: ColorsManager.textFieldGreyColor, width: MediaQuery.of(context).size.width * 0.7, height: MediaQuery.of(context).size.height * 0.6, - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { int selectedIndex = -1; if (state is SpaceModelSelectedState) { @@ -59,7 +59,7 @@ class LinkSpaceModelDialog extends StatelessWidget { final isSelected = selectedIndex == index; return GestureDetector( onTap: () { - bloc.add(SpaceModelSelectedEvent(index)); + bloc.add(LinkSpaceModelSelectedEvent(index)); }, child: Container( margin: const EdgeInsets.all(10.0), @@ -93,7 +93,7 @@ class LinkSpaceModelDialog extends StatelessWidget { label: 'Cancel', ), const SizedBox(width: 10), - BlocBuilder( + BlocBuilder( builder: (context, state) { final isEnabled = state is SpaceModelSelectedState && state.selectedIndex >= 0; diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index 22378f1f..3323fe6e 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -11,6 +11,7 @@ class SpaceTemplateModel extends Equatable { List? subspaceModels; final List? tags; String internalId; + String? createdAt; @override List get props => [modelName, subspaceModels, tags]; @@ -21,6 +22,7 @@ class SpaceTemplateModel extends Equatable { required this.modelName, this.subspaceModels, this.tags, + this.createdAt, }) : internalId = internalId ?? const Uuid().v4(); factory SpaceTemplateModel.fromJson(Map json) { @@ -28,6 +30,7 @@ class SpaceTemplateModel extends Equatable { return SpaceTemplateModel( uuid: json['uuid'] ?? '', + createdAt: json['createdAt'] ?? '', internalId: internalId, modelName: json['modelName'] ?? '', subspaceModels: (json['subspaceModels'] as List?) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart new file mode 100644 index 00000000..2a39d67b --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ConfirmMergeDialog extends StatelessWidget { + const ConfirmMergeDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: Text( + 'Merge', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 30), + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Are you sure you want to merge?', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Cancel", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.secondaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Ok", + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 16, + color: ColorsManager.whiteColors, + ), + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart new file mode 100644 index 00000000..0497b570 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ConfirmOverwriteDialog extends StatelessWidget { + const ConfirmOverwriteDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: Text( + 'Overwrite', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 30), + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Are you sure you want to overwrite?', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + Center( + child: Text( + 'Selected spaces already have linked space \nmodel / sub-spaces and devices', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + color: ColorsManager.grayColor), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Cancel", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const LinkingSuccessful(); + }, + ); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.secondaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Ok", + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 16, + color: ColorsManager.whiteColors, + ), + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart new file mode 100644 index 00000000..e0260887 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart @@ -0,0 +1,75 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart'; + + +class CustomLoadingIndicator extends StatefulWidget { + @override + _CustomLoadingIndicatorState createState() => _CustomLoadingIndicatorState(); +} + +class _CustomLoadingIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(seconds: 1), + )..repeat(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 50, + height: 50, + child: AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Transform.rotate( + angle: _controller.value * 2 * pi, + child: CustomPaint( + painter: LoadingPainter(), + ), + ); + }, + ), + ); + } +} + +class LoadingPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final Paint paint = Paint() + ..strokeWidth = 5 + ..strokeCap = StrokeCap.round + ..style = PaintingStyle.stroke; + + final double radius = size.width / 2; + final Offset center = Offset(size.width / 2, size.height / 2); + + for (int i = 0; i < 12; i++) { + final double angle = (i * 30) * (pi / 180); + final double startX = center.dx + radius * cos(angle); + final double startY = center.dy + radius * sin(angle); + final double endX = center.dx + (radius - 8) * cos(angle); + final double endY = center.dy + (radius - 8) * sin(angle); + + paint.color = Colors.blue.withOpacity(i / 12); // Gradient effect + canvas.drawLine(Offset(startX, startY), Offset(endX, endY), paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart new file mode 100644 index 00000000..8c6ef3e9 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart @@ -0,0 +1,221 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class LinkSpaceModelSpacesDialog extends StatefulWidget { + final SpaceTemplateModel spaceModel; + + const LinkSpaceModelSpacesDialog({super.key, required this.spaceModel}); + + @override + State createState() => + _LinkSpaceModelSpacesDialogState(); +} + +class _LinkSpaceModelSpacesDialogState + extends State { + @override + void initState() { + context.read().add(LinkSpaceModelSelectedIdsEvent()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + backgroundColor: Colors.white, + content: SizedBox( + width: MediaQuery.of(context).size.width * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDialogContent(), + _buildActionButtons(), + ], + ), + ), + ); + } + + Widget _buildDialogContent() { + return Expanded( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Center( + child: Text( + "Link Space Model to Spaces", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.blueAccent, + ), + ), + ), + const Divider(), + const SizedBox(height: 16), + _buildDetailRow( + "Space model name:", widget.spaceModel.modelName), + _buildDetailRow("Creation date and time:", + widget.spaceModel.createdAt.toString()), + _buildDetailRow("Created by:", "Admin"), + const SizedBox(height: 12), + const Text( + "Link to:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const Text( + "Please select all the spaces where you would like to link the Routine.", + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + const SizedBox(height: 8), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 7, + child: Container( + color: ColorsManager.whiteColors, + child: SpaceTreeView( + isSide: true, + onSelect: () { + context + .read() + .add( + LinkSpaceModelSelectedIdsEvent()); + }))) + ], + ), + ), + ), + const SizedBox( + height: 20, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildActionButtons() { + return Row( + children: [ + Expanded( + child: Container( + height: 50, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayColor, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayColor, + width: 1, + ), + ), + ), + child: _buildButton("Cancel", Colors.grey, () { + Navigator.of(context).pop(); + }), + ), + ), + Expanded( + child: Container( + height: 50, + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.grayColor, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayColor, + width: 1.0, + ), + ), + ), + child: _buildButton( + "Confirm", + ColorsManager.onSecondaryColor, + () { + final spaceModelBloc = context.read(); + if (!spaceModelBloc.hasSelectedSpaces) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Please select at least one space")), + ); + return; + } else { + spaceModelBloc.add(ValidateSpaceModelEvent(context: context)); + } + }, + ), + ), + ), + ], + ); + } +} + +Widget _buildDetailRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Expanded( + child: Text( + label, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + value, + style: const TextStyle( + fontWeight: FontWeight.bold, color: Colors.black), + ), + ), + ], + ), + ); +} + +Widget _buildButton(String text, Color color, VoidCallback onPressed) { + return InkWell( + onTap: onPressed, + child: Center( + child: Text( + text, + style: + TextStyle(color: color, fontWeight: FontWeight.w400, fontSize: 14), + ), + ), + ); +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/linking_attention_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/linking_attention_dialog.dart new file mode 100644 index 00000000..15d92029 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/linking_attention_dialog.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class LinkingAttentionDialog extends StatelessWidget { + const LinkingAttentionDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: Text( + 'Linking Attention', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 30), + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Do you want to merge or overwrite?', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + const SizedBox(height: 8), + Text( + 'Selected spaces already have commissioned Devices', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 14), + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // Cancel Button + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const ConfirmOverwriteDialog(); + }, + ); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Overwrite", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + const SizedBox(width: 10), + + // OK Button + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const ConfirmMergeDialog(); + }, + ); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Merge", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart b/lib/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart new file mode 100644 index 00000000..6a228fc1 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class LinkingSuccessful extends StatelessWidget { + const LinkingSuccessful({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: SvgPicture.asset( + Assets.successIcon, + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Linking successful', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + const SizedBox(height: 25), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart new file mode 100644 index 00000000..9f57a4b1 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +void showOverwriteDialog( + BuildContext context, LinkSpaceToModelBloc bloc, SpaceTemplateModel model) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return SizedBox( + child: Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + elevation: 10, + backgroundColor: Colors.white, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.3, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + "Overwrite", + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + const SizedBox(height: 15), + const Text( + "Are you sure you want to overwrite?", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + const SizedBox(height: 5), + Text( + "Selected spaces already have linked space model / sub-spaces and devices", + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: Colors.grey[200], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + ), + child: const Text( + "Cancel", + style: TextStyle( + fontSize: 16, + color: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton( + onPressed: () { + bloc.add(LinkSpaceModelEvent( + isOverWrite: true, + selectedSpaceMode: model.uuid)); + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: Colors.blue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: const Text( + "OK", + style: TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + }, + ); +} diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index f446695e..e9be3829 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -1,13 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; - import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.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/space_model/bloc/space_model_bloc.dart'; - import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/delete_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart'; @@ -69,48 +74,174 @@ class SpaceModelCardWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: Text( - model.modelName, - style: - Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.black, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - if (!topActionsDisabled) - GestureDetector( - onTap: () => _showDeleteDialog(context), - child: Container( - width: 36, // Adjust size as needed - height: 36, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 2), - ), - ], + Text( + model.modelName, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, ), - child: Center( - child: SvgPicture.asset( - Assets.deleteSpaceModel, // Your actual SVG path - width: 20, - height: 20, - colorFilter: const ColorFilter.mode( - Colors.grey, BlendMode.srcIn), - ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Row( + children: [ + InkWell( + onTap: () { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return BlocProvider( + create: (_) => LinkSpaceToModelBloc(), + child: BlocListener( + listener: (context, state) { + final _bloc = + BlocProvider.of( + context); + if (state is SpaceModelLoading) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(20)), + elevation: 10, + backgroundColor: Colors.white, + child: Padding( + padding: + const EdgeInsets.symmetric( + vertical: 30, + horizontal: 50), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CustomLoadingIndicator(), + const SizedBox(height: 20), + const Text( + "Linking in progress", + style: TextStyle( + fontSize: 16, + fontWeight: + FontWeight.w500, + color: Colors.black87, + ), + ), + ], + ), + ), + ); + }, + ); + } else if (state + is AlreadyHaveLinkedState) { + Navigator.of(dialogContext).pop(); + showOverwriteDialog( + context, _bloc, model); + } else if (state + is SpaceValidationSuccess) { + _bloc.add(LinkSpaceModelEvent( + isOverWrite: false, + selectedSpaceMode: model.uuid)); + + Future.delayed(const Duration(seconds: 1), + () { + Navigator.of(dialogContext).pop(); + Navigator.of(dialogContext).pop(); + Navigator.of(dialogContext).pop(); + }); + + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const LinkingSuccessful(); + }, + ).then((v) { + Future.delayed( + const Duration(seconds: 2), () { + Navigator.of(dialogContext).pop(); + }); + }); + } else if (state is SpaceModelLinkSuccess) { + Navigator.of(dialogContext).pop(); + Navigator.of(dialogContext).pop(); + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const LinkingSuccessful(); + }, + ); + } + }, + child: LinkSpaceModelSpacesDialog( + spaceModel: model, + ), + ), + ); + }, + ); + }, + child: SvgPicture.asset( + Assets.spaceLinkIcon, + fit: BoxFit.contain, ), ), - ), + if (!topActionsDisabled) + InkWell( + onTap: () { + _showDeleteDialog(context); + }, + child: SvgPicture.asset( + Assets.deleteSpaceLinkIcon, + fit: BoxFit.contain, + ), + ), + ], + ), + // Expanded( + // child: Text( + // model.modelName, + // style: + // Theme.of(context).textTheme.headlineMedium?.copyWith( + // color: Colors.black, + // fontWeight: FontWeight.bold, + // ), + // maxLines: 1, + // overflow: TextOverflow.ellipsis, + // ), + // ), + // if (!topActionsDisabled) + // GestureDetector( + // onTap: () => _showDeleteDialog(context), + // child: Container( + // width: 36, // Adjust size as needed + // height: 36, + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // color: Colors.white, + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.1), + // spreadRadius: 2, + // blurRadius: 5, + // offset: const Offset(0, 2), + // ), + // ], + // ), + // child: Center( + // child: SvgPicture.asset( + // Assets.deleteSpaceModel, // Your actual SVG path + // width: 20, + // height: 20, + // colorFilter: const ColorFilter.mode( + // Colors.grey, BlendMode.srcIn), + // ), + // ), + // ), + // ), ], ), if (!showOnlyName) ...[ diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 15764083..5253c73e 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -2,7 +2,6 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/create_sp import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; class SpaceModelManagementApi { Future> listSpaceModels( @@ -61,6 +60,36 @@ class SpaceModelManagementApi { return response; } + Future linkSpaceModel( + {required String spaceModelUuid, + required String projectId, + required List spaceUuids, + required bool isOverWrite}) async { + final response = await HTTPService().post( + path: ApiEndpoints.linkSpaceModel + .replaceAll('{projectId}', projectId) + .replaceAll('{spaceModelUuid}', spaceModelUuid), + showServerMessage: true, + body: {"spaceUuids": spaceUuids, "overwrite": isOverWrite}, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + Future validateSpaceModel(String projectId, List spaceUuids) async { + final response = await HTTPService().post( + path: ApiEndpoints.validateSpaceModel + .replaceAll('{projectId}', projectId), + showServerMessage: true, + body: {"spacesUuids": spaceUuids}, + expectedResponseModel: (json) { + return json; + }); + return response; + } + Future deleteSpaceModel(String spaceModelUuid, String projectId) async { final response = await HTTPService().delete( path: ApiEndpoints.getSpaceModel diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index a4bcc0da..4e506093 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -52,7 +52,7 @@ abstract class ColorsManager { static const Color semiTransparentBlackColor = Color(0x3F000000); static const Color transparentColor = Color(0x00000000); static const Color spaceColor = Color(0xB2023DFE); - static const Color counterBackgroundColor = Color(0xCCF4F4F4); + static const Color counterBackgroundColor = Color.fromARGB(204, 105, 2, 2); static const Color neutralGray = Color(0xFFE5E5E5); static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 1022d734..630f59c0 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -9,15 +9,19 @@ abstract class ApiEndpoints { static const String sendOtp = '/authentication/user/send-otp'; static const String verifyOtp = '/authentication/user/verify-otp'; static const String getRegion = '/region'; - static const String visitorPassword = '/projects/{projectId}/visitor-password'; - static const String getDevices = '/projects/{projectId}/visitor-password/devices'; + static const String visitorPassword = + '/projects/{projectId}/visitor-password'; + static const String getDevices = + '/projects/{projectId}/visitor-password/devices'; - static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time'; + static const String sendOnlineOneTime = + '/visitor-password/temporary-password/online/one-time'; static const String sendOnlineMultipleTime = '/visitor-password/temporary-password/online/multiple-time'; //offline Password - static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time'; + static const String sendOffLineOneTime = + '/visitor-password/temporary-password/offline/one-time'; static const String sendOffLineMultipleTime = '/visitor-password/temporary-password/offline/multiple-time'; @@ -39,32 +43,45 @@ abstract class ApiEndpoints { static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; // Space Module - static const String createSpace = '/projects/{projectId}/communities/{communityId}/spaces'; - static const String listSpaces = '/projects/{projectId}/communities/{communityId}/spaces'; + static const String createSpace = + '/projects/{projectId}/communities/{communityId}/spaces'; + static const String listSpaces = + '/projects/{projectId}/communities/{communityId}/spaces'; static const String deleteSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; static const String updateSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; - static const String getSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; - static const String getSpaceHierarchy = '/projects/{projectId}/communities/{communityId}/spaces'; + static const String getSpace = + '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; + static const String getSpaceHierarchy = + '/projects/{projectId}/communities/{communityId}/spaces'; // Community Module static const String createCommunity = '/projects/{projectId}/communities'; static const String getCommunityList = '/projects/{projectId}/communities'; - static const String getCommunityById = '/projects/{projectId}/communities/{communityId}'; - static const String updateCommunity = '/projects/{projectId}/communities/{communityId}'; - static const String deleteCommunity = '/projects/{projectId}communities/{communityId}'; - static const String getUserCommunities = '/projects/{projectId}/communities/user/{userUuid}'; - static const String createUserCommunity = '/projects/{projectId}/communities/user'; + static const String getCommunityById = + '/projects/{projectId}/communities/{communityId}'; + static const String updateCommunity = + '/projects/{projectId}/communities/{communityId}'; + static const String deleteCommunity = + '/projects/{projectId}communities/{communityId}'; + static const String getUserCommunities = + '/projects/{projectId}/communities/user/{userUuid}'; + static const String createUserCommunity = + '/projects/{projectId}/communities/user'; static const String getDeviceLogsByDate = '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; - static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}'; - static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}'; - static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; + static const String getScheduleByDeviceId = + '/schedule/{deviceUuid}?category={category}'; + static const String deleteScheduleByDeviceId = + '/schedule/{deviceUuid}/{scheduleUuid}'; + static const String updateScheduleByDeviceId = + '/schedule/enable/{deviceUuid}'; static const String factoryReset = '/device/factory/reset/{deviceUuid}'; - static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status'; + static const String powerClamp = + '/device/{powerClampUuid}/power-clamp/status'; //product static const String listProducts = '/products'; @@ -75,7 +92,8 @@ abstract class ApiEndpoints { static const String createAutomation = '/automation'; static const String getUnitScenes = '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; - static const String getAutomationDetails = '/automation/details/{automationId}'; + static const String getAutomationDetails = + '/automation/details/{automationId}'; static const String getScene = '/scene/tap-to-run/{sceneId}'; static const String deleteScene = '/scene/tap-to-run/{sceneId}'; @@ -87,8 +105,15 @@ abstract class ApiEndpoints { //space model static const String listSpaceModels = '/projects/{projectId}/space-models'; static const String createSpaceModel = '/projects/{projectId}/space-models'; - static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; - static const String updateSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; + static const String getSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}'; + static const String updateSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}'; + static const String linkSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}/spaces/link'; + + static const String validateSpaceModel = + '/projects/{projectId}/spaces/validate'; static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; @@ -99,7 +124,8 @@ abstract class ApiEndpoints { static const String getUserById = '/projects/{projectId}/user/{userUuid}'; static const String editUser = '/invite-user/{inviteUserUuid}'; static const String deleteUser = '/invite-user/{inviteUserUuid}'; - static const String changeUserStatus = '/invite-user/{invitedUserUuid}/disable'; + static const String changeUserStatus = + '/invite-user/{invitedUserUuid}/disable'; static const String terms = '/terms'; static const String policy = '/policy'; static const String userAgreements = '/user/agreements/web/{userUuid}'; diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index d7b4d283..b7a0115f 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -402,5 +402,10 @@ class Assets { static const String link = 'assets/icons/link.svg'; static const String duplicate = 'assets/icons/duplicate.svg'; static const String spaceDelete = 'assets/icons/space_delete.svg'; + + static const String deleteSpaceLinkIcon = + 'assets/icons/delete_space_link_icon.svg'; + static const String spaceLinkIcon = 'assets/icons/space_link_icon.svg'; + static const String successIcon = 'assets/icons/success_icon.svg'; + } -//user_management.svg